仿微信小程序下拉组件

1,917 阅读7分钟
原文链接: mp.weixin.qq.com
code小生,一个专注 Android 领域的技术平台 公众号回复 Android 加入我的安卓技术群

作者:colinWong链接:https://www.jianshu.com/p/739bb01eee80 声明:本文已获colinWong 授权发表,转发等请联系原作者授权

1563442819594.gif

设计思路

1.自定义个组件类似RelativeLayout2.可以内部放子View,然后就是滑动主体在前,小程序View在后3.重写dispatchTouchEvent 控制这两个子View的位置4.加上临界点回弹动画5.手势判断(惯性效果)

1.继承RelativeLayout

如果要从新写一个GroupView组件需要measure → layout → draw 很多细节要处理也不一定处理的好。所以直接用系统提供的RelativeLayout

        public class MoreHeadLayout extends RelativeLayout {        ...}

2.内部子View 结构就两个,如下图

image.png
    private View mHeadView;    private NewNestedScrollView mBodyView;   void init(){        mHeadView = findViewById(R.id.head_layout);//view为head        mBodyView = findViewById(R.id.body_layout);    }

xml中的代码

image.png

3.重写dispatchTouchEvent

这一步主要是做控制手指滑动跟随,就是bodyView跟随你的手指滑动先写一个方法,就是控制bodyView纵坐标的位置

   private void setMarginTop(int offY) {        RelativeLayout.LayoutParams paramsBody = (LayoutParams) mBodyView.getLayoutParams();        int marginTop = paramsBody.topMargin - offY;        if (marginTop < 0) {            marginTop = 0;        } else if (marginTop > mHeadView.getHeight()) {            marginTop = mHeadView.getHeight();        }        paramsBody.topMargin = marginTop;        mBodyView.setLayoutParams(paramsBody);    }

再计算滑动距离,然后调用setMarginTop 方法

 @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        int y = (int) ev.getRawY();        int offY = lastY - y;        lastY = y;        switch (ev.getAction()) {             ...            case MotionEvent.ACTION_MOVE:              if (isBodyTop && headViewVisible()) {                    setMarginTop((int) (offY * damp));                    return true;                }                if (isBodyTop && offY < 0) {                    setMarginTop((int) (offY * damp));                    return true;                }                break;              ...    }

这样bodyView 就可以跟随手指动了,若需要一些阻尼效果可以添加系数damp,就是bodyView移动的距离等于手指滑动的距离乘以系数damp。

4.临界点与回弹

先写一个动画方法,该动画方法就是bodyView 从开始的高度移动到结束的高度。

private ValueAnimator mAnim;   /**     * 收起或打开动画     *     * @param start 开始高度     * @param end   结束高度     * @param time  动画时间     */    public void startAnimator(int start, int end, long time) {        if (mAnim != null && mAnim.isRunning()) {            mAnim.cancel();        }        mAnim = ValueAnimator.ofInt(start, end);        mAnim.setDuration(time);        mAnim.setTarget(mBodyView);        mAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                int currentValue = (Integer) animation.getAnimatedValue();                RelativeLayout.LayoutParams params = (LayoutParams) mBodyView.getLayoutParams();                params.topMargin = currentValue;                mBodyView.setLayoutParams(params);            }        });        mAnim.start();    }

然后设置一个触发的临界点

    private float closeOrOpen = 0.4f;//动画关闭还是打开的点(相对于头部高度的比例)

触发时机例如手指抬起事件

            case MotionEvent.ACTION_UP:            ...             if (mBodyView.getTop() < mHeadView.getHeight() * closeOrOpen) {                        startAnimator(mBodyView.getTop(), 0);//收起动画                    } else {                        startAnimator(mBodyView.getTop(), mHeadView.getMeasuredHeight());//打开动画                    }

5.手势判断(惯性效果)

添加手势判断会有更好的使用体验效果,方法如下

    private GestureDetector mGestureDetector;    mGestureDetector = new GestureDetector(getContext(), new MoreGestureListener()); class MoreGestureListener implements GestureDetector.OnGestureListener {        @Override        public boolean onDown(MotionEvent e) {            mIsHaveScrolled = false;            return false;        }        @Override        public void onShowPress(MotionEvent e) {        }        @Override        public boolean onSingleTapUp(MotionEvent e) {            return false;        }        @Override        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {            mIsHaveScrolled = true;            return false;        }        @Override        public void onLongPress(MotionEvent e) {        }        @Override        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {            //当头部显示并且向上fling时候关闭头部(惯性视觉)            if (mBodyView.getTop() > 0 && velocityY < 0) {                startAnimator(mBodyView.getTop(), 0);            }            return false;        }    }

在dispatchTouchEvent 方法里每个事件下面把事件传进去

                mGestureDetector.onTouchEvent(ev);//为什么不放在最前面,因为会比关闭/打开动画先触发

其他

大概的雏形就是这样可以扩展其他功能如,三个小点或者震动、headView跟随bodyView 移动等

附上用例地址https://github.com/collinWong/wx_drawer

推荐阅读仿简书动态 searchview 的实现,代码就这么多点如何阅读代码(八点要记牢)

扫一扫 关注我的公众号 如果你想要跟大家分享你的文章,欢迎投稿~