ViewRoot 與 DecorView
ViewRoot 是連接 WindowManager 和 DecorView 的紐帶,其實現(xiàn)類是 ViewRootImpl 類。View 的三大流程均由ViewRoot 完成澜术。Activity 被創(chuàng)建完成后蔫缸,會將 DecorView 添加到 Window 中,同時創(chuàng)建 ViewRootImpl 對象贺嫂,并與DecorView 建立關(guān)聯(lián)滓鸠。
root = new ViewRootImpl(view.getContext(),display);
root.setView(view,wparams, panelParentView);
View 的繪制流程從 ViewRoot 的 performTraversals 方法開始:
performMeasure、performLayout第喳、performDraw 三個方法分別完成頂級 View 的measure糜俗、layout、draw 這三大流程。其中悠抹,performMeasure 中調(diào)用 measure 方法珠月,measure 中又調(diào)用 onMeasure 方法,onMeasure中會對所有子元素進行 measure 過程楔敌。measure 流程就是這樣從父元素傳遞到子元素中啤挎,子元素會重復(fù)這個過程,從而完成整個 View樹的遍歷卵凑。performLayout庆聘、performDraw 的傳遞流程與此類似(draw方法中通過 dispatchDraw 來實現(xiàn)傳遞過程)。
- measure 過程決定 View 的寬高勺卢,完成后可通過
getMeasuredWidth
和getMeasuredHeight
方法獲取測量后的寬高伙判,一般此寬高就是 View 的最終寬高(特殊情況下例外)。 - layout 過程決定 View 的四個頂點和實際寬高黑忱,完成后可通過
getTop
宴抚、getBotton
、getLeft
和getRight
拿到 View 的四個頂點坐標甫煞,且可以通過getWidth
菇曲、getHeight
方法拿到最終寬高。
*draw 過程決定了 View 的顯示危虱。
DecorView作為頂級View羊娃,其實是一個 FrameLayout ,它包含一個豎直方向的 LinearLayout 埃跷,這個LinearLayout 分為標題欄和內(nèi)容欄兩個部分蕊玷。
Activity通過setContextView所設(shè)置的布局文件其實就是被加載到內(nèi)容欄之中的。這個內(nèi)容欄的id是
R.android.id.content
弥雹,通過 ViewGroup content = findViewById(R.android.id.content);
可以得到這個contentView垃帅。content.getChildAt(0)
可以獲得我們設(shè)置的 View。
MeasureSpec
MeasureSpec 參與 View 的 measure 的過程剪勿,類似于一個測量規(guī)格的概念贸诚。其創(chuàng)建過程受父容器影響。測量過程中厕吉,系統(tǒng)根據(jù) View 的 LayoutParams 根據(jù)父容器所施加規(guī)則轉(zhuǎn)換成對應(yīng)MeasureSpec酱固,根據(jù)這個 measureSpec 得到View 的測量寬高。
MeasureSpec 代表一個 32 位 int 值头朱,高 2 位代表 SpecMode运悲,低 30 位 代表 SpecSize。
MeasureSpec 通過 makeMeasureSpec(int size , int mode)
方法將模式和尺寸打包成一個 int 值项钮,且可以通過getMode
和 getSize
方法解包原始值(此處 MeasureSpec 指其代表 int 值班眯,而非對象本身)希停。
SpecMode有三類:
- UNSPECIFIED 父容器不對View有限制,要多大給多大署隘,系統(tǒng)內(nèi)部用宠能。
- EXACTLY 父容器已檢測出 View 所需精確大小。View 最終大小就是 SpecSize 指定值磁餐,對應(yīng)
match_parent 和具體數(shù)值违崇。 - AT_MOST 父容器指定了一個可用大小即 SpecSize,View 的大小不能大于這個值崖媚,具體值要看 View 的具體實現(xiàn)亦歉。對應(yīng) wrap_content恤浪。
DecorView 的 MeasureSpec 由窗口尺寸及自身參數(shù)共同決定畅哑。
普通 View 的 MeasureSpec 則由父容器與自身參數(shù)共同決定。
這里的parentSize 實際上是父容器剩余的可用空間水由,要減去父容器 padding荠呐、自身 margin、和父容器已經(jīng)使用的部分砂客。
View 的工作流程
measure
View 的 measure 過程
getSuggestedMinimumSize()
方法中泥张,若有設(shè)置背景,會獲取背景(Drawable)的原始寬高作為備選的返回值鞠值。Drawable若無原始寬高會默認為0媚创。ShapeDrawable 無原始寬高,BitmapDrawable 有原始寬高(圖片的尺寸)彤恶。直接繼承 View 的自定義控件需要重寫 onMeasure 方法并設(shè)置 wrap_content 時的自身大小钞钙,否則
wrap_content 效果等同 matct_parent。原因通過閱讀源碼可知声离,getDefaultSize 方法中只判斷了是否為 UNSPECIFIED 模式芒炼,對EXACTLY 和 AT_MOST 處理是一樣的,即返回 MeasureSpec 的 size 作為 View 的測量寬高术徊。通過View 的 MeasureSpec 的創(chuàng)建規(guī)則表可知本刽,size 為父容器剩余的可用空間。
//設(shè)置 wrap_content 時的自身大小
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//mWidth, mHeight 為自定義的默認內(nèi)部寬高
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mHeight);
}
}
ViewGroup 的 measure 過程
ViewGroup 除了完成自身的 measure 過程外赠涮,應(yīng)該去遍歷所有子 View 的measure 方法子寓,各個子元素再去遞歸執(zhí)行這個過程。但是 ViewGroup 是一個抽象類笋除,并沒有重寫 onMeasure 方法斜友。所以自定義 ViewGroup 時應(yīng)該根據(jù)所需重寫 onMeasure 方法。
VIewGroup 雖然沒有重寫 onMeasure 方法株憾,但是相應(yīng)提供了一些自定義時可以直接使用的方法:
measureChild()
:取出子 View 的LayoutParams 和本身的MeasureSpec 來創(chuàng)建子 View 的 MeasureSpec(通過getChildMeasureSpec
方法)蝙寨,直接傳遞給子 View 的measure 方法進行測量晒衩。measureChildren()
:作用是遍歷所有子 View,對所有可見不為 GONE 的 View 使用measureChild()
方法墙歪。直接放到ViewGroup 的onMeasure 中可以實現(xiàn)類似無 wrap_content 功能的 FrameLayout 的效果听系。resolveSizeAndState
:主要是根據(jù)期望值(根據(jù)業(yè)務(wù)邏輯在重寫的 onMeasure 中計算的尺寸)和ViewGroup 的 MeasureSpec 的模式和尺寸,判斷最終返回的測量尺寸虹菲,一般用于重寫 onMeasure 方法最后 setMeasureDimension 的參數(shù)處理靠胜。防止出現(xiàn)超過父容器剩余空間等情況。
一般重寫 ViewGroup 的onMeasure 方法的思路是毕源,遍歷子元素進行 measure浪漠,按需求保存某方向尺寸的累加值或者最大值,加上padding霎褐、子View 的 margin等址愿。最終使用這個值設(shè)定 VIewGroup 的測量尺寸。
具體實現(xiàn)可以看看這篇文章冻璃,自定義ViewGroup响谓。
獲取 View 的寬高
measure 過程完成后,理論上可以通過 getMeasureWidth/Height 方法獲取 View 的測量寬高省艳,但是極端情況下娘纷,系統(tǒng)可能需要多次 measure 才能確定最終測量寬高,這種情況下獲取數(shù)值可能出錯跋炕。
應(yīng)該在 onLayout 方法中去獲取 VIew 的測量寬高或者最終寬高赖晶。
由于 View 的 measure 過程和 Activity 的生命周期并不是同步執(zhí)行,無法保證在Activity的 onCreate辐烂、onStart遏插、onResume 時某個View就已經(jīng)測量完畢。想要在 Activity 啟動的時候就獲取一個View的寬高的方法有以下 4 種:
- Activity / View #
onWindowFocusChanged
:這個方法的含義是:View 已經(jīng)初始化完畢了棉圈,寬高已經(jīng)準備好了涩堤,需要注意:它會被調(diào)用多次,當 Activity 的窗口得到焦點和失去焦點均會被調(diào)用分瘾。 -
view.post(runnable)
:通過 post 將一個 runnable 投遞到消息隊列的尾部胎围,當 Looper 調(diào)用此 runnable 的時候,View 也初始化好了德召。 - ViewTreeObserver : 使用 ViewTreeObserver 的眾多回調(diào)可以完成這個功能白魂,比如 OnGlobalLayoutListener 這個接口,當 View 樹的狀態(tài)發(fā)送改變或 View 樹內(nèi)部的 View 的可見性發(fā)生改變時上岗,onGlobalLayout 方法會被回調(diào)福荸,這是獲取 View 寬高的好時機。需要注意的是肴掷,伴隨著 View 樹狀態(tài)的改變敬锐, onGlobalLayout 會被回調(diào)多次背传。
-
view.measure(int widthMeasureSpec,int heightMeasureSpec)
:手動對view進行measure。需要根據(jù)View的layoutParams分情況處理台夺。
為 match_parent 時径玖,無法 measure 出具體的寬高,因為不知道父容器的剩余空間颤介,無法測量出 View 的大小梳星。
為具體的數(shù)值時( dp/px):
//假設(shè)寬高為 100
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
為 wrap_content 時:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
// View的尺寸使用30位二進制表示,最大值30個1滚朵,在AT_MOST模式下冤灾,我們用View理論上能支持的最大
//值去構(gòu)造MeasureSpec是合理的
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
layout
layout 過程比較簡單,layout 的方法大致流程是:setFrame 方法設(shè)定 4 個頂點位置(初始化 mLeft辕近、mRight韵吨、mTop、mBottom 4 個值)亏推,然后調(diào)用 onLayout 方法(View 中為空實現(xiàn)学赛、ViewGroup 為抽象方法)年堆,基本實現(xiàn)思路是:遍歷所有子 View吞杭,獲取其測量寬高,和根據(jù)需求計算其中兩個頂點变丧,根據(jù)這 4 個值對子 View 使用 layout 方法芽狗。
getWidth/Height 與getMeasureWidth/Height 方法的區(qū)別
public final int getHeight() {
return mBottom - mTop;
}
public final int getWidth() {
return mRight - mLeft;
}
在View 的默認實現(xiàn)中,VIew 的測量寬高等于最終寬高痒蓬,但是測量寬高的形成時間先于最終寬高童擎。在日常開發(fā)中,一般來說兩者相等攻晒。但是存在特殊情況導(dǎo)致兩者不等顾复。如在 layout 方法中操作 setFrame 方法參數(shù)。另外一些情況下鲁捏,View 需要多次 measure 才能確定自己的測量寬高芯砸,這會導(dǎo)致在前幾次測量之后得出的測量寬高與最終寬高不等。
draw
比較簡單大致流程如下:
- 繪制背景 background.draw(canvas)
- 繪制自己(onDraw)
- 繪制 children (dispatchDraw)
- 繪制裝飾 (onDrawScrollBars)
需要注意兩個方法 :
dispatchDraw
方法會遍歷調(diào)用所有子元素的 draw 方法给梅。
setWillNotDraw
操作一個標記位假丧,在 View 默認為 false,ViewGroup 中默認為 true动羽。作用是View本身不需要繪制任何內(nèi)容時(繪制自己包帚,即 onDraw 方法沒干啥事),系統(tǒng)會進行相應(yīng)優(yōu)化运吓。所以渴邦,當一個 ViewGroup 需要通過 onDraw 來繪制內(nèi)容時(重寫了 onDraw 方法時)疯趟,需要顯示的關(guān)閉該標記位。
自定義View
自定義 View 的分類與須知
自定義 VIew 的 4 類:
- 繼承View 重寫 onDraw 方法:主要用于實現(xiàn)不規(guī)則效果谋梭,往往需要靜態(tài)或動態(tài)繪制一些不規(guī)則圖形迅办。需要自己實現(xiàn)支持 wrap_content 與 padding。
- 繼承 ViewGroup 派生特殊的 Layout:用于實現(xiàn)自定義布局章蚣,實現(xiàn)比較復(fù)雜站欺,需要處理自身和子元素的 measure、layout 兩個過程纤垂。
- 繼承特定 View:較常見做法矾策,一般用于擴展已有 View 的功能。不需要自己實現(xiàn)支持 wrap_content 與 padding峭沦。
- 繼承特定 ViewGroup:較常見做法贾虽,用于實現(xiàn) 多種 View 組合的效果。不需自己處理自身和子元素的 measure吼鱼、layout 蓬豁。
自定義 View 須知:
- 直接繼承 View 或者 ViewGroup 時要自己處理支持 wrap_content 效果。
- 直接繼承 View 的控件菇肃,需要在 draw 方法中處理 padding地粪。直接繼承 ViewGroup 的控件需要在 onMeasure 和 onLayout 中考慮 padding、子元素 margin 的影響琐谤。
- 盡量不要在 View 中使用 Handler蟆技,因為 View 本身提供 post 系列方法。
- View 中如果有線程或者動畫需要停止斗忌,考慮在 onDetachedFromWindow 中處理质礼。該方法當包含此 View 的 Activity 退出或者 View 被 remove 時會被調(diào)用。
- View 帶有嵌套滑動情形時织阳,需要處理好滑動沖突眶蕉。
自定義 View 示例
直接繼承 View 的示例,主要 3 個點:
- 支持 padding:onDraw 里面獲取 padding唧躲,并進行處理造挽。
- 支持 wrap_content:onMeasure 里面判斷 AT_MOST 模式,使用需要的大小
- 支持自定義屬性:寫一個自定義屬性集合的 xml 文件惊窖,在構(gòu)造方法中加載刽宪、解析即可。
以下是實現(xiàn)上述 3 點界酒,作用為畫一個圓形的自定義 View 代碼:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--自定義屬性結(jié)合名-->
<declare-styleable name="CircleView">
<!--屬性id 和值的格式 還可以是 string/integer/boolean 等-->
<attr name="circle_color" format="color"/>
</declare-styleable>
</resources>
public class CircleView extends View {
private int mColor = Color.RED;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, @Nullable AttributeSet attrs) {
//使用自定義屬性時 應(yīng)該為調(diào)用另一個構(gòu)造方法
this(context, attrs, 0);
}
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//加載自定義屬性集合 CircleView
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
//解析 CircleView 屬性集合 中 id 為 circleView_circle_color 的屬性
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
a.recycle();
init();
}
private void init() {
mPaint.setColor(mColor);
}
//支持padding
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int pL = getPaddingLeft();
int pR = getPaddingRight();
int pT = getPaddingTop();
int pB = getPaddingBottom();
int w = getWidth() - pL - pR;
int h = getHeight() - pT - pB;
int radius = Math.min(w, h) / 2;
//把圓心定在左和上方除去padding后的位置加上設(shè)定寬高的一半的位置
canvas.drawCircle(w / 2 + pL, h / 2 + pT, radius, mPaint);
}
//支持 wrap_content 即當寬高設(shè)為 wrap_content 時默認為 200 pt
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, 200);
} else if (wSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, hSpecSize);
} else if (hSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(wSpecSize, 200);
}
}
}
直接繼承 ViewGroup 示例:
這種方法實現(xiàn)比較復(fù)雜圣拄,以下是一個帶有水平滑動效果的類似 LinearLayout(水平方向上)的自定義View,基本思路是 onMeasure 里毁欣,當判斷模式為 wrap_content 時庇谆,獲取子 View 的寬高岳掐,進行相應(yīng)處理,onLayout 中饭耳,使用一個變量保存寬度累加值串述,實現(xiàn)水平排列。另外此例實現(xiàn)了內(nèi)容滑動效果寞肖,且解決了滑動沖突纲酗。
public class HorizontalScrollViewEx extends ViewGroup {
private int mChildrenSize; //子View數(shù)量
private int mChildWidth; //子 View 寬度 本例默認子View大小是一樣的
private int mChildIndex; //處于屏幕可見最左邊的子 View 序號
private int mLastX;
private int mLastY;
private int mLastXInterceptTouchEvent;
private int mLastYInterceptTouchEvent;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
public HorizontalScrollViewEx(Context context) {
super(context);
init();
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
//實例化 Scroller 和 VelocityTracker(速度追蹤)
private void init() {
if (mScroller == null) {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
}
//包含此 View 的 Activity 退出或者 View 被 remove 時會被調(diào)用 回收資源
@Override protected void onDetachedFromWindow() {
mVelocityTracker.recycle();
super.onDetachedFromWindow();
}
@Override public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;
if (!mScroller.isFinished()) {
//滑動動畫效果未完成時 再次傳來事件序列 則中止滑動動畫 且攔截事件
mScroller.abortAnimation();
intercepted = true;
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastXInterceptTouchEvent;
int deltaY = y - mLastYInterceptTouchEvent;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
//如果 Y 軸滑動距離大于 X 軸,不攔截事件
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;
}
mLastX = x;
mLastY = y;
mLastXInterceptTouchEvent = x;
mLastYInterceptTouchEvent = y;
return intercepted;
}
//自身的滑動事件
@Override public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y = mLastY;
scrollBy(-deltaX, 0);
break;
case MotionEvent.ACTION_UP:
//獲取當前View邊緣與View內(nèi)容左邊緣的距離
int scrollX = getScrollX();
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
//mChildIndex代表在屏幕最左邊的子View的序號 0開始
if (Math.abs(xVelocity) > 50) {
//如果是快速滑動 判斷滑動方向 使最左邊一個子View的序號加減1
mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
} else {
//當前View邊緣與View內(nèi)容左邊緣的距離加上半個子 View 的寬度 除去子View寬新蟆,使滑動超過半個子View寬情況下
// 滑動到下個子View 否則只滑動整數(shù)個子View寬度
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
//使mChildIndex不會超過實際子View數(shù)觅赊,以及防止負數(shù)出現(xiàn)
mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
//在smoothScrollBy調(diào)用前 實際狀態(tài)是滑動距離是 正scrollX 所以減去
// 使得手指離開后一直處于滑動整數(shù)個子View的狀態(tài)
int dx = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dx, 0);
mVelocityTracker.clear();
break;
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
//緩慢滑動 需要 computeScroll 配合
private void smoothScrollBy(int dx, int dy) {
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
invalidate();
}
@Override public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = 0;
int measuredHeight = 0;
final int childCount = getChildCount();
//遍歷子View 進行measure
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
//若無子元素,設(shè)置寬高為0
if (childCount == 0) {
setMeasuredDimension(0, 0);
} else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
//若寬高都使用了 wrap content
final View childView = getChildAt(0);
//使用第一個子View的寬乘以數(shù)量 其他需求可以考慮改寫 measureChildren 等方法
measuredWidth = childView.getMeasuredWidth() * childCount;
//使用第一個子View的高
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(measuredWidth, measuredHeight);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
//若高度使用了 wrap content
final View childView = getChildAt(0);
//使用第一個子View的高度
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpecSize, measuredHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
//若寬度使用了 wrap content
final View childView = getChildAt(0);
//使用第一個子View的寬乘以數(shù)量 其他需求可以考慮改寫 measureChildren 等方法
measuredWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measuredWidth, heightSpecSize);
}
}
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
mChildrenSize = childCount;
for (int i = 0; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
final int childWidth = childView.getMeasuredWidth();
mChildWidth = childWidth;
childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
//累加所有子View的寬度
childLeft += childWidth;
}
}
}
}
以上代碼存在兩個不規(guī)范之處琼稻,一是吮螺,沒有子元素時不應(yīng)該直接設(shè)寬高為 0。應(yīng)該根據(jù) LayoutParams 進行處理帕翻。二是鸠补,在measure 和 layout 中都沒有根據(jù)自身的 padding 和子 View 的 margin 進行處理。