美文网首页Android技术知识
自定义View - 仿酷狗音乐主页面侧滑效果

自定义View - 仿酷狗音乐主页面侧滑效果

作者: 搬砖小老弟 | 来源:发表于2022-04-25 13:42 被阅读0次

    作者:Delusion
    链接:https://juejin.cn/post/7085922621761519623

    概述

    最近在用酷狗音乐时发现酷狗音乐的主页侧滑效果不错,忍不住手痒痒,就想实现一下看看

    效果分析

    • 我们可以看出分为两个部分 (菜单页面、主题页面)所以这里采用自定义ViewGroup
    • 向左滑动时会有一个放大和缩小的动画,在这里基本可以确定需要处理触摸事件,及触摸时的动画
    • 快速滑动时的打开关闭处理(GestureDetector收拾处理类)

    实现思路

    这里我们选择继承HorizontalScrollView 水平滚动布局 来实现,然后写好布局文件这个和HorizontalScrollView的用法一样,初时进来滚动到只有主页面的地方,在onTouch中根据滑动的距离来控制布局的缩放动画

    实现

    public KGSlidingMenu(Context context) {
        this(context, null);
    }
    
    public KGSlidingMenu(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    
    public KGSlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    

    运行起来之后发现布局不对,完全乱了,这里我们在代码里设置菜单页面、主题页面的宽度

    • 菜单页面的宽度 这里我们定义一个属性 menu_width
    • 主题页面的宽度 = 屏幕的宽度
    private View mMenuView;
    private View mContainerView;
    private int mMenuWidth;//需要自定义属性获取
    
    
    
    public KGSlidingMenu(Context context) {
        this(context, null);
    }
    
    public KGSlidingMenu(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    
    public KGSlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.KGSlidingMenu);
        mMenuWidth = Math.round(ta.getDimension(R.styleable.KGSlidingMenu_menu_width, 325));
        ta.recycle();
     }
     
     
    @Override
    protected void onFinishInflate() {//在onCreate中执行
        super.onFinishInflate();
        //这个方法代表整个布局解析完毕
        //指定宽高 :内容页的宽度为屏幕的宽度、菜单页的宽度由使用者自己在xml中定义  app:menu_width=""
        //获取菜单和容器View
        //获取的是LinearLayout
        ViewGroup mRootView = (ViewGroup) getChildAt(0);
        if (mRootView == null) {
            throw new NullPointerException("child can not be null !!!");
        }
        if (mRootView.getChildCount() != 2) {
            throw new RuntimeException("Only two can be placed View !!!");
        }
        //获取的是菜单和容器的跟布局
        mMenuView = mRootView.getChildAt(0);
        mContainerView = mRootView.getChildAt(1);
        //给菜单布局设置指定宽度  给容器布局设置屏幕的宽高
        //只能通过layoutParams设置宽高
        mMenuView.getLayoutParams().width = mMenuWidth;
        mContainerView.getLayoutParams().width = getScreenWidth(mContext);
      
    }
    
    //获取屏幕宽度
    public int getScreenWidth(Context mContext) {
        WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(displayMetrics);
        return displayMetrics.widthPixels;
    }
    

    目前的效果就是可以滑动,并且菜单和主页面内容的布局宽度正常,现在存在的问题:

    • 在刚进来时,菜单是打开的,正常情况应该是关闭的
    • 在手指抬起的时候,应该根据当前位置去判断菜单时打开,还是关闭
    • 在滑动时 没有相应的动画

    第一点 在刚进来时,菜单是打开的,这一点我们可以在布局摆放完成时,是我们KGSlidingMenu滚动到相应的位置 具体实现如下

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {//在onResume中执行
        super.onLayout(changed, l, t, r, b);
        //初始化进来 mMenuView 模式是不显示的
        //用来摆放子View,等所有子View  摆放完毕才能滚动
        scrollTo(mMenuWidth, 0);
    }
    

    第二点 在手指抬起的时候,应该根据当前位置去判断菜单时打开,还是关闭。判断菜单打开关闭的条件是我们滑动的距离和菜单的宽做对比

    • 1).在关闭时,向右滑动,如果滑动的距离小于菜单宽度的一半,这是当我们抬起手指时,应当关闭菜单,大于菜单宽度的一半时,应当打开菜单.
    • 2).在打开时,向左滑动,如果滑动的距离小于菜单宽度的一半,这是当我们抬起手指时,应当打开菜单,大于菜单宽度的一半时,应当关闭菜单.

    因此我们需要在onTouchEvent()中处理相应的触摸滑动事件

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mGestureDetector != null) {
            if (mGestureDetector.onTouchEvent(event)) return true; //快速滑动执行了,就不要执行onTouch中手指抬起事件
        }
        if (onInterceptTouchEvent(event)) return true;
    
        //手指抬起
        if (MotionEvent.ACTION_UP == event.getAction()) {
            int mCurrentScrollX = getScrollX();
            float mCurrentScrollY = getScrollY(); //避免竖向触摸时触发事件
            if (Math.abs(mCurrentScrollX) > Math.abs(mCurrentScrollY) * 2 / 3) {
                if (mCurrentScrollX < mMenuWidth / 2) {//未滚动到mMenuView宽度的一半
                    //打开菜单
                    openMenu();
                } else {
                    //关闭菜单
                    closeMenu();
                }
            }
    
            return false;//确保 super.onTouchEvent(event)  不会执行
        }
    
        return super.onTouchEvent(event);
    }
    private void closeMenu() {
        smoothScrollTo(mMenuWidth, 0)
    }
    
    
    private void openMenu() {
        smoothScrollTo(0, 0);
    }
    

    第三点 在滑动时 没有相应的动画 通过效果我们可以看到

    • 1). 右边的动画效果为缩放效果
    • 2). 左边的动画效果有渐变、缩放和位移

    这些动画执行的进度和效果都是根据滑动的距离来决定时,因此我们需要获得滑动的距离

    //滚动回调  处理右边View的缩放  左边View的缩放和透明度
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
       super.onScrollChanged(l, t, oldl, oldt);
       //l:left 变化mMenuWidth -> 0
       //算一个梯度值
       float scale = 1f * l / mMenuWidth; //从1->0
      
       //计算右边的缩放值
       float rightScaleValue = 0.85f + 0.15f * scale;
       //设置右边的缩放  默认以view的中心点缩放
       ViewCompat.setPivotX(mContainerView, 0);
       ViewCompat.setPivotY(mContainerView, mContainerView.getMeasuredHeight() / 2);
       ViewCompat.setScaleX(mContainerView, rightScaleValue);
       ViewCompat.setScaleY(mContainerView, rightScaleValue);
    
       //设置右边的菜单的透明度  由半透明到全透明 0。85-1
       //缩放 0.85-1
    
       //透明度
       float leftAlphaValue = (1 - scale) * 0.5f + 0.5f;
       ViewCompat.setAlpha(mMenuView, leftAlphaValue);
    
       float leftScaleValue = (1 - scale) * 0.15f + 0.85f;
       ViewCompat.setScaleX(mMenuView, leftScaleValue);
       ViewCompat.setScaleY(mMenuView, leftScaleValue);
    
       //刚开始退出是在右边而不是左边
       //设置平移
       // ViewCompat.setTranslationX(mMenuView,l);抽屉效果
       ViewCompat.setTranslationX(mMenuView, 0.2f * l);
       }
    

    到此我们可以看到效果基本成型了,下面处理一下细节方面的优化

    处理一下在手指快速滑动的是效果,如果我们的触摸事件响应了快速滑动的话,就不要执行onTouchEvent中的相关操作,否则会出现效果不对的冲突事件,因此我们需要在onTouchEvent中添加一下这段代码

    if (mGestureDetector != null) {
        if (mGestureDetector.onTouchEvent(event)) return true; //快速滑动执行了,就不要执行onTouch中手指抬起事件
    }
    

    mGestureDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() {
    
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {//快速滑动
            //只要快速滑动就会回调
            //打开的时候往右快速滑动,就去关闭  关闭的时候往左边快速滑动就去打开
            //快速往右边滑动是一个正数  快速往左边滑动是一个负数
            if (Math.abs(velocityX) < Math.abs(velocityY) * 2 / 3) return false;
            if (isMenuOpen) {
                //打开的时候往右快速滑动,就去关闭
                if (velocityX < 0) {
                    closeMenu();
                    return true;
                }
            } else {
                //关闭的时候往左边快速滑动就去打开
                if (velocityX > 0) {
                    openMenu();
                    return true;
                }
            }
            return false;
        }
    });
    

    注意查看的话可以发现当菜单打开时,我们们点击菜单的右边主题部分,不会响应主题页面的相关事件,只会关闭菜单,只有在菜单关闭时,才会触发右边主题部分的相关事件,因此我们这地方需要在拦截事件onInterceptTouchEvent中判断菜单是否关闭,进行事件的拦截,因此我们需要在上面打开关闭菜单的方法里添加一个标识符isMenuOpen来区分菜单是否打开,这里事件拦截之后我们直接关闭了菜单,所以不需要执行onTouchEvent中手指抬起的相关操作。

    if (onInterceptTouchEvent(event)) return true;
    

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (isMenuOpen && event.getX() > mMenuView.getWidth() && event.getAction() == MotionEvent.ACTION_UP) {
            closeMenu();
            return true;
        }
        return false;
    }
    

    至此相关效果基本就完成了,我们来看一下吧

    完工!

    相关文章

      网友评论

        本文标题:自定义View - 仿酷狗音乐主页面侧滑效果

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