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     }
View Code

相关文章: