1 scrollyTo和ScrollyBy
首先scrollyTo和ScrollyBy移动的都是View里的内容。
源码中:
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
mScrollX和mScrollY为View现在的位置离初始位置的距离。
所以scrollyTo(x,y)移动距初始位置x和y的长度。
ScrollyBy(x,y)移动距上次位置x和y的长度。
第一个参数x表示相对于当前位置横向移动的距离,正值向左移动,负值向右移动,单位是像素。第二个参数y表示相对于当前位置纵向移动的距离,正值向上移动,负值向下移动,单位是像素。
2 onMeasure 测量视图大小
了解View的测量过程,先了解MeasureSpec。
我们先来瞅瞅官方文档对于MeasureSpec 的介绍:
A MeasureSpec encapsulates the layout requirements passed from parent to child.Each MeasureSpec represents a requirement for either the width or the height.A MeasureSpec is comprised of a size and a mode.
请注意这段话所包含的重要信息点:
1 MeasureSpec封装了父布局传递给子View的布局要求。
2 MeasureSpec可以表示宽和高
3 MeasureSpec由size和mode组成
MeasureSpec通常翻译为”测量规格”,它是一个32位的int数据.
其中高2位代表SpecMode即某种测量模式,低30位为SpecSize代表在该模式下的规格大小.
可以通过如下方式分别获取这两个值:
获取SpecSize
int specSize = MeasureSpec.getSize(measureSpec);
获取specMode
int specMode = MeasureSpec.getMode(measureSpec);
当然,也可以通过这两个值生成新的MeasureSpec
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
SpecMode一共有三种:
MeasureSpec.EXACTLY , MeasureSpec.AT_MOST , MeasureSpec.UNSPECIFIED
View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
………
onMeasure(widthMeasureSpec, heightMeasureSpec);
………
}
widthMeasureSpec和heightMeasureSpec从何而来?
这两个值都是由根视图经过计算后传递给子视图的,分析ViewRoot中的源码中可以知道,这两个值的mode是MeasureSpec.EXACTLY,size是windowSize,也就意味着根视图总是会充满全屏的。
onMeasure()默认执行
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
返回specSize,即默认返回windowSize。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= MEASURED_DIMENSION_SET;
}
这样一次measure过程就结束了。
当然,一个界面的展示可能会涉及到很多次的measure,因为一个布局中一般都会包含多个子视图,每个视图都需要经历一次measure过程。ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小,如下所示:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
这里首先会去遍历当前布局下的所有子视图,然后逐个调用measureChild()方法来测量相应子视图的大小,如下所示:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
这个方法通过调用getChildMeasureSpec(int spec, int padding, int childDimension)计算出子view的MeasureSpec,然后调用子view的measure方法去测量,这就回到了我们上面分析的流程。
在ViewGroup中还有一个方法用来测量子view的MeasureSpec,
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureChild和measureChildWithMargins的处理逻辑是一样的,区别在于measureChildWithMargins带上了子view的margin和父view已用空间。
计算子view的MeasureSpec的逻辑在getChildMeasureSpec()这个方法里,方法比较长,就不贴出来了,总结如下图:
一句话,父容器(如LinearLayout)的MeasureSpec和子View的LayoutParams共同决定了子View的MeasureSpec!
在ViewGroup中没有onMeasure方法,而是提供measureChildren等方法供子类选择调用。
需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。
3 onLayout()确定视图的位置
ViewRoot的performTraversals()方法会在measure结束后继续执行,并调用View的layout()方法来执行此过程,如下所示:
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重绘
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
onLayout()默认执行:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
ViewGroup会调用onLayout()决定子View的显示位置:
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
这就是说ViewGroup的子类都必须重写这个方法,实现自己的逻辑。
onLayout()用于指定子View的大小和位置,所以没有子view的view不用实现这个方法,ViewGroup的子类需要去实现这个方法。View用layout()方法确定自己本身在其父View的位置。
getMeasuredWidth()和getWidth()的区别?
getMeasuredWidth()在onMeasure结束后得到,getWidth()在onLayout结束后得到。
getMeasuredWidth()为测量结果,getWidth()为右边坐标 - 左边坐标。
一般情况下,getMeasuredWidth() = getWidth()。
view.getLeft(),view.getRight(),view.getBottom(),view.getTop()?
下图表示:
4 onDraw()视图绘制
measure和layout的过程都结束后,接下来就进入到draw的过程了。
Draw()过程分为6个步骤:
(1) Draw the background 绘制背景
(2) If necessary, save the canvas’ layers to prepare for fading
保存当前画布的堆栈状态并在该画布上创建Layer用于绘制View在滑动时的边框渐变效果,
通常情况下我们是不需要处理这一步的。
(3) Draw view’s content
绘制View的内容,
这一步是整个draw阶段的核心,在此会调用onDraw()方法绘制View的内容。在此onDraw()是一个空方法;因为每个View所要绘制的内容不同,所以需要由具体的子View去实现各自不同的需求。
protected void onDraw(Canvas canvas) {
}
(4)Draw children
调用dispatchDraw()绘制View的子View
protected void dispatchDraw(Canvas canvas) {
}
有子view的需要去实现。
(5) If necessary, draw the fading edges and restore layers
绘制当前视图在滑动时的边框渐变效果,
通常情况下我们是不需要处理这一步的。
(6) Draw decorations (scrollbars for instance)
绘制View的滚动条,
其实,不单单是常见的ScrollView和ListView等滑动控件任何一个View(比如:TextView,Button)都是有滚动条的,只是一般情况下我们都没有将它显示出来而已。