Android View

来源:互联网 发布:学英语软件 编辑:程序博客网 时间:2024/06/08 14:15

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的显示位置:

@Overrideprotected 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)都是有滚动条的,只是一般情况下我们都没有将它显示出来而已。

0 0
原创粉丝点击