简单研究Android View绘制三 布局过程

2015-07-28 17:29:19

这一篇主要看看布局过程

一、布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下:

 1     /**
 2      * Assign a size and position to a view and all of its
 3      * descendants
 4      *
 5      * <p>This is the second phase of the layout mechanism.
 6      * (The first is measuring). In this phase, each parent calls
 7      * layout on all of its children to position them.
 8      * This is typically done using the child measurements
 9      * that were stored in the measure pass().</p>
10      *
11      * <p>Derived classes should not override this method.
12      * Derived classes with children should override
13      * onLayout. In that method, they should
14      * call layout on each of their children.</p>
15      *
16      * @param l Left position, relative to parent
17      * @param t Top position, relative to parent
18      * @param r Right position, relative to parent
19      * @param b Bottom position, relative to parent
20      */
21     @SuppressWarnings({"unchecked"})
22     public void layout(int l, int t, int r, int b) {
23         if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
24             onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
25             mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
26         }
27
28         int oldL = mLeft;
29         int oldT = mTop;
30         int oldB = mBottom;
31         int oldR = mRight;
32
33         boolean changed = isLayoutModeOptical(mParent) ?
34                 setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
35         if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
36             /// M: Monitor onLayout time if longer than 3s print log.
37             long logTime = System.currentTimeMillis();
38             onLayout(changed, l, t, r, b);
39             long nowTime = System.currentTimeMillis();
40             if (nowTime - logTime > DBG_TIMEOUT_VALUE) {
41                 Xlog.d(VIEW_LOG_TAG, "[ANR Warning]onLayout time too long, this =" + this + "time =" + (nowTime - logTime));
42             }
43             mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
44
45             ListenerInfo li = mListenerInfo;
46             if (li != null && li.mOnLayoutChangeListeners != null) {
47                 ArrayList<OnLayoutChangeListener> listenersCopy =
48                         (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
49                 int numListeners = listenersCopy.size();
50                 for (int i = 0; i < numListeners; ++i) {
51                     listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
52                 }
53             }
54         }
55
56         mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
57         mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
58     }
59
60     /**
61      * Called from layout when this view should
62      * assign a size and position to each of its children.
63      *
64      * Derived classes with children should override
65      * this method and call layout on each of
66      * their children.
67      * @param changed This is a new size or position for this view
68      * @param left Left position, relative to parent
69      * @param top Top position, relative to parent
70      * @param right Right position, relative to parent
71      * @param bottom Bottom position, relative to parent
72      */
73     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
74     }

简单的翻译一下layout()方法的那段注释哈~E文不好~

“指定一个view以及它的所有子孙节点的大小和位置,这是布局机制的第二阶段(第一阶段是测量),在这一阶段,父view调用所有子view的layout()方法以确定他们所在的位置,通常是使用子View存储的自身的尺寸。派生类不应该重写此方法,应该重写onLayout()方法,在派生类重写的onLayout()方法中,应该调用每一个子View的layout方法。”啰嗦一句,int l, int t, int r, int b都是相对于父节点的坐标值。

注意layout方法中的红色代码,调用了onLayout。而onLayout在view中实现为空。现在来看看ViewGroup中的这两个方法。

 1     /**
 2      * {@inheritDoc}
 3      */
 4     @Override
 5     public final void layout(int l, int t, int r, int b) {
 6         if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
 7             if (mTransition != null) {
 8                 mTransition.layoutChange(this);
 9             }
10             super.layout(l, t, r, b);
11         } else {
12             // record the fact that we noop‘d it; request layout when transition finishes
13             mLayoutCalledWhileSuppressed = true;
14         }
15     }
16
17     /**
18      * {@inheritDoc}
19      */
20     @Override
21     protected abstract void onLayout(boolean changed,
22             int l, int t, int r, int b);

在ViewGroup的layout方法中,mSuppressLayout用来控制是否禁止调用layout(),该值由如下方法来控制:

 1     /**
 2      * Tells this ViewGroup to suppress all layout() calls until layout
 3      * suppression is disabled with a later call to suppressLayout(false).
 4      * When layout suppression is disabled, a requestLayout() call is sent
 5      * if layout() was attempted while layout was being suppressed.
 6      *
 7      * @hide
 8      */
 9     public void suppressLayout(boolean suppress) {
10         mSuppressLayout = suppress;
11         if (!suppress) {
12             if (mLayoutCalledWhileSuppressed) {
13                 requestLayout();
14                 mLayoutCalledWhileSuppressed = false;
15             }
16         }
17     }

这个方法不是对外公开的,所以不了解它也行。可以简单地理解ViewGroup的layout方法,它直接调用了父类View的layout()方法即可。至于onLayout方法,竟然被搞成了abstract的,这是逼着ViewGroup的子类必须得去实现啊~当然了,你必须得实现啊,你作为一个容器类,如何摆放你的子孙控件,是你义不容辞的责任啊。

至此我们已经明白了几点:

1. 派生类不需要重写layout(),而应该重写onLayout()方法,因为在layout()方法中就调用了onLayout()。

2. 在重写onLayout()方法时,我们需要显式的调用每一个childView的layout方法,把它摆放在合适的位置上。前提是在调用之前,得先计算好该childView的坐标。

3. 如果直接继承自View,那么可以不用重写onLayout()方法,比如ImageView、ImageButton等都没有重写该方法,所以不重写这个方法对于自定义View影响不大,至于TextView比较特殊,它重写了该方法,如下:

  1     @Override
  2     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  3         super.onLayout(changed, left, top, right, bottom);
  4         if (mDeferScroll >= 0) {
  5             int curs = mDeferScroll;
  6             mDeferScroll = -1;
  7             bringPointIntoView(Math.min(curs, mText.length()));
  8         }
  9     }
 10
 11     /**
 12      * Move the point, specified by the offset, into the view if it is needed.
 13      * This has to be called after layout. Returns true if anything changed.
 14      */
 15     public boolean bringPointIntoView(int offset) {
 16         if (isLayoutRequested()) {
 17             mDeferScroll = offset;
 18             return false;
 19         }
 20         boolean changed = false;
 21
 22         Layout layout = isShowingHint() ? mHintLayout: mLayout;
 23
 24         if (layout == null) return changed;
 25
 26         int line = layout.getLineForOffset(offset);
 27
 28         int grav;
 29
 30         switch (layout.getParagraphAlignment(line)) {
 31             case ALIGN_LEFT:
 32                 grav = 1;
 33                 break;
 34             case ALIGN_RIGHT:
 35                 grav = -1;
 36                 break;
 37             case ALIGN_NORMAL:
 38                 grav = layout.getParagraphDirection(line);
 39                 break;
 40             case ALIGN_OPPOSITE:
 41                 grav = -layout.getParagraphDirection(line);
 42                 break;
 43             case ALIGN_CENTER:
 44             default:
 45                 grav = 0;
 46                 break;
 47         }
 48
 49         // We only want to clamp the cursor to fit within the layout width
 50         // in left-to-right modes, because in a right to left alignment,
 51         // we want to scroll to keep the line-right on the screen, as other
 52         // lines are likely to have text flush with the right margin, which
 53         // we want to keep visible.
 54         // A better long-term solution would probably be to measure both
 55         // the full line and a blank-trimmed version, and, for example, use
 56         // the latter measurement for centering and right alignment, but for
 57         // the time being we only implement the cursor clamping in left to
 58         // right where it is most likely to be annoying.
 59         final boolean clamped = grav > 0;
 60         // FIXME: Is it okay to truncate this, or should we round?
 61         final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
 62         final int top = layout.getLineTop(line);
 63         final int bottom = layout.getLineTop(line + 1);
 64
 65         int left = (int) FloatMath.floor(layout.getLineLeft(line));
 66         int right = (int) FloatMath.ceil(layout.getLineRight(line));
 67         int ht = layout.getHeight();
 68
 69         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
 70         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
 71         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
 72             // If cursor has been clamped, make sure we don‘t scroll.
 73             right = Math.max(x, left + hspace);
 74         }
 75
 76         int hslack = (bottom - top) / 2;
 77         int vslack = hslack;
 78
 79         if (vslack > vspace / 4)
 80             vslack = vspace / 4;
 81         if (hslack > hspace / 4)
 82             hslack = hspace / 4;
 83
 84         int hs = mScrollX;
 85         int vs = mScrollY;
 86
 87         if (top - vs < vslack)
 88             vs = top - vslack;
 89         if (bottom - vs > vspace - vslack)
 90             vs = bottom - (vspace - vslack);
 91         if (ht - vs < vspace)
 92             vs = ht - vspace;
 93         if (0 - vs > 0)
 94             vs = 0;
 95
 96         if (grav != 0) {
 97             if (x - hs < hslack) {
 98                 hs = x - hslack;
 99             }
100             if (x - hs > hspace - hslack) {
101                 hs = x - (hspace - hslack);
102             }
103         }
104
105         if (grav < 0) {
106             if (left - hs > 0)
107                 hs = left;
108             if (right - hs < hspace)
109                 hs = right - hspace;
110         } else if (grav > 0) {
111             if (right - hs < hspace)
112                 hs = right - hspace;
113             if (left - hs > 0)
114                 hs = left;
115         } else /* grav == 0 */ {
116             if (right - left <= hspace) {
117                 /*
118                  * If the entire text fits, center it exactly.
119                  */
120                 hs = left - (hspace - (right - left)) / 2;
121             } else if (x > right - hslack) {
122                 /*
123                  * If we are near the right edge, keep the right edge
124                  * at the edge of the view.
125                  */
126                 hs = right - hspace;
127             } else if (x < left + hslack) {
128                 /*
129                  * If we are near the left edge, keep the left edge
130                  * at the edge of the view.
131                  */
132                 hs = left;
133             } else if (left > hs) {
134                 /*
135                  * Is there whitespace visible at the left?  Fix it if so.
136                  */
137                 hs = left;
138             } else if (right < hs + hspace) {
139                 /*
140                  * Is there whitespace visible at the right?  Fix it if so.
141                  */
142                 hs = right - hspace;
143             } else {
144                 /*
145                  * Otherwise, float as needed.
146                  */
147                 if (x - hs < hslack) {
148                     hs = x - hslack;
149                 }
150                 if (x - hs > hspace - hslack) {
151                     hs = x - (hspace - hslack);
152                 }
153             }
154         }
155
156         if (hs != mScrollX || vs != mScrollY) {
157             if (mScroller == null) {
158                 scrollTo(hs, vs);
159             } else {
160                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
161                 int dx = hs - mScrollX;
162                 int dy = vs - mScrollY;
163
164                 if (duration > ANIMATED_SCROLL_GAP) {
165                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
166                     awakenScrollBars(mScroller.getDuration());
167                     invalidate();
168                 } else {
169                     if (!mScroller.isFinished()) {
170                         mScroller.abortAnimation();
171                     }
172
173                     scrollBy(dx, dy);
174                 }
175
176                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
177             }
178
179             changed = true;
180         }
181
182         if (isFocused()) {
183             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
184             // requestRectangleOnScreen() is in terms of content coordinates.
185
186             // The offsets here are to ensure the rectangle we are using is
187             // within our view bounds, in case the cursor is on the far left
188             // or right.  If it isn‘t withing the bounds, then this request
189             // will be ignored.
190             if (mTempRect == null) mTempRect = new Rect();
191             mTempRect.set(x - 2, top, x + 2, bottom);
192             getInterestingRect(mTempRect, line);
193             ///M: ALPS00605613 requestRectangleOnScreen() will return error result if setting the mTempRect to mScrollX, mScrollY
194             //mTempRect.offset(mScrollX, mScrollY);
195
196             if (requestRectangleOnScreen(mTempRect)) {
197                 changed = true;
198             }
199         }
200
201         return changed;
202     }

有点没看明白bringPointIntoView方法的作用是什么,还得再研究。不过这不影响我们分析布局过程。

二、结合自己写的一个小Demo继续分析,代码如下:

MyLinear2.java

  1 public class MyLinear2 extends ViewGroup {
  2     private static final String TAG = "David_MyLinear2";
  3
  4     public MyLinear2(Context context) {
  5         super(context);
  6     }
  7
  8     public MyLinear2(Context context, AttributeSet attrs) {
  9         super(context, attrs);
 10     }
 11
 12     @Override
 13     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 14         int count = getChildCount();
 15         int cWidth = 0;
 16         int cHeight = 0;
 17         int top = 0;
 18         MarginLayoutParams params = null;
 19         Log.e(TAG + " onLayout", "l = " + l);
 20         Log.e(TAG + " onLayout", "t = " + t);
 21         Log.e(TAG + " onLayout", "r = " + r);
 22         Log.e(TAG + " onLayout", "b = " + b);
 23
 24         for (int i = 0; i < count; i++) {
 25             Log.e(TAG + " onLayout", "====================i = " + i);
 26             View childView = getChildAt(i);
 27             cWidth = childView.getMeasuredWidth();
 28             cHeight = childView.getMeasuredHeight();
 29             params = (MarginLayoutParams) childView.getLayoutParams();
 30
 31             Log.e(TAG + " onLayout", "params.height = " + params.height);
 32             Log.e(TAG + " onLayout", "params.width = " + params.width);
 33             int leftMargin = params.leftMargin;
 34             Log.e(TAG + " onLayout", "leftMargin = " + leftMargin);
 35             Log.e(TAG + " onLayout", "params.rightMargin = " + params.rightMargin);
 36             /*Log.e(TAG + " onLayout", "cWidth = " + cWidth);
 37             Log.e(TAG + " onLayout", "cHeight = " + cHeight);
 38             Log.e(TAG + " onLayout", "getWidth() = " + childView.getWidth());
 39             Log.e(TAG + " onLayout", "getHeight() = " + childView.getHeight());*/
 40             int cl = 0, ct = 0, cr = 0, cb = 0;
 41             cl = leftMargin;
 42             cr = cl + cWidth;
 43             ct = top;
 44             cb = cHeight + ct;
 45             /*Log.e(TAG + " onLayout", "cr = " + cr);
 46             Log.e(TAG + " onLayout", "ct = " + ct);
 47             Log.e(TAG + " onLayout", "cb = " + cb);
 48             Log.e(TAG + " onLayout", "top = " + top);*/
 49             childView.layout(cl, ct, cr, cb);
 50             top += cHeight;
 51         }
 52     }
 53
 54     @Override
 55     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
 56         Log.e(TAG, "generateLayoutParams attrs");
 57         return new MarginLayoutParams(getContext(), attrs);
 58     }
 59
 60     @Override
 61     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
 62         Log.e(TAG, "generateDefaultLayoutParams");
 63         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
 64     }
 65
 66     @Override
 67     protected boolean checkLayoutParams(LayoutParams p) {
 68         return super.checkLayoutParams(p);
 69     }
 70
 71     @Override
 72     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
 73         Log.e(TAG, "generateLayoutParams p");
 74         return new MarginLayoutParams(p);
 75     }
 76
 77     @Override
 78     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 79         int measuredHeight = measureHeight(heightMeasureSpec);
 80         int measuredWidth = measureWidth(widthMeasureSpec);
 81         Log.e(TAG, "onMeasure measuredHeight = " + measuredHeight);
 82         Log.e(TAG, "onMeasure measuredWidth = " + measuredWidth);
 83         setMeasuredDimension(measuredWidth, measuredHeight);
 84         measureChildren(widthMeasureSpec, heightMeasureSpec);
 85     }
 86
 87     private int measureHeight(int measureSpec) {
 88         int specMode = MeasureSpec.getMode(measureSpec);
 89         int specSize = MeasureSpec.getSize(measureSpec);
 90
 91         int result = 500;
 92         if (specMode == MeasureSpec.AT_MOST){
 93             result = specSize;
 94         } else if (specMode == MeasureSpec.EXACTLY){
 95             result = specSize;
 96         }
 97         return result;
 98     }
 99
100     private int measureWidth(int measureSpec) {
101         int specMode = MeasureSpec.getMode(measureSpec);
102         int specSize = MeasureSpec.getSize(measureSpec);
103
104         int result = 100;
105         if (specMode == MeasureSpec.AT_MOST){
106             result = specSize;
107         } else if (specMode == MeasureSpec.EXACTLY){
108             result = specSize;
109         }
110         return result;
111     }
112 }

MyLinear2时模仿纵向布局的LinearLayout。

MyTextView.java

 1 public class MyTextView extends View {
 2     private static final String TAG = "David___MyTextView";
 3
 4     public MyTextView(Context context, AttributeSet attrs) {
 5         super(context, attrs);
 6     }
 7
 8     public MyTextView(Context context) {
 9         super(context);
10     }
11
12     @Override
13     protected void onDraw(Canvas canvas) {
14         super.onDraw(canvas);
15         Paint paint = new Paint();
16         paint.setTextSize(22);
17         paint.setTextAlign(Align.CENTER);
18         Log.e(TAG, "onDraw getTop() = " + getTop());
19         Log.e(TAG, "onDraw getLeft() = " + getLeft());
20         canvas.drawText("nihao ----", getTop(), getLeft(), paint);
21     }
22
23     @Override
24     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
25         Log.e(TAG, "onLayout left = " + left);
26         Log.e(TAG, "onLayout top = " + top);
27         Log.e(TAG, "onLayout right = " + right);
28         Log.e(TAG, "onLayout bottom = " + bottom);
29         //super.onLayout(changed, left, top, right, bottom);
30     }
31
32     @Override
33     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
34         int measuredHeight = measureHeight(heightMeasureSpec);
35         int measuredWidth = measureWidth(widthMeasureSpec);
36         Log.e(TAG, "onMeasure measuredHeight = " + measuredHeight);
37         Log.e(TAG, "onMeasure measuredWidth = " + measuredWidth);
38         setMeasuredDimension(measuredWidth, measuredHeight);
39     }
40
41     private int measureHeight(int measureSpec) {
42         int specMode = MeasureSpec.getMode(measureSpec);
43         int specSize = MeasureSpec.getSize(measureSpec);
44
45         int result = 500;
46         if (specMode == MeasureSpec.AT_MOST){
47             result = specSize;
48         } else if (specMode == MeasureSpec.EXACTLY){
49             result = specSize;
50         }
51         return result;
52     }
53
54     private int measureWidth(int measureSpec) {
55         int specMode = MeasureSpec.getMode(measureSpec);
56         int specSize = MeasureSpec.getSize(measureSpec);
57
58         int result = 500;
59         if (specMode == MeasureSpec.AT_MOST){
60             result = specSize;
61         } else if (specMode == MeasureSpec.EXACTLY){
62             result = specSize;
63         }
64         return result;
65     }
66 }

这是一个简易的View,先不要关心onDraw()方法。布局文件如下:

 1 <com.test.touch.MyLinear2 xmlns:android="http://schemas.android.com/apk/res/android"
 2     android:layout_width="300dp"
 3     android:layout_height="200dp"
 4     android:paddingTop="22dp"
 5     android:layout_marginLeft="10dp" >
 6
 7     <com.test.touch.MyTextView
 8         android:layout_width="match_parent"
 9         android:layout_height="120dp"
10         android:background="#0f0"
11         android:layout_marginLeft="30dp"
12         android:paddingTop="22dp"
13         android:text="Hello World" />
14
15     <TextView
16         android:id="@+id/tv1"
17         android:layout_width="match_parent"
18         android:layout_height="110dp"
19         android:background="#f00"
20         android:gravity="center_vertical"
21         android:layout_marginLeft="10dp"
22         android:text="Hello World Text" />
23 
24
25     <com.test.touch.MyTextView
26         android:layout_width="match_parent"
27         android:layout_height="60dp"
28         android:background="#00f"
29         android:text="Hello World88" />
30
31 </com.test.touch.MyLinear2>

OK,运行打印的日志如下:

这段日志其实是会被打印好几遍的,我只截取了完整的一次日志。从日志可以看出,首先执行的是测量过程,这个之前分析过了,请参考http://www.cnblogs.com/wlrhnh/p/4680636.html。然后才是布局过程,由于我们在MyLinear2的onLayout()中显式调用childView的layout()方法,代码如下:

 1     @Override
 2     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 3         int count = getChildCount();
 4         int cWidth = 0;
 5         int cHeight = 0;
 6         int top = 0;
 7         MarginLayoutParams params = null;
 8         Log.e(TAG + " onLayout", "l = " + l);
 9         Log.e(TAG + " onLayout", "t = " + t);
10         Log.e(TAG + " onLayout", "r = " + r);
11         Log.e(TAG + " onLayout", "b = " + b);
12
13         for (int i = 0; i < count; i++) {
14             Log.e(TAG + " onLayout", "====================i = " + i);
15             View childView = getChildAt(i);
16             cWidth = childView.getMeasuredWidth();
17             cHeight = childView.getMeasuredHeight();
18             params = (MarginLayoutParams) childView.getLayoutParams();
19
20             Log.e(TAG + " onLayout", "params.height = " + params.height);
21             Log.e(TAG + " onLayout", "params.width = " + params.width);
22             int leftMargin = params.leftMargin;
23             Log.e(TAG + " onLayout", "leftMargin = " + leftMargin);
24             Log.e(TAG + " onLayout", "params.rightMargin = " + params.rightMargin);
25             int cl = 0, ct = 0, cr = 0, cb = 0;
26             cl = leftMargin;
27             cr = cl + cWidth;
28             ct = top;
29             cb = cHeight + ct;
30             childView.layout(cl, ct, cr, cb);
31             top += cHeight;
32         }
33     }

所以日志中显示:先调用了ViewGroup的onLayout,然后遍历每一个childView,取出它们已经计算好的坐标值,按照ViewGroup的既定布局策略,给childView布局,调用他们的layout()方法,进而调用了onLayout()。而很多继承自View的组件,比如ImageView等,由于没有实现onLayout()方法,那么其实只调用了View的layout()和View的空实现的onLayout()方法。当然了,我在MyTextView中重写onLayout()只是为了打印日志而已,并没有做什么实际操作。在日志的最后,打印了onDraw()方法,可见,先测量、后布局、最后才统一draw,并不是布局完一个就draw一个。而且我发现一个有意思的现象,那就是如果你的父控件拥有的space不足以显示所有的子View,那么不能显示出来的子View的onDraw方法是不会被调用的,这点好理解~

三、关于LayoutParams

注意上面的一行红色代码,每个childView都有LayoutParams,而且可以强转为MarginLayoutParams呢?注意看这几行代码:

 1     @Override
 2     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
 3         Log.e(TAG, "generateLayoutParams attrs");
 4         return new MarginLayoutParams(getContext(), attrs);
 5     }
 6
 7     @Override
 8     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
 9         Log.e(TAG, "generateDefaultLayoutParams");
10         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
11     }
12
13     @Override
14     protected boolean checkLayoutParams(LayoutParams p) {
15         return super.checkLayoutParams(p);
16     }
17
18     @Override
19     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
20         Log.e(TAG, "generateLayoutParams p");
21         return new MarginLayoutParams(p);
22     }

看一下日志:

看黄色部分日志,主要执行了generateLayoutParams(AttributeSet attrs)方法,而且是先于测量过程执行的,这个方法我们有自己的实现,具体的请参考http://www.cnblogs.com/wlrhnh/p/4683542.html

简单的总结一下布局过程:

Android View绘制系统首先取到Activity布局的根View,当然这一般是一个ViewGroup了。先测量,后布局。布局的时候调用ViewGroup子类比如MyLinear2的layout()方法,由于ViewGroup强制子类实现onLayout()方法,所以会调到MyLinear2的onLayout()方法,在这个方法中,需要遍历子View,按照布局策略,计算每一个子View的坐标,然后将它放在合适的位置上。至于继承自View的子类,则不需要实现onLayout,毕竟onLayout的作是布局,这是容器类该干的事情。而子View要做的就是根据父View在指定给自己的空间中draw。

通过以上三篇文章,相比应该可以多Android系统测量、布局View的流程有了一个大概的了解,这是一条主线。当然了,这里面涉及到的细节其实有很多,最好的办法是自己去看源码喽~

项目代码和View.java ViewGroup.java LinearLayout.java Button.java等源码请下载

下载源码

后记:分析完三步骤之后觉得不过瘾,想起网上有人写过这样的布局,就顺手写了个,抄袭人家的创意了,但是代码是自己的写的~

  1 public class MyLinear1 extends ViewGroup {
  2     private static final String TAG = "David_MyLinear1";
  3
  4     public MyLinear1(Context context) {
  5         super(context);
  6     }
  7
  8     public MyLinear1(Context context, AttributeSet attrs) {
  9         super(context, attrs);
 10     }
 11
 12     @Override
 13     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 14         int measuredHeightSize = MeasureSpec.getSize(heightMeasureSpec);
 15         int measuredWidthSize = MeasureSpec.getSize(widthMeasureSpec);
 16
 17         int measuredHeightMode = MeasureSpec.getMode(heightMeasureSpec);
 18         int measuredWidthMode = MeasureSpec.getMode(widthMeasureSpec);
 19
 20         measureChildren(widthMeasureSpec, heightMeasureSpec);
 21
 22         int lHeight = 0, rHeight = 0;
 23         int tWidth = 0, bWidth = 0;
 24
 25         View childView = null;
 26         MarginLayoutParams params = null;
 27         for (int i = 0; i < getChildCount(); i++) {
 28             childView = getChildAt(i);
 29             params = (MarginLayoutParams) childView.getLayoutParams();
 30
 31             switch (i) {
 32                 case 0:
 33                     lHeight += childView.getMeasuredHeight();
 34                     tWidth += childView.getMeasuredWidth();
 35                     break;
 36                 case 1:
 37                     rHeight += childView.getMeasuredHeight();
 38                     tWidth += childView.getMeasuredWidth();
 39                     break;
 40                 case 2:
 41                     lHeight += childView.getMeasuredHeight();
 42                     bWidth += childView.getMeasuredWidth();
 43                     break;
 44                 case 3:
 45                     rHeight += childView.getMeasuredHeight();
 46                     bWidth += childView.getMeasuredWidth();
 47                     break;
 48
 49                 default:
 50                     break;
 51             }
 52         }
 53
 54         int realHeightSize = Math.max(lHeight, rHeight);
 55         int realWidthSize = Math.max(bWidth, tWidth);
 56         int h = measuredHeightMode == MeasureSpec.EXACTLY ? measuredHeightSize : realHeightSize;
 57         int w = measuredWidthMode == MeasureSpec.EXACTLY ? measuredWidthSize : realWidthSize;
 58         setMeasuredDimension(w, h);
 59     }
 60
 61     @Override
 62     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 63         int realLeft = 0;
 64         int realTop = 0;
 65         int realRight = 0;
 66         int realBottom = 0;
 67         int count = getChildCount();
 68
 69         View childView = null;
 70         MarginLayoutParams params = null;
 71         for (int i = 0; i < count; i++) {
 72             Log.e(TAG, "------------------------- i = " + i);
 73             childView = getChildAt(i);
 74             params = (MarginLayoutParams) childView.getLayoutParams();
 75             Log.d(TAG, "childView.getMeasuredWidth() = " + childView.getMeasuredWidth());
 76             Log.d(TAG, "childView.getMeasuredHeight() = " + childView.getMeasuredHeight());
 77             switch (i) {
 78                 case 0:
 79                     realLeft = l;
 80                     realTop = t;
 81                     realRight = realLeft + childView.getMeasuredWidth();
 82                     realBottom = realTop + childView.getMeasuredHeight();
 83                     break;
 84                 case 1:
 85                     realLeft = r - childView.getMeasuredWidth();
 86                     realTop = t;
 87                     realRight = realLeft + childView.getMeasuredWidth();
 88                     realBottom = realTop + childView.getMeasuredHeight();
 89                     break;
 90                 case 2:
 91                     realLeft = l;
 92                     realTop = b - childView.getMeasuredHeight();
 93                     realRight = realLeft + childView.getMeasuredWidth();
 94                     realBottom = realTop + childView.getMeasuredHeight();
 95                     break;
 96                 case 3:
 97                     realLeft = r - childView.getMeasuredWidth();
 98                     realTop = b - childView.getMeasuredHeight();
 99                     realRight = realLeft + childView.getMeasuredWidth();
100                     realBottom = realTop + childView.getMeasuredHeight();
101                     break;
102
103                 default:
104                     break;
105             }
106             childView.layout(realLeft, realTop, realRight, realBottom);
107         }
108     }
109
110     @Override
111     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
112         return new MarginLayoutParams(getContext(), attrs);
113     }
114 }

代码随手写的,有些问题考虑的还不够周全,各位自己完善吧~而且没有做注释,但是相信看完这三篇文章,不需要看注释了吧~

时间: 2024-10-19 02:08:08

简单研究Android View绘制三 布局过程的相关文章

简单研究Android View绘制一

2015-07-27 16:52:58 一.如何通过继承ViewGroup来实现自定义View?首先得搞清楚Android时如何绘制View的,参考Android官方文档:How Android Draws Views 以下翻译摘自:http://blog.csdn.net/linghu_java/article/details/23882681,这也是一片好文章,推荐大家看看- When an Activity receives focus, it will be requested to d

简单研究Android View绘制二 LayoutParams

2015-07-28 17:23:20 本篇是关于LayoutParams相关 ViewGroup.LayoutParams文档解释如下: LayoutParams are used by views to tell their parents how they want to be laid out. See ViewGroup Layout Attributes for a list of all child view attributes that this class supports.

源码解析Android中View的layout布局过程

Android中的Veiw从内存中到呈现在UI界面上需要依次经历三个阶段:量算 -> 布局 -> 绘图,关于View的量算.布局.绘图的总体机制可参见博文 < Android中View的布局及绘图机制>.量算是布局的基础,如果想了解量算的细节,可参见博文<源码解析Android中View的measure量算过程>.本文将从源码角度解析View的布局layout过程,本文会详细介绍View布局过程中的关键方法,并对源码加上了注释以进行说明. 对View进行布局的目的是计算

Android 性能优化 三 布局优化ViewStub标签的使用

小黑与小白的故事,通过虚拟这两个人物进行一问一答的形式来共同学习ViewStub的使用 小白:Hi,小黑,ViewStub是什么?听说可以用来进行布局优化. 小黑:ViewStub 是一个隐藏的,不占用内存空间的视图对象,它可以在运行时延迟加载布局资源文件.(更多详细的API等信息可以查看官方文档ViewStub),计算机行业一向是实践里面出真知,下面用一个例子演示下效果. 小黑:说说概念只是为了概括性的了解下,还是用个实例来演示下.先来创建一个Activity中使用的布局文件,文件名是:act

Android View 绘制过程

Android的View绘制是从根节点(Activity是DecorView)开始,他是一个自上而下的过程.View的绘制经历三个过程:Measure.Layout.Draw.基本流程如下图: performTraversals函数,具体的可以参考一下源代码: 1 private void performTraversals() { 2 final View host = mView; 3 ... 4 host.measure(childWidthMeasureSpec, childHeight

[Android][转]Android View绘制13问13答

转自:http://www.androidchina.net/4458.html 1.view的绘制流程分几步,从哪开始?哪个过程结束以后能看到view? 答:从ViewRoot的performTraversals开始,经过measure,layout,draw 三个流程.draw流程结束以后就可以在屏幕上看到view了. 2.view的测量宽高和实际宽高有区别吗? 答:基本上百分之99的情况下都是可以认为没有区别的.有两种情况,有区别.第一种 就是有的时候会因为某些原因 view会多次测量,那

Android View绘制及实践

概述 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为: - 判断是否需要重新计算视图大小(measure) - 判断是否重新需要安置视图的位置(layout) - 判断是否需要重绘(draw) 其整个流程图如下: 图片来自:Android 开源项目源码解析 公共技术点中的 View 绘制流程 在Android中View的整个生命周期,调用invalidate和requestLayout会触发一系列的方法,

对于Android View绘制的一些思考

AT_MOST 表示最大是多大. UNSPECIFIED 不确定是多大, 你想多大就多大,我尽量满足你. EXACTLY 就这么大 已经指定了大小 MATCH_PARENT 为什么说MATCH_PARENT 因为 MATCH_PARENT 就是间接说 我要占据父控件剩下的那部分了. 这就相当于指定了确定的宽或高. 一个view 的绘制需要三个阶段 measure -> layout -> draw view的测量阶段 measure-> onMeasure() 我们就看单纯一个view的

Android View 绘制流程(Draw) 完全解析

前言 前几篇文章,笔者分别讲述了DecorView,measure,layout流程等,接下来将详细分析三大工作流程的最后一个流程--绘制流程.测量流程决定了View的大小,布局流程决定了View的位置,那么绘制流程将决定View的样子,一个View该显示什么由绘制流程完成.以下源码均取自Android API 21. 从performDraw说起 前面几篇文章提到,三大工作流程始于ViewRootImpl#performTraversals,在这个方法内部会分别调用performMeasure