ViewDragHelper解读

精彩推荐

Android ViewDragHelper完全解析 自定义ViewGroup神
模仿探探首页卡片左右滑动效果,滑动流畅,卡片view无限重生

案例效果图

这里写图片描述

案例注释

public class SlideLayout extends ViewGroup {

    private List<CardItemView> viewList = new ArrayList<>(); // 存放的是每一层的view,从顶到底
    private List<View> releasedViewList = new ArrayList<>(); // 手指松开后存放的view列表

    private final ViewDragHelper mDragHelper; // 拖拽工具类
    private int initCenterViewX = 0, initCenterViewY = 0; // 最初时,中间View的x位置,y位置
    private int allWidth = 0; // 面板的宽度
    private int allHeight = 0; // 面板的高度
    private int childWith = 0; // 每一个子View对应的宽度

    private static final float SCALE_STEP = 0.08f; // view叠加缩放的步长
    private static final int MAX_SLIDE_DISTANCE_LINKAGE = 400; // 水平距离+垂直距离,超过这个值,则下一层view完成向上一层view的过渡
    private View bottomLayout; // 卡片下边的三个按钮布局

    private int bottomMarginTop = 40;//底部按钮和图片的间距
    private int yOffsetStep = 40; // view叠加垂直偏移量的步长

    private static final int X_VEL_THRESHOLD = 900;
    private static final int X_DISTANCE_THRESHOLD = 300;

    public static final int VANISH_TYPE_LEFT = 0;//左侧按钮点击事件
    public static final int VANISH_TYPE_RIGHT = 1;//右侧按钮点击事件

    private Object obj1 = new Object();

    private CardSwitchListener cardSwitchListener; // 回调接口
    private List<CardDataItem> dataList; // 存储的数据链表
    private int isShowing = 0; // 当前正在显示的小项
    private View leftBtn, rightBtn;
    private boolean btnLock = false;
    private GestureDetectorCompat moveDetector;
    private OnClickListener btnListener;

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

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

    public SlideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.card);
        bottomMarginTop = (int) array.getDimension(R.styleable.card_bottomMarginTop, bottomMarginTop);
        yOffsetStep = (int) array.getDimension(R.styleable.card_yOffsetStep, yOffsetStep);
        array.recycle();
        mDragHelper = ViewDragHelper.create(this, 10, new DragHelperCallback());

        moveDetector = new GestureDetectorCompat(context, new MoveDetector());

        btnListener = new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (view instanceof ImageView) {
                    // 点击的是卡片
                    if (null != cardSwitchListener && view.getScaleX() > 1 - SCALE_STEP) {
                        cardSwitchListener.onItemClick(view, isShowing);
                    }
                } else {
                    // 点击的是bottomLayout里面的一些按钮
                    btnLock = true;
                    int type = -1;
                    if (view == leftBtn) {
                        type = VANISH_TYPE_LEFT;
                    } else if (view == rightBtn) {
                        type = VANISH_TYPE_RIGHT;
                    }
                    vanishOnBtnClick(type);
                }
            }
        };
    }

    //手势监听
    class MoveDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {
            // 拖动了,touch不往下传递
            return Math.abs(dy) + Math.abs(dx) > 5;
        }
    }

    //拖动事件处理核心方法
    public class DragHelperCallback extends ViewDragHelper.Callback {

        //是否可以拖动view
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            // 如果数据List为空,或者子View不可见,则不予处理
            if (child == bottomLayout || dataList == null || dataList.size() == 0
                    || child.getVisibility() != View.VISIBLE || child.getScaleX() <= 1.0f - SCALE_STEP) {
                // 一般来讲,如果拖动的是第三层、或者第四层的View,则直接禁止
                // 此处用getScale的用法来巧妙回避
                return false;
            }

            if (btnLock) {
                return false;
            }

            // 只捕获顶部view(rotation=0)
            int childIndex = viewList.indexOf(child);
            if (childIndex > 0) {
                return false;
            }

            return true;
        }

        //松手之后View的处理
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            animToSide(releasedChild, xvel, yvel);
        }

        //View位置发生变化时
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            // 调用offsetLeftAndRight导致viewPosition改变,会调到此处,所以此处对index做保护处理
            int index = viewList.indexOf(changedView);
            if (index + 2 > viewList.size()) {
                return;
            }
            processLinkageView(changedView);
        }

        //View滑动的X坐标
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return left;
        }

        //View滑动的Y坐标
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return top;
        }
    }

    //重测View的大小
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
        int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
        //设置模式和大小
        setMeasuredDimension(
                resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
                resolveSizeAndState(maxHeight, heightMeasureSpec, 0));

        allWidth = getMeasuredWidth();
        allHeight = getMeasuredHeight();
    }

    //放置布局
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        // 布局卡片view
        int size = viewList.size();
        for (int i = 0; i < size; i++) {
            View viewItem = viewList.get(i);
            int childHeight = viewItem.getMeasuredHeight();
            viewItem.layout(left, top, right, top + childHeight);
            int offset = yOffsetStep * i;
            float scale = 1 - SCALE_STEP * i;
            if (i > 2) {
                // 备用的view
                offset = yOffsetStep * 2;
                scale = 1 - SCALE_STEP * 2;
            }

            viewItem.offsetTopAndBottom(offset);
            viewItem.setScaleX(scale);
            viewItem.setScaleY(scale);
        }

        // 布局底部按钮的View
        if (null != bottomLayout) {
            int layoutTop = viewList.get(0).getMeasuredHeight() + bottomMarginTop;
            bottomLayout.layout(left, layoutTop, right, layoutTop
                    + bottomLayout.getMeasuredHeight());
        }

        // 初始化一些中间参数
        initCenterViewX = viewList.get(0).getLeft();
        initCenterViewY = viewList.get(0).getTop();
        childWith = viewList.get(0).getMeasuredWidth();
    }

    //设置卡片操作回调
    public void setCardSwitchListener(CardSwitchListener cardSwitchListener) {
        this.cardSwitchListener = cardSwitchListener;
    }

    //顶层卡片View位置改变,底层的位置需要调整
    private void processLinkageView(View changedView) {
        int changeViewLeft = changedView.getLeft();
        int changeViewTop = changedView.getTop();
        int distance = Math.abs(changeViewTop - initCenterViewY)
                + Math.abs(changeViewLeft - initCenterViewX);
        float rate = distance / (float) MAX_SLIDE_DISTANCE_LINKAGE;

        float rate1 = rate;
        float rate2 = rate - 0.2f;

        if (rate > 1) {
            rate1 = 1;
        }

        if (rate2 < 0) {
            rate2 = 0;
        } else if (rate2 > 1) {
            rate2 = 1;
        }

        ajustLinkageViewItem(changedView, rate1, 1);
        ajustLinkageViewItem(changedView, rate2, 2);
    }

    //由index对应view变成index-1对应的view
    private void ajustLinkageViewItem(View changedView, float rate, int index) {
        int changeIndex = viewList.indexOf(changedView);
        int initPosY = yOffsetStep * index;
        float initScale = 1 - SCALE_STEP * index;

        int nextPosY = yOffsetStep * (index - 1);
        float nextScale = 1 - SCALE_STEP * (index - 1);

        int offset = (int) (initPosY + (nextPosY - initPosY) * rate);
        float scale = initScale + (nextScale - initScale) * rate;

        View ajustView = viewList.get(changeIndex + index);
        ajustView.offsetTopAndBottom(offset - ajustView.getTop()
                + initCenterViewY);
        ajustView.setScaleX(scale);
        ajustView.setScaleY(scale);
    }

    //处理是否拦截滑动事件
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev);
        boolean moveFlag = moveDetector.onTouchEvent(ev);
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            // ACTION_DOWN的时候就对view重新排序
            orderViewStack();
            // 保存初次按下时arrowFlagView的Y坐标
            // action_down时就让mDragHelper开始工作,否则有时候导致异常
            mDragHelper.processTouchEvent(ev);
        }

        return shouldIntercept && moveFlag;
    }

    //处理是否处理滑动事件
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }

    //对View重新排序
    private void orderViewStack() {
        synchronized (obj1) {
            if (releasedViewList.size() == 0) {
                return;
            }

            CardItemView changedView = (CardItemView) releasedViewList.get(0);
            if (changedView.getLeft() == initCenterViewX) {
                releasedViewList.remove(0);
                return;
            }

            // 1. 消失的卡片View位置重置,由于大多手机会重新调用onLayout函数,所以此处大可以不做处理,不信你注释掉看看
            changedView.offsetLeftAndRight(initCenterViewX - changedView.getLeft());
            changedView.offsetTopAndBottom(initCenterViewY - changedView.getTop() + yOffsetStep * 2);
            float scale = 1.0f - SCALE_STEP * 2;
            changedView.setScaleX(scale);
            changedView.setScaleY(scale);

            // 2. 卡片View在ViewGroup中的顺次调整
            int num = viewList.size();
            for (int i = num - 1; i > 0; i--) {
                View tempView = viewList.get(i);
                tempView.bringToFront();//调整到前面来
            }

            // 3. changedView填充新数据
            int newIndex = isShowing + 4;
            if (newIndex < dataList.size()) {
                CardDataItem dataItem = dataList.get(newIndex);
                changedView.fillData(dataItem);
            } else {
                changedView.setVisibility(View.INVISIBLE);
            }

            // 4. viewList中的卡片view的位次调整
            viewList.remove(changedView);
            viewList.add(changedView);
            releasedViewList.remove(0);

            // 5. 更新showIndex、接口回调
            if (isShowing + 1 < dataList.size()) {
                isShowing++;
            }
            if (null != cardSwitchListener) {
                cardSwitchListener.onShow(isShowing);
            }
        }
    }

    @Override
    public void computeScroll() {
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        } else {
            // 动画结束
            synchronized (this) {
                if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
                    orderViewStack();
                    btnLock = false;
                }
            }
        }
    }

    //松手时处理滑动到边缘的动画
    private void animToSide(View changedView, float xvel, float yvel) {
        int finalX = initCenterViewX;
        int finalY = initCenterViewY;
        int flyType = -1;

        // 1. 下面这一坨计算finalX和finalY,要读懂代码需要建立一个比较清晰的数学模型才能理解,不信拉倒
        int dx = changedView.getLeft() - initCenterViewX;
        int dy = changedView.getTop() - initCenterViewY;
        if (dx == 0) {
            // 由于dx作为分母,此处保护处理
            dx = 1;
        }
        if (xvel > X_VEL_THRESHOLD || dx > X_DISTANCE_THRESHOLD) {
            finalX = allWidth;
            finalY = dy * (childWith + initCenterViewX) / dx + initCenterViewY;
            flyType = VANISH_TYPE_RIGHT;
        } else if (xvel < -X_VEL_THRESHOLD || dx < -X_DISTANCE_THRESHOLD) {
            finalX = -childWith;
            finalY = dy * (childWith + initCenterViewX) / (-dx) + dy
                    + initCenterViewY;
            flyType = VANISH_TYPE_LEFT;
        }

        // 如果斜率太高,就折中处理
        if (finalY > allHeight) {
            finalY = allHeight;
        } else if (finalY < -allHeight / 2) {
            finalY = -allHeight / 2;
        }

        // 如果没有飞向两侧,而是回到了中间,需要谨慎处理
        if (finalX != initCenterViewX) {
            releasedViewList.add(changedView);
        }

        // 2. 启动动画
        if (mDragHelper.smoothSlideViewTo(changedView, finalX, finalY)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }


        // 3. 消失动画即将进行,listener回调
        if (flyType >= 0 && cardSwitchListener != null) {
            cardSwitchListener.onCardVanish(isShowing, flyType);
        }
    }

    //卡片回调接口
    public interface CardSwitchListener {
        /**
         * 新卡片显示回调
         *
         * @param index 最顶层显示的卡片的index
         */
        public void onShow(int index);

        /**
         * 卡片飞向两侧回调
         *
         * @param index 飞向两侧的卡片数据index
         * @param type  飞向哪一侧{@link #VANISH_TYPE_LEFT}或{@link #VANISH_TYPE_RIGHT}
         */
        public void onCardVanish(int index, int type);

        /**
         * 卡片点击事件
         *
         * @param cardImageView 卡片上的图片view
         * @param index         点击到的index
         */
        public void onItemClick(View cardImageView, int index);
    }

    //本来想写成Adapter适配,想想还是算了,这种比较简单
    public void fillData(List<CardDataItem> dataList) {
        this.dataList = dataList;

        int num = viewList.size();
        for (int i = 0; i < num; i++) {
            CardItemView itemView = viewList.get(i);
            itemView.fillData(dataList.get(i));
            itemView.setVisibility(View.VISIBLE);
        }

        if (null != cardSwitchListener) {
            cardSwitchListener.onShow(0);
        }
    }

    //布局加载完成
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // 渲染完成,初始化卡片view列表
        viewList.clear();
        int num = getChildCount();
        for (int i = num - 1; i >= 0; i--) {
            View childView = getChildAt(i);
            if (childView.getId() == R.id.card_bottom_layout) {
                bottomLayout = childView;
                initBottomLayout();
            } else {
                CardItemView viewItem = (CardItemView) childView;
                viewItem.setTag(i + 1);
                viewItem.imageView.setOnClickListener(btnListener);
                viewList.add(viewItem);
            }
        }
    }

    //初始化底部按钮
    private void initBottomLayout() {
        leftBtn = bottomLayout.findViewById(R.id.card_left_btn);
        rightBtn = bottomLayout.findViewById(R.id.card_right_btn);

        leftBtn.setOnClickListener(btnListener);
        rightBtn.setOnClickListener(btnListener);
    } //点击按钮消失动画

    //按钮事件处理
    private void vanishOnBtnClick(int type) {
        synchronized (obj1) {
            View animateView = viewList.get(0);
            if (animateView.getVisibility() != View.VISIBLE || releasedViewList.contains(animateView)) {
                return;
            }

            int finalX = 0;
            if (type == VANISH_TYPE_LEFT) {
                finalX = -childWith;
            } else if (type == VANISH_TYPE_RIGHT) {
                finalX = allWidth;
            }

            if (finalX != 0) {
                releasedViewList.add(animateView);
                if (mDragHelper.smoothSlideViewTo(animateView, finalX, initCenterViewY + allHeight)) {
                    ViewCompat.postInvalidateOnAnimation(this);
                }
            }

            if (type >= 0 && cardSwitchListener != null) {
                cardSwitchListener.onCardVanish(isShowing, type);
            }
        }
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值