绘制滚动条是通过在View类 的 draw()函数中调用onDrawScrollBar()完成的。每个视图都可以有滚
* 动条,请 注 意 是 “每个”,因为滚动条是视图中包含的基本元素,就像视图的背景一样。举个例子,一
* 个 按 钮 Button)也可以有滚动条,读者可能觉得奇怪,按钮怎么会有滚动条呢?但的确是这样,而且
* 让按钮显示滚动条是件非常容易的事情,因为按钮本身也是一个视图,只_是从用户的角度来讲,按钮不
* 需要显示这个滚动条而已。
* 滚动条包含垂直滚动条和水平滚动条,滚动条本身是一个Drawable对象,View类内部使用
* ScrollBarDrawable类表示滚动条,View可以同时绘制水平滚动条和垂直滚动条。
* 在 ScrollBarDrawable类中,包含三个基本尺寸,分别是range、 offset、 extent。
* • range:代表该滚动条从头到尾滚动中所跨越的范围有多大。比如想用一个滚动条标识一万行代
* 码,那 么 range可以设为10000。
* • offset:代表滚动条当前的偏移量。比如当前在看第600行代码,那 么 offset就 是 600。
* • extent:代表该滚动条在屏幕上的实际高度,比如200,单位是dip。
* 有了以上三个尺寸后,ScrollBarDrawable内部就可以计算出滚动条的高度及滚动条的位置。
* 除了以上尺寸外,ScrollBarDrawable类内部还包含两个Drawable对象,一个标识滚动条的背景,
* 另一个标识滚动条自身。这两个Drawable对象分别是track和 thumble,水平方向和垂直方向分别有两
* 个该Drawable对象.**
protected final void onDrawScrollBars(Canvas canvas) {
// scrollbars are drawn only when the animation is running
/**
* onDrawScrollBarQ函数内部的执行流程如下。
* 1) 判断该View对象内部是否存在ScrollabilityCache对象,即变量mScrollCache是否为空。对应
* 用程序而言,当需要视图显示滚动条时,可以在XM L文件中使用androidiscrollbars属性为视图指定滚
* 动条,属性值可以是vertical、 horzontal、 none,从而在View的构造函数中将会调用initializeScrollbars()
* 创建一个ScrollablilityCache对象。如果View对象不是从XML文件中产生,而是通过程序动态产生,
* 则可以调用View类 的 setScrollbarFaddingEnable(boolean)函数设置滚动条是否“ 自动隐藏(fadding)”,该
* 函数内部则会调用initializeScrollbars()初 始 化 ScrollabilityCache对象。什么是“自动隐藏”?Android
* 中的滚动条和PC上的滚动条有所不同,对 于 PC上的滚动条而言,在一般情况下滚动条会一直存在,
* 在默认情况下滚动条并不显示,只有当用户做滚动操作时才会显示,滚动完毕后,滚动条就会自动藏起
* 来,这就叫自动隐藏。应用程序可以调用setScmllbarFaddingEnable()设置是否自动隐藏,在默认情况下
* 是自动隐。
*/
final ScrollabilityCache cache = mScrollCache;
if (cache != null) {
/**
* 2)如 果 cache存在,则继续判断cache的 状 态 (state)。 ScrollablilityCache内部有两种状态,分别
* 为 ON和 OFF,ON意味着滚动条处于显示状态,OFF意味着滚动条处于隐藏状态。因此,本步骤判断
* 如果是OFF状态的话,就直接返回。
* 那么,什么时候是ON,什么时候是OFF呢?如果滚动条是自动隐藏,那么,在
* setScrollbarFaddingEnable()函数中会将该cache的状态置为OFF,否则置为ON。如果是自动隐藏,那么
* 当应用程序调用scrollBy()函数时,该函数内部会间接调用awakenScrollBars(),该函数中会把cache的
* 状 态 “暂时置” 为 ON。为什么这里是暂时呢?因为该函数将cache状态置为ON后,紧接着会发送一
* 个异步延迟消息,在指定的延迟时间后,消息的处理函数又会重新将cache的状态置为OFF,从而使得
* 在下次绘制滚动条时会在本步骤中直接返回,这也就是“ 自动隐藏” 的具体过程。
* awakenScrollBars()函数的类型是protected,意味着该函数只能被重载,而不能被应用程序直接调用,
* 应用程序一般只能调用scrollBy()函数。而对于自定义View而言,比如ListView,其内部实际上并没有
* 滚动值,所以也就不能调用 scrollBy()函数,而它实现滚动条自动隐藏的效果正是借助于
* awakenScrollBars()函数。
*/
int state = cache.state;
if (state == ScrollabilityCache.OFF) {
return;
}
boolean invalidate = false;
/**
* 3 判 断 cache的状态是否为FADING。FADDING的本质上依然是ON,只 是 它 “正在隐藏”,
* 所 谓 “正在隐藏” 的效果一般就是滚动条“逐渐消失”,其实现方法是逐渐减少滚动条的阿尔法通道值。
* 如果不是FADDING,则将阿尔法值设为Oxff,也就是完全不透明。
*/
if (state == ScrollabilityCache.FADING) {
// We're fading -- get our fade interpolation
if (cache.interpolatorValues == null) {
cache.interpolatorValues = new float[1];
}
float[] values = cache.interpolatorValues;
// Stops the animation if we're done
if (cache.scrollBarInterpolator.timeToValues(values) ==
Interpolator.Result.FREEZE_END) {
cache.state = ScrollabilityCache.OFF;
} else {
cache.scrollBar.setAlpha(Math.round(values[0]));
}
// This will make the scroll bars inval themselves after
// drawing. We only want this when we're fading so that
// we prevent excessive redraws
invalidate = true;
} else {
// We're just on -- but we may have been fading before so
// reset alpha
cache.scrollBar.setAlpha(255);
}
final int viewFlags = mViewFlags;
/**
* 4 如果存在水平或者垂直滚动条,则逐个进行绘制。( 1 ) 首先调用scrollBar.getSizeO获
* 得滚动条的大小。对于垂直滚动条而言,大小就是指track或者
* thumb的宽度;水平方向是指track或 者 thumb的高度,只 有
* 当 size小 于 0 时,才 会使用X M L 中
* android:scrollbarSize 属性的值。( 2 ) 绘制水平滚动条。( 3 ) 绘制
* 垂直滚动条。绘制时,首先回调computeVerticalRange()等三个函数,获得当前的range、
* offset及 extent值,然后将这三个值设置到scrollBar中。这就是为什么滚动条会“滚动” 的原因。自定
* 义视图的设计者应该重载这三个computeXXXO函数,并在函数实现中根揮自定义的滚动情况返回相应
* 的值,从而使得View系统能够绘制出具有正确位置的滚动条。源码中有一段注释是关于RTL语言的,
* RTL是指从右向左阅读的语言。设置完这三个值后,回 调 onDrawVerticalScrollBarO函数,
*
* 即首先设置滚动条对应的剪切区,然后调用scroUBar的 draw()函数将其绘制到剪切区中。
*/
final boolean drawHorizontalScrollBar =
(viewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL;
final boolean drawVerticalScrollBar =
(viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL
&& !isVerticalScrollBarHidden();
if (drawVerticalScrollBar || drawHorizontalScrollBar) {
final int width = mRight - mLeft;
final int height = mBottom - mTop;
final ScrollBarDrawable scrollBar = cache.scrollBar;
int size = scrollBar.getSize(false);
if (size <= 0) {
size = cache.scrollBarSize;
}
final int scrollX = mScrollX;
final int scrollY = mScrollY;
final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
int left, top, right, bottom;
if (drawHorizontalScrollBar) {
scrollBar.setParameters(computeHorizontalScrollRange(),
computeHorizontalScrollOffset(),
computeHorizontalScrollExtent(), false);
final int verticalScrollBarGap = drawVerticalScrollBar ?
getVerticalScrollbarWidth() : 0;
top = scrollY + height - size - (mUserPaddingBottom & inside);
left = scrollX + (mPaddingLeft & inside);
right = scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap;
bottom = top + size;
onDrawHorizontalScrollBar(canvas, scrollBar, left, top, right, bottom);
if (invalidate) {
invalidate(left, top, right, bottom);
}
}
/**
* 以上步骤已经完成了一次绘制,但如果当前滚动条正处于滚动状态,则需要继续调用invalidateO
* 发起一次重绘消息。而判断是否处于滚动状态的变量是invalidate,其值正是当cache状态为FADDING
* 时被赋值为true。调 用 invalidateO时,参数对应的矩形区仅仅是滚动条所在的区域。
* 至此,滚动条绘制就完成了。
*/
if (drawVerticalScrollBar) {
scrollBar.setParameters(computeVerticalScrollRange(),
computeVerticalScrollOffset(),
computeVerticalScrollExtent(), true);
// TODO: Deal with RTL languages to position scrollbar on left
left = scrollX + width - size - (mUserPaddingRight & inside);
top = scrollY + (mPaddingTop & inside);
right = left + size;
bottom = scrollY + height - (mUserPaddingBottom & inside);
onDrawVerticalScrollBar(canvas, scrollBar, left, top, right, bottom);
if (invalidate) {
invalidate(left, top, right, bottom);
}
}
}
}
}