Android View视图系统分析和Scroller和OverScroller分析
View 视图分析
而ViewGroup也是对于View的一种实现。所以说全部的View元素在根本上都是一样的。当然这并不等于说View == ViewGroup,就好比仅仅有ViewGroup才可以addView。
当然,假设你不是非常理解也不是非常致命的问题。以下我会做一些解释。
同理,这也就解释了我们队Canvas採取translate(平移)、clipRect(剪切)一系列变换的意义所在。那么Canvas没有边界。是不是就意味着我们的View资源一定都能如我们所愿吗。
Canvas是没有边界的,可是我们的Layout区域确实有边界的。假设,我们希望看到我们的View子元素,那么他就必须在我们的视图坐标(可视区域),可是在此处还要注意Layout区域并不一定都可见。
我相信在你第一眼看到这张图片的时候你就能够瞬间的对于Layout坐标(视图区域)有了一个认识了。没错,你所示那些蓝角红线组成的长方形区域就是我们的布局区域,那么他们的框定是源自于哪里呢。
这里还记得我上面强调过的
LastCallMessageContainer.layout(left, top, left+ LastCallMessageContainer.getWidth(), top+ LastCallMessageContainer.getHeight());
这样的方式来实现尾随手指移动的View。可是这样的方式好不好呢。答案非常明显,并不好,有机会的话给打击分享一下实现方式吧。
scrollTo()和scrollBy()分析
scrollTo()和scrollBy()分析
在View.java中提供了了例如以下两个变量以及对应的属性方法去读取滚动值 ,例如以下: View.java类中
- /**
- * The offset, in pixels, by which the content of this view is scrolled
- * horizontally.
- * {@hide}
- */
- protected int mScrollX; //该视图内容相当于视图起始坐标Layout的偏移量 , X轴 方向
- /**
- * The offset, in pixels, by which the content of this view is scrolled
- * vertically.
- * {@hide}
- */
- protected int mScrollY; //该视图内容相当于视图起始坐标Layout的偏移量 , Y轴方向
- /**
- * Return the scrolled left position of this view. This is the left edge of
- * the displayed part of your view. You do not need to draw any pixels
- * farther left, since those are outside of the frame of your view on
- * screen.
- *
- * @return The left edge of the displayed part of your view, in pixels.
- */
- public final int getScrollX() {
- return mScrollX;
- }
- /**
- * Return the scrolled top position of this view. This is the top edge of
- * the displayed part of your view. You do not need to draw any pixels above
- * it, since those are outside of the frame of your view on screen.
- *
- * @return The top edge of the displayed part of your view, in pixels.
- */
- public final int getScrollY() {
- return mScrollY;
- }
Word后面的图案是TextView的背景。
public void scrollTo(int x, int y)
说明:在当前视图内容偏移至(x , y)坐标处,即位于Layout区域(x , y)坐标处。
方法原型为: View.java类中
- /**
- * Set the scrolled position of your view. This will cause a call to
- * {@link #onScrollChanged(int, int, int, int)} and the view will be
- * invalidated.
- * @param x the x position to scroll to
- * @param y the y position to scroll to
- */
- public void scrollTo(int x, int y) {
- //偏移位置发生了改变
- if (mScrollX != x || mScrollY != y) {
- int oldX = mScrollX;
- int oldY = mScrollY;
- mScrollX = x; //赋新值,保存当前廉价量
- mScrollY = y;
- //回调onScrollChanged方法
- onScrollChanged(mScrollX, mScrollY, oldX, oldY);
- if (!awakenScrollBars()) {
- invalidate(); //一般都引起重绘
- }
- }
- }
public void scrollBy(int x, int y)
说明:在当前视图内容在Layout布局中继续偏移(x , y)个单位。
方法原型为: View.java类中
- /**
- * Move the scrolled position of your view. This will cause a call to
- * {@link #onScrollChanged(int, int, int, int)} and the view will be
- * invalidated.
- * @param x the amount of pixels to scroll by horizontally
- * @param y the amount of pixels to scroll by vertically
- */
- // 看出原因了吧 。
。
mScrollX 与 mScrollY 代表我们当前偏移的位置 , 在当前位置继续偏移(x ,y)个单位
- public void scrollBy(int x, int y) {
- scrollTo(mScrollX + x, mScrollY + y);
- }
大家自己想一想。
Scroller 和OverScroller分析
我们知道想把一个View的内容偏移至指定坐标(x,y)处,利用scrollTo()方法直接调用就OK了。但我们不能忽视的是,该方法本身来的的副作用:很迅速的将View/ViewGroup偏移至目标点,而没有对这个偏移过程有不论什么控制。对用户而言可能是不太友好的。于是,基于这样的偏移控制,Scroller类被设计出来了,该类的主要作用是为偏移过程制定一定的控制流程(后面我们会知道的很多其它),从而使偏移更流畅,更完美。
可能上面说的比較悬乎,道理也没有讲透。以下我就依据特定情景帮助大家分析下:
情景: 从上海怎样到武汉?
普通的人可能会想,so easy : 飞机、轮船、11路公交车...
文艺的人可能会想, 小 case : 时空忍术(火影的招数)、翻个筋斗(孙大圣的招数)...
无论怎么样,我们想出来的套路可能有两种:
1、有个时间控制过程才干抵达(缓慢的前进) ----- 相应于Scroller的作用
如果做火车,这个过程可能包含: 火车速率,花费周期等。
2、瞬间移动(超神太快了。都眩晕了,用户体验不太好) ------ 相应于scrollTo()的作用
我们看看View里面的computeScroll()做了些什么
/**
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
* object.
*/
public void computeScroll() {
}
空的。意思非常明显。自己加入。
computeScroll()方法介绍
为了易于控制滑屏控制,Android框架提供了 computeScroll()方法去控制这个流程。在绘制View时。会在draw()过程调用该方法。因此。 再配合使用Scroller实例。我们就能够获得当前应该的偏移坐标,手动使View/ViewGroup偏移至该处。
computeScroll()方法原型例如以下,该方法位于ViewGroup.java类中
- /**
- * Called by a parent to request that a child update its values for mScrollX
- * and mScrollY if necessary. This will typically be done if the child is
- * animating a scroll using a {@link android.widget.Scroller Scroller}
- * object.
- */由父视图调用用来请求子视图依据偏移值 mScrollX,mScrollY又一次绘制
- public void computeScroll() { //空方法 。自己定义ViewGroup必须实现方法体
- }
为了实现偏移控制,一般自己定义View/ViewGroup都须要重载该方法 。
其调用过程位于View绘制流程draw()过程中,例如以下:
- @Override
- protected void dispatchDraw(Canvas canvas){
- ...
- for (int i = 0; i < count; i++) {
- final View child = children[getChildDrawingOrder(count, i)];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- }
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- ...
- child.computeScroll();
- ...
- }
既然如今我们知道了:
其实讲到这里,有的同学可能比較迷惑,OverScroller和Scroller有什么差别呢?其实,这两个类都属于Scrollers,Scroller出现的比較早,在API1就有了。OverScroller是在API9才加入上的,出现的比較晚,所以功能比較完好,Over的意思就是超出。即OverScroller提供了对超出滑动边界的情况的处理,这两个类80%的API是一致的。OverScroller比Scroller加入了一下几个方法
☞ isOverScrolled()
☞ springBack(int startX, int startY, int minX, int maxX, int minY, int maxY)
☞ fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY)
☞ notifyHorizontalEdgeReached(int startX, int finalX, int overX)
☞ notifyVerticalEdgeReached(int startY, int finalY, int overY)
从名字也能看出来。都是对Over功能的支持。其它的API都一样。所以介绍通用API的时候。并不区分OverScroller和Scroller。
以下简介一下经常使用的API。
☞ startScroll(int startX, int startY, int dx,
int dy)
☞ startScroll(int
startX, int startY, int dx, int dy,int duration)
核心用来开启滚动的方法:
參数
startX 滚动起始值
startY 滚动起始值
dx 水平方向滑动的距离,正值会使滚动向左滚动
dy 垂直方向滑动的距离。正值会使滚动向上滚动
duration 滚动持续时间,以毫秒计。缺省值250ms作为持续时间。
☞ computeScrollOffset() 这个函数非常核心,表面上来看他仅仅是用来推断我们的滚动是否结束
if (mScroller.computeScrollOffset())
看起来似乎没有什么价值,起始不然
//依据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中
public boolean computeScrollOffset() {
if (mFinished) { //已经完毕了本次动画控制,直接返回为false
return false;
}
.......
mScrollerX.updateScroll(q);
mScrollerY.updateScroll(q);
.......
}
这就是为什么我们称Scroller为滚动的助手类,原来如此,我们在startScroll中设好初始值 、滚动距离、滚动时间。那么
computeScrollOffset会帮我们计算出在特定的时间内应该滚动在什么地方,到时候我们仅仅要通过getCurrX()和getCurrY()得到就能够了
☞ getCurrX() 这个就是获取当前滑动的坐标值,由于Scrollers仅仅是一个辅助计算类,所以假设我们想获取滑动时的时时坐标,就能够通过这种方法获得。然后在computeScroll()里面调用
☞ getFinalX() 这个是用来获取终于滑动停止时的坐标
☞ isFinished() 用来推断当前滚动是否结束
那么 。到了这里我们就一目了然。Scroller的基本用法不外乎:
mScroller.startScroll(<span style="color: rgb(51, 51, 51); font-family: 宋体;font-size:18px; line-height: 30px; text-indent: 28px;">startX ,<span style="color: rgb(51, 51, 51); font-family: 宋体;font-size:18px; line-height: 30px; text-indent: 28px;">startY,dx,dy,duration</span></span>);
@Override
public void computeScroll() { // 先推断mScroller滚动是否完毕
if (mScroller.computeScrollOffset()) {
// 这里调用View的scrollTo()完毕实际的滚动
scrollTo( mScroller.getCurrX(), mScroller .getCurrY());
// 必须调用该方法,否则不一定能看到滚动效果
invalidate();
}
super.computeScroll();
}
那么到了这里,基本上我们对于Scroller的基本使用应该就不是问题了。
今天就到这里吧。下次再写个高于高级使用续篇吧。