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的变化实时做出响应。

代码实现

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

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

以下是demo的实现效果,点击不同tab,下面菜单实现连续切换:

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

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

时间: 2024-10-07 09:49:21

Android自定义控件:仿美团下拉菜单及相关代码优化的相关文章

MeiTuanRefreshListView高仿美团下拉刷新《IT蓝豹》

MeiTuanRefreshListView高仿美团下拉刷新 MeiTuanRefreshListView高仿美团下拉刷新,本项目来自:https://github.com/nugongshou110/MeiTuanRefreshListView项目主要构成部分:自定义MeiTuanRefreshFirstStepView,MeiTuanRefreshSecondStepView,MeiTuanRefreshThirdStepView,其中自定义MeiTuanListView继承了ListVie

仿QQ下拉菜单

<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="description" content=""><meta name="keywords" content=""><script src="http://libs.baidu.com/jquery/1

仿美团下拉的二级菜单

@interface JSIndexPath : NSObject @property (nonatomic, assign) NSInteger column; @property (nonatomic, assign) NSInteger leftOrRight; @property (nonatomic, assign) NSInteger leftRow; @property (nonatomic, assign) NSInteger row; - (instancetype)initW

Android—自定义控件实现ListView下拉刷新

这篇博客为大家介绍一个android常见的功能——ListView下拉刷新(参考自他人博客,网址忘记了,阅读他的代码自己理解注释的,希望能帮助到大家): 首先下拉未松手时候手机显示这样的界面: 下面的代码是自定的扎样的控件: package com.dhsr.smartID.view; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import andr

Android ActionBar中的下拉菜单

在ActionBar中添加下拉菜单,主要有一下几个关键步骤: 1. 生成一个SpinnerAdapter,设置ActionBar的下拉菜单的菜单项 2. 实现ActionBar.OnNavigationListener接口,当点击ActionBar的菜单项是进行相应的操作 3. 调用setNavigationMode()方法将ActionBar的操作模型设置为ActionBar.NAVIGATION_MODE_LIST. 注意:这个步骤应该在Activity的onCreate()回调函数时执行

Android 自定义仿IOS上拉菜单实现

最近在做一个歪果仁给我外包的项目,主页需要做一个类似于IOS那种上拉菜单的功能,于是一时间试了各种方法,什么Spinner.Drawlayout,SlidingMenu等等等等,都搞不了,后面实在被逼无奈自己写了一个上拉菜单控件,居然还能凑合着用! 姑且可以叫他MyPullUpMenu! 有时间我会封装一下发到GitHub. 效果图如下: 实现的功能有仨: 1.上拉位置未超过一定距离时,松开自动往下滚动. 2.上拉位置超过一定距离时,松开自动网上滚动直至菜单全展开. 3.菜单滚动到顶部并停止滚动

Android自定义控件——ListView的下拉刷新与上拉加载

转载请注明出处:http://blog.csdn.net/allen315410/article/details/39965327 1.简介 无疑,在Android开发中,ListView是使用非常频繁的控件之一,ListView提供一个列表的容易,允许我们以列表的形式将数据展示到界面上,但是Google给我们提供的原生ListView的控件,虽然在功能上很强大,但是在用户体验和动态效果上,还是比较差劲的.为了改善用户体验,市面上纷纷出现了各种各样的自定义的ListView,他们功能强大,界面美

Android自定义控件——仿优酷圆盘菜单

尊重作者劳动成果,转载时请标明该文章出自  http://blog.csdn.net/allen315410/article/details/39232535 最近学习的时候,看见一份资料上教怎么写自定义控件,上面的示例用的是优酷早期版本的客户端,该客户端的菜单就是一个自定义的组件(现在的版本就不清楚有没有了,没下载过了),好吧,废话不多说,先上优酷的原型图. 这个自定义组件感官上看是,里外三层设计,每一层上有布置不同的菜单按钮,每一层又设置了进入和退出的动画,来增强用户的体验效果.这种设计非常

Android开发仿微信下拉关闭图片11

图片会跟随手指移动,只有是下滑时才会退出查看页面,其他情况会复位,直接当做ImageView使用即可,setViewCall方法是在下滑完成后要执行的操作,上,左,右,可自行扩展 onTouchEvent 监听手指坐标,GestureDetector 监听滑动的惯性,ViewHelper设置图片位移动画 public class FriendCircleView extends android.support.v7.widget.AppCompatImageView implements Ges