View源碼分析
- Android 6.0 & API Level 23
- Github: Nvsleep
- 郵箱: lizhenqiao@126.com
簡(jiǎn)述
- 主要分析從XML資源文件中生成View對(duì)象過程;
- 以及View的構(gòu)造函數(shù),measure(),layout()方法分析;
- invalidate()請(qǐng)求刷新重繪視圖過程分析;
- View自身touch事件處理onTouchEvent()方法分析
View對(duì)象的生成
一般View對(duì)象的生成有兩種,從XML文件中通過LayoutInflater(子類PhoneLayoutInflater)的inflate()經(jīng)反射生成對(duì)象,一種是直接new View(Context)直接通過構(gòu)造函數(shù)生成;
在Activity.onCreate()中的setContentView(@LayoutRes int layoutResID),最終調(diào)用的是其window(PhoneWindow)的setContentView(int layoutResID):
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 填充資源文件中視圖到activity的window的頂層視圖內(nèi)容部分
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
PhoneWindow是在Activity.attach()中生成的,且傳入的Context是當(dāng)前Activity對(duì)象本身,并由window的final Context mContext成員變量引用;
在為window初始化頂層視圖DecorView后,會(huì)調(diào)用LayoutInflater的inflate(@LayoutRes int resource, @Nullable ViewGroup root)方法,下面具體看下該過程:
// root參數(shù)為由資源文件生成的view的父view
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
// attachToRoot參數(shù)表示是否將生成的view 添加add到root父view中
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
// 從資源文件中解析出"layout"即與布局相關(guān)的Parser對(duì)象
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
// 主要方法
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
// 轉(zhuǎn)換為AttributeSet屬性集合
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
// 首先看首節(jié)點(diǎn)是否屬于"merge"標(biāo)簽,如果inflate時(shí)候沒有給予父view,則會(huì)拋出異常
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
// 真正生成view的地方
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
// 傳入給予的父view不為空,則根據(jù)屬性集合在父view中為當(dāng)前生成的view生成一個(gè)LayoutParams布局參數(shù)對(duì)象
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
// 如果傳入父view不為空,一般attachToRoot==true,就不會(huì)在這里直接為生成的view賦值LayoutParams
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
// 這里去為當(dāng)前生成的view填充其節(jié)點(diǎn)下面的子view
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
// 這里將由xml生成的view樹addview到傳入的父view中
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
// 如果調(diào)用inflate()沒有傳入root父view,或者attachToRoot==false,則當(dāng)前方法返回的對(duì)象是反射生成的view本身,否則返回的其實(shí)是父view即傳入的root參數(shù)
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
看下createViewFromTag()的實(shí)現(xiàn)
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
View view;
// 一般情況這里的mFactory2==null,mFactory==null
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
// 在acitivity.attach()中為其window的LayoutInflater mLayoutInflater設(shè)置了mPrivateFactory = acitivity本身.
// 這里主要用于在xml中直接使用Fragment標(biāo)簽,會(huì)去acitivity中生成該Fragment對(duì)象并返回其Fragment.onCreatView()中返回的該fragment封裝的view
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
// 通過反射生成Android系統(tǒng)view
view = onCreateView(parent, name, attrs);
} else {
// 通過反射生成我們自定義的view
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
}
}
看下為當(dāng)前節(jié)點(diǎn)view填充子節(jié)點(diǎn)view所調(diào)用的rInflateChildren()方法過程:
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
// 解析"tag"標(biāo)簽節(jié)點(diǎn)
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
// 解析"include"標(biāo)簽節(jié)點(diǎn)
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
// 由此可見"merge"節(jié)點(diǎn)只能是首節(jié)點(diǎn)
throw new InflateException("<merge /> must be the root element");
} else {
// 這里同之前一樣,先生成該節(jié)點(diǎn)view對(duì)象,然后循環(huán)填充該節(jié)點(diǎn)view的子view,最后會(huì)將該節(jié)點(diǎn)addview到該節(jié)點(diǎn)view的上一層級(jí)節(jié)點(diǎn)view即傳入父節(jié)點(diǎn)view中.
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
// 通知該父節(jié)點(diǎn)View其內(nèi)部所有子view已經(jīng)由xml打入填充完畢
if (finishInflate) {
parent.onFinishInflate();
}
}
- 可以看出,在調(diào)用Acitivity.setContentView()方法完成后,已經(jīng)將視圖樹建立完畢,即將資源文件各個(gè)view節(jié)點(diǎn)分別通過反射生成對(duì)象然后addview到父view中,且將xml資源文件的根view填充依附到window的頂層視圖容器DecorView的內(nèi)容部分mContentParent中.這一過程中,所有view都在構(gòu)造函數(shù)中做了初始化,且都設(shè)置了由父view容器調(diào)用generateLayoutParams()生成對(duì)應(yīng)的LayoutParams對(duì)象.
- 手動(dòng)調(diào)用LayoutInflater.inflate()資源文件的時(shí)候,最大的區(qū)別就在inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 中的root參數(shù)和attachToRoot參數(shù)設(shè)置,如果root為null則返回?zé)o設(shè)置LayoutParams()的xml根節(jié)點(diǎn)view對(duì)象,若root!=null,但是attachToRoot==false則返回是設(shè)置LayoutParams()的xml根節(jié)點(diǎn)view對(duì)象,若root!=null同時(shí)attachToRoot==true則將xml根view生成LayoutParams()且通過父view即root依附addview到root上
View的構(gòu)造函數(shù)
view的構(gòu)造函數(shù)有5個(gè),除了無參數(shù)的構(gòu)造函數(shù)一般最后都調(diào)用如下2個(gè):
public View(Context context) {
mContext = context;
mResources = context != null ? context.getResources() : null;
mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
// Set some flags defaults
mPrivateFlags2 =
(LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
(TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) |
(PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) |
(TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
(PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
(IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
mRenderNode = RenderNode.create(getClass().getName(), this);
if (!sCompatibilityDone && context != null) {
final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
// Older apps may need this compatibility hack for measurement.
sUseBrokenMakeMeasureSpec = targetSdkVersion <= JELLY_BEAN_MR1;
// Older apps expect onMeasure() to always be called on a layout pass, regardless
// of whether a layout was requested on that View.
sIgnoreMeasureCache = targetSdkVersion < KITKAT;
Canvas.sCompatibilityRestore = targetSdkVersion < M;
// In M and newer, our widgets can pass a "hint" value in the size
// for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
// know what the expected parent size is going to be, so e.g. list items can size
// themselves at 1/3 the size of their container. It breaks older apps though,
// specifically apps that use some popular open source libraries.
sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < M;
sCompatibilityDone = true;
}
}
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
this(context);
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
if (mDebugViewAttributes) {
saveAttributeData(attrs, a);
}
Drawable background = null;
int leftPadding = -1;
int topPadding = -1;
int rightPadding = -1;
int bottomPadding = -1;
int startPadding = UNDEFINED_PADDING;
int endPadding = UNDEFINED_PADDING;
int padding = -1;
int viewFlagValues = 0;
int viewFlagMasks = 0;
boolean setScrollContainer = false;
int x = 0;
int y = 0;
float tx = 0;
float ty = 0;
float tz = 0;
float elevation = 0;
float rotation = 0;
float rotationX = 0;
float rotationY = 0;
float sx = 1f;
float sy = 1f;
boolean transformSet = false;
int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
int overScrollMode = mOverScrollMode;
boolean initializeScrollbars = false;
boolean initializeScrollIndicators = false;
boolean startPaddingDefined = false;
boolean endPaddingDefined = false;
boolean leftPaddingDefined = false;
boolean rightPaddingDefined = false;
final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
....
}
}
....
setOverScrollMode(overScrollMode);
if (background != null) {
setBackground(background);
}
internalSetPadding(
mUserPaddingLeftInitial,
topPadding >= 0 ? topPadding : mPaddingTop,
mUserPaddingRightInitial,
bottomPadding >= 0 ? bottomPadding : mPaddingBottom);
if (x != 0 || y != 0) {
scrollTo(x, y);
}
if (transformSet) {
setTranslationX(tx);
setTranslationY(ty);
setTranslationZ(tz);
setElevation(elevation);
setRotation(rotation);
setRotationX(rotationX);
setRotationY(rotationY);
setScaleX(sx);
setScaleY(sy);
}
....
}
可以看出,view的構(gòu)造函數(shù)設(shè)置了初始化了一些基本屬性,比如Context,mRenderNode等,主要還是解析并獲取了對(duì)應(yīng)屬性集合中一些參數(shù)并賦值給成員變量,比如mBackground,mPaddingTop,mPaddingLeft,mPaddingRight,mPaddingBottom等等,為mRenderNode賦值設(shè)置translationX,translationY,rotationX等相關(guān)顯示參數(shù)等;
measure()測(cè)量方法
view的measure()是由父view分發(fā)調(diào)用(最開始是從ViewRootImpl的performMeasure()測(cè)量DecroView開始的)的,傳入的參數(shù)widthMeasureSpec和heightMeasureSpec,是由32位int值來表示,高2位代表該view的測(cè)量模式,剩余30位代表了實(shí)際了父view根據(jù)自身尺寸情況和子view布局參數(shù)生成的一個(gè)期望值.
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 通常沒有設(shè)置layoutMode,這里為false
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
// 生成mMeasureCache Long-long稀疏數(shù)組中保存測(cè)量值的key
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
// 這里看flag的PFLAG_FORCE_LAYOUT位是否被強(qiáng)制置位,或者測(cè)量參數(shù)與上次是否有不一樣
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
// 清除flag的PFLAG_MEASURED_DIMENSION_SET位,表明view當(dāng)前沒有設(shè)置尺寸
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
// 查詢保存在緩存中是否有當(dāng)前測(cè)量值的key,有的話同時(shí)flag的PFLAG_FORCE_LAYOUT位沒有強(qiáng)制設(shè)置的話則不需要重新onMeasure()測(cè)量
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
// 這里去具體設(shè)置該view的測(cè)量寬mMeasuredWidth和高mMeasuredHeight
onMeasure(widthMeasureSpec, heightMeasureSpec);
// 清除flag3的PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT位
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
// 如果是直接拿緩存沒有走onMeasure()則在這里置該位,在layout()中再去onMeasure()
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
// 這里檢查一下,防止子類重新沒有真正為該view賦值設(shè)置測(cè)量寬高
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
// 置flag的PFLAG_LAYOUT_REQUIRED位表明需要走layout()
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
// 保存當(dāng)前由父view下發(fā)的測(cè)量值
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
// View默認(rèn)的onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// 這里可以看出,view默認(rèn)的測(cè)量中l(wèi)ayoutParams寬高設(shè)置為match_parent和wrap_content效果一樣,均為父view計(jì)算給出的size
// 所以自定義view一般要重寫onMeasure()區(qū)分設(shè)置layoutParams為wrap_content情況
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;
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
// 一般情況沒有設(shè)置layoutmode時(shí)候,為false
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
// 這里真正為該view賦值mMeasuredWidth和mMeasuredHeight,同時(shí)標(biāo)記flag的PFLAG_MEASURED_DIMENSION_SET位,表明已經(jīng)設(shè)置好測(cè)量寬高
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
view的measure方法是final修飾的,通常在派生類中重新onMeasure()方法可以根據(jù)layoutParams的情況(match_parent或是wrap_content)重新設(shè)置賦值測(cè)量的寬和高.對(duì)于自定義ViewGroup,還要在onMeasure()中去遍歷測(cè)量其子view.
layout()布局方法
View的layout()方法也是由父view分發(fā)調(diào)用的,最開始是從ViewRootImpl的performLayout()開始的
/**
* @param l Left position, relative to parent 該view左邊界距離父容器view左邊界距離
* @param t Top position, relative to parent 該view上邊界距離父容器view上邊界距離
* @param r Right position, relative to parent 該view右邊界距離父容器view左邊界距離
* @param b Bottom position, relative to parent 該view下邊界距離父容器view上邊界距離
*/
// 很明顯,傳入的參數(shù)分別是當(dāng)前view相對(duì)父view的左邊界,上邊界,由邊界,下邊界的距離
public void layout(int l, int t, int r, int b) {
// 這里如果在上面的measeure()中是直接從緩存中拿的沒有走onMeasure()的話要去走一次onMeasure()
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// 首先記錄上次該view四邊與其父view左邊界,上邊界相應(yīng)的距離,僅僅用于通知位置改變回調(diào)
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// ture 表示該view在父view中位置發(fā)生變化,即該view有一邊與其父view左or上邊界相應(yīng)的距離發(fā)生改變
// 真正改變位置的一般是在setFrame()中
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 如果該view位置改變,或者flag的PFLAG_LAYOUT_REQUIRED被強(qiáng)制置位,則去重新onLayout()布局子view
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 當(dāng)前該view位置在其父view中位置變化了,則要重新去布局其子view,當(dāng)然如果有子view的話
// 一般想系統(tǒng)的LinearLayout,FrameLayout,RelativeLayout以及自定義viewGroup進(jìn)要重新該方法
// 去分配設(shè)置子view的相對(duì)該view的位置,View.java默認(rèn)為空實(shí)現(xiàn),ViewGroup一般要去重寫該方法
onLayout(changed, l, t, r, b);
// 自己和子view都已經(jīng)分配好位置后,清除flag的PFLAG_LAYOUT_REQUIRED位
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
// 通知位置變化的監(jiān)聽回調(diào)
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);
}
}
}
// 清除flag的PFLAG_FORCE_LAYOUT位
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
// 為當(dāng)前view指定其在父容器view中具體位置的是在該方法中
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
// 首先記錄當(dāng)前的flag的PFLAG_DRAWN位
int drawn = mPrivateFlags & PFLAG_DRAWN;
// 每個(gè)view都是一個(gè)矩形,實(shí)際寬度為對(duì)應(yīng)邊界
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
// 該view的位置即將改變,先去刷新一遍該view的當(dāng)前視圖區(qū)域
invalidate(sizeChanged);
// 分配賦值新位置,同時(shí)更新RenderNode mRenderNode的參數(shù)
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
// 置位flag的PFLAG_HAS_BOUNDS位,表示該view有邊界束縛范圍了
mPrivateFlags |= PFLAG_HAS_BOUNDS;
// view寬度or高度變化的方法通知
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
// If we are visible, force the DRAWN bit to on so that
// this invalidate will go through (at least to our parent).
// This is because someone may have invalidated this view
// before this call to setFrame came in, thereby clearing
// the DRAWN bit.
// 因?yàn)橐粋€(gè)invalidate()過程開始進(jìn)入后會(huì)清除flag的PFLAG_DRAWN位,直到當(dāng)次UI繪制結(jié)束會(huì)重新置該位表示當(dāng)次UI繪制完成
// 所以這里為了保證一定走該方法去刷新視圖,故先強(qiáng)制設(shè)置
mPrivateFlags |= PFLAG_DRAWN;
// 這里是真正去刷新位置變化后該view的視圖了
invalidate(sizeChanged);
// parent display list may need to be recreated based on a change in the bounds
// of any child
invalidateParentCaches();
}
// Reset drawn bit to original value (invalidate turns it off)
// 在invalidate()方法調(diào)用之后,恢復(fù)之前 的flag的PFLAG_DRAWN位
mPrivateFlags |= drawn;
// 該view位置變化的同時(shí)告知其背景size改變了
mBackgroundSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
從上面看出,view的layout()主要為該view指定其在父容器view中位置,且會(huì)兩次調(diào)用invalidate()方法去刷新UI,之后會(huì)走onLayout()方法,如果該view本身是一個(gè)容器ViewGroup子類的話,則需要在該方法中調(diào)用子view的layout()方法為其子view分配指定相對(duì)其左邊界和上邊界的相對(duì)位置
invalidate()
這個(gè)方法通常用來刷新某個(gè)view的UI視圖,主要是會(huì)調(diào)用onDraw()方法;
該方法內(nèi)部十分復(fù)雜,下面簡(jiǎn)要分析其大概過程
/**
* Invalidate the whole view. If the view is visible,
* {@link #onDraw(android.graphics.Canvas)} will be called at some point in
* the future.
* <p>
* This must be called from a UI thread. To call from a non-UI thread, call
* {@link #postInvalidate()}.
*/
// 由注釋可知,調(diào)用該方法必須在主線程且會(huì)在隨后調(diào)用onDraw()方法
public void invalidate() {
invalidate(true);
}
/**
* This is where the invalidate() work actually happens. A full invalidate()
* causes the drawing cache to be invalidated, but this function can be
* called with invalidateCache set to false to skip that invalidation step
* for cases that do not need it (for example, a component that remains at
* the same dimensions with the same content).
*
* @param invalidateCache Whether the drawing cache for this view should be
* invalidated as well. This is usually true for a full
* invalidate, but may be set to false if the View's contents or
* dimensions have not changed.
*/
// invalidateCache為ture會(huì)使view的flag的置位PFLAG_INVALIDATED,清除PFLAG_DRAWING_CACHE_VALID位
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
// 一般如果該view不是VISIBLE狀態(tài)且不處于動(dòng)畫狀態(tài)則中斷刷新UI
if (skipInvalidate()) {
return;
}
// 首先判斷PFLAG_DRAWN位上傳UI繪制是否完成,PFLAG_HAS_BOUNDS位是否已經(jīng)指定該view四邊界位置,在layout()的setFrame()方法調(diào)用中置位了
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)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
// 這里清楚該位表明當(dāng)次UI繪制已經(jīng)開始沒有完成
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags |= PFLAG_DIRTY;
// PFLAG_INVALIDATED表示需要重新創(chuàng)建View的DisplayListCanvas
// PFLAG_DRAWING_CACHE_VALID表示view的Bitmap drawing cache緩存有效可用,清除該位表明需要
// 去創(chuàng)建該view的Bitmap drawing cache
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Propagate the damage rectangle to the parent view.
// mAttachInfo這個(gè)成員變量是在繪制顯示窗口頂層視圖時(shí)候創(chuàng)建ViewRootImpl這個(gè)頂級(jí)ViewParent對(duì)象的時(shí)候生成的
// 并在performTraversals()中調(diào)用host.dispatchAttachedToWindow(mAttachInfo, 0)方法沿著view樹體系向下賦值引用
final AttachInfo ai = mAttachInfo;
// 每個(gè)view的容器父view都賦值引用到子View的mParent成員變量,最頂層的容器View即DecorView的mParent指向了頂級(jí)ViewParent即ViewRootImpl對(duì)象
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
// Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
// Damage the entire IsolatedZVolume receiving this view's shadow.
if (isHardwareAccelerated() && getZ() != 0) {
damageShadowReceiver();
}
}
}
由上面可以看出,view.invalidate()最后是把其自身邊界尺寸傳遞到上一層父view中,讓父view調(diào)用invalidateChild()方法,一般父容器view是ViewGroup這個(gè)實(shí)現(xiàn)了ViewParent的抽象類的派生類,最頂層的容器View即DecorView的mParent指向了頂級(jí)ViewParent即ViewRootImpl,下面看下ViewGroup的invalidateChild()方法:
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
....
final int[] location = attachInfo.mInvalidateChildLocation;
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
....
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}
// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
// flag coming from the child that initiated the invalidate
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
}
// 重點(diǎn)方法,這里parent先就是當(dāng)前this對(duì)象
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) (boundingRect.left - 0.5f),
(int) (boundingRect.top - 0.5f),
(int) (boundingRect.right + 0.5f),
(int) (boundingRect.bottom + 0.5f));
}
}
} while (parent != null);
}
/**
* Don't call or override this method. It is used for the implementation of
* the view hierarchy.
*
* This implementation returns null if this ViewGroup does not have a parent,
* if this ViewGroup is already fully invalidated or if the dirty rectangle
* does not intersect with this ViewGroup's bounds.
*/
// 聯(lián)合設(shè)置統(tǒng)一當(dāng)前需要重繪的UI視圖矩形與該view本身的四邊界
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
FLAG_OPTIMIZE_INVALIDATE) {
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
final int left = mLeft;
final int top = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
dirty.setEmpty();
}
}
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
}
return mParent;
} else {
mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = mLeft;
location[CHILD_TOP_INDEX] = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
} else {
// in case the dirty rect extends outside the bounds of this container
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
}
return mParent;
}
}
return null;
}
invalidateChild()里面的do..while()循環(huán)一直不端的調(diào)用invalidateChildInParent(),invalidateChildInParent()返回父ViewParent并聯(lián)合設(shè)置統(tǒng)一當(dāng)前需要重繪的UI矩形與view本身四邊界,直到view樹向上到達(dá)ViewRootImpl的invalidateChildInParent()方法返回null中斷循環(huán),看下頂層ViewParent實(shí)現(xiàn)類的ViewRootImpl的invalidateChildInParent()方法;
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
// 檢查方法調(diào)用的線程是否就是UI線程即APP主線程,不是則拋出異常,這也是為何invaldate()要在主線程調(diào)用的直接原因
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
invalidateRectOnScreen(dirty);
return null;
}
// mThread即當(dāng)前APP進(jìn)程主線程,在ViewRootImpl構(gòu)造函數(shù)中賦值,因?yàn)閂iewRootImpl必然是在主線程創(chuàng)建的
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
// 合并由view樹體系向上傳來的需要重繪的區(qū)域與成員變量mDirty區(qū)域,并保存在成員變量mDirty中
// 之后調(diào)用scheduleTraversals()向主線程Looper的MessageQueue消息隊(duì)列發(fā)送一個(gè)Message去請(qǐng)求刷新視圖了
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
// Add the new dirty rect to the current one
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
// Intersect with the bounds of the window to skip
// updates that lie outside of the visible region
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
如果當(dāng)前已經(jīng)有一個(gè)UI刷新過程在進(jìn)行中的時(shí)候,mWillDrawSoon為true,則不再去刷新視圖了;
onTouchEvent()
不管是ViewGroup容器還是View本身,首先都是一個(gè)View,作為View都有自身的基本的touch事件處理邏輯,即View類中的onTouchEvent()方法;
如果是ViewGroup的話,一般是沒有子view處理分發(fā)的事件或者自己攔截了事件強(qiáng)制分發(fā)到自身處理,然后調(diào)用其onTouchEvent()方法
PS:寫到這里,不想在這篇文章分析view的touch事件處理了,今天中秋節(jié),想想很有空,還是寫一篇View和ViewGroup的事件分發(fā),攔截,自身處理的源碼分析文章.
- Android 6.0 & API Level 23
- Github: Nvsleep
- 郵箱: lizhenqiao@126.com