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

  • Post author:
  • Post category:其他


前言:

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

效果图:


结构分析:

整个控件我么用一个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,现实情况下,我们的数据也是通常要去网络获取,所以不能写死。

/**
 * 筛选菜单的 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 里面来
        // 当菜单是打开的状态 不要执行动画只要切换
    }

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



版权声明:本文为qq_39312146原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。