Android自定義控件:仿美團下拉菜單及相關代碼優化

qibaoan8 9年前發布 | 55K 次閱讀 Android開發 移動開發

背景

最近的項目中用到了類似美團中的下拉多選菜單,在實際開發過程中,也發現了一些問題,主要歸納如下:

1.當菜單較為復雜時,如果不能設計好代碼邏輯,將造成控件難于維護

2.美團菜單可以連續點擊頂部tab,切換不同菜單,而我使用的popupWindow似乎在展開一個菜單時點擊其他tab,菜單就會收回。 </code></pre>

本文將針對如上兩個問題進行一些討論,最終給出較為合理的解決方案。

程序結構

由于菜單涉及多級多項,如果把UI和其他邏輯堆在一起寫,必然會造成代碼過于龐大,甚至沒有辦法擴展,更談不上及時變更需求。

ViewHolder與組合控件結合分割菜單邏輯

這里我采用了組合控件和ViewHolder結合的辦法來處理耦合的問題。

組合控件的特點是可以直接定義在xml里無需做其他任何多余的操作,ViewHolder則可以靈活地提供View,并將這些View貼到需要的地方。

基于上述特征,我將固定的菜單欄設計為組合控件,提供各項菜單的tab,而將菜單的具體內容使用ViewHolder封裝,在需要的時候從ViewHoder中拿到View,貼到我們需要放置的地方。同時,每個菜單中的UI邏輯也會被封裝到ViewHolder中,這樣,如果我們需要修改需求,直接改動對應的ViewHolder的代碼,而不會影響其他代碼。

這樣我們代碼就可以將復雜的UI邏輯分成相互獨立的小塊,想改哪里改哪里,媽媽再也不用擔心產品經理為難我了…………

使用布局文件代替popupWindow

翻閱網上很多仿制的美團菜單例程,幾乎都沒有真正和美團app的菜單一樣,我們可以查看官方app,點擊一個tab展開菜單,當在點擊下一個tab時,菜單并沒有收回,而是顯示了當前tab對應的內容。

由于很多demo都是使用popupWindow作為菜單的載體,而我在實際操作過程中發現popupWindow作為模態對話框非常難控制,而且還會引起其他問題,總之,我認為此處使用使用popupWindow并不合適。我在給菜單欄下面放了一塊空布局,當向空布局中添加View時,空布局擴大,也就形成了下拉菜單的效果。

那么有同學要問了,這樣的話不就會影響下面的其他布局的位置了?是的,所以我們的菜單欄必須放在RelativeLayout或者FrameLayout這類結構中,而且必須放在其頂層。

控件原型

了解了上述兩個問題的解決方法,我們就可以大概勾勒一樣我們的控件大體的模樣了。如下圖:
這里寫圖片描述
點擊TAB1,TAB2,TAB3,內容View被對應的Holder中維護的View替換,我們清空內容View中的view時,由于這個View是包裹內容的,內容為空時高度自然變成0,也就是菜單收起的狀態。我們可以為每個Holder設置相應的回調接口,這樣我們的菜單View就能根據Holder的變化實時做出響應。

代碼實現

1.ViewHolder

ViewHolder用來維護一個我們手動inflate出來的View,并提供刷新數據的方法。我們可以以此為基類,封裝UI邏輯,ViewHolder間也可以靈活替換。

/*  自繪控件封裝類  Created by vonchenchen on 2015/11/3 0003. /
public abstract class BaseWidgetHolder<T> {

protected View mRootView;

protected Context mContext;

public abstract View initView();
public abstract void refreshView(T data);

public BaseWidgetHolder(Context context){
    mContext = context;
    mRootView = initView();
    mRootView.setTag(this);
}

public View getRootView(){
    return mRootView;
}

}</code></pre>

2.菜單View

這個View就是菜單欄主View,包括了三個TAB和下面的內容View,我們只需在工程中直接將這個類放入我們的布局文件中就可以了,注意,必須放在RelativieLayout或者FrameLayout中,而且必須是最頂層,否則內容View展開時會“擠”到其他布局。此處我們采用這種方式而不是popupWindow是因為popupWindow焦點改變可能會觸發消失,這樣無法實現點擊不同的tab時,連續切換菜單的效果。

/*   搜索菜單欄  Created by vonchenchen on 2016/4/5 0005. */
public class SelectMenuView extends LinearLayout{

private static final int TAB_SUBJECT = 1;
private static final int TAB_SORT = 2;
private static final int TAB_SELECT = 3;

private Context mContext;

private View mSubjectView;
private View mSortView;
private View mSelectView;

private View mRootView;

private View mPopupWindowView;

private RelativeLayout mMainContentLayout;
private View mBackView;

/** type1 */
private SubjectHolder mSubjectHolder;
/** type2 */
private SortHolder mSortHolder;
/** type3 */
private SelectHolder mSelectHolder;

/** 與外部通信傳遞數據的接口 */
private OnMenuSelectDataChangedListener mOnMenuSelectDataChangedListener;

private RelativeLayout mContentLayout;

private TextView mSubjectText;
private ImageView mSubjectArrowImage;
private TextView mSortText;
private ImageView mSortArrowImage;
private TextView mSelectText;
private ImageView mSelectArrowImage;

private List<String> mGroupList;
private List<String> mPrimaryList;
private List<String> mJuniorList;
private List<String> mHighList;
private List<List<String>> mSubjectDataList;

private int mTabRecorder = -1;

public SelectMenuView(Context context) {
    super(context);
    this.mContext = context;
    this.mRootView = this;
    init();
}

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

private void init(){

    mGroupList = new ArrayList<String>();
    mGroupList.add("A");
    mGroupList.add("B");
    mGroupList.add("C");
    mPrimaryList = new ArrayList<String>();
    mPrimaryList.add("A1");
    mPrimaryList.add("A2");
    mPrimaryList.add("A3");
    mJuniorList = new ArrayList<String>();
    mJuniorList.add("B1");
    mJuniorList.add("B2");
    mJuniorList.add("B3");
    mJuniorList.add("B4");
    mJuniorList.add("B5");
    mJuniorList.add("B6");
    mJuniorList.add("B7");
    mJuniorList.add("B8");
    mJuniorList.add("B9");
    mHighList = new ArrayList<String>();
    mHighList.add("C1");
    mHighList.add("C2");
    mHighList.add("C3");
    mHighList.add("C4");
    mHighList.add("C5");
    mHighList.add("C6");
    mHighList.add("C7");
    mHighList.add("C8");
    mHighList.add("C9");

    mSubjectDataList = new ArrayList<List<String>>();
    mSubjectDataList.add(mGroupList);
    mSubjectDataList.add(mPrimaryList);
    mSubjectDataList.add(mJuniorList);
    mSubjectDataList.add(mHighList);


    //type1
    mSubjectHolder = new SubjectHolder(mContext);
    mSubjectHolder.refreshData(mSubjectDataList, 0, -1);
    mSubjectHolder.setOnRightListViewItemSelectedListener(new SubjectHolder.OnRightListViewItemSelectedListener() {
        @Override
        public void OnRightListViewItemSelected(int leftIndex, int rightIndex, String text) {

            if(mOnMenuSelectDataChangedListener != null){
                int grade = leftIndex+1;
                int subject = getSubjectId(rightIndex);
                mOnMenuSelectDataChangedListener.onSubjectChanged(grade+"", subject+"");
            }

            dismissPopupWindow();
            //Toast.makeText(UIUtils.getContext(), text, Toast.LENGTH_SHORT).show();
            mSubjectText.setText(text);
        }
    });

    //type2
    mSortHolder = new SortHolder(mContext);
    mSortHolder.setOnSortInfoSelectedListener(new SortHolder.OnSortInfoSelectedListener() {
        @Override
        public void onSortInfoSelected(String info) {

            if(mOnMenuSelectDataChangedListener != null){
                mOnMenuSelectDataChangedListener.onSortChanged(info);
            }

            dismissPopupWindow();
            mSortText.setText(getSortString(info));
            //Toast.makeText(UIUtils.getContext(), info, Toast.LENGTH_SHORT).show();
        }
    });

    //type3
    mSelectHolder = new SelectHolder(mContext);
    mSelectHolder.setOnSelectedInfoListener(new SelectHolder.OnSelectedInfoListener() {
        @Override
        public void OnselectedInfo(String gender, String type) {

            if(mOnMenuSelectDataChangedListener != null){
                mOnMenuSelectDataChangedListener.onSelectedChanged(gender, type);
            }

            dismissPopupWindow();
            //Toast.makeText(UIUtils.getContext(), gender+" "+type, Toast.LENGTH_SHORT).show();
        }
    });
}

private int getSubjectId(int index){
    return index;
}

@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    View.inflate(mContext, R.layout.layout_search_menu, this);

    mSubjectText = (TextView) findViewById(R.id.subject);
    mSubjectArrowImage = (ImageView) findViewById(R.id.img_sub);

    mSortText = (TextView) findViewById(R.id.comprehensive_sorting);
    mSortArrowImage = (ImageView) findViewById(R.id.img_cs);

    mSelectText = (TextView) findViewById(R.id.tv_select);
    mSelectArrowImage = (ImageView) findViewById(R.id.img_sc);

    mContentLayout = (RelativeLayout) findViewById(R.id.rl_content);

    mPopupWindowView = View.inflate(mContext, R.layout.layout_search_menu_content, null);
    mMainContentLayout = (RelativeLayout) mPopupWindowView.findViewById(R.id.rl_main);
    //mBackView = mPopupWindowView.findViewById(R.id.ll_background);

    mSubjectView = findViewById(R.id.ll_subject);
    mSortView = findViewById(R.id.ll_sort);
    mSelectView = findViewById(R.id.ll_select);

    //點擊 type1 彈出菜單
    mSubjectView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if(mOnMenuSelectDataChangedListener != null){
                mOnMenuSelectDataChangedListener.onViewClicked(mSubjectView);
            }
            handleClickSubjectView();
        }
    });
    //點擊 type2 彈出菜單
    mSortView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if(mOnMenuSelectDataChangedListener != null){
                mOnMenuSelectDataChangedListener.onViewClicked(mSortView);
            }
            handleClickSortView();
        }
    });
    //點擊 type3 彈出菜單
    mSelectView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if(mOnMenuSelectDataChangedListener != null){
                mOnMenuSelectDataChangedListener.onViewClicked(mSelectView);
            }
            handleClickSelectView();
        }
    });

    //點擊黑色半透明部分,菜單收回
    mContentLayout.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            dismissPopupWindow();
        }
    });
}

private void handleClickSubjectView(){
    //清空內容View中的View
    mMainContentLayout.removeAllViews();
    //將我們已經創建好的ViewHolder拿出,取出其中的View貼到內容View中
    mMainContentLayout.addView(mSubjectHolder.getRootView(), ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    //處理彈窗動作
    popUpWindow(TAB_SUBJECT);
}

private void handleClickSortView(){

    mMainContentLayout.removeAllViews();
    mMainContentLayout.addView(mSortHolder.getRootView(), ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

    popUpWindow(TAB_SORT);
}

private void handleClickSelectView(){

    mMainContentLayout.removeAllViews();
    mMainContentLayout.addView(mSelectHolder.getRootView(), ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

    popUpWindow(TAB_SELECT);
}

private void popUpWindow(int tab){
    if(mTabRecorder != -1) {
        resetTabExtend(mTabRecorder);
    }
    extendsContent();
    setTabExtend(tab);
    mTabRecorder = tab;
}

private void extendsContent(){
    mContentLayout.removeAllViews();
    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    mContentLayout.addView(mPopupWindowView, params);
}

private void dismissPopupWindow(){
    mContentLayout.removeAllViews();
    setTabClose();
}

public void setOnMenuSelectDataChangedListener(OnMenuSelectDataChangedListener onMenuSelectDataChangedListener){
    this.mOnMenuSelectDataChangedListener = onMenuSelectDataChangedListener;
}

public interface OnMenuSelectDataChangedListener{

    void onSubjectChanged(String grade, String subjects);
    void onSortChanged(String sortType);

    void onSelectedChanged(String gender, String classType);

    void onViewClicked(View view);

    //篩選菜單,當點擊其他處菜單收回后,需要更新當前選中項
    void onSelectedDismissed(String gender, String classType);
}

private void setTabExtend(int tab){
    if(tab == TAB_SUBJECT){
        mSubjectText.setTextColor(getResources().getColor(R.color.blue));
        mSubjectArrowImage.setImageResource(R.mipmap.ic_up_blue);
    }else if(tab == TAB_SORT){
        mSortText.setTextColor(getResources().getColor(R.color.blue));
        mSortArrowImage.setImageResource(R.mipmap.ic_up_blue);
    }else if(tab == TAB_SELECT){
        mSelectText.setTextColor(getResources().getColor(R.color.blue));
        mSelectArrowImage.setImageResource(R.mipmap.ic_up_blue);
    }
}

private void resetTabExtend(int tab){
    if(tab == TAB_SUBJECT){
        mSubjectText.setTextColor(getResources().getColor(R.color.gray));
        mSubjectArrowImage.setImageResource(R.mipmap.ic_down);
    }else if(tab == TAB_SORT){
        mSortText.setTextColor(getResources().getColor(R.color.gray));
        mSortArrowImage.setImageResource(R.mipmap.ic_down);
    }else if(tab == TAB_SELECT){
        mSelectText.setTextColor(getResources().getColor(R.color.gray));
        mSelectArrowImage.setImageResource(R.mipmap.ic_down);
    }
}

private void setTabClose(){

    mSubjectText.setTextColor(getResources().getColor(R.color.text_color_gey));
    mSubjectArrowImage.setImageResource(R.mipmap.ic_down);

    mSortText.setTextColor(getResources().getColor(R.color.text_color_gey));
    mSortArrowImage.setImageResource(R.mipmap.ic_down);

    mSelectText.setTextColor(getResources().getColor(R.color.text_color_gey));
    mSelectArrowImage.setImageResource(R.mipmap.ic_down);
}

private String getSortString(String info){
    if(SortHolder.SORT_BY_NORULE.equals(info)){
        return "sort1";
    }else if(SortHolder.SORT_BY_EVALUATION.equals(info)){
        return "sort2";
    }else if(SortHolder.SORT_BY_PRICELOW.equals(info)){
        return "sort3";
    }else if(SortHolder.SORT_BY_PRICEHIGH.equals(info)){
        return "sort4";
    }else if(SortHolder.SORT_BY_DISTANCE.equals(info)){
        return "sort5";
    }
    return "sort1";
}

public void clearAllInfo(){
    //清除控件內部選項
    mSubjectHolder.refreshData(mSubjectDataList, 0, -1);
    mSortHolder.refreshView(null);
    mSelectHolder.refreshView(null);

    //清除菜單欄顯示
    mSubjectText.setText("type1");
    mSortText.setText("type2");
}

}</code></pre>

以下是demo的實現效果,點擊不同tab,下面菜單實現連續切換:
這里寫圖片描述

這里寫圖片描述

這里寫圖片描述

代碼地址 https://git.oschina.net/vonchenchen/menu_demo.git

下載地址 http://download.csdn.net/detail/lidec/9498648

來自: http://blog.csdn.net/lidec/article/details/51205413

 本文由用戶 qibaoan8 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!