Android自定义菜单选择条目

一.需求

有时候我们需要在项目里做一个菜单样式的功能,比如美团的

Android自定义菜单选择条目
image.png

再比如JD的

Android自定义菜单选择条目
image.png

有时候很多个app需要这样的UI,或者一个app里面有多个地方需要,那如果重复写的话就会很麻烦,我们可以自定义一个View,然后在多处复用。我知道你恨牛逼搭这个页面只要一两分钟,但是即使再快,重复去做相同的事情也是不聪明的选择。

二.自定义View实现菜单选择条目

首先确定我们需要做出一个什么样的效果。看了很多地方,我觉得可以做成这样子的

Android自定义菜单选择条目
image.png

左右两个textview,两个imageview,但是我觉得有些可能需要显示在中间,所以我做了3个textview。全显示的话大概会是这个样子

Android自定义菜单选择条目
image.png

特定的需求可以隐藏特定地方的控件。

代码:

1.xml文件
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/rl_content"
    >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:id="@+id/tv_title_center"
        android:paddingTop="15dp"
        android:paddingBottom="15dp"
        android:text="cccc"
        />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/iv_left"
        android:layout_centerVertical="true"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/iv_left"
        android:layout_toLeftOf="@+id/tv_title_center"
        android:text="aaaa"
        android:layout_centerVertical="true"
        android:paddingTop="15dp"
        android:paddingBottom="15dp"
        android:id="@+id/tv_title_left"
        android:maxLines="1"
        android:ellipsize="end"
        />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:id="@+id/iv_right"
        />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingTop="15dp"
        android:paddingBottom="15dp"
        android:text="bbb"
        android:layout_centerVertical="true"
        android:layout_toRightOf="@+id/tv_title_center"
        android:layout_toLeftOf="@+id/iv_right"
        android:id="@+id/tv_title_right"
        android:gravity="right"
        android:maxLines="1"
        android:ellipsize="end"
        />

</RelativeLayout>
2.attrs
<!-- 菜单选择栏 -->
    <declare-styleable name="menu_options_view_style">
        <attr name="title_left" format="string"/>
        <attr name="title_right" format="string"/>
        <attr name="title_center" format="string"/>
        <attr name="img_left_src" format="reference"/>
        <attr name="img_right_src" format="reference"/>
        
        <attr name="imgLpadding" format="dimension"/>
        <attr name="imgRpadding" format="dimension"/>
        
        <attr name="imgLmargin" format="dimension"/>
        <attr name="imgRmargin" format="dimension"/>

        <attr name="leftTvSize" format="dimension"/>
        <attr name="rightTvSize" format="dimension"/>
        <attr name="centerTvSize" format="dimension"/>
        
    </declare-styleable>
3.自定义View
public class MenuOptionsView extends FrameLayout implements View.OnClickListener{

    private RelativeLayout rlContent;
    private ImageView leftImg;
    private ImageView rightImg;
    private TextView leftText;
    private TextView rightText;
    private TextView centerText;
    private View contentView;

    private Context context;
    private String leftTitle = null;
    private String rightTitle = null;
    private String centerTitle = null;
    private int leftImgIds;
    private int rightImgIds;
    private float imgLpadding;
    private float imgRpadding;
    private float imgLmargin;
    private float imgRmargin;
    private float leftTvSize;
    private float rightTvSize;
    private float centerTvSize;

    private MenuOptionsLImgClickListener menuOptionsLImgClickListener;
    private MenuOptionsRImgClickListener menuOptionsRImgClickListener;
    private MenuOptionsLTvClickListener menuOptionsLTvClickListener;
    private MenuOptionsRTvClickListener menuOptionsRTvClickListener;
    private MenuOptionsCTvClickListener menuOptionsCTvClickListener;
    private MenuOptionsClickListener menuOptionsClickListener;

    public MenuOptionsView(Context context) {
        super(context);
        this.context = context;
    }

    public MenuOptionsView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init(context, attrs);
    }

    public MenuOptionsView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        init(context, attrs);
    }

    private void init(Context context,AttributeSet attrs)  {
        TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.menu_options_view_style);
        leftTitle = typedArray.getString(R.styleable.menu_options_view_style_title_left);
        rightTitle = typedArray.getString(R.styleable.menu_options_view_style_title_right);
        centerTitle = typedArray.getString(R.styleable.menu_options_view_style_title_center);
        leftImgIds = typedArray.getResourceId(R.styleable.menu_options_view_style_img_left_src,-1);
        rightImgIds = typedArray.getResourceId(R.styleable.menu_options_view_style_img_right_src,-1);

        imgLpadding = typedArray.getDimension(R.styleable.menu_options_view_style_imgLpadding,0);
        imgRpadding = typedArray.getDimension(R.styleable.menu_options_view_style_imgRpadding,0);
        imgLmargin = typedArray.getDimension(R.styleable.menu_options_view_style_imgLmargin,0);
        imgRmargin = typedArray.getDimension(R.styleable.menu_options_view_style_imgRmargin,0);

        leftTvSize = typedArray.getDimension(R.styleable.menu_options_view_style_leftTvSize,0);
        rightTvSize = typedArray.getDimension(R.styleable.menu_options_view_style_rightTvSize,0);
        centerTvSize = typedArray.getDimension(R.styleable.menu_options_view_style_centerTvSize,0);
        typedArray.recycle();
        initView();
    }

    private void initView(){
        contentView = LayoutInflater.from(context).inflate(R.layout.layout_menu_options,null);
        this.addView(contentView);

        leftImg = (ImageView) contentView.findViewById(R.id.iv_left);
        rightImg = (ImageView) contentView.findViewById(R.id.iv_right);
        leftText = (TextView) contentView.findViewById(R.id.tv_title_left);
        rightText = (TextView) contentView.findViewById(R.id.tv_title_right);
        centerText = (TextView) contentView.findViewById(R.id.tv_title_center);
        rlContent = (RelativeLayout) contentView.findViewById(R.id.rl_content);

        tvStatChange();

        leftImg.setImageResource(leftImgIds);
        rightImg.setImageResource(rightImgIds);
        leftText.setText(leftTitle);
        rightText.setText(rightTitle);
        centerText.setText(centerTitle);

        leftText.setTextSize(TypedValue.COMPLEX_UNIT_PX,leftTvSize+1);
        rightText.setTextSize(TypedValue.COMPLEX_UNIT_PX,rightTvSize+1);
        centerText.setTextSize(TypedValue.COMPLEX_UNIT_PX,centerTvSize+1);

        leftImg.setPadding((int)imgLpadding,(int)imgLpadding,(int)imgLmargin,(int)imgLpadding);
        rightImg.setPadding((int)imgRmargin,(int)imgRpadding,(int)imgRpadding,(int)imgRpadding);

        leftImg.setOnClickListener(this);
        rightImg.setOnClickListener(this);
        leftText.setOnClickListener(this);
        rightText.setOnClickListener(this);
        centerText.setOnClickListener(this);
        rlContent.setOnClickListener(this);
    }

    private int dp2px(float dpValue){
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.iv_left:
                if (menuOptionsClickListener != null){
                    menuOptionsClickListener.layoutClick();
                    break;
                }
                if (menuOptionsLImgClickListener != null){
                    menuOptionsLImgClickListener.leftImgClick();
                }
                break;
            case R.id.iv_right:
                if (menuOptionsClickListener != null){
                    menuOptionsClickListener.layoutClick();
                    break;
                }
                if (menuOptionsRImgClickListener != null){
                    menuOptionsRImgClickListener.rightImgClick();
                }
                break;
            case R.id.tv_title_left:
                if (menuOptionsClickListener != null){
                    menuOptionsClickListener.layoutClick();
                    break;
                }
                if (menuOptionsLTvClickListener != null){
                    menuOptionsLTvClickListener.leftTvClick();
                }
                break;
            case R.id.tv_title_right:
                if (menuOptionsClickListener != null){
                    menuOptionsClickListener.layoutClick();
                    break;
                }
                if (menuOptionsRTvClickListener != null){
                    menuOptionsRTvClickListener.rightTvClick();
                }
                break;
            case R.id.tv_title_center:
                if (menuOptionsClickListener != null){
                    menuOptionsClickListener.layoutClick();
                    break;
                }
                if (menuOptionsCTvClickListener != null){
                    menuOptionsCTvClickListener.centerClick();
                }
                break;
            case R.id.rl_content:
                if (menuOptionsClickListener != null){
                    menuOptionsClickListener.layoutClick();
                }
                break;
        }
    }

    public int sp2px(float spVal)
    {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, context.getResources().getDisplayMetrics());
    }

    /**
     *  动态设置3个TextView的显示和隐藏
     */
    public void tvStatChange(){
        int k = 0;
        centerText.setVisibility(GONE);

        if ((centerTitle == null)||(centerTitle.equals(""))){
            centerText.setVisibility(GONE);
        }else {
            // 显示中间的内容时候,左右就隐藏
            centerText.setVisibility(VISIBLE);
            leftText.setVisibility(GONE);
            rightText.setVisibility(GONE);
            return;
        }

        if ((leftTitle == null)||(leftTitle.equals(""))){
            leftText.setVisibility(GONE);
        }else {
            leftText.setVisibility(VISIBLE);
            k++;
        }

        if ((rightTitle == null)||(rightTitle.equals(""))){
            rightText.setVisibility(GONE);
        }else {
            rightText.setVisibility(VISIBLE);
            k++;
        }
        // 如果显示两边的情况下,让中间分割
        if (k == 2){
            centerText.setVisibility(VISIBLE);
        }

    }

    public void setLeftText(String str){
        leftTitle = str;
        leftText.setText(leftTitle);
        tvStatChange();
    }

    public void setRightText(String str){
        rightTitle = str;
        rightText.setText(rightTitle);
        tvStatChange();
    }

    public void setCenterText(String str){
        centerTitle = str;
        centerText.setText(centerTitle);
        tvStatChange();
    }

    public String getLeftTitle() {
        return leftTitle;
    }

    public String getRightTitle() {
        return rightTitle;
    }

    public String getCenterTitle() {
        return centerTitle;
    }

    public void setLeftImgSrc(int Ids){
        leftImg.setImageResource(Ids);
    }

    public void setRightImgSrc(int Ids){
        rightImg.setImageResource(Ids);
    }

    public interface MenuOptionsLImgClickListener{
        void leftImgClick();
    }

    public interface MenuOptionsRImgClickListener{
        void rightImgClick();
    }

    public interface MenuOptionsLTvClickListener{
        void leftTvClick();
    }

    public interface MenuOptionsRTvClickListener{
        void rightTvClick();
    }

    public interface MenuOptionsCTvClickListener{
        void centerClick();
    }

    public interface MenuOptionsClickListener{
        void layoutClick();
    }

    public void setMenuOptionsLImgClickListener(MenuOptionsLImgClickListener menuOptionsLImgClickListener) {
        this.menuOptionsLImgClickListener = menuOptionsLImgClickListener;
    }

    public void setMenuOptionsRImgClickListener(MenuOptionsRImgClickListener menuOptionsRImgClickListener) {
        this.menuOptionsRImgClickListener = menuOptionsRImgClickListener;
    }

    public void setMenuOptionsLTvClickListener(MenuOptionsLTvClickListener menuOptionsLTvClickListener) {
        this.menuOptionsLTvClickListener = menuOptionsLTvClickListener;
    }

    public void setMenuOptionsRTvClickListener(MenuOptionsRTvClickListener menuOptionsRTvClickListener) {
        this.menuOptionsRTvClickListener = menuOptionsRTvClickListener;
    }

    public void setMenuOptionsCTvClickListener(MenuOptionsCTvClickListener menuOptionsCTvClickListener) {
        this.menuOptionsCTvClickListener = menuOptionsCTvClickListener;
    }

    public void setMenuOptionsClickListener(MenuOptionsClickListener menuOptionsClickListener) {
        this.menuOptionsClickListener = menuOptionsClickListener;
    }
}

代码虽然有些长,但是挺简单的。我这里没有写完设置所有的属性,因为有点多,我只写了一些我暂时用到的,其它的常用属性在需要用到的时候我再加上去,比如说字体的颜色之类的。

这段代码有几个地方注意一下就行。
(1)设置字体大小的时候用.setTextSize(TypedValue.COMPLEX_UNIT_PX,leftTvSize+1); 这样才能对应在xml中设置sp的正确尺寸。
(2)我这定义了6个接口来写点击事件,分别是两个imageview和3个textview和父布局的点击事件。我这做操作,如果设置父布局的点击监听,那就会无效所有子控件的点击。
(3)tvStatChange()这个方法是用来做3个textview的动态布局操作,防止他们在某个时刻展示冲突。

4.调用

<com.xxxxx.xxxxx.components.widget.view.MenuOptionsView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:img_left_src = "@mipmap/setting_security"
app:img_right_src = "@mipmap/icon_arrow_right"
app:title_left = "aaaaaa"
app:imgLpadding = "15dp"
app:imgRpadding = "8dp"
app:imgLmargin = "15dp"
app:leftTvSize = "16sp"
android:id="@+id/test"
android:background="@color/white"
>
</com.xxxxx.xxxxx.components.widget.view.MenuOptionsView>

可以看出调用非常简单,唯一的缺点是 app:这个标签无法联想,所以有时候属性多了要进attrs里面去查属性。

三.总结

写这个东西也不难,唯一的不足是子view的属性多,但是可以在用到的时候再去添加特定的属性。
写这个的目的是为了说明有时候我们很犹豫一个东西要不要封装起来,就像这个一个,写个imageview和textview有必要封装吗?我复制粘贴就能实现了。我个人比较建议封装,再小的*也是*,而且这些东西封装起来也不难,大部分都是添加属性,然后加些简单的逻辑判断。
其实我更想说的是,有时候会要求你的页面改变状态,比如


Android自定义菜单选择条目
image.png

要求登录的时候才显示右边,然后未登录是不显示的,比如这些动态的情况,如果布局是拼起来的话,一个一个改状态就很不容易管理,把它封装起来,变成一个整体的话,改状态就很方便。

所以我建议造这种小*的目的一是为了更快开发不做重复事情,二是为了方便管理。

上一篇:Android自定义密码键盘


下一篇:java中接口与抽象类的区别