在View的幾種移動方法中我相信Scorller+scrollTo或者scrollBy是你們比較接受.咱們再使用的時候老是會碰到一些奇怪的問題,能夠得出如下幾點:android
scrollTo和scrollBy只是移動本身的內容.
也就是若是ViewGroup設置scrollTo或者scrollBy的話,只有它的子View會有位移效果.若是是TextView設置scrollTo或者scrollBy的話只會讓它內部的文字發生位移.json
scrollBy仍是調用的scrollTo,但scrollBy的起始座標是相對於上次結束時的mScrollX和mScrollY.scroolTo的起始座標是相對於父佈局的左上角,以後起始座標是不會變的.以下代碼,Button:scroolto的內容只能發生一次位移.Button:scroolby的內容能夠屢次位移.canvas
Button scroolto,scroolby;
scroolto = findViewById(R.id.scroolto);
scroolby = findViewById(R.id.scroolby);
item.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
scroolto.scrollTo(-100,-100);
scroolby.scrollBy(-100,-100);
}
});複製代碼
//獲取位於屏幕中的位置
int[] location = new int[2];
scroolto.getLocationOnScreen(location);
Log.e("WANG","ScreenX"+location[0]);
Log.e("WANG","ScreenY"+location[1]);
//獲取位於父佈局中的位置
Log.e("WANG","ViewX"+scroolto.getX());
Log.e("WANG","ViewY"+scroolto.getY());複製代碼
這些就是小編大概的瞭解了,可能不太全面,但願讀者們能指點一二.接下來咱們會有個疑問,爲何再使用scrollTo(100,100)或者scrollBy(100,100)的時候,咱們的內容是往負方向移動的呢?設置成-100,-100的時候是往正方向移動的呢,你們能夠去看一下Android座標系?bash
這裏呢咱們要知道,控件的x和y座標的增值是正值的話就是往正方向移動,負值就是往負方向移動.這是通常的規則.而後scrollTo和scrollBy倒是例外.咱們以後去看下源碼了,這裏呢會涉及到View的繪製過程,也就是從Activity的setContentView()到View出如今手機屏幕中的過程.這裏就大概說一下,下一篇會詳細的介紹.app
/**
* 爲你的View設置滾動的位置,這裏會調用{@link #onScrollChanged(int, int, int, int)} 隨後這個View將調用失效.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
public void scrollTo(int x, int y) {
//這裏是否是就能夠解釋爲何scrollTo只能調用一次.
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
//咱們能夠監聽到的方法
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
//這裏有個判斷的條件,也就是postInvalidateOnAnimation這個方法不管scrollbars是否開始繪製都會執行.
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}複製代碼
這裏面的awakenScrollBars()方法呢是去喚醒ScrollBar的繪製.任何View都是有ScrollBar的.scrollBar執行繪製流程的時候該方法會返回true,這樣的話將不會執行scrollTo裏面的postInvalidateOnAnimation方法,如何返回fasle則相反.再awakenScrollBars()裏面也會調用到postInvalidateOnAnimation這個方法.咱們來看下源碼.less
/**
* <p>
* 觸發scrollbars去繪製.當調用這個方法的時候將開啓一個延遲動畫去隱藏scrollbars,若是有個子類提供了滑動的動畫,那麼這個延遲的時間要和子類動畫執行的實行進行對比.
* </p>
*
* <p>
* 只有在scrollbars可用的時候纔會開啓動畫.調用 {@link #isHorizontalScrollBarEnabled()} 和
* {@link #isVerticalScrollBarEnabled()}.
當動畫執行的時候該方法會返回true, 其餘狀況將返回false. 如何動畫執行將調用到invalidate()方法區重繪.
* @see #scrollBy(int, int)
* @see #scrollTo(int, int)
* @see #isHorizontalScrollBarEnabled()
* @see #isVerticalScrollBarEnabled()
* @see #setHorizontalScrollBarEnabled(boolean)
* @see #setVerticalScrollBarEnabled(boolean)
*/
protected boolean awakenScrollBars(int startDelay, boolean invalidate) {
final ScrollabilityCache scrollCache = mScrollCache;
if (scrollCache == null || !scrollCache.fadeScrollBars) {
return false;
}
if (scrollCache.scrollBar == null) {
//初始化了繪製scrollBar所需的一些參數,這裏沒初始化的時候再draw(canvas方法裏面將不會開啓繪製流程)
scrollCache.scrollBar = new ScrollBarDrawable();
scrollCache.scrollBar.setState(getDrawableState());
scrollCache.scrollBar.setCallback(this);
}
if (isHorizontalScrollBarEnabled() || isVerticalScrollBarEnabled()) {
//這裏就是調用從新繪製的方法.invalidate的值爲true.
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);
}
//開啓動畫
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;
}複製代碼
一切的結果都將調用到postInvalidateOnAnimation()那咱們就跟着去看下該方法.ide
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); } }複製代碼
attachInfo再View跟window鏈接上的時候就開始賦值,這裏是不爲null的,而後將調用mViewRootImpl裏面的方法.咱們先來看下ViewRootImpl類.源碼分析
/**
* 在試圖層級的頂層, 實現了View和Windowmanage質檢須要的協議,內部實現的細節要去看這個類{@link WindowManagerGlobal}.
*/
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {複製代碼
對於該類的定義咱們再下節詳細講解.該類呢實現了ViewPaent的接口,ViewParent的方法都在該類中獲得重寫.那就去看下dispatchInvalidateOnAnimation(this)方法.佈局
public void dispatchInvalidateOnAnimation(View view) {
mInvalidateOnAnimationRunnable.addView(view);
}複製代碼
這裏面呢mInvalidateOnAnimationRunnable是一個實現了Runable接口的內部類.這裏呢將會調用View的invalidate()方法,這個方法咱們在寫自定義View的時候都用過吧,調用以後講會通知View進行draw.才InvalidateOnAnimationRunnable的addView中將會調用到postIfNeededLocked()方法.post
private void postIfNeededLocked() {
//mPosted初始值是false,這裏保證該方法只被執行一次.再run方法執行以後會被從新賦值成false.
if (!mPosted) {
//這裏將執行mInvalidateOnAnimationRunnable這個實現類.
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
mPosted = true;
}
}複製代碼
mInvalidateOnAnimationRunnable執行的時候將會調用本身的run方法裏面將遍歷存儲View的集合,挨個的調用本身的invalidate()方法而後將存儲的View對象釋放掉.
for (int i = 0; i < viewCount; i++) {
mTempViews[i].invalidate();
mTempViews[i] = null;
}複製代碼
下面是mInvalidateOnAnimationRunnable的源碼:
final class InvalidateOnAnimationRunnable implements Runnable {
private boolean mPosted;
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();
}
}
public void addViewRect(AttachInfo.InvalidateInfo info) {
synchronized (this) {
mViewRects.add(info);
postIfNeededLocked();
}
}
public void removeView(View view) {
synchronized (this) {
mViews.remove(view);
for (int i = mViewRects.size(); i-- > 0; ) {
AttachInfo.InvalidateInfo info = mViewRects.get(i);
if (info.target == view) {
mViewRects.remove(i);
info.recycle();
}
}
if (mPosted && mViews.isEmpty() && mViewRects.isEmpty()) {
mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, this, null);
mPosted = false;
}
}
}
@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();
}
}
//這裏將調用View的繪製步驟.
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() {
if (!mPosted) {
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
mPosted = true;
}
}
}複製代碼
最後仍是調用到了View的invalidate()方法,那就去看下這個方法吧.
/**
* 使整個試圖無效,若是試圖是可見的,
* {@link #onDraw(android.graphics.Canvas)}以後將會調用這個方法
*/
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
................省略代碼..........
// 將矩形的信息給父類.
//damage:損壞,髒.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
................省略代碼..........
}
}複製代碼
這裏又調用到了ViewParent.invalidateChild方法,由於ViewParent是一個接口,也就是調用了ViewRootlmpl類重寫的invalidateChild方法.咱們跟一下源碼.
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
..........
invalidateRectOnScreen(dirty);
return null;
}
private void invalidateRectOnScreen(Rect dirty) {
.......
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
//這個是啓動View繪製三部曲的入口,measure,layout,draw.
//牽扯到View的繪製源碼.
scheduleTraversals();
}
}複製代碼
當調用到scheduleTraversals()方法的時候將會調用到View繪製的入口,也就是從佈局的measure,layout,draw方法最開始被調用的地方,這裏其實咱們如今沒必要要去了解,咱們只須要去看下源碼就行了,最終確定會調用到View的draw(canvas)方法,canvas對象是在scheduleTraversals()這一系列方法中建立而後傳給View的.咱們直接看下View的draw方法.
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
//這個是本身實現而後實現本身的繪製邏輯
if (!dirtyOpaque) onDraw(canvas);
//這個通常是ViewGroup纔會重寫的方法.
// Step 4, draw the children
dispatchDraw(canvas);
...................
// Step 6, draw decorations (foreground, scrollbars)
//這裏面繪製了scrollBar
onDrawForeground(canvas);
// we're done... return; } ............. }複製代碼
draw方法裏面能夠看出不少都是空的方法須要子類本身去實現繪製邏輯,只有那個onDrawForeground(canvas)繪製前景,這裏面纔用到了mScrollX和mScrollY變量.跟着走源碼:
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
........
}複製代碼
接着看源碼:
protected final void onDrawScrollBars(Canvas canvas) {
// scrollbars are drawn only when the animation is running
//再scrollTo方法裏面已經給ScrollabilityCache賦值了.
final ScrollabilityCache cache = mScrollCache;
if (cache != null) {
int state = cache.state;
if (state == ScrollabilityCache.OFF) {
return;
}
boolean invalidate = false;
.......
if (drawHorizontalScrollBar) {
scrollBar.setParameters(computeHorizontalScrollRange(),
computeHorizontalScrollOffset(),
computeHorizontalScrollExtent(), false);
final Rect bounds = cache.mScrollBarBounds;
getHorizontalScrollBarBounds(bounds, null);
onDrawHorizontalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
bounds.right, bounds.bottom);
if (invalidate) {
//看到這個方法咱們就發現了但願.
invalidate(bounds);
}
}
if (drawVerticalScrollBar) {
scrollBar.setParameters(computeVerticalScrollRange(),
computeVerticalScrollOffset(),
computeVerticalScrollExtent(), true);
final Rect bounds = cache.mScrollBarBounds;
getVerticalScrollBarBounds(bounds, null);
onDrawVerticalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
bounds.right, bounds.bottom);
if (invalidate) {
invalidate(bounds);
}
}
}
}
}複製代碼
最會咱們發現onDrawScrollBars裏再次調用了invalidate方法,不過參數是一個矩形的對象.繼續跟源碼:
public void invalidate(int l, int t, int r, int b) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
}複製代碼
這裏咱們能大體的看到矩形的left 是 l - scrollx等等,這就是爲何再scrollto或者scrollBy裏面設置正直的話就是往反方向走,設置父值得話就是往正確的方向.咱們繼續跟着源碼走的話能夠看到這裏又從新的調用了ViewRootlmpl類的invalidateChild方法,又重複了以前的繪製過程.好吧源碼勉強就看到這把,其實遺留了不少的問題小編也是無奈的,Android源碼太強大!
這裏面就簡單的跟着scrollTo的源碼走了一遍,還有不少的問題沒有解決.這裏就算是給你們指出一條看源碼的思路吧.隨後還會繼續更新這裏面沒有解決的幾個問題,什麼問題呢我想跟着走一遍源碼的時候你本身就會發現了.
歡迎你們的糾正~
感受能夠就給個贊支持一下吧~