背景

最近的项目中用到了类似美团中的下拉多选菜单,在实际开发过程中,也发现了一些问题,主要归纳如下:

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);//type1mSubjectHolder = new SubjectHolder(mContext);mSubjectHolder.refreshData(mSubjectDataList, 0, -1);mSubjectHolder.setOnRightListViewItemSelectedListener(new SubjectHolder.OnRightListViewItemSelectedListener() {@Overridepublic 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);}});//type2mSortHolder = new SortHolder(mContext);mSortHolder.setOnSortInfoSelectedListener(new SortHolder.OnSortInfoSelectedListener() {@Overridepublic void onSortInfoSelected(String info) {if(mOnMenuSelectDataChangedListener != null){mOnMenuSelectDataChangedListener.onSortChanged(info);}dismissPopupWindow();mSortText.setText(getSortString(info));//Toast.makeText(UIUtils.getContext(), info, Toast.LENGTH_SHORT).show();}});//type3mSelectHolder = new SelectHolder(mContext);mSelectHolder.setOnSelectedInfoListener(new SelectHolder.OnSelectedInfoListener() {@Overridepublic 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;}@Overrideprotected 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() {@Overridepublic void onClick(View v) {if(mOnMenuSelectDataChangedListener != null){mOnMenuSelectDataChangedListener.onViewClicked(mSubjectView);}handleClickSubjectView();}});//点击 type2 弹出菜单mSortView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if(mOnMenuSelectDataChangedListener != null){mOnMenuSelectDataChangedListener.onViewClicked(mSortView);}handleClickSortView();}});//点击 type3 弹出菜单mSelectView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if(mOnMenuSelectDataChangedListener != null){mOnMenuSelectDataChangedListener.onViewClicked(mSelectView);}handleClickSelectView();}});//点击黑色半透明部分,菜单收回mContentLayout.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {dismissPopupWindow();}});}private void handleClickSubjectView(){//清空内容View中的ViewmMainContentLayout.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

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

  1. 仿美团下拉菜单 html,React Native仿美团下拉菜单的实例代码

    本文介绍了React Native仿美团下拉菜单的实例代码,最近也在学习React Native,顺便分享给大家 在很多产品中都会涉及到下拉菜单选择功能,用的最好的当属美团了,其效果如下: 要实现上面 ...

  2. Android仿美团下拉菜单

    下拉菜单现在在项目中挺常见的,比如说我现在写的这个项目六个小模块都用到,所以当项目完成之后,我把一些需要注意的项在这里整理出来,希望对大家也能有所帮助: 当然,为了简单,我也是在网上找了一个第三方的控 ...

  3. React Native仿美团下拉菜单

    本篇博客转自:http://blog.csdn.net/xiangzhihong8/article/details/76862097 在很多产品中都会涉及到下拉菜单选择功能,用的最好的当属美团了,其效 ...

  4. android组件的下拉回弹,Android自定义控件仿ios下拉回弹效果

    网上有很多类似的文章,大多数还是继承listview来实现(主要是listview.addHeaderView()和listview.addFooterView在listview的首尾添加view,也 ...

  5. android 美团下拉菜单,Android仿美团分类下拉菜单实例代码

    本文实例为大家分享了Android仿美团下拉菜单的实现代码,分类进行选择,供大家参考,具体内容如下 效果图 操作平台 AS2.0 第三方框架:butterknife build.gradle depe ...

  6. 仿Ios下拉菜单,android Spinner效果(美团下拉效果)

    先上效果图: 直接上代码: Ios spinner文件 package com.choe.iosspinner;import android.app.Activity; import android. ...

  7. 仿美团下拉刷新控件(一)

    如果想学习更多进阶知识,可以关注我的微信公众号:Android小菜. 也可以直接扫描二维码关注: 转载本专栏文章,请注明出处,尊重原创 .文章博客地址:道龙的博客 很有幸能进入美团.本文就仿写一下美团 ...

  8. 仿美团下拉刷新控件(二)

    如果想学习更多进阶知识,可以关注我的微信公众号:Android小菜. 也可以直接扫描二维码关注: 转载本专栏文章,请注明出处,尊重原创 .文章博客地址:道龙的博客 本篇是实现仿美团下拉刷新控件的第二篇 ...

  9. android二级菜单实现,Android编程实现二级下拉菜单及快速搜索的方法

    本文实例讲述了Android编程实现二级下拉菜单及快速搜索的方法.分享给大家供大家参考,具体如下: 一.我们要做什么? 上面有个搜索框,下面是一个二级下拉菜单. 输入查询内容,下面列表将显示查询结果. ...

最新文章

  1. 如何解决在数据显示的时候,页面出现null的情况
  2. python中Json、os、sys、hashlib等内置模块
  3. 数据库-优化-MYSQL的执行顺序
  4. grad在python什么模块_深度学习(Deep Learning)基础概念1:神经网络基础介绍及一层神经网络的python实现...
  5. [scikit-learn 机器学习] 8. 非线性分类和决策树
  6. bmp文件头_图像算法原理与实践——图像文件存储
  7. PostgreSQL 性能优化方法 - 1
  8. 开课吧:分享C++代码的整洁之道!
  9. 应用安全-安全设备-Waf系列-软Waf-安全狗(Safedog)
  10. 三个比较经典的策略: Dual Thrust、R-Breaker、Dynamic Breakout II
  11. 【毕业设计】深度学习YOLO安检管制物品识别与检测 - python opencv
  12. python turtle 海龟绘图详解(官方文档中文版)
  13. KeyError问题求助
  14. java xmladapte_三步解决JAXB生成XML包含CDATA问题—JAVA编程
  15. 腾讯混合云存储 TStor 系列再添新成员,并行存储一体机正式发布​
  16. SAP UI5 应用开发教程之七十九 - 采用测试驱动开发理念(Test Driven Development)进行 SAP UI5 应用的功能开发(一)的试读版
  17. 为什么安装step7时要重启计算机,step7安装提示重启怎么解决
  18. 轻松理解 客户端和服务器端的区别
  19. 宝塔面板PM2 nodejs自动化部署
  20. php获取用户豆瓣电影,php代码获取豆瓣网上电影信息的简介

热门文章

  1. HTML+CSS+JavaScript❤制作浪漫气球520告白相册❤
  2. mysql 怎么反选_MySQL 范式与反范式的选择 | 剑花烟雨江南
  3. el-date-picker change事件不触发
  4. DataFrame分组排序
  5. python3安装urllib2_python3.6想使用urllib2包怎么办
  6. TKImageView-可定制样式的图片裁剪工具类
  7. 微信小程序自定义navbar
  8. Ant Design Vue 新手入门使用教程
  9. 吸顶灯怎么固定天花板_如何将物品固定在天花板上同时不破坏天花板和墙面
  10. 移动硬盘插入提示需要格式化RAW_U盘格式化造成文件丢失的问题解答【迷你兔数据恢复吧】...