在前边的文章中,咱们已经对Android触摸事件处理有了大体的了解,而且详细探讨了MotionEvent
的相关用法。对以前文章中的知识还不是很了解的同窗,请阅读《Android MotionEvent详解》。html
今天,咱们就来探讨一下Android中界面滚动效果的相关机制,本篇文章主要讲解一下滚动相关的知识点,以后的文章会涉及实际的代码和原理。但愿你们阅读完这篇文章以后,可以了解或者掌握一下知识:java
Android 视图的组成部分android
mScrollX
和mScrollY
对视图显示的影响git
scrollTo
和scrollBy
的使用github
invalidate
和postInvalidate
的区别canvas
咱们都知道,View
中有两个重要的成员变量,mScrollX
,mScrollY
.它们分别表明视图内容(view content)水平方向和竖直方向的滚动距离。咱们能够经过setScrollX
和setScrollY
来个函数来改变它们的值,从而来滚动视图的内容。app
在这里须要强调的是,mScrollX
和mScrollY
会致使视图内容(view content)变化,可是不会影响视图背景(background)。函数
看到这里同窗们或许会有写疑问,视图的内容和背景有什么区别呢?视图还有哪些组成部分呢?布局
咱们能够从View的draw
方法中得知View的组成部分。post
// http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/view/View.java#View public void draw(Canvas canvas) { ........ /* * 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 if (!dirtyOpaque) { drawBackground(canvas); } ....... // Step 2, save the canvas' layers ....... // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers ....... if (drawTop) { matrix.setScale(1, fadeHeight * topFadeStrength); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); } ..... // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); ...... }
View显示内容由一下几个部分组成:
背景(background)
自己的内容(content)
子视图
边界渐变效果(fade effect),上下左右四个边界均可能会有渐变效果,代码中只显示了上边界的渐变效果绘制。
边框或者装饰效果(decorations),好比滚动条
举个例子吧,咱们都知道在布局文件中,TextView
有两个比较重要的属性:background
,text
。background
能够设置TextView的背景,而text
则是设置要绘制字体内容。
<TextView android:layout_width="wrap_content" android:background="@drawable/ic_launcher" android:text="Test" android:layout_height="wrap_content" />
mScrollX
和mScrollY
对除了自己内容外的部分的绘制都有影响。只是不会影响视图背景的绘制。
咱们都知道,在Android的视图中,布局相关的数值都是有方向性的,好比mLeft
,mTop
。
由上图咱们能够知道,Android视图坐标的原点在屏幕的左上方,x轴正方向是向右,y轴正方向是向下。因此,当你将mLeft
和mTop
的数值加10而且重绘视图时,视图会向右下移动。
那么mScrollY
和mScrollX
也在这样一个坐标域中吗?它们的正方向和mTop
和mLeft
是同样的吗?是的,它们属于同一个坐标域,方向性相同。
可是若是你将mScrollX
和mScrollY
的数值都增大10,而后调用invalidate()
从新绘制界面的话,你会发现视图中的内容都向左上角移动啦!
这是怎么回事呢?从概念上你能够先这样解:mScrollX
和mScrollY
改变致使View的可视区域的移动,并非致使View的视图区域的移动。
View的视图区域至关于无限大的,你能够在onDraw
函数中的canvas
中绘制任意大的图像,可是你会发现,最终屏幕上显示出来的只会是一部分,由于View自身还有大小概念,也就是measure
和layout
时,视图会被设置长宽还有界面中位置,这样的话,视图可视区域就被肯定啦。
作一个形象的比喻。View的可视区域就是一面墙上的窗户,View的视图区域就至关于墙后边的优美景色。墙外风光无线,可是你只能看到窗户中的景色。若是窗户变大啦,外边风景不变,你看到的景色就大了一点;若是窗户向右下角移动了一段距离,你就会发现外边的景色好像是向左上角"移动"了一段距离。
这两个函数是用来滚动视图的API
public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { postInvalidateOnAnimation(); } } } public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
你们看源代码很容易就理解了两者的做用和区别:scrollTo
就是直接改变mScrollX
和mScrollY
;而scrollBy
则是给mScrollX
和mScrollY
加上增量。
上边这两个函数都是请求视图从新绘制的API,可是两者的使用有些区别。
invalidate
必须在主线程(UI Thread)中调用,而postInvalidate
能够在非主线程(Non UI Thread)中调用。
除此以外,两者还有点小区别。
调用invalidate
时,它会检查上一次请求的UI重绘是否完成,若是没有完成的话,那么它就什么都不作。
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { ..... //DRAWN和HAS_BOUNDS是否被设置为1,说明上一次请求执行的UI绘制已经完成,那么能够再次请求执行 if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || (fullInvalidate && isOpaque() != mLastIsOpaque)) { ...... final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); p.invalidateChild(this, damage);//TODO:这是invalidate执行的主体 ..... } }
而postInvalidate
则不会这样,它是向主线程发送个Message
,而后handleMessage
时,调用了invalidate()
函数。
//View.java public void postInvalidateDelayed(long delayMilliseconds) { ... attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); ... } // ViewRootImpl 发送Message public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); } // ViewRootImpl 处理Message public void handleMessage(Message msg) { switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; } }
因此,两者的调用时机仍是有区别的,就好比使用Scroller
进行视图滚动时,两者的调用就有所不一样。
以后还有会两篇博文,一篇是《Android Scroll详解(二):OverScroller实战》讲解具体代码实现,另一篇是《Android Scroll详解(三):Android 绘制过程详解》主要是从滚动角度理解Android绘制过程,请你们多多关注啊。