RelativeLayout的onMeasure源码分析
来源:互联网 发布:淘宝赚钱 编辑:程序博客网 时间:2024/06/06 03:54
都知道RelativeLayout的一次测量调用两次子视图测量循环
横向一次 纵向一次
带着目的, 我们来分析源码
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mDirtyHierarchy) { mDirtyHierarchy = false; sortChildren(); }一上来就是重要代码!
如果布局层次是脏的(无效、或过时失效) 那么sortChildren!!!! 这个方法是简历Relative布局关系的核心
private void sortChildren() { final int count = getChildCount();//懒加载初始化两个View数组 分别存放横向 纵向 if (mSortedVerticalChildren == null || mSortedVerticalChildren.length != count) { mSortedVerticalChildren = new View[count]; } if (mSortedHorizontalChildren == null || mSortedHorizontalChildren.length != count) { mSortedHorizontalChildren = new View[count]; } final DependencyGraph graph = mGraph; //描述关系的graph graph.clear();//初始 清空graph for (int i = 0; i < count; i++) { graph.add(getChildAt(i)); //先都add收集 } graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL); //再把两个方向分别sort graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL); }CoordinatorLayout里的DAG这里没有使用, 而是用了一个内部类DependencyGraph描述graph
private static class DependencyGraph {
mNodes存放所有一级子View
/** * List of all views in the graph. */ private ArrayList<Node> mNodes = new ArrayList<Node>();mKeyNodes存放有id的一级子View
/** * List of nodes in the graph. Each node is identified by its * view id (see View#getId()). */ private SparseArray<Node> mKeyNodes = new SparseArray<Node>();mRoots临时数据结构,用于有序的放置目标的View数组,注意:这里的数据结构是ArrayDeque 双端队列 为什么要用这个数据结构 在getSortedViews里会讲到
/** * Temporary data structure used to build the list of roots * for this graph. */ private ArrayDeque<Node> mRoots = new ArrayDeque<Node>();
循环收集“”一级”子View的方法 add(注意是一级,二级里加dependency xml也会给你报错!)
/** * Adds a view to the graph. 把view都收集在graph里 * * @param view The view to be added as a node to the graph. */ void add(View view) { final int id = view.getId(); final Node node = Node.acquire(view); //内部类Node描述节点 if (id != View.NO_ID) { mKeyNodes.put(id, node); //所有有id的View都会被存放在mKeyNodes里,因为有可能被依赖 } mNodes.add(node); //所有的子View都收集在mNodes里 }核心方法getSortedViews排序后放入View数组里
/** * Builds a sorted list of views. The sorting order depends on the dependencies * between the view. For instance, if view C needs view A to be processed first * and view A needs view B to be processed first, the dependency graph * is: B -> A -> C. The sorted array will contain views B, A and C in this order. * 创建有序的view数组。“序”取决于View之间的dependencies关系。 * 例如,如果viewC的布局需要先viewA先处理,而viewA的布局又需要先viewB先处理 * 那么依赖图就是:B -> A -> C 有序数组将这么排序:{viewB,viewA, viewC}。 * * @param sorted The sorted list of views. The length of this array must * be equal to getChildCount(). * @param rules The list of rules to take into account. */ void getSortedViews(View[] sorted, int... rules) { final ArrayDeque<Node> roots = findRoots(rules); //找出所有的没有依赖的node 就是root int index = 0; Node node;// 啥是pollLast: 获取并移除此列表尾部的最后一个元素 while ((node = roots.pollLast()) != null) { //把roots根节点依次pollLast出来 final View view = node.view; final int key = view.getId(); sorted[index++] = view; //然后设置到数组的对应位置 //是不是搞错了 为什么只加了根节点?别急 // 在这个方法内部 所有的根节点rootA被设置完之后, //实际上会把(被rootA直接依赖的)rootB加在roots尾部,进行下一次while循环... //直到这个rootN没有被依赖的root了,才会while到下一个findRoots方法出来的无依赖root final ArrayMap<Node, DependencyGraph> dependents = node.dependents; final int count = dependents.size(); //找到这个node的被依赖树graph for (int i = 0; i < count; i++) { final Node dependent = dependents.keyAt(i); final SparseArray<Node> dependencies = dependent.dependencies; dependencies.remove(key); //优化,移除掉处理过的root if (dependencies.size() == 0) { //把被依赖树的顶层(也就是当前root的直接被依赖) //作为下一次while循环的root对象 add到roots的尾部 roots.add(dependent); } } } if (index < sorted.length) { //如果数组没设置满就跳出了while循环, //说明有循环依赖! 源码是会抛出异常的!!! throw new IllegalStateException("Circular dependencies cannot exist" + " in RelativeLayout"); } }
findRoots方法private的,暂放置,以后再分析。
此时,所有的横向上的依赖“序”都被排序进了mSortedHorizontalChildren;所有的纵向上的依赖“序”都被排序进了mSortedVerticalChildren
回来看onMeasure 下面是一段局部变量的初始化把orientation specSize specMode等信息都初始化出来
int myWidth = -1; int myHeight = -1; int width = 0; int height = 0; final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); final int widthSize = MeasureSpec.getSize(widthMeasureSpec); final int heightSize = MeasureSpec.getSize(heightMeasureSpec); // Record our dimensions if they are known; if (widthMode != MeasureSpec.UNSPECIFIED) { myWidth = widthSize; } if (heightMode != MeasureSpec.UNSPECIFIED) { myHeight = heightSize; } if (widthMode == MeasureSpec.EXACTLY) { width = myWidth; } if (heightMode == MeasureSpec.EXACTLY) { height = myHeight; } View ignore = null; int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; final boolean horizontalGravity = gravity != Gravity.START && gravity != 0; gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0; int left = Integer.MAX_VALUE; int top = Integer.MAX_VALUE; int right = Integer.MIN_VALUE; int bottom = Integer.MIN_VALUE; boolean offsetHorizontalAxis = false; boolean offsetVerticalAxis = false; if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) { ignore = findViewById(mIgnoreGravity); } final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY; final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;以上没有什么重点代码
下面处理RTL模式
// We need to know our size for doing the correct computation of children positioning in RTL // mode but there is no practical way to get it instead of running the code below. // So, instead of running the code twice, we just set the width to a "default display width" // before the computation and then, as a last pass, we will update their real position with // an offset equals to "DEFAULT_WIDTH - width". // 我们需要知道在正确的计算RTL模式下子View定位时我们自己的大小, // 但实际上没有捷径来获得,只能运行下面的代码。而不是运行代码的两倍! // 我们只是在计算前设置宽度为“默认显示宽度”,之后作为最后一关, // 我们将更新他们的真实位置的偏移量等于“DEFAULT_WIDTH - width” final int layoutDirection = getLayoutDirection(); if (isLayoutRtl() && myWidth == -1) { myWidth = DEFAULT_WIDTH; }
进入正题! 先循环测量水平方向的
//先循环测量水平方向的 View[] views = mSortedHorizontalChildren; int count = views.length; for (int i = 0; i < count; i++) { View child = views[i]; if (child.getVisibility() != GONE) { //GONE的child是不会被measure的 LayoutParams params = (LayoutParams) child.getLayoutParams(); int[] rules = params.getRules(layoutDirection); //取出子view的lp水平方向的所有rule // 这个getRules里的mRules看起来是个临时输出变量//把水平方向上的align_left left_of right_of等等 全部替换成//padding margin mLeft mRight之类的像素属性applyHorizontalSizeRules(params, myWidth, rules);//这里调用子view的measure方法 measureChildHorizontal(child, params, myWidth, myHeight);//最终全部替换成mLeft mRight属性 if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) { offsetHorizontalAxis = true; } } }
依次分析三个主要方法applyHorizontalSizeRules、 measureChildHorizontal、 positionChildHorizontal
嗯 先是applyHorizontalSizeRules
private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) { RelativeLayout.LayoutParams anchorParams; // VALUE_NOT_SET indicates a "soft requirement" in that direction. For example: // left=10, right=VALUE_NOT_SET means the view must start at 10, but can go as far as it // wants to the right // left=VALUE_NOT_SET, right=10 means the view must end at 10, but can go as far as it // wants to the left // left=10, right=20 means the left and right ends are both fixed // VALUE_NOT_SET表示“软要求”在那个方向。例如: // 左为10,右为VALUE_NOT_SET指view必须从10开始,往右边想走多远走多远 // 左= VALUE_NOT_SET,右= 10 意味着view必须结束在10,但左边想走多远走多远 // 左= 10,右= 20 意味着左右两端都是固定的。 childParams.mLeft = VALUE_NOT_SET; childParams.mRight = VALUE_NOT_SET; anchorParams = getRelatedViewParams(rules, LEFT_OF); //下面我们就拿这个属性来先做分析 if (anchorParams != null) { // childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin + childParams.rightMargin); } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) { if (myWidth >= 0) { childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; } }//以下有类同 anchorParams = getRelatedViewParams(rules, RIGHT_OF); if (anchorParams != null) { childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin + childParams.leftMargin); } else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) { childParams.mLeft = mPaddingLeft + childParams.leftMargin; } anchorParams = getRelatedViewParams(rules, ALIGN_LEFT); if (anchorParams != null) { childParams.mLeft = anchorParams.mLeft + childParams.leftMargin; } else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) { childParams.mLeft = mPaddingLeft + childParams.leftMargin; } anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT); if (anchorParams != null) { childParams.mRight = anchorParams.mRight - childParams.rightMargin; } else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) { if (myWidth >= 0) { childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; } } if (0 != rules[ALIGN_PARENT_LEFT]) { childParams.mLeft = mPaddingLeft + childParams.leftMargin; } if (0 != rules[ALIGN_PARENT_RIGHT]) { if (myWidth >= 0) { childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; } } }
这里我们就拿第一个rule LEFT_OF属性的apply来分析举例, 其他几个属性RIGHT_OF ALIGN_LEFT ALIGN_RIGHT ALIGN_PARENT_LEFT ALIGN_PARENT_RIGHT 举一反三即可
anchorParams = getRelatedViewParams(rules, LEFT_OF); //我们就拿LEFT_OF来分析举例 if (anchorParams != null) { ////child的lp的右边属性将被赋值为 = 锚点(rule所依赖)View的左边属性 childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin + childParams.rightMargin); } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) { //注意 特例child有LEFT_OF属性但getRelatedViewParams又没有找到可见的(不为GONE)依赖锚点(下面分析有说明原因)//这时候 如果设置了alignWithParent 那么child会视为对齐parentLeft//那么问题来了 这个alignWithParent是什么鬼? 从哪里冒出来的? if (myWidth >= 0) { childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; } }
alignWithParent寻踪?
老套路 如果是lp属性
generatorLayoutParams会调用RelativeLayout.LayoutParams的构造方法 ,从attr里取出
我们来看
public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.RelativeLayout_Layout); final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion; mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 || !c.getApplicationInfo().hasRtlSupport()); final int[] rules = mRules; //noinspection MismatchedReadAndWriteOfArray final int[] initialRules = mInitialRules; final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); switch (attr) { case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing: alignWithParent = a.getBoolean(attr, false);//原来有这么个属性 是不是以前没用过? break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf: rules[LEFT_OF] = a.getResourceId(attr, 0);果然有哎~~~ 打开layout文件的XML编辑器 嘿~ 还真有这属性 涨姿势
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="demo2.fd.com.demo2application.MainActivity"> <TextView android:layout_alignWithParentIfMissing="true" android:id="@+id/sample_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" />
上面提到了apply操作主要来自使用getRelatedViewParams方法寻找是否有可见的依赖View的lp,追进这个方法
private LayoutParams getRelatedViewParams(int[] rules, int relation) { View v = getRelatedView(rules, relation); //寻找可用的rule依赖对象View if (v != null) { ViewGroup.LayoutParams params = v.getLayoutParams(); if (params instanceof LayoutParams) { return (LayoutParams) v.getLayoutParams(); } } return null; }
找lp先找view 找到了view 那么lp手到擒来 追进getRelatedView方法
private View getRelatedView(int[] rules, int relation) { int id = rules[relation]; if (id != 0) { //寻找目标rules依赖的view的id //之前提到过(见DependencyGraph分析) 有id的View 应该从mKeyNodes这个集合里找 DependencyGraph.Node node = mGraph.mKeyNodes.get(id); if (node == null) return null; View v = node.view; //找到node>>取出rule对应的依赖view>>返回 // Find the first non-GONE view up the chain //如果这个依赖view是GONE的话! rule依赖是不会生效的 //注意! 但是以下while代码会继续寻找依赖链! // 比如有一个childView寻找自己的LEFT_OF属性找到了R.id.title,是GONE的; // 但这个R.id.title也有LEFT_OF属性, 依赖于R.id.sub_title // 是可见的~ 那么childView会根据LEFT_OF依赖于R.id.sub_title进行measure while (v.getVisibility() == View.GONE) { rules = ((LayoutParams) v.getLayoutParams()).getRules(v.getLayoutDirection()); node = mGraph.mKeyNodes.get((rules[relation])); if (node == null) return null; v = node.view; } return v; } return null; }到此为止 applyHorizontalSizeRules方法的操作全揭秘
拖走 下一个主要方法 measureChildHorizontal(View child, LayoutParams params, int myWidth, int myHeight)
measureChildHorizontal方法主要分为三部分
第一部分 计算宽度MeasureSpec 通过getChildMeasureSpec方法
第二部分 计算高度MeasureSpec 注意:这里只是临时measure,意思一下。 后面还会专门再算一遍高度
第三部分 当然就是调用他啦child.measure
private void measureChildHorizontal( View child, LayoutParams params, int myWidth, int myHeight) { //1.先计算宽度MeasureSpec final int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight, params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight, myWidth);//2. 再计算高度MeasureSpec final int childHeightMeasureSpec;//mAllowBrokenMeasureSpecs = version <= Build.VERSION_CODES.JELLY_BEAN_MR1;//也就是说sdk 4.2及以前 mAllowBrokenMeasureSpecs这个值是true 之后是false// myHeight判断负值 下面有说明 if (myHeight < 0 && !mAllowBrokenMeasureSpecs) { if (params.height >= 0) { //RelativeLayout没有具体高度但子view的lp有具体高度就按exactly来 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( params.height, MeasureSpec.EXACTLY); } else {// 在RelativeLayout的测量过程中,mySize/myWidth/myWidth中的负值意味着:// 我们从spec中获取了一个UNSPECIFIED模式 // Negative values in a mySize/myWidth/myWidth value in // RelativeLayout measurement is code for, "we got an // unspecified mode in the RelativeLayout's measure spec." // Carry it forward.RelativeLayout//子view的lp和RelativeLayout都没有具体高度用UNSPECIFIED模式去测量 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } } else { //4.3以后新SDK下 或RelativeLayout给了具体高 final int maxHeight; if (mMeasureVerticalWithPaddingMargin) { maxHeight = Math.max(0, myHeight - mPaddingTop - mPaddingBottom - params.topMargin - params.bottomMargin); } else { maxHeight = Math.max(0, myHeight); } //高度取0和myHeight的最大值 final int heightMode; if (params.height == LayoutParams.MATCH_PARENT) { heightMode = MeasureSpec.EXACTLY; //充满时exactly模式 } else { heightMode = MeasureSpec.AT_MOST; //其他at_molst } childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode); }//调用子view的measure child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
第二部分不是我们现在分析的重点 我们现在正处于社会主义初级阶段 第一遍(水平方向)测量
所以我们重点来看第一部分getChildMeasureSpec方法的调用
这个方法是水平/垂直两用的!因为现在是水平测量,所以我们看到这里的调用:
getChildMeasureSpec(params.mLeft, params.mRight, params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight, myWidth);清一色的全部是lp.mLeft lp.mRight lp.leftMargin lp.rightMargin, mPaddingLeft, mPaddingRight, 以及水平方向上的父布局size: myWidth
/** * Get a measure spec that accounts for all of the constraints on this view. * This includes size constraints imposed by the RelativeLayout as well as * the View's desired dimension. 获取一个度量规范spec来描述此view的所有约束。 这包括RelativeLayout施加的尺寸约束、以及view自己的尺寸诉求。 * * @param childStart The left or top field of the child's layout params * @param childEnd The right or bottom field of the child's layout params * @param childSize The child's desired size (the width or height field of * the child's layout params) * @param startMargin The left or top margin * @param endMargin The right or bottom margin * @param startPadding mPaddingLeft or mPaddingTop * @param endPadding mPaddingRight or mPaddingBottom * @param mySize The width or height of this view (the RelativeLayout) * @return MeasureSpec for the child */ private int getChildMeasureSpec(int childStart, int childEnd, int childSize, int startMargin, int endMargin, int startPadding, int endPadding, int mySize) { int childSpecMode = 0; int childSpecSize = 0; // Negative values in a mySize value in RelativeLayout // measurement is code for, "we got an unspecified mode in the // RelativeLayout's measure spec." // 跟之前解释的一样 负的mySize代表UNSPECIFIED模式 final boolean isUnspecified = mySize < 0;//isUnspecified代表 RelativeLayout的宽度有没有被指定 if (isUnspecified && !mAllowBrokenMeasureSpecs) { if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {//childStart是左上 childEnd是右下//如果之前applyHorizontalSizeRules处理过的左右都有依赖//也就是lp.mLeft和lp.mRight都有值,那么被夹紧的宽度 就是exactly的//注意! 在RelativeLayout里这是优先于指定宽度的 // Constraints fixed both edges, so child has an exact size. childSpecSize = Math.max(0, childEnd - childStart); childSpecMode = MeasureSpec.EXACTLY; } else if (childSize >= 0) {//指定的宽度 当然也是exactly的 // The child specified an exact size. childSpecSize = childSize; childSpecMode = MeasureSpec.EXACTLY; } else {//RelativeLayout的宽度有没有被指定 child的lp又没法知道确定高度//那只好UNSPECIFIED了 // Allow the child to be whatever size it wants. childSpecSize = 0; childSpecMode = MeasureSpec.UNSPECIFIED; } return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode); }//sdk4.3及以上 或者指定了elativeLayout的宽度 才能进入以下代码 // Figure out start and end bounds. int tempStart = childStart; int tempEnd = childEnd;//因为这时候mySize有值(sdk4.3及以上 不是有可能没值嘛? 这里存疑)//如果左右没有依赖view,也就是说apply之前没有给lp赋值mLeft或mRight的话//把左右先赋值(RelativeLayout的mySize去掉padding和margin) // If the view did not express a layout constraint for an edge, use // view's margins and our padding if (tempStart == VALUE_NOT_SET) { tempStart = startPadding + startMargin; } if (tempEnd == VALUE_NOT_SET) { tempEnd = mySize - endPadding - endMargin; }//子view宽度的极限 右-左咯 // Figure out maximum size available to this view final int maxAvailable = tempEnd - tempStart; if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) { // Constraints fixed both edges, so child must be an exact size. childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY;//mode取决于RelativeLayout的size//也就是说 4.3以上 未指定宽度的RelativeLayout这里用UNSPECIFIED 否则用EXACTLY childSpecSize = Math.max(0, maxAvailable); //跟之前逻辑一样 有明确的左右夹紧依赖 //这里也是优先于子View自己诉求宽度的 } else { if (childSize >= 0) {//lp里有明确的子View诉求宽度 EXACTLY // Child wanted an exact size. Give as much as possible. childSpecMode = MeasureSpec.EXACTLY; if (maxAvailable >= 0) { // We have a maximum size in this dimension. //如果maxAvailable比子View诉求宽度还小 用maxAvailable //比如左右都没有依赖view的时候,maxAvailable就应该等于 //RelativeLayout的宽度-padding-margin //结果RelativeLayout父布局满足不了那么子view要的那么宽 就能给多少给多少 childSpecSize = Math.min(maxAvailable, childSize); } else { // We can grow in this dimension. childSpecSize = childSize; } } else if (childSize == LayoutParams.MATCH_PARENT) { //子View没有明确诉求下分两种情况 要么MATCH_PARENT 要么WRAP_CONTENT // MATCH_PARENT的情况下 父布局RelativeLayout能给多少给多少 // Child wanted to be as big as possible. Give all available // space. childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY; childSpecSize = Math.max(0, maxAvailable); } else if (childSize == LayoutParams.WRAP_CONTENT) {//子View没有明确诉求下 子view是WRAP_CONTENT的情况 // Child wants to wrap content. Use AT_MOST to communicate // available space if we know our max size. if (maxAvailable >= 0) { // We have a maximum size in this dimension. childSpecMode = MeasureSpec.AT_MOST; //就只能给到AT_MOST咯 childSpecSize = maxAvailable; } else { // We can grow in this dimension. Child can be as big as it // wants. childSpecMode = MeasureSpec.UNSPECIFIED; childSpecSize = 0; } } }
第一次(水平方向)测量 到此为止 分析完毕
马上下面就来到了第二次(垂直方向)测量啦 非常非常类似
代码有点长 今天就分析到这里 未完待续
- RelativeLayout的onMeasure源码分析
- view的onMeasure,onLayout,onDraw源码分析
- 关于RelativeLayout中onMeasure()的理解
- 源码分析:onAttach, onMeasure, onLayout, onDraw 的顺序。
- [置顶] 源码分析:onAttach, onMeasure, onLayout, onDraw 的顺序。
- onMeasure()源码分析及自定义View对于wrap_content的支持
- 源码分析:onAttach, onMeasure, onLayout, onDraw 的顺序。
- view的onMeasure,onLayout,onDraw源码分析(下)
- NullPointerException - RelativeLayout.onMeasure
- android.widget.RelativeLayout.onMeasure出现的java.lang.NullPointerException错误
- Android 关于RelativeLayout.onMeasure出现的NullPointerException错误
- ViewPager源码分析(1):onMeasure、onLayout
- 从源码角度分析一下OnMeasure
- Android应用开发原理之从源码分析看Linearlayout、Relativelayout,Framelayout的布局差别(Relativelayout分析)
- #Android源码#View的onMeasure方法
- RelativeLayout(相对布局)的分析
- ViewGroup的onMeasure和onLayout分析
- ViewGroup的onMeasure和onLayout分析
- 未能加载文件或程序集“System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”或它的某一个依
- 理解MVC模式和MVVM
- Hadoop搭建Hive异常处理
- java中的反转和换行相关问题
- 安装mysql的主从架构
- RelativeLayout的onMeasure源码分析
- C++简单实现GC和内存池
- Android 中 View移动总结:ViewDragHelper学习及用法详解
- 寻找合伙人 一起写书
- CSS:如何清除a标签之间的默认留白间距
- Failed to read candidate component class错误分析
- Android 开源框架Universal-Image-Loader完全解析(二)--- 图片缓存策略详解
- 快速开发android应用5-使用picasso实现轮播图
- Android 开源框架Universal-Image-Loader完全解析(三)---源代码解读