• ViewPager的两个问题


    ViewPager滑动抽搐bug

    当只有一个item,并且widthFactor<1手指向左会出现滑动抽搐

    或者所有的offset加起来也<1时也会出现抽搐

    问题原因

     经过一下午代码追踪找到问题位置

    androidx.viewpager.widget.ViewPager#calculatePageOffsets

    1 int pos = curItem.position - 1;
    2 mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
    3 mLastOffset = curItem.position == N - 1
    4         ? curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;

     此时mLastOffset 就会为负数这个有什么用呢继续看下边

    当我们手指滑动时最后会调用到ViewPager#performDrag其中就用到这个来计算滚动距离

    private boolean performDrag(float x) {
        boolean needsInvalidate = false;
    
        final float deltaX = mLastMotionX - x;
        mLastMotionX = x;
    
        float oldScrollX = getScrollX();
        float scrollX = oldScrollX + deltaX;
        final int width = getClientWidth();
    
        float leftBound = width * mFirstOffset;
        float rightBound = width * mLastOffset;
        boolean leftAbsolute = true;
        boolean rightAbsolute = true;
    
        final ItemInfo firstItem = mItems.get(0);
        final ItemInfo lastItem = mItems.get(mItems.size() - 1);
    
    // 此处不会执行,不会赋值
        if (firstItem.position != 0) {
            leftAbsolute = false;
            leftBound = firstItem.offset * width;
        }
        if (lastItem.position != mAdapter.getCount() - 1) {
            rightAbsolute = false;
            rightBound = lastItem.offset * width;
        }
    
        if (scrollX < leftBound) {
            if (leftAbsolute) {
                float over = leftBound - scrollX;
                mLeftEdge.onPull(Math.abs(over) / width);
                needsInvalidate = true;
            }
            scrollX = leftBound;
        } else if (scrollX > rightBound) {
            if (rightAbsolute) {
                float over = scrollX - rightBound;
                mRightEdge.onPull(Math.abs(over) / width);
                needsInvalidate = true;
            }
            scrollX = rightBound;
        }
        // Don't lose the rounded component
        mLastMotionX += scrollX - (int) scrollX;
        scrollTo((int) scrollX, getScrollY());
        pageScrolled((int) scrollX);
    
        return needsInvalidate;
    }

     因为mLastOffset 为负数所以rightBound为负数那么就会让scrollX为负数

    那么第一次虽然手指向左但最终却向相反方向

    第二次计算后scrollX0因为上次负数此时会和手指方向一致

    这样反反复复就出现了滑动抽搐

    解决方法

     在androidx.viewpager.widget.ViewPager#calculatePageOffsets中添加一个校验即可

    private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
            final int N = mAdapter.getCount();
            final int width = getClientWidth();
     
            // Base all offsets off of curItem.
            final int itemCount = mItems.size();
            float offset = curItem.offset;
            int pos = curItem.position - 1;
            mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
    //        mLastOffset = curItem.position == N - 1 ? curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
            if (curItem.position == N - 1) {
                if (useLastOffset) {
                    mLastOffset = curItem.offset + curItem.widthFactor - 1;
                } else {
                    mLastOffset = curItem.offset;
                }
            } else {
                mLastOffset = Float.MAX_VALUE;
            }
    
    
            // 计算前面页面的偏移量(根据当前页面计算)
            // Previous pages
            for (int i = curIndex - 1; i >= 0; i--, pos--) {
                final ItemInfo ii = mItems.get(i);
                while (pos > ii.position) {
                    offset -= mAdapter.getPageWidth(pos--) + marginOffset;
                }
                offset -= ii.widthFactor + marginOffset;
                ii.offset = offset;
                if (ii.position == 0) mFirstOffset = offset;
            }
            offset = curItem.offset + curItem.widthFactor + marginOffset;
            pos = curItem.position + 1;
    
            // 计算后面页面的偏移量(根据当前页面计算)
            // Next pages
            for (int i = curIndex + 1; i < itemCount; i++, pos++) {
                final ItemInfo ii = mItems.get(i);
                while (pos < ii.position) {
                    offset += mAdapter.getPageWidth(pos++) + marginOffset;
                }
                if (ii.position == N - 1) {
    //                mLastOffset = offset + ii.widthFactor - 1;
                    if (useLastOffset) {
                        mLastOffset = offset + ii.widthFactor - 1;
                    } else {
                        mLastOffset = offset;
                    }
                }
                ii.offset = offset;
                offset += ii.widthFactor + marginOffset;
            }
    
            mNeedCalculatePageOffsets = false;
        }

    ViewPager fakeDrag 精度损失

    如果做动画来fakeDrag时损失的精度累加会很大,导致最终滑动不精确。

    下边分析是用的VerticalViewPager

    public void fakeDragBy(float yOffset) {
        if (!mFakeDragging) {
            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
        }
    
        if (mAdapter == null) {
            return;
        }
    
        mLastMotionY += yOffset;
    
        float oldScrollY = getScrollY();
        float scrollY = oldScrollY - yOffset;
        final int height = getClientHeight();
    
        // Don't lose the rounded component
        mLastMotionY += scrollY - (int) scrollY;
        scrollTo(getScrollX(), (int) scrollY);
        pageScrolled((int) scrollY);
    
        // Synthesize an event for the VelocityTracker.
        final long time = SystemClock.uptimeMillis();
        final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, 0, mLastMotionY, 0);
        mVelocityTracker.addMovement(ev);
        ev.recycle();
    }

    虽然我们传递的offsetfloat但最终会强转成int会损失精度如果做动画来fakeDrag时损失的精度累加会很大导致最终滑动不精确

    解决方法

    l 如果不要求太精准传参时进行四舍五入

    l 如果要求精准那么可以如下处理

    val dargDistancePx = extra?.getFloat(UgcVideoGuideConstants.PARAM_KEY_DRAG_DISTANCE_PX) ?: 0f
    var previousValue = 1f
    var lossAccuracy = 0f
    val startScrollY = parent.scrollY
    addUpdateListener { valueAnimator ->
        val currentValue = valueAnimator.animatedValue as Float
        val dy = (previousValue - currentValue) * dargDistancePx
        val intDy = dy.toInt().toFloat()
        lossAccuracy += dy - intDy
        if (parent.isFakeDragging) {
            parent.fakeDragBy(intDy + lossAccuracy.toInt())
            lossAccuracy -= lossAccuracy.toInt().toFloat()
        }
        previousValue = currentValue
        if (valueAnimator.animatedFraction == 1f) {
            WZLogUtils.printInfoWithDefaultTag("scrollTo.total:${parent.scrollY - startScrollY}, lossAccuracy:$lossAccuracy")
        }
    }

    具体就是累计损失的精度超过1后会把整数部分算上然后继续累计

  • 相关阅读:
    小程序路由
    机器学习笔记—支持向量机(1)
    用极大似然估计法推出朴素贝叶斯法中的先验概率估计公式
    机器学习笔记—生成学习
    机器学习笔记—再谈广义线性模型
    机器学习笔记—指数分布簇和广义线性模型
    机器学习笔记—Logistic 回归
    机器学习笔记—局部权重线性回归
    机器学习笔记—线性回归
    机器学习笔记1
  • 原文地址:https://www.cnblogs.com/muouren/p/16171726.html
Copyright © 2020-2023  润新知