編輯:關於Android編程
ViewGroup也是繼承於View,下面看看繪制過程中依次會調用哪些函數。
說明:
measure()和onMeasure()
在View.Java源碼中:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
public final void measure(int widthMeasureSpec,int heightMeasureSpec){
...
onMeasure
...
}
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
可以看出measure()是被final修飾的,這是不可被重寫。onMeasure在measure方法中調用的,當我們繼承View的時候通過重寫onMeasure方法來測量控件大小。
layout()和onLayout(),draw()和onDraw()類似。
dispatchDraw()
View 中這個函數是一個空函數,ViewGroup 復寫了dispatchDraw()來對其子視圖進行繪制。自定義的 ViewGroup 一般不對dispatchDraw()進行復寫。
requestLayout()
當布局變化的時候,比如方向變化,尺寸的變化,會調用該方法,在自定義的視圖中,如果某些情況下希望重新測量尺寸大小,應該手動去調用該方法,它會觸發measure()和layout()過程,但不會進行 draw。
自定義ViewGroup的時候一般復寫
onMeasure()方法:
計算childView的測量值以及模式,以及設置自己的寬和高
onLayout()方法,對其所有childView的位置進行定位
View樹:
樹的遍歷是有序的,由父視圖到子視圖,每一個 ViewGroup 負責測繪它所有的子視圖,而最底層的 View 會負責測繪自身。
measure:
自上而下進行遍歷,根據父視圖對子視圖的MeasureSpe╧y"/kf/yidong/wp/" target="_blank" class="keylink">WPS1LywQ2hpbGRWaWV319TJ7bXEss7K/aOszai5/TwvcD4NCjxwcmUgY2xhc3M9"brush:java;">
getChildMeasureSpec(parentHeightMeasure,mPaddingTop+mPaddingBottom,lp.height)
獲取ChildView的MeasureSpec,回調ChildView.measure最終調用setMeasuredDimension得到ChildView的尺寸:
mMeasuredWidth 和 mMeasuredHeight
Layout :
也是自上而下進行遍歷的,該方法計算每個ChildView的ChildLeft,ChildTop;與measure中得到的每個ChildView的mMeasuredWidth 和 mMeasuredHeight,來對ChildView進行布局。
child.layout(left,top,left+width,top+height)
measure過程會為一個View及所有子節點的mMeasuredWidth
和mMeasuredHeight變量賦值,該值可以通過getMeasuredWidth()和getMeasuredHeight()方法獲得。
onMeasure過程傳遞尺寸的兩個類:
ViewGroup.LayoutParams (ViewGroup 自身的布局參數)
用來指定視圖的高度和寬度等參數,使用 view.getLayoutParams() 方法獲取一個視圖LayoutParams,該方法得到的就是其所在父視圖類型的LayoutParams,比如View的父控件為RelativeLayout,那麼得到的 LayoutParams 類型就為RelativeLayoutParams。
①具體值
②MATCH_PARENT 表示子視圖希望和父視圖一樣大(不包含 padding 值)
③WRAP_CONTENT 表示視圖為正好能包裹其內容大小(包含 padding 值)
MeasureSpecs
測量規格,包含測量要求和尺寸的信息,有三種模式:
①UNSPECIFIED
父視圖不對子視圖有任何約束,它可以達到所期望的任意尺寸。比如 ListView、ScrollView,一般自定義 View 中用不到
②EXACTLY父視圖為子視圖指定一個確切的尺寸,而且無論子視圖期望多大,它都必須在該指定大小的邊界內,對應的屬性為 match_parent 或具體值,比如 100dp,父控件可以通過MeasureSpec.getSize(measureSpec)直接得到子控件的尺寸。
③AT_MOST
父視圖為子視圖指定一個最大尺寸。子視圖必須確保它自己所有子視圖可以適應在該尺寸范圍內,對應的屬性為 wrap_content,這種模式下,父控件無法確定子 View 的尺寸,只能由子控件自己根據需求去計算自己的尺寸,這種模式就是我們自定義視圖需要實現測量邏輯的情況。
子視圖的具體位置都是相對於父視圖而言的。View 的 onLayout 方法為空實現,而 ViewGroup 的 onLayout 為 abstract 的,因此,如果自定義的自定義ViewGroup 時,必須實現 onLayout 函數。
在 layout 過程中,子視圖會調用getMeasuredWidth()和getMeasuredHeight()方法獲取到 measure 過程得到的 mMeasuredWidth 和 mMeasuredHeight,作為自己的 width 和 height。然後調用每一個子視圖的layout(l, t, r, b)函數,來確定每個子視圖在父視圖中的位置。
先上效果圖:
代碼中有詳細的注釋,結合上文中的說明,理解應該沒有問題。這裡主要貼出核心代碼。
FlowLayout.java中(參照陽神的慕課課程)
onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// 獲得它的父容器為它設置的測量模式和大小
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
// 用於warp_content情況下,來記錄父view寬和高
int width = 0;
int height = 0;
// 取每一行寬度的最大值
int lineWidth = 0;
// 每一行的高度累加
int lineHeight = 0;
// 獲得子view的個數
int cCount = getChildCount();
for (int i = 0; i < cCount; i++)
{
View child = getChildAt(i);
// 測量子View的寬和高(子view在布局文件中是wrap_content)
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到LayoutParams
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 根據測量寬度加上Margin值算出子view的實際寬度(上文中有說明)
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
// 根據測量高度加上Margin值算出子view的實際高度
int childHeight = child.getMeasuredHeight() + lp.topMargin+ lp.bottomMargin;
// 這裡的父view是有padding值的,如果再添加一個元素就超出最大寬度就換行
if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight())
{
// 父view寬度=以前父view寬度、當前行寬的最大值
width = Math.max(width, lineWidth);
// 換行了,當前行寬=第一個view的寬度
lineWidth = childWidth;
// 父view的高度=各行高度之和
height += lineHeight;
//換行了,當前行高=第一個view的高度
lineHeight = childHeight;
} else{
// 疊加行寬
lineWidth += childWidth;
// 得到當前行最大的高度
lineHeight = Math.max(lineHeight, childHeight);
}
// 最後一個控件
if (i == cCount - 1)
{
width = Math.max(lineWidth, width);
height += lineHeight;
}
}
/**
* EXACTLY對應match_parent 或具體值
* AT_MOST對應wrap_content
* 在FlowLayout布局文件中
* android:layout_width="fill_parent"
* android:layout_height="wrap_content"
*
* 如果是MeasureSpec.EXACTLY則直接使用父ViewGroup傳入的寬和高,否則設置為自己計算的寬和高。
*/
setMeasuredDimension(
modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop()+ getPaddingBottom()
);
}
onLayout方法
//存儲所有的View
private List> mAllViews = new ArrayList>();
//存儲每一行的高度
private List mLineHeight = new ArrayList();
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
mAllViews.clear();
mLineHeight.clear();
// 當前ViewGroup的寬度
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
// 存儲每一行所有的childView
List lineViews = new ArrayList();
int cCount = getChildCount();
for (int i = 0; i < cCount; i++)
{
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin+ lp.bottomMargin);
lineViews.add(child);
// 換行,在onMeasure中childWidth是加上Margin值的
if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight())
{
// 記錄行高
mLineHeight.add(lineHeight);
// 記錄當前行的Views
mAllViews.add(lineViews);
// 新行的行寬和行高
lineWidth = 0;
lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
// 新行的View集合
lineViews = new ArrayList();
}
}
// 處理最後一行
mLineHeight.add(lineHeight);
mAllViews.add(lineViews);
// 設置子View的位置
int left = getPaddingLeft();
int top = getPaddingTop();
// 行數
int lineNum = mAllViews.size();
for (int i = 0; i < lineNum; i++)
{
// 當前行的所有的View
lineViews = mAllViews.get(i);
lineHeight = mLineHeight.get(i);
for (int j = 0; j < lineViews.size(); j++)
{
View child = lineViews.get(j);
// 判斷child的狀態
if (child.getVisibility() == View.GONE)
{
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();
// 為子View進行布局
child.layout(lc, tc, rc, bc);
left += child.getMeasuredWidth() + lp.leftMargin+ lp.rightMargin;
}
left = getPaddingLeft() ;
top += lineHeight ;
}
}
/**
* 因為我們只需要支持margin,所以直接使用系統的MarginLayoutParams
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs)
{
return new MarginLayoutParams(getContext(), attrs);
}
以及MainActivity.java
public class MainActivity extends Activity {
LayoutInflater mInflater;
@InjectView(R.id.id_flowlayout1)
FlowLayout idFlowlayout1;
@InjectView(R.id.id_flowlayout2)
FlowLayout idFlowlayout2;
private String[] mVals = new String[]
{"Do", "one thing", "at a time", "and do well.", "Never", "forget",
"to say", "thanks.", "Keep on", "going ", "never give up."};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
mInflater = LayoutInflater.from(this);
initFlowlayout2();
}
public void initFlowlayout2() {
for (int i = 0; i < mVals.length; i++) {
final RelativeLayout rl2 = (RelativeLayout) mInflater.inflate(R.layout.flow_layout, idFlowlayout2, false);
TextView tv2 = (TextView) rl2.findViewById(R.id.tv);
tv2.setText(mVals[i]);
rl2.setTag(i);
idFlowlayout2.addView(rl2);
rl2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int i = (int) v.getTag();
addViewToFlowlayout1(i);
rl2.setBackgroundResource(R.drawable.flow_layout_disable_bg);
}
});
}
}
public void addViewToFlowlayout1(int i){
RelativeLayout rl1 = (RelativeLayout) mInflater.inflate(R.layout.flow_layout, idFlowlayout1, false);
ImageView iv = (ImageView) rl1.findViewById(R.id.iv);
iv.setVisibility(View.VISIBLE);
TextView tv1 = (TextView) rl1.findViewById(R.id.tv);
tv1.setText(mVals[i]);
rl1.setTag(i);
idFlowlayout1.addView(rl1);
rl1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int i = (int) v.getTag();
idFlowlayout1.removeView(v);
View view = idFlowlayout2.getChildAt(i);
view.setBackgroundResource(R.drawable.flow_layout_bg);
}
});
}
這個項目源碼已近上傳,想要看源碼的朋友可以
點擊 FlowLayout
如果有什麼疑問可以給我留言,不足之處歡迎在github上指出,謝謝!
華為榮耀暢玩5x於去年10月份上市至今,是華為的中低端手機中口碑較好的一款,而華為榮耀5c是今年4月份剛剛上市的,剛上市就有花粉在問華為榮耀5x和5c哪個好
直接給圖,一目了然!
CollapsingToolbarLayout作用是提供了一個可以折疊的Toolbar,它繼承至FrameLayout,給它設置layout_scrollFlags,它可
具體代碼如下:main.xml復制代碼 代碼如下:<LinearLayout xmlns:android=http://schemas.android.com/ap