天天看點

Android自定義菜單選擇條目

一.需求

有時候我們需要在項目裡做一個菜單樣式的功能,比如美團的

Android自定義菜單選擇條目

image.png

再比如JD的

Android自定義菜單選擇條目

有時候很多個app需要這樣的UI,或者一個app裡面有多個地方需要,那如果重複寫的話就會很麻煩,我們可以自定義一個View,然後在多處複用。我知道你恨牛逼搭這個頁面隻要一兩分鐘,但是即使再快,重複去做相同的事情也是不聰明的選擇。

二.自定義View實作菜單選擇條目

首先确定我們需要做出一個什麼樣的效果。看了很多地方,我覺得可以做成這樣子的

Android自定義菜單選擇條目

左右兩個textview,兩個imageview,但是我覺得有些可能需要顯示在中間,是以我做了3個textview。全顯示的話大概會是這個樣子

Android自定義菜單選擇條目

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

代碼:

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自定義菜單選擇條目

要求登入的時候才顯示右邊,然後未登入是不顯示的,比如這些動态的情況,如果布局是拼起來的話,一個一個改狀态就很不容易管理,把它封裝起來,變成一個整體的話,改狀态就很友善。

是以我建議造這種小輪子的目的一是為了更快開發不做重複事情,二是為了友善管理。

繼續閱讀