关于onMeasure()、onLayout()方法的使用
在写继承ViewGroup的自定义View时通常会重写onMeasure()、onLayout()方法,下面分别说下这2个方法。
1、 onMeasure():
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
}
此方法用于测量自身显示在页面上的大小。参数widthMeasureSpec为父类传过来的宽度的"建议值",参数heightMeasureSpec为父类传过来的高度的"建议值"。这2个参数不是简单的整数类型,而是2位整数(模式类型UNSPECIFIED|EXACTLY|AT_MOST)和30位整数(实际尺寸) 的组合值。
UNSPECIFIED:父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小。对应十进制的值为0。
EXACTLY:父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小。对应十进制的值为1。
AT_MOST:子元素至多达到指定大小的值。对应十进制的值为2。
获取宽高模式int值方法:
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
获取宽高尺寸int值方法:
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
2、 onLayout():
protected void onLayout(boolean changed, int l, int t, int r, int b){
}
只要是继承ViewGroup的类都必须重写该方法,严格来说应该是有子view的ViewGroup要重写此方法。此方法是用来实现该控件内部子控件的布局情况。
案例1:
这里写一个自定义类继承ViewGroup实现LinearLayout垂直排列的效果(忽略margin和pading设置):
/**
* Created by shengqf
* Email : [email protected]
* date : 2019/2/25
* describe : 垂直方向的线性布局(忽略margin和pading设置)
*/
public class VerticalLinearLayout extends ViewGroup {
public VerticalLinearLayout(Context context) {
super(context);
}
public VerticalLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VerticalLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
int height = 0;//所有子控件的宽高总和
int width = 0;//所有子控件里宽度最大的宽度
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
int childHeight = childView.getMeasuredHeight();
int childWidth = childView.getMeasuredWidth();
height += childHeight;
width = Math.max(childWidth, width);
}
//设置VerticalLinearLayout的宽高
setMeasuredDimension(measureWidthMode == MeasureSpec.EXACTLY ? measureWidth : width,
measureHeightMode == MeasureSpec.EXACTLY ? measureHeight : height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int top = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
int childHeight = childView.getMeasuredHeight();
int childWidth = childView.getMeasuredWidth();
//以父容器左上角为原点,垂直往下摆放子view
childView.layout(0, top, childWidth, top + childHeight);
top += childHeight;
}
}
}
布局:
<?xml version="1.0" encoding="utf-8"?>
<com.shengqf.view.flowlayout.VerticalLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF0">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</com.shengqf.view.flowlayout.VerticalLinearLayout>
看下效果:
案例2:
为加强对onMeasure()、onLayout()方法的理解,这里再写一个线性自动换行布局(简称流式布局),:
参考:https://github.com/hongyangAndroid/FlowLayout
/**
* Created by shengqf
* Email : [email protected]
* date : 2019/2/25
* describe : 线性自动换行布局(流式布局)
* 参考:https://github.com/hongyangAndroid/FlowLayout
*/
public class FlowLayout extends ViewGroup {
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);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
/**
* 以下代码是FlowLayout的宽或高设置为wrap_content的情况
* 如果FlowLayout的宽高均为确定值时,最后的测量直接写成:
* setMeasuredDimension(measureWidth,measureHeight);
*/
/**
* FlowLayout的宽度
* a、当FlowLayout的宽设置为wrap_content时,width为FlowLayout里面所有行中最宽一行的宽度
* b、当FlowLayout的宽设置为确定值时,width为measure的宽度measureWidth
*/
int width = 0;
/**
* FlowLayout的高度
* a、当FlowLayout的高设置为wrap_content时,height为FlowLayout里面所有行高的总合
* b、当FlowLayout的宽设置为确定值时,height为measure的高度measureHeight
*/
int height = 0;
int lineWidth = 0;//记录FlowLayout里面每一行子view的宽度
int lineHeight = 0;//记录FlowLayout里面每一行子view的高度
int count = getChildCount();
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
if (childView.getVisibility() == View.GONE) {
if (i == count - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
continue;
}
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams marginParams = (MarginLayoutParams) childView.getLayoutParams();
int childWidth = childView.getMeasuredWidth()
+ marginParams.leftMargin + marginParams.rightMargin;
int childHeight = childView.getMeasuredHeight()
+ marginParams.topMargin + marginParams.bottomMargin;
if (lineWidth + childWidth > measureWidth - getPaddingLeft() - getPaddingRight()) {
width = Math.max(width, lineWidth);
lineWidth = childWidth;
height += lineHeight;
lineHeight = childHeight;
} else {//一行没填满,继续往后添加子view
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
if (i == count - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
}
int tempWidth = measureWidthMode == MeasureSpec.EXACTLY ?
measureWidth : width + getPaddingLeft() + getPaddingRight();
int tempHeight = measureHeightMode == MeasureSpec.EXACTLY ?
measureHeight : height + getPaddingTop() + getPaddingBottom();
setMeasuredDimension(tempWidth, tempHeight);
}
private List<View> lineViews = new ArrayList<>();//某一行子view的集合
protected List<List<View>> mAllViews = new ArrayList<>();//所有行的集合
protected List<Integer> mLineHeight = new ArrayList<>();//所有行中每一行子view的最大的高度的集合
protected List<Integer> mLineWidth = new ArrayList<>();//所有行中每一行子view的宽度和的集合
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mAllViews.clear();
mLineHeight.clear();
mLineWidth.clear();
lineViews.clear();
int width = getWidth();//当宽设置为wrap_content时,值为屏幕宽度
int lineWidth = 0;
int lineHeight = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
if (childView.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams marginParams = (MarginLayoutParams) childView.getLayoutParams();
int childWidth = childView.getMeasuredWidth()
+ marginParams.leftMargin + marginParams.rightMargin;
int childHeight = childView.getMeasuredHeight()
+ marginParams.topMargin + marginParams.bottomMargin;
if (childWidth + lineWidth > width - getPaddingLeft() - getPaddingRight()) {
mLineHeight.add(lineHeight);
mLineWidth.add(lineWidth);
mAllViews.add(lineViews);
lineWidth = 0;
lineHeight = childHeight;
lineViews = new ArrayList<>();
}
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
lineViews.add(childView);
}
mLineHeight.add(lineHeight);
mLineWidth.add(lineWidth);
mAllViews.add(lineViews);
int left;
int top = getPaddingTop();
int lineNum = mAllViews.size();//行数
for (int i = 0; i < lineNum; i++) {//遍历所有行
lineViews = mAllViews.get(i);
lineHeight = mLineHeight.get(i);
//左对齐显示
left = getPaddingLeft();
//居中显示
//left = (width - mLineWidth.get(i)) / 2 + getPaddingLeft();
//右对齐显示
//left = width - mLineWidth.get(i) + getPaddingLeft();
for (int j = 0; j < lineViews.size(); j++) {//遍历某一行中的所有子view
View childView = lineViews.get(j);
if (childView.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams marginParams = (MarginLayoutParams) childView.getLayoutParams();
int lc = left + marginParams.leftMargin;
int tc = top + marginParams.topMargin;
int rc = lc + childView.getMeasuredWidth();
int bc = tc + childView.getMeasuredHeight();
//摆放子view
childView.layout(lc, tc, rc, bc);
left += childView.getMeasuredWidth()
+ marginParams.leftMargin + marginParams.rightMargin;
}
top += lineHeight;
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
}
布局:
<?xml version="1.0" encoding="utf-8"?>
<com.shengqf.view.flowlayout.FlowLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/flow_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:background="#FF0">
</com.shengqf.view.flowlayout.FlowLayout>
使用:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vertical_linear_layout);
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add("士大夫士大夫");
list.add("蒙多");
list.add("司法所地方");
list.add("是否");
list.add("大幅度");
list.add("的撒范德的");
list.add("的撒范德萨的撒范德");
list.add("士大");
list.add("野第三方的撒范德萨佛挡杀佛");
list.add("订单");
list.add("都是所得税");
list.add("所得税");
}
FlowLayout flowLayout = findViewById(R.id.flow_layout);
for (int j = 0; j < 10; j++) {
Button button = new Button(this);
button.setText(list.get(j));
ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(10,10,0,0);
button.setLayoutParams(params);
flowLayout.addView(button);
}
}
运行: