Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義控件:仿美團下拉菜單及相關代碼優化

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

編輯:關於Android編程

背景

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

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

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

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

程序結構

由於菜單涉及多級多項,如果把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的變化實時做出響應。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMyBpZD0="代碼實現">代碼實現

1.ViewHolder

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

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

    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;
    }
}

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 mGroupList;
    private List mPrimaryList;
    private List mJuniorList;
    private List mHighList;
    private List> 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();
        mGroupList.add("A");
        mGroupList.add("B");
        mGroupList.add("C");
        mPrimaryList = new ArrayList();
        mPrimaryList.add("A1");
        mPrimaryList.add("A2");
        mPrimaryList.add("A3");
        mJuniorList = new ArrayList();
        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();
        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>();
        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");
    }
}

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

這裡寫圖片描述

這裡寫圖片描述

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

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved