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 }