最终代码在最后,每行都注释了,不想看写的文字的自己取(我写的是仿照流式布局形式写的,你可以借鉴这个思路写)显示效果在最后。
首先我先简单解释一下原理 , 上图片
ww2
接下来我将一段段的讲解,最终代码在最后,都有我写的注释。
首先写Android这么久了肯定知道,写一个控件有三种方式来控制控件的大小:
match_parent 、wrap_content、(给定确切大小如200dp)
所以自定义View同样也会有这样三种模式,给定View的大小咱就先不说了,就先谈没给定大小, 怎么获得View的宽高。
自定义View跟ViewGroup有一定区别:View可以只走onMeasure(),onDraw 就可以运行,ViewGroup走onMeasure ->onLayou 可以运行;
所以测量ViewGroup 跟View的大小在onMeasure()方法内进行
在onMeasure()方法内我们肯定要先计算View的大小,当然如果我们将所有的View的都添加进ViewGroup却大于ViewGroup的容器肯定不会显示超越ViewGroup的部分,所以这个要注意,当我们View未超过ViewGroup则可以按照我们的思路排序。排序完成后在确定ViewGroup的大小显示,如果是固定值那么我们获得的ViewGroup还是他的固定值。
计算ViewGroup下的所有子View:
因为我写的是一个流式的布局类似,所以我循环内得这样写
首先我的显示第一行的内容,以行为单位显示(显示代码在OnLayout)所以我要计算每个孩子的宽高(孩子的宽相加用于判断当前是否超过了这一行的最大限制)(孩子的高保留最大的用于判断这行高度),如果这行已经占用大宽度已经不能容纳下一个View的时候我们要换行。换行就该把记录的List>去记录这一行的View,与这行的高度(LineHeight)。
int childCount = getChildCount();//获得孩子的个数以便操作循环
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);//获得第i个孩子的View
LayoutParams child = childView.getLayoutParams();//获得孩子布局的参数
//widthMeasureSpec父布局的宽 getPaddingLeft() + getPaddingRight() 获取父布局边界值 child.width获得孩子View的宽
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight(), child.width);
//heightMeasureSpec的高 getPaddingLeft() + getPaddingRight() 获取边界值 child.height获得孩子View的高
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom(), child.height);
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//给定某个孩子的大小
//测量完成判此View是否还可以在此行放下
//判断此行是否还可以放下一个子View,如果放不下换行,将数据清空。
// selfWeight ViewGroup的总宽度
int childMeasureWidth = childView.getMeasuredWidth();
int childMeasureHeight = childView.getMeasuredHeight();
if (childMeasureWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
allLines.add(lineViews);
linHeight.add(thisLineHeight);
parentNeededHeight = parentNeededHeight + thisLineHeight + mHorizontalSpacing;// 高度相加
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);//判断宽度那个宽选择那个
Log.i("TAG1111", "孩子的控件 " + lineViews.size());
lineViews = new ArrayList<>();//重置布局List
lineWidthUsed = 0;//重置宽
thisLineHeight = 0;//重置宽高
}
lineViews.add(childView);
lineWidthUsed = lineWidthUsed + childMeasureWidth + mHorizontalSpacing;
thisLineHeight = Math.max(thisLineHeight, childMeasureHeight);
//判断如何换行
}
当判断完所有的孩子后我们会的到如下数据:
(每一行的行高相加的结果、每一行作比较求的的最大行宽)
这是我们做判断是不是给了match_parent 或 确定值,如果没给那就用我们用我们计算的值,给了就用给定的值。这样就给规定好了ViewGroup的大小,同时孩子也都计算过自己的值了。
int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;//如果父布局给确切大小 selWeight,如果不是给我们算出来的Size;
int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;
setMeasuredDimension(realWidth, realHeight);//给定父布局的大小
我么在通过OnLayout显示以西我们想要的这种流式布局。
两层for循环是判断用于显示具体的View,只要获得每哥View的测量好的宽高加上我们设置的间距就行。
int curLeft = getPaddingLeft();//获得父母的padding
int curTop = getPaddingTop();
//布局依托与父布局
int lineCount = allLines.size();
for (int i = 0; i < lineCount; i++) {
List lineView = allLines.get(i);
int linHeights = linHeight.get(i);
for (int j = 0; j < lineView.size(); j++) {
View view = lineView.get(j);
int left = curLeft;
int top = curTop;
int right = left + view.getMeasuredWidth();//getMeasuredWidth度量之后就有一个正确的值
int bottom = top + view.getMeasuredHeight();
view.layout(left, top, right, bottom);
curLeft = right + mHorizontalSpacing;
}
curTop = curTop + linHeights + mHorizontalSpacing;
curLeft = getPaddingLeft();
}
最终代码如下
如何写我已经写出来了,怎样显示就怎样改。 而且每行都有注释,相信你能看懂。
MyView.Java
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MyView extends ViewGroup {
private List> allLines; //记录所有行,一行行的储存,用于layout
List linHeight = new ArrayList<>();//记录每一行的的行高,用于 layout
//定义padding边距
private int mHorizontalSpacing = 40;
//代码new使用
public MyView(Context context) {
super(context);
}
//反射
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
//主题style
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init() {
if (allLines != null) {
allLines.clear();
} else {
allLines = new ArrayList<>();
}
linHeight = new ArrayList<>();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int curLeft = getPaddingLeft();//获得父母的padding
int curTop = getPaddingTop();
//布局依托与父布局
int lineCount = allLines.size();
for (int i = 0; i < lineCount; i++) {
List lineView = allLines.get(i);
int linHeights = linHeight.get(i);
for (int j = 0; j < lineView.size(); j++) {
View view = lineView.get(j);
int left = curLeft;
int top = curTop;
int right = left + view.getMeasuredWidth();//getMeasuredWidth度量之后就有一个正确的值
int bottom = top + view.getMeasuredHeight();
view.layout(left, top, right, bottom);
curLeft = right + mHorizontalSpacing;
}
curTop = curTop + linHeights + mHorizontalSpacing;
curLeft = getPaddingLeft();
}
}
//测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
init();
List lineViews = new ArrayList<>();//保存一行有多少东西的List
int lineWidthUsed = 0;//一行已使用了多长
int thisLineHeight = 0;//这行的高度
int selfWidth = MeasureSpec.getSize(widthMeasureSpec);//解析父布局的宽
int selfHeight = MeasureSpec.getSize(heightMeasureSpec);//解析父布局的高
int parentNeededWidth = 0; //记录父布局的宽
int parentNeededHeight = 0;//记录父布局的高
//度量孩子
int childCount = getChildCount();//获得孩子的个数以便操作循环
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);//获得第i个孩子的View
LayoutParams child = childView.getLayoutParams();//获得孩子布局的参数
//widthMeasureSpec父布局的宽 getPaddingLeft() + getPaddingRight() 获取父布局边界值 child.width获得孩子View的宽
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight(), child.width);
//heightMeasureSpec的高 getPaddingLeft() + getPaddingRight() 获取边界值 child.height获得孩子View的高
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom(), child.height);
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//给定某个孩子的大小
//测量完成判此View是否还可以在此行放下
//判断此行是否还可以放下一个子View,如果放不下换行,将数据清空。
// selfWeight ViewGroup的总宽度
int childMeasureWidth = childView.getMeasuredWidth();
int childMeasureHeight = childView.getMeasuredHeight();
if (childMeasureWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
allLines.add(lineViews);
linHeight.add(thisLineHeight);
parentNeededHeight = parentNeededHeight + thisLineHeight + mHorizontalSpacing;// 高度相加
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);//判断宽度那个宽选择那个
lineViews = new ArrayList<>();//重置布局List
lineWidthUsed = 0;//重置宽
thisLineHeight = 0;//重置宽高
}
lineViews.add(childView);
lineWidthUsed = lineWidthUsed + childMeasureWidth + mHorizontalSpacing;
thisLineHeight = Math.max(thisLineHeight, childMeasureHeight);
//判断如何换行
}
allLines.add(lineViews);
linHeight.add(thisLineHeight);
int widthMode = MeasureSpec.getMode(widthMeasureSpec); //获得ViewGroup的宽
int heightMode = MeasureSpec.getMode(heightMeasureSpec);//获得ViewGroup的高
int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;//如果父布局给确切大小 selWeight,如果不是给我们算出来的Size;
int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;
setMeasuredDimension(realWidth, realHeight);//给定父布局的大小
}
MainActivity.xml



