仿大众点评多条目下拉菜单筛选

作者: zkxok | 来源:发表于2017-07-26 21:07 被阅读310次

    分析:

    首先来看看我们要实现的效果,下面这张图是大众点评APP里面的一个多条目下拉菜单筛选的一个效果,这是很多App里面都比较常见的一种多条目筛选菜单。

    GIF.gif

    结构分析:

    Screenshot_2017-08-01-20-44-12-15.png

    整个控件我么用一个LinearLayout 实现,所以我们要继承LinearLayout .然后是上面红色长方形部分的Tab栏,下面最外层黑色框框是一个FrameLayout用来存放阴影(绿色框框部分)和菜单布局(最里面红色框框部分)

    实现

    分析完了,我们就可以用代码来实现了,代码如下:

    1、基本布局:

    public class ListDataScreenView extends LinearLayout  {
        private Context mContext;
        // 1.1 创建头部用来存放 Tab
        private LinearLayout mMenuTabView;
        // 1.2 创建 FrameLayout 用来存放 = 阴影(View) + 菜单内容布局(FrameLayout)
        private FrameLayout mMenuMiddleView;
        // 阴影
        private View mShadowView;
        // 创建菜单用来存放菜单内容
        private FrameLayout mMenuContainerView;
        // 阴影的颜色
        private int mShadowColor = 0x88888888;
        // 筛选菜单的 Adapter
        private BaseMenuAdapter mAdapter;
        // 内容菜单的高度
        private int mMenuContainerHeight;
        // 当前打开的位置
        private int mCurrentPosition = -1;
        private long DURATION_TIME = 350;
        // 动画是否在执行
        private boolean mAnimatorExecute;
    
        public ListDataScreenView(Context context) {
            this(context, null);
        }
    
        public ListDataScreenView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public ListDataScreenView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            mContext = context;
            initLayout();
        }
    
        /**
         * 1.布局实例化好 (组合控件)
         */
        private void initLayout() {
            //  1. 先创建一个 xml 布局 ,再加载,findViewById
            //  2. 简单的效果用代码去创建  早期IOS 用代码创建布局
            setOrientation(VERTICAL);
    
            // 1.1 创建头部用来存放 Tab
            mMenuTabView = new LinearLayout(mContext);
            mMenuTabView.setLayoutParams(new LinearLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            addView(mMenuTabView);
    
            // 1.2 创建 FrameLayout 用来存放 = 阴影(View) + 菜单内容布局(FrameLayout)
            mMenuMiddleView = new FrameLayout(mContext);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, 0);
            params.weight = 1;
            mMenuMiddleView.setLayoutParams(params);
            addView(mMenuMiddleView);
    
            // 创建阴影 可以不用设置 LayoutParams 默认就是 MATCH_PARENT ,MATCH_PARENT
            mShadowView = new View(mContext);
            mShadowView.setBackgroundColor(mShadowColor);
            mShadowView.setAlpha(0f);
            mShadowView.setOnClickListener(this);
            mShadowView.setVisibility(GONE);
            mMenuMiddleView.addView(mShadowView);//阴影View在下面
    
            // 创建菜单用来存放菜单内容
            mMenuContainerView = new FrameLayout(mContext);
            mMenuContainerView.setBackgroundColor(Color.WHITE);
            mMenuMiddleView.addView(mMenuContainerView);//菜单内容在上面
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            Log.e("TAG", "onMeasure");
            int height = MeasureSpec.getSize(heightMeasureSpec);
            if (mMenuContainerHeight == 0 && height > 0) {
                // 内容的高度应该不是全部  应该是整个 View的 75%
                mMenuContainerHeight = (int) (height * 75f / 100);
                //获取菜单内容View的LayoutParams
                ViewGroup.LayoutParams params = mMenuContainerView.getLayoutParams();
                //设置菜单内容的高度
                params.height = mMenuContainerHeight;
                mMenuContainerView.setLayoutParams(params);
                // 进来的时候阴影不显示 ,内容也是不显示的(把它移上去)
                //放菜单内容
                mMenuContainerView.setTranslationY(-mMenuContainerHeight);
            }
        }
    
    }
    
    

    2、设置点击事件,以及过程中的动画

     /**
         * 设置tab的点击
         *
         * @param tabView
         * @param position
         */
        private void setTabClick(final View tabView, final int position) {
            tabView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mCurrentPosition == -1) {
                        // 没打开
                        openMenu(position, tabView);
                    } else {
                        if (mCurrentPosition == position) {
                            // 打开了,关闭
                            closeMenu();
                        } else {
                            // 切换一下显示
                            //拿到菜单容器里的子view(TextView)
                            View currentMenu = mMenuContainerView.getChildAt(mCurrentPosition);
                            currentMenu.setVisibility(View.GONE);//将子view(TextView)设置无不可见
                            mAdapter.menuClose(mMenuTabView.getChildAt(mCurrentPosition));
    
                            //拿到当前点击的位置
                            mCurrentPosition = position;
                            currentMenu = mMenuContainerView.getChildAt(mCurrentPosition);
                            currentMenu.setVisibility(View.VISIBLE);
                            mAdapter.menuOpen(mMenuTabView.getChildAt(mCurrentPosition));
                        }
                    }
                }
            });
        }
    
        /**
         * 关闭菜单
         */
        private void closeMenu() {
            //动画正在执行,点击无效
            if (mAnimatorExecute) {
                return;
            }
            // 关闭动画  位移动画  透明度动画
            ObjectAnimator translationAnimator = ObjectAnimator.ofFloat(mMenuContainerView, "translationY", 0, -mMenuContainerHeight);
            translationAnimator.setDuration(DURATION_TIME);
            translationAnimator.start();
            mShadowView.setVisibility(View.VISIBLE);
            ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(mShadowView, "alpha", 1f, 0f);
            alphaAnimator.setDuration(DURATION_TIME);
            // 要等关闭动画执行完才能去隐藏当前菜单
            alphaAnimator.addListener(new AnimatorListenerAdapter() {
                //动画执行完毕
                @Override
                public void onAnimationEnd(Animator animation) {
                    View menuView = mMenuContainerView.getChildAt(mCurrentPosition);
                    menuView.setVisibility(View.GONE);
                    mCurrentPosition = -1;
                    mShadowView.setVisibility(GONE);
                    mAnimatorExecute = false;
                }
    
                //关闭菜单动画开始
                @Override
                public void onAnimationStart(Animator animation) {
                    mAnimatorExecute = true;
                    mAdapter.menuClose(mMenuTabView.getChildAt(mCurrentPosition));
                }
            });
            alphaAnimator.start();
        }
    
        /**
         * 打开菜单
         *
         * @param position
         * @param tabView
         */
        private void openMenu(final int position, final View tabView) {
            //动画正在执行,点击无效
            if (mAnimatorExecute) {
                return;
            }
            //设置阴影可见
            mShadowView.setVisibility(View.VISIBLE);
            // 获取当前位置显示当前菜单,菜单是加到了菜单容器
            View menuView = mMenuContainerView.getChildAt(position);
            menuView.setVisibility(View.VISIBLE);
    
            // 打开开启动画  位移动画  透明度动画
            //位移动画
            ObjectAnimator translationAnimator = ObjectAnimator.ofFloat(mMenuContainerView, "translationY", -mMenuContainerHeight, 0);
            translationAnimator.setDuration(DURATION_TIME);
            translationAnimator.start();
    
            //透明度动画
            ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(mShadowView, "alpha", 0f, 1f);
            alphaAnimator.setDuration(DURATION_TIME);
            alphaAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mAnimatorExecute = false;
                    mCurrentPosition = position;
                }
    
                @Override
                public void onAnimationStart(Animator animation) {
                    mAnimatorExecute = true;
                    // 把当前的 tab 传到外面
                    mAdapter.menuOpen(tabView);
                }
            });
            alphaAnimator.start();
        }
    

    3、写Adapter提供数据源

    这个时候我们的效果还是死的,我么得给他设置写一个Adapter,现实情况下,我们的数据也是通常要去网络获取,所以不能写死。

    >BaseMenuAdapter 
    /**
     * 筛选菜单的 Adapter
     */
    
    public abstract class BaseMenuAdapter {
        // 获取总共有多少条
        public abstract int getCount();
        // 获取当前的TabView
        public abstract View getTabView(int position, ViewGroup parent);
        // 获取当前的菜单内容
        public abstract View getMenuView(int position, ViewGroup parent);
    
        /**
         * 菜单打开
         * @param tabView
         */
        public void menuOpen(View tabView) {
    
        }
    
        /**
         * 菜单关闭
         * @param tabView
         */
        public void menuClose(View tabView) {
    
        }
    }
    
    

    具体的Adapter:

    public class ListScreenMenuAdapter extends BaseMenuAdapter{
        private Context mContext;
    
        public ListScreenMenuAdapter(Context context){
            this.mContext = context;
        }
    
        private String[] mItems ={"附近","美食","智能排序","筛选"};
    
        @Override
        public int getCount() {
            return mItems.length;
        }
    
        @Override
        public View getTabView(int position, ViewGroup parent) {
            TextView tabView = (TextView) LayoutInflater.from(mContext).inflate(R.layout.ui_list_data_screen_tab,parent,false);
            tabView.setText(mItems[position]);
            tabView.setTextColor(Color.BLACK);
            return tabView;
        }
    
        @Override
        public View getMenuView(int position, ViewGroup parent) {
            // 真正开发过程中,不同的位置显示的布局不一样
            TextView menuView = (TextView) LayoutInflater.from(mContext).inflate(R.layout.ui_list_data_screen_menu,parent,false);
            menuView.setText(mItems[position]);
            return menuView;
        }
    
        @Override
        public void menuClose(View tabView) {
            TextView tabTv = (TextView) tabView;
            tabTv.setTextColor(Color.BLACK);
        }
    
        @Override
        public void menuOpen(View tabView) {
            TextView tabTv = (TextView) tabView;
            tabTv.setTextColor(Color.RED);
        }
    }
    
    
    

    有了Adapter之后,我们就可以给ListDataScreenView 以下方法,是不是感觉跟ListView很像。

     /**
         * 设置 Adapter
         *
         * @param adapter
         */
        public void setAdapter(BaseMenuAdapter adapter) {
            this.mAdapter = adapter;
            // 获取有多少条
            int count = mAdapter.getCount();
            for (int i = 0; i < count; i++) {
                // 获取菜单的Tab
                View tabView = mAdapter.getTabView(i, mMenuTabView);
                mMenuTabView.addView(tabView);//加一个TextView
                LinearLayout.LayoutParams params = (LayoutParams) tabView.getLayoutParams();
                params.weight = 1;
                tabView.setLayoutParams(params);
    
                // 设置tab点击事件
                setTabClick(tabView, i);
    
                // 获取菜单的内容(一个TextView)
                View menuView = mAdapter.getMenuView(i, mMenuContainerView);
                menuView.setVisibility(GONE);
                mMenuContainerView.addView(menuView);
            }
            // 内容还没有显示出来,打开的时候显示当前位置的菜单,关闭的时候隐藏,阴影点击应该要关闭菜单
            // 动画在执行的情况下就不要在响应动画事件
            // 打开和关闭 变化tab的显示 , 肯定不能把代码写到 ListDataScreen 里面来
            // 当菜单是打开的状态 不要执行动画只要切换
        }
    
       
    

    最后看下我们实现的效果:

    GI88F.gif

    全部代码下载地址:https://github.com/zkxok/MultipleItemSelection

    相关文章

      网友评论

        本文标题:仿大众点评多条目下拉菜单筛选

        本文链接:https://www.haomeiwen.com/subject/ucnokxtx.html