仿微信下拉小程序列表展示java版

        最近开发遇到一个需求,要仿微信下拉出现类似小程序的页面,拿到需求第一时间当然是网上先搜索一通,最后发现了一个博主写过一个高仿版的,参考高仿微信下拉小程序入口动画 - 掘金。但是,由于这篇文章里面的代码是kotlin的,本人对kotlin不是很精通,我们自己的项目又是java语言的,所以我就在此基础上,照葫芦画瓢,搞了一个java版的。

      主要的两个类就是下面这两个:

public class WXMainPullViewGroup extends ViewGroup {

    public ViewDragHelper viewDragHelper = ViewDragHelper.create(this, 0.5f, new DragHandler());

    WXPullHeaderMaskView headerMaskView;

    boolean isOpen = false;

    int NAVIGATION_HEIGHT = 100;

    private RecyclerView recyclerView;

    private int bottomMargin = DisplayUtil.dp2px(AppApplication.getContext(),12);


    public WXMainPullViewGroup(Context context) {
        super(context);
    }

    public WXMainPullViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public WXMainPullViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ViewDragHelper viewDragHelper = ViewDragHelper.create((ViewGroup) this, 0.9F, (ViewDragHelper.Callback) (new WXMainPullViewGroup.DragHandler()));
        this.viewDragHelper = viewDragHelper;
        NAVIGATION_HEIGHT = 100;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < getChildCount(); i++) {
            if (getChildAt(i) != headerMaskView) {
                getChildAt(i).layout(l, getPaddingTop(), r, b);
            }
        }
        //Log.i("hty", "onLayout");
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (viewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
        //Log.i("hty", "computeScroll");
    }

    //这里接收的是子布局中的可滑动的布局,可能是NestedScrollView、RecycleView,ListView等
    public void setChildScrollView(RecyclerView recyclerView) {
        this.recyclerView = recyclerView;
        //Log.i("hty", "setChildScrollView");
    }

    float mDownY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
//        if (isOpen) {
//            return true;
//        }
        //Log.i("hty", "onInterceptTouchEvent:"+ev.getAction());
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownY = ev.getY();
                viewDragHelper.processTouchEvent(ev);
//                Log.e("hty", "mDownY: " + mDownY);
//                Log.e("hty", "isOpen: " + isOpen);
                break;
            case MotionEvent.ACTION_MOVE:
                float moveY = ev.getY();
//                Log.e("hty", "moveY: " + moveY);
//                Log.e("hty", " scroll is " + nestedScrollView.canScrollVertically(-1));
                if ((moveY - mDownY) > 4 && !recyclerView.canScrollVertically(-1)) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent event) {
        //Log.i("hty", "onTouchEvent y : " + event.getY());
        this.viewDragHelper.processTouchEvent(event);
        return true;
    }

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Long startTime = System.currentTimeMillis();
        this.measureChildren(widthMeasureSpec, heightMeasureSpec);
        //Log.i("hty", "onMeasure 耗时: " + (System.currentTimeMillis()-startTime));
    }

    public final void createMaskView() {
        if (this.headerMaskView == null) {
            this.headerMaskView = new WXPullHeaderMaskView(this.getContext(), (AttributeSet) null, 0);
            this.addView((View) this.headerMaskView);
        }

    }

    public void setOpen(boolean open) {
        isOpen = open;
    }

    public boolean getOpen(){
        return isOpen;
    }

    class DragHandler extends ViewDragHelper.Callback {

        @Override
        public boolean tryCaptureView(@NonNull View child, int pointerId) {
            return child instanceof FrameLayout;
        }

        @Override
        public void onViewDragStateChanged(int state) {
            super.onViewDragStateChanged(state);
            //Log.e("hty", "onViewDragStateChanged....state "+state);
        }

        @Override
        public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            //Log.i("hty", "onViewPositionChanged start...");
            createMaskView();
            View programView = getChildAt(0);
            BigDecimal divide = (new BigDecimal(String.valueOf(top))).divide(new BigDecimal(getMeasuredHeight() - NAVIGATION_HEIGHT), 4, 4);
            divide = divide.multiply(new BigDecimal("100"));
            divide = divide.multiply(new BigDecimal("0.002"));
            divide = divide.add(new BigDecimal("0.8"));

            if (!isOpen) {
                programView.setScaleX(divide.floatValue());
                programView.setScaleY(divide.floatValue());
            } else {
                programView.setTop(getPaddingTop() + (-((getMeasuredHeight() - NAVIGATION_HEIGHT) - top)));
            }
            WXPullHeaderMaskView headerMaskView1 = headerMaskView;
            if (headerMaskView1 == null) {
                return;
            }
            headerMaskView1.maxHeight = getMeasuredHeight() / 3;
            headerMaskView1.layout(0, getPaddingTop(), getMeasuredWidth(), top);
            float progress = Float.parseFloat(top + "") / Float.parseFloat(((getMeasuredHeight() - (NAVIGATION_HEIGHT + getPaddingTop())) / 3) + "") * 100;
            headerMaskView1.setProgress(progress, getMeasuredHeight() - (NAVIGATION_HEIGHT + getPaddingTop()));

            if (onProgressListener != null) {
                onProgressListener.onProgress(progress);
            }
            if (top == getPaddingTop()) {
                isOpen = false;
//                Log.e("hty","onViewPositionChanged isOpen = false");
            }

            //Log.i("hty", "onViewPositionChanged getMeasuredHeight: " + getMeasuredHeight());
            if (top == getMeasuredHeight() - NAVIGATION_HEIGHT - bottomMargin) {
                isOpen = true;
//                Log.e("hty","onViewPositionChanged isOpen = true");
            }

//            Log.i("hty", "onViewPositionChanged end... isopen is " + isOpen);
        }

        public void onViewCaptured(View capturedChild, int activePointerId) {
            super.onViewCaptured(capturedChild, activePointerId);
            View programView = getChildAt(0);
            programView.setTop(getPaddingTop());
        }


        public void onViewReleased(View releasedChild, float xvel, float yvel) {
//            Log.e("hty", "onViewReleased start... isopen is " + isOpen);
            releaseChild = releasedChild;
            if (isOpen || releasedChild.getTop() + getPaddingTop() <= getMeasuredHeight() / 3) {
                viewDragHelper.smoothSlideViewTo(releasedChild, 0, getPaddingTop());
                ViewCompat.postInvalidateOnAnimation((View) WXMainPullViewGroup.this);
                //收起 隐藏小程序页面,显示底部菜单
                if (onPageChangListener != null) {
                    onPageChangListener.onPageClosed();
                }
            } else {
                //此处需要加上首页底部菜单的高度
                int bottom = Math.round(AppApplication.getContext().getResources().getDimension(R.dimen.iconsize_58));
//                Log.e("hty", "onViewReleased2 bottom:" + bottom);
//                Log.e("hty", "getMeasuredHeight:" + getMeasuredHeight());
                viewDragHelper.smoothSlideViewTo(releasedChild, 0, getMeasuredHeight() + bottom - NAVIGATION_HEIGHT - bottomMargin);
                ViewCompat.postInvalidateOnAnimation((View) WXMainPullViewGroup.this);
                //展开 显示小程序页面,并且隐藏菜单底部
                if (onPageChangListener != null) {
                    onPageChangListener.onPageOpened();
                }
            }
        }

        public int clampViewPositionVertical(View child, int top, int dy) {
            return top <= getPaddingTop() ? getPaddingTop() : (int) ((double) child.getTop() + (double) dy / 1.3D);
        }
    }

    private View releaseChild;

    public void resumeView() {
        if (releaseChild != null && viewDragHelper != null) {
            viewDragHelper.smoothSlideViewTo(releaseChild, 0, getPaddingTop());
            ViewCompat.postInvalidateOnAnimation((View) WXMainPullViewGroup.this);
        }
    }

    private OnPageChangListener onPageChangListener;

    public interface OnPageChangListener {
        void onPageOpened();

        void onPageClosed();
    }

    public void setOnPageChangeListener(OnPageChangListener onPageChangeListener) {
        this.onPageChangListener = onPageChangeListener;
    }

    private OnProgressListener onProgressListener;

    public interface OnProgressListener {
        void onProgress(float progress);
    }

    public void setOnProgressListener(OnProgressListener onProgressListener) {
        this.onProgressListener = onProgressListener;
    }
}
public class WXPullHeaderMaskView extends View {

    private boolean isVibrator = false;
    private int progress = 0;
    public int maxHeight = 0;
    private float CIRCLE_MAX_SIZE = 32;
    private int parentHeight = 0;
    private Paint paint = new Paint();
    private float DEFAULT_CIRCLE_SIZE = 8f;
    private Context context;

    public WXPullHeaderMaskView(Context context) {
        super(context);
    }

    public WXPullHeaderMaskView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public WXPullHeaderMaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        this.context = context;
        setBackgroundColor(Color.argb(255, 40, 40, 40));
        paint.setAlpha(255);
        paint.setAntiAlias(true);
        paint.setColor(ContextCompat.getColor(context, R.color.color_grey_d0d0d0));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float width = Float.parseFloat(getWidth() + "");
        float height = Float.parseFloat(getHeight() + "");
        float value = height / maxHeight;

        if (height <= maxHeight / 2) {
            canvas.drawCircle(width / 2, height / 2, CIRCLE_MAX_SIZE * value, paint);
        } else {
            if (progress < 100) {
                float diff = (value - 0.5f) * CIRCLE_MAX_SIZE;
                canvas.drawCircle((width / 2 - ((0.4f - value) * 100)), height / 2, DEFAULT_CIRCLE_SIZE, paint);
                canvas.drawCircle((width / 2 + ((0.4f - value) * 100)), height / 2, DEFAULT_CIRCLE_SIZE, paint);
                if ((CIRCLE_MAX_SIZE * 0.5f) - diff <= DEFAULT_CIRCLE_SIZE) {
                    canvas.drawCircle(width / 2, height / 2, DEFAULT_CIRCLE_SIZE, paint);
                } else {
                    canvas.drawCircle(width / 2, height / 2, (CIRCLE_MAX_SIZE * 0.5f) - diff, paint);
                }

            } else {
                paint.setAlpha(getAlphaValue());
                canvas.drawCircle(width / 2, height / 2, DEFAULT_CIRCLE_SIZE, paint);
                canvas.drawCircle(width / 2 - ((0.4f) * 100), height / 2, DEFAULT_CIRCLE_SIZE, paint);
                canvas.drawCircle(width / 2 + (((0.4f) * 100)), height / 2, DEFAULT_CIRCLE_SIZE, paint);
            }
        }
    }

    private int getAlphaValue() {
        int dc = parentHeight / 3 - CheckBoxUtil.getStatusBarHeight();
        float alpha = (Float.parseFloat(getHeight() + "") - dc) / (parentHeight - (dc));
//        Log.e("hty", "dc:" + dc + "   alpha:" + alpha);
        return 255 - Math.round(255 * alpha);
    }

    private void vibrator() {
        Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            VibrationEffect createOneShot = VibrationEffect.createOneShot(7, 255);
            vibrator.vibrate(createOneShot);
        } else {
            vibrator.vibrate(7);
        }
    }

    public void setProgress(float value, int parentHeight) {
//        Log.e("hty", "progress: " + value);
        this.progress = Math.round(value);
        this.parentHeight = parentHeight;
        if (value >= 100 && !isVibrator) {
            vibrator();
            isVibrator = true;
        }
        if (value < 100) {
            isVibrator = false;
        }
        if (progress >= 100) {
//            Log.e("hty", "alpha:" + getAlphaValue());
//            setBackgroundColor(Color.argb(getAlphaValue(), 40, 40, 40));
            setAlpha(Float.parseFloat(getAlphaValue()+"")/255);
        } else {
//            setBackgroundColor(Color.argb(255, 40, 40, 40));
            setAlpha(1);
            boolean isDark = DarkModeUtils.spDark(context);
            if (!isDark) {
                setBackgroundResource(R.drawable.status_bar_bg_selector);
            } else {
                setBackgroundResource(R.drawable.status_bar_night_bg_selector);
            }
        }
        invalidate();
    }
}

使用方式也很简单,在布局文件中直接引用就行。

注意:给布局直接添加两个child布局,第一个是下拉要展示的布局,第二个是当前看到的布局,布局类型自己定义。由于第二个布局我需要用FrameLayout,所以我就在WXMainPullViewGroup.java中的 tryCaptureView方法中返回此布局。

 由于我的第二个布局里面用到了Recycleview,我就处理了Recycleview和ViewDragHelper的滑动冲突问题,可以借鉴。同时,我还添加了页面滑动进度的回调和打开关闭的回调,可以直接监听。切记,Recycleview外层不要嵌套scrollview了,在布局重绘计算的时候,会非常的卡顿。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值