欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

scrollTo,scrollBy

程序员文章站 2022-05-05 08:55:03
...
   public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

 public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
       /**
     * Used to indicate that the parent of this view should clear its caches. This functionality
     * is used to force the parent to rebuild its display list (when hardware-accelerated),
     * which is necessary when various parent-managed properties of the view change, such as
     * alpha, translationX/Y, scrollX/Y, scaleX/Y, and rotation/X/Y. This method only
     * clears the parent caches and does not causes an invalidate event.
     *
     * @hide
     */
     //父容器清楚它的缓存,这个方法强制父容器重建显示列表(使用硬件加速的情况下)。
     //该功能是必须的,让有View属性改变时
     //例如 alpha, translationX/Y, scrollX/Y, scaleX/Y, and rotation/X/Y.
     //该方法智慧清楚父类换曾,不会引起重绘操作
     
            invalidateParentCaches();
            //滚动时调用监听
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            //核心代码1
            //
            //
            if (!awakenScrollBars()) {
            //核心代码2
            //
            //
                postInvalidateOnAnimation();
            }
        }
    }

从中我们可以发现scrollBy调用scrollTo,且scrollTo会记录原来的位置坐标,如果位置参数一样每次调用都会不执行操作,而scrollBy会累加位置坐标。

用法总结

scrollTo移动到某位置,scrollBy 移动多少位置。

PS:
这就和英语中To 和By的用法一样

reduce the price to 2 yuan 降到2元
reduce the price by 2 yuan 降低了2元

我们接着看源码核心代码1的源码

protected boolean awakenScrollBars() {
        return mScrollCache != null &&
                awakenScrollBars(mScrollCache.scrollBarDefaultDelayBeforeFade, true);
    }
 protected boolean awakenScrollBars(int startDelay, boolean invalidate) {
        final ScrollabilityCache scrollCache = mScrollCache;

        if (scrollCache == null || !scrollCache.fadeScrollBars) {
            return false;
        }

        if (scrollCache.scrollBar == null) {
            scrollCache.scrollBar = new ScrollBarDrawable();
            scrollCache.scrollBar.setState(getDrawableState());
            scrollCache.scrollBar.setCallback(this);
        }

        if (isHorizontalScrollBarEnabled() || isVerticalScrollBarEnabled()) {

            if (invalidate) {
                // Invalidate to show the scrollbars
                postInvalidateOnAnimation();
            }

            if (scrollCache.state == ScrollabilityCache.OFF) {
                // FIXME: this is copied from WindowManagerService.
                // We should get this value from the system when it
                // is possible to do so.
                final int KEY_REPEAT_FIRST_DELAY = 750;
                startDelay = Math.max(KEY_REPEAT_FIRST_DELAY, startDelay);
            }

            // Tell mScrollCache when we should start fading. This may
            // extend the fade start time if one was already scheduled
            long fadeStartTime = AnimationUtils.currentAnimationTimeMillis() + startDelay;
            scrollCache.fadeStartTime = fadeStartTime;
            scrollCache.state = ScrollabilityCache.ON;

            // Schedule our fader to run, unscheduling any old ones first
            if (mAttachInfo != null) {
                mAttachInfo.mHandler.removeCallbacks(scrollCache);
                mAttachInfo.mHandler.postAtTime(scrollCache, fadeStartTime);
            }

            return true;
        }

        return false;
    }

你会发现,如果需要绘制scrollBar会调用postInvalidateOnAnimation 函数返回true,之后跳过最外层的postInvalidateOnAnimation的调用,如果不需要绘制scrollBar直接返回false,反而调用最外层的postInvalidateOnAnimation。

所以最后都会调用postInvalidateOnAnimation函数

   public void postInvalidateOnAnimation() {
        // We try only with the AttachInfo because there's no point in invalidating
        // if we are not attached to our window
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            attachInfo.mViewRootImpl.dispatchInvalidateOnAnimation(this);
        }
    }

最后我们会看见调用mViewRootImpl的dispatchInvalidateOnAnimation函数,这个mViewRootImpl学过View绘制流程的人都知道,他是控制着根View,且把要移动的View当参数传递进去。现在来看ViewRootImpl类。

 public void dispatchInvalidateOnAnimation(View view) {
        mInvalidateOnAnimationRunnable.addView(view);
    }
    final InvalidateOnAnimationRunnable mInvalidateOnAnimationRunnable =
            new InvalidateOnAnimationRunnable();
    final class InvalidateOnAnimationRunnable implements Runnable {
        private boolean mPosted;
        //mViews 是一个数组,存储着需要滚动的View
        private final ArrayList<View> mViews = new ArrayList<View>();
        private final ArrayList<AttachInfo.InvalidateInfo> mViewRects =
                new ArrayList<AttachInfo.InvalidateInfo>();
        private View[] mTempViews;
        private AttachInfo.InvalidateInfo[] mTempViewRects;

        public void addView(View view) {
            synchronized (this) {
                mViews.add(view);
          
                postIfNeededLocked();
            }
        }

     //。。。。
        @Override
        public void run() {
            final int viewCount;
            final int viewRectCount;
            synchronized (this) {
                mPosted = false;

                viewCount = mViews.size();
                if (viewCount != 0) {
                    mTempViews = mViews.toArray(mTempViews != null
                            ? mTempViews : new View[viewCount]);
                    mViews.clear();
                }

                viewRectCount = mViewRects.size();
                if (viewRectCount != 0) {
                    mTempViewRects = mViewRects.toArray(mTempViewRects != null
                            ? mTempViewRects : new AttachInfo.InvalidateInfo[viewRectCount]);
                    mViewRects.clear();
                }
            }
//核心代码
//
//
            for (int i = 0; i < viewCount; i++) {
                mTempViews[i].invalidate();
                mTempViews[i] = null;
            }

            for (int i = 0; i < viewRectCount; i++) {
                final View.AttachInfo.InvalidateInfo info = mTempViewRects[i];
                info.target.invalidate(info.left, info.top, info.right, info.bottom);
                info.recycle();
            }
        }

        private void postIfNeededLocked() {
         //mPosted初始值是false,这里保证该方法只被执行一次.再run方法执行之后会被重新赋值成false.
            if (!mPosted) {
                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
                mPosted = true;
            }
        }
    }

mViews时一个数组,存储着需要被滚动的View,拖过异步消息,最后UI线程会进行跟新,通过invalidate方法。
这个方法最终会进行组建的测量,布局,绘制过程。