欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

自定义view——仿酷狗的侧滑菜单

程序员文章站 2022-05-30 20:33:47
...

自定义view——仿酷狗的侧滑菜单

直接贴源码:注解内容里面都有

public class SlidingMenu extends HorizontalScrollView {
    private final int mMenuWidth;
    private View mMenuView;
    private View mContentView;
    //GestureDetector处理快速滑动
    private GestureDetector mGestureDetector;
    // 7.手指快速滑动 - 菜单是否打开
    private boolean mMenuIsOpen = false;
    private boolean mIsIntercept = false;

    public SlidingMenu(Context context) {
        this(context, null);
    }

    public SlidingMenu(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
        float rightMarign = ta.getDimension(R.styleable.SlidingMenu_menuRightMargin, dip2px(context, 50));
        //菜单的宽度是屏幕的宽度-右边的一部分距离(自定义属性)
        mMenuWidth = (int) (getScreenWidth(context) - rightMarign);
        ta.recycle();
        mGestureDetector = new GestureDetector(context, mGestureListener);
    }

    private GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            //快速滑动
            Log.e("TAG", "velocityX—>" + velocityX);//向右滑动大于0,向左滑动小于0

            // Bug  判断左右还是上下   只有左右快速滑动才切换
            if(Math.abs(velocityY)>Math.abs(velocityX)){
                return super.onFling(e1, e2, velocityX, velocityY);//默认返回的是false
            }
            if (mMenuIsOpen) {//菜单是打开的,且当向左滑动的时候关闭菜单
                if (velocityX < 0) {
                    closeMenu();
                    return true;
                }
            } else {//菜单是关闭的,且当向右滑动的时候打开菜单
                if (velocityX > 0) {
                    openMenu();
                    return true;
                }
            }
            return super.onFling(e1, e2, velocityX, velocityY);//默认返回的是false
        }
    };

    //1.宽度不对,指定宽高
    @Override
    protected void onFinishInflate() {
        //这个方法是布局解析完毕
        super.onFinishInflate();
        //指定宽高
        ViewGroup container = (ViewGroup) getChildAt(0);//LinearLayout
        //1.内容的宽度是屏幕的宽度
        int childCount = container.getChildCount();
        if (childCount != 2) {
            throw new RuntimeException("只能放置两个子View!");
        }
        mContentView = container.getChildAt(1);

        ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
        layoutParams.width = getScreenWidth(getContext());
        mContentView.setLayoutParams(layoutParams);
        //2.菜单的宽度是屏幕的宽度-右边的一部分距离(自定义属性)
        mMenuView = container.getChildAt(0);
        ViewGroup.LayoutParams menuParams = mMenuView.getLayoutParams();
        menuParams.width = mMenuWidth;
        mMenuView.setLayoutParams(menuParams);
    }

    //4.处理右边的缩放,左边的缩放和透明度,需要获取当前不断变化的位置
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        //Log.e("TAG", "l -> " + l);// 变化是 mMenuWidth - 0
        float scale = 1f * l / mMenuWidth;//变化是1->0
        //右边的缩放 最小0.7f,最大1f
        float rightScale = 0.7f + 0.3f * scale;
        //设置右边的缩放,默认是中心点(ViewCompat兼容)
        //设置中心点
        ViewCompat.setPivotX(mContentView, 0);
        ViewCompat.setPivotY(mContentView, mContentView.getMeasuredHeight() / 2);
        ViewCompat.setScaleX(mContentView, rightScale);
        ViewCompat.setScaleY(mContentView, rightScale);

        //左边缩放和透明度变化
        //缩放是从0.7到1完全展开
        float leftScale = 0.7f + (1 - scale) * 0.3f;
        ViewCompat.setScaleX(mMenuView, leftScale);
        ViewCompat.setScaleY(mMenuView, leftScale);
        //透明度,半透明到完全透明
        float leftAlpha = 0.5f + (1 - scale) * 0.5f;
        ViewCompat.setAlpha(mMenuView, leftAlpha);
        // 最后一个效果 退出这个按钮刚开始是在右边,安装我们目前的方式永远都是在左边
        // 设置平移,先看一个抽屉效果
        // ViewCompat.setTranslationX(mMenuView,l);
        // 平移 l*0.25f
        ViewCompat.setTranslationX(mMenuView, 0.25f * l);
    }

    //点击右边内容部分关闭菜单
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        mIsIntercept = false;
        if (mMenuIsOpen) {
            float currentX = ev.getX();
            if (currentX > mMenuWidth) {
                //关闭菜单
                closeMenu();
                //消费当前事件,拦截子view的事件,但是此时会调用自己的onTouchEvent事件
                mIsIntercept = true;
                return true;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //2.默认应该是关闭
        scrollTo(mMenuWidth, 0);
    }

    //3.当抬起的时候二选一,要么关闭要么打开
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mIsIntercept) {//当是拦截事件,消费当前事件
            return true;
        }
        if (mGestureDetector.onTouchEvent(ev)) {//当点击了快速滑动,则消费当前事件
            return true;
        }
        // 1. 获取手指滑动的速率,当期大于一定值就认为是快速滑动 , GestureDetector(系统提供好的类)
        // 2. 处理事件拦截 + ViewGroup 事件分发的源码实践
        //    当菜单打开的时候,手指触摸右边内容部分需要关闭菜单,还需要拦截事件(打开情况下点击内容页不会响应点击事件)

        if (ev.getAction() == MotionEvent.ACTION_UP) {
            int currentScrollX = getScrollX();
            if (currentScrollX > mMenuWidth / 2) {
                //关闭
                closeMenu();
            } else {
                //打开
                openMenu();
            }
            //确保不会调用super.onTouchEvent(ev),否则会不起作用
            return true;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 打开菜单
     */
    private void openMenu() {
        //smoothScrollTo有动画
        smoothScrollTo(0, 0);
        mMenuIsOpen = true;
    }

    /**
     * 关闭菜单
     */
    private void closeMenu() {
        //smoothScrollTo有动画
        smoothScrollTo(mMenuWidth, 0);
        mMenuIsOpen = false;
    }

    /**
     * Dip into pixels
     */
    private int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 获得屏幕宽度
     */
    private int getScreenWidth(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.widthPixels;
    }
}

在上面的基础上实现QQ6.0侧滑效果

自定义view——仿酷狗的侧滑菜单

首先去掉缩放,其次添加阴影,缩放把之前代码直接注解掉就可以了

给内容布局添加阴影:首先将内容布局抠出来,然后套一个Relayout布局添加内容和阴影,然后再设置回去

 //1.内容的宽度是屏幕的宽度
        int childCount = container.getChildCount();
        if (childCount != 2) {
            throw new RuntimeException("只能放置两个子View!");
        }
        mContentView = container.getChildAt(1);
        ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
        //内容布局部分添加阴影
        //把内容布局提取出来
        container.removeView(mContentView);
        //在外面添加一层阴影
        RelativeLayout contentContainer = new RelativeLayout(getContext());
        contentContainer.addView(mContentView);
        mShadowView = new View(getContext());
        mShadowView.setBackgroundColor(Color.parseColor("#55000000"));
        contentContainer.addView(mShadowView);
        //最后把容器放回原来的位置
        layoutParams.width = getScreenWidth(getContext());
        contentContainer.setLayoutParams(layoutParams);
        container.addView(contentContainer);
        mShadowView.setAlpha(0);

onScrollChanged方法中动态设置透明度

 float scale = 1f * l / mMenuWidth;//变化是1->0
 float alpha=1-scale;
 mShadowView.setAlpha(alpha);
 ViewCompat.setTranslationX(mMenuView, 0.6f * l);