案例:自定義一個(gè)ViewGroup谱姓,將子view進(jìn)行流式布局
流程:
- 測量子view的大小 → onMeasure
測量每一個(gè)子view的寬和高瘟斜,待擺放的子view的寬度如果小于當(dāng)前行的剩余寬度則
放進(jìn)入,否則將其擺放到下一行莱褒。 - 擺放子view的位置 → onLayout (必須)
遍歷記錄的每一行所有子view和每一行的高對子view進(jìn)行擺放 -
對子view進(jìn)行繪制 → onDraw
當(dāng)前案例無需重構(gòu)此方法
布局如下圖
public class FlowLayout extends ViewGroup {
private int mHorizontalSpacing = dp2px(16); //每個(gè)item橫向間距
private int mVerticalSpacing = dp2px(8); //每個(gè)item橫向間距
private List<List<View>> allLines;// 記錄所有的行肚吏,一行一行的存儲
private List<Integer> heights; // 記錄每一行的行高
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void initMeasureParams() {
allLines = new ArrayList<>();
heights = new ArrayList<>();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
initMeasureParams(); //此方法不能在構(gòu)造函數(shù)調(diào)用,生命周期
int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //獲取當(dāng)前類控件的寬度
//獲取內(nèi)間距以便通過getChildMeasureSpec方法獲取子view的度量規(guī)則
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int childCount = getChildCount(); //或者子view的數(shù)量
int lineWidthUsed = 0; //當(dāng)前行已占用了的寬度
int lineHeight = 0; //當(dāng)前行占用最大的高度
List<View> lineViews = new ArrayList<>(); //記錄每一行所擺放的控件
int parentNeededWidth = 0; //當(dāng)前類控件至少所需要的寬度
int parentNeededHeight = 0; //當(dāng)前類控件至少所需要的高度
//遍歷子view進(jìn)行度量荤崇,以便能獲取度量之后的尺寸大小
// childView.getMeasuredWidth()是取度量之后的大小拌屏,childView.getWidth()是取onLayout之后的大小,這里需要前者
for (int i = 0; i < childCount; i++) {
//獲取子View术荤;
View childView = getChildAt(i);
LayoutParams childLP = childView.getLayoutParams();
//獲取子view的度量規(guī)則
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,paddingLeft + paddingRight, childLP.width);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);
//度量子view倚喂、設(shè)置子View的尺寸
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//確定是否換行
int childMeasureWidth = childView.getMeasuredWidth();
int childMeasureHeight = childView.getMeasuredHeight();
if (childMeasureWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
allLines.add(lineViews);
heights.add(lineHeight);
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
lineViews = new ArrayList<>();
lineWidthUsed = 0;
lineHeight = 0;
}
lineViews.add(childView);
lineWidthUsed = lineWidthUsed + childMeasureWidth + mHorizontalSpacing;
lineHeight = Math.max(lineHeight, childMeasureHeight);
//最后一個(gè)子view所在的那一行需要手動(dòng)加入
if(i == childCount - 1){
allLines.add(lineViews);
heights.add(lineHeight);
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
}
}
//確定流式布局自身的寬高
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int selfHeight = MeasureSpec.getSize(heightMeasureSpec);
int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;
int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;
setMeasuredDimension(realWidth,realHeight); //度量之后存儲該類控件自身寬高值,否則將觸發(fā)測量時(shí)出現(xiàn)異常
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int lineCount = allLines.size();
int curX = getPaddingLeft(); //設(shè)置子view左邊的起始位置
int curT = getPaddingTop(); //設(shè)置子view頂部的起始位置
for (int i = 0; i < lineCount; i++) {
List<View> lineViews = allLines.get(i);
int lineHeight = heights.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View view = lineViews.get(j);
int left = curX;
int top = curT;
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
view.layout(left, top, right, bottom); //參數(shù)值是子view距離當(dāng)前控件的四個(gè)距離
curX = right + mHorizontalSpacing;
}
curT = curT + lineHeight + mVerticalSpacing;
curX = getPaddingLeft();
}
}
public static int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
}
}
總結(jié)知識點(diǎn):
度量規(guī)則MeasureSpec:
是view的內(nèi)部類瓣戚,里面定義了三個(gè)int類型的變量來表示三種模式:據(jù)傳入的SpecSize來確定SpecMode, 如果傳入的SpecSize是Match_parent或者是精確值端圈,SpecMode是EXACTY焦读,如果傳入的是Wrap_content的話,SpecMode是AT_MOST, 若不對VIew的大小做出限制的話舱权,比如listview吨灭、recycleview這些的SpecMode就是UNSPECIFIED;widthMeasureSpec和heightMeasureSpec是32位的int類型刑巧,其中包括了模式和尺寸大小,前兩位表示模式无畔,后30位表示尺寸大小啊楚。
//通過度量規(guī)則獲取尺寸大小
MeasureSpec.getSize(MeasureSpec)
//獲取子view的度量規(guī)則
getChildMeasureSpec(當(dāng)前view的度量規(guī)則,paddingLeft + paddingRight, 子view的LayoutParams的寬高)
//對子view進(jìn)行度量之后才能獲取度量之后的準(zhǔn)確值
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//在度量階段獲取子view的大小要取度量之后的值
int childMeasureWidth = childView.getMeasuredWidth();
// 獲取度量規(guī)則的模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//度量之后存儲該類控件自身寬高值(度量自己),否則將觸發(fā)測量時(shí)出現(xiàn)異常
setMeasuredDimension(realWidth,realHeight);
//獲取該view到父view的距離
int left = getLeft();
//對子view進(jìn)行l(wèi)ayout擺放浑彰,方法參數(shù)是子view到父view的四個(gè)距離
view.layout(left, top, right, bottom);
//MotionEvent中 get()和getRaw()的區(qū)別
//get() :觸摸點(diǎn)相對于其所在組件坐標(biāo)系的坐標(biāo)
//getRaw() :觸摸點(diǎn)相對于屏幕默認(rèn)坐標(biāo)系的坐標(biāo)