自定ViewGroup要比自定義View要復雜一點箱蝠,因為自定義ViewGroup不僅測量自身還要測量子元素和及重寫onLayout()來一次排列子View卖宠。下面這篇文章是關于自定義ViewGroup的一些基本知識巧娱,這些主要內(nèi)容來自《android開發(fā)藝術探索》赌躺,在文章最后又這本書的網(wǎng)上版本。
目錄
- ViewGroup的measure過程
- onMeasure()函數(shù)
- onLayout()函數(shù)
- 對Padding和Margin的處理
- 在Activity中獲取View的寬高
ViewGroup的measure過程
ViewGroup是一個抽象類徐勃,他沒有重寫View的onMeasure()方法事示。因此并沒有定義具體的測量過程,具體的測量過程交給了他的子類來完成僻肖,比如:LinearLayout
肖爵、RelativeLayout
等。ViewGroup提供了一個measureChildren
的方法來測量子View:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
//測量子元素
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
下面為measureChild()的源碼:
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//獲取子元素寬度MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
//獲取子元素高度MeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
//子元素進行測量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureChild()方法會先獲取子View的LayoutParams參數(shù)臀脏,然后再通過getChildMeasureSpec()獲取子View的寬高MeasureSpec劝堪,最后將獲取到的MeasureSpec傳遞給view的()方法進行測量。具體的執(zhí)行過程我在《View的繪制流程》這篇文章中介紹過揉稚,這里就不在多少說了秒啦。
onMeasure()方法
因為ViewGroup沒有重寫View的onMeasure方法,我們在自定義的時候集成了ViewGruppo成了View的子類搀玖,因此要寫自己布局的測量過則余境。那我上篇文章《自定義ViewGroup—FlowLayout》中的部分代碼為例:
@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
//##1
//循環(huán)遍歷子View
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
//先測量子View
measureChild(childView, widthMeasureSpec, widthMeasureSpec);
....
//計算剩余空間
remaining = widthSize - usedWidth;
//##2
//判斷子view的測量寬度是否大于剩余空間,如果大于則另起一行
if (childWidth > remaining) {
//另起一行灌诅,已用寬度應該為零
usedWidth = 0;
//添加View
mLineView = new LineView();
mLineViewList.add(mLineView);
}
mLineView.addView(childView);
//已用寬度累加
usedWidth += childWidth;
mLineView.setTotalWidth(usedWidth);
}
//##3
for (int i = 0; i < mLineViewList.size(); i++) {
//總高度=所有行數(shù)相加
totalHeight += mLineViewList.get(i).mHeight;
}
//父容器的總高度=上下padding的高度芳来,在最后額外加一個底部marginBottom
totalHeight += getPaddingTop() + getPaddingBottom() + marginBottom;
//##4
setMeasuredDimension(widthSize, heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight);
}
上面代碼中主要是測量FlowLayout的高度。它是通過遍歷子View(//##1)計算剩余寬度猜拾,再通過子View的寬度和剩余寬度比較來判斷是否換行(//##2)即舌。FlowLayout的高度如果是在warp_cotent模式下高度就為子View的行數(shù)乘上子View的高度(//##3),最后通過setMeasuredDimension()
計算View的寬高并保存起來关带。
onLayout()函數(shù)
onLayout()函數(shù)式是ViewGroup的一個抽象函數(shù)侥涵,ViewGroup的子類必須實現(xiàn)該函數(shù)沼撕,用于定義View的擺放規(guī)則。
注:該方法有一個指的探討的問題芜飘,下面onLayout()是被@Override注釋著的务豺,也就是這個方法是復用了View的onLayout()方法。那么問題來了嗦明,java中父類的方法是否能把子類重寫為抽象方法笼沥?這個問題我在好多技術群中向大神請教過,有的說能有的說不能娶牌。后來自己在項目中親自測試奔浅,發(fā)現(xiàn)可以重寫但是不能調(diào)用。如果讀者知道這個問題诗良,歡迎在下方留言汹桦。
@Override
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
該方法的調(diào)用是在View的layout被調(diào)用,我們可以查看ViewGroup的layout()放知道ViewGroup的layout過程是交給父類完成的鉴裹。
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
//調(diào)用父類的layout方法
super.layout(l, t, r, b);
} else {
transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
View的layout方法中調(diào)用了onLayout()方法
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
...
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...
}
...
}
對Padding和Margin的處理
Padding的處理比較簡單舞骆,只需要getPaddingXXX()來獲取padding的值,在計算ViewGroup的寬高的時候?qū)⑵浼由霞纯?
paddingLeft = getPaddingLeft();
paddingTop = getPaddingTop();
paddingRight = getPaddingRight();
paddingBottom = getPaddingBottom();
//ViewGroup寬高計算
viewGroupWidth = paddingLeft + viewsWidth + paddingRight;
viewGroupHeight = paddingTop + viewsHeight + paddingBottom;
Margin的處理比較麻煩一點径荔,首先他要先從子View中獲取layoutParams屬性督禽,通過子View的LayoutParams屬性來獲取設置的Margin值。其layoutParams獲取方法為childView.getLayoutParams()
总处。要注意下面兩點:
- 獲取的要是MarginLayoutParams()類型,記得做類型強轉(zhuǎn)狈惫。
- 重新generateLayoutParams(),返回類型為MarginLayoutParams類或他的子類鹦马。否則回報類型轉(zhuǎn)換異常
下面為實現(xiàn)代碼:
@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//獲取Margin值
MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
marginLeft = Math.max(marginLeft, lp.rightMargin);
marginTop = Math.max(marginTop, lp.topMargin);
marginRight = Math.max(marginRight, lp.rightMargin);
marginBottom = lp.bottomMargin;
//計算子View四個坐標位置
int cLeft = left + marginLeft;
int cRight = left + childWidth + marginRight;
int cTop = top + marginTop;
int cBottom = top + childHeight + marginBottom;
//設置View的具體位置
childView.layout(cLeft, cTop, cRight, cBottom);
}
//重寫generateLayoutParams()
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
在Activity中獲取View的寬高
android中不能再Activity的生命周期onCreate()
胧谈、onStart()
、onResume()
生命周期中獲取到View的寬高菠红,這是因為Activity的生命周期和View的測量過程不是同步執(zhí)行的第岖。對于上面的問題有四種解決方案。下面為三種解決方法试溯,第四種方案比較復雜就沒寫出來蔑滓。
在onWindowFoucusChanged()
中獲取
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
int width = mFlowL_testCustom.getMeasuredWidth();
int height = mFlowL_testCustom.getMeasuredHeight();
Log.i(TAG, "onWindowFocusChanged()測量的寬為:" + width + "高為:" + height);
}
通過View的post方法,經(jīng)請求發(fā)送到消息隊列中執(zhí)行遇绞。
mFlowL_testCustom.post(new Runnable() {
@Override
public void run() {
int width = mFlowL_testCustom.getMeasuredWidth();
int height = mFlowL_testCustom.getMeasuredHeight();
Log.i(TAG, "mFlowL_testCustom.post()測量的寬為:" + width + "高為:" + height);
}
});
通過為ViewTreeObserver添加OnGlobalLayoutListener()來實現(xiàn)
ViewTreeObserver treeObserver=mFlowL_testCustom.getViewTreeObserver();
treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int width = mFlowL_testCustom.getMeasuredWidth();
int height = mFlowL_testCustom.getMeasuredHeight();
Log.i(TAG, "addOnGlobalLayoutListener()測量的寬為:" + width + "高為:" + height);
}
});
總結(jié)
文章先寫到這里吧键袱!最近一直在堅持每天寫技術筆記,希望能慢慢將這種堅持當成一種習慣摹闽。最后祝所有看到這篇文章的人工作順利蹄咖,工資翻番。