首先需要了解 onLayout(),onMeasure(),方法
总而言之,对于viewGroup来说,onMeasure就是测绘本身的大小,如何测绘呢?先测绘所有子view的大小,子view也会递归的进行这个过程,然后onLayout就是放置子view的位置。附上源码。
package com.example.testviewgroup;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
public class mViewGroup extends ViewGroup {
private boolean HorV;
public mViewGroup(Context context) {
this(context, null);
}
public mViewGroup(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public mViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.mViewGroup);
HorV= a.getBoolean(R.styleable.mViewGroup_viewGroup_HorV,true);//默认为水平
a.recycle();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if(HorV) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View v = getChildAt(i);
if (v.getVisibility() != View.GONE) {
int childWidth = v.getMeasuredWidth();
int childHeight = v.getMeasuredHeight();
//开始摆放
v.layout(l, t, l + childWidth, t + childHeight);
//把左边的锚定位置往右移
//如果在垂直方向继续累加l偏移量,那么显示出来的三个子view呈现阶梯状。
l += childWidth;
//垂直方向累计坐标量
// t += childHeight;
}
}
}
else{
int count = getChildCount();
for (int i = 0; i < count; i++) {
CustomView v = (CustomView) getChildAt(i);
if (v.getVisibility() != View.GONE) {
int childWidth = v.getMeasuredWidth();
int childHeight = v.getMeasuredHeight();
//开始摆放
v.layout(l, t, l + childWidth, t + childHeight);
//把左边的锚定位置往右移
//如果在垂直方向继续累加l偏移量,那么显示出来的三个子view呈现阶梯状。
//l += childWidth;
//垂直方向累计坐标量
t += childHeight;
}
}
}// else end 也就是垂直的逻辑部分
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(HorV) {
int measuredWidth = 0;
int measuredHeight = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View v = getChildAt(i);
if (v.getVisibility() != View.GONE) {
measureChild(v, widthMeasureSpec, heightMeasureSpec);
measuredHeight = Math.max(measuredHeight, v.getMeasuredHeight());
measuredWidth += v.getMeasuredWidth();
}
}
measuredWidth += getPaddingLeft() + getPaddingRight();
measuredHeight += getPaddingTop() + getPaddingBottom();
//可选
//measuredWidth = Math.max(measuredWidth, getSuggestedMinimumWidth());
//measuredHeight = Math.max(measuredHeight, getSuggestedMinimumHeight());
//另外一种set度量值的方法
//setMeasuredDimension(resolveSize(measuredWidth, widthMeasureSpec),resolveSize(measuredHeight, heightMeasureSpec));
setMeasuredDimension(measuredWidth, measuredHeight);
}
else{
int measuredWidth = 0;
int measuredHeight = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View v = getChildAt(i);
if (v.getVisibility() != View.GONE) {
measureChild(v, widthMeasureSpec, heightMeasureSpec);
measuredWidth = Math.max(measuredWidth, v.getMeasuredWidth());
measuredHeight += v.getMeasuredHeight();
}
}
measuredWidth += getPaddingLeft() + getPaddingRight();
measuredHeight += getPaddingTop() + getPaddingBottom();
//可选
//measuredWidth = Math.max(measuredWidth, getSuggestedMinimumWidth());
//measuredHeight = Math.max(measuredHeight, getSuggestedMinimumHeight());
//另外一种set度量值的方法
//setMeasuredDimension(resolveSize(measuredWidth, widthMeasureSpec),resolveSize(measuredHeight, heightMeasureSpec));
setMeasuredDimension(measuredWidth, measuredHeight);
}//else end 也就是垂直的逻辑部分`在这里插入代码片`
}
}
这里又增加了一个新的需求,就是我们假设我们自定义的CustView控件增加了优先级属性,如何展示实现根据优先级显示?比如 最右边的控件优先级最高,它增大的话,左边的控件就会被挤走。最中间的优先级最高,它如果很大,导致剩下的空间不够,应该把两边的挤走?
先说思路,思路如下:给子view组件注册一个自定义优先级属性和一个是否可见的属性(默认可见)。然后提供对应的set,get方法。
在viewGroup里的写一个优先级判断方法。先取出可显示区域的大小(这里假设是水平,取出显示的width),然后从优先级搞到低逐个判断是否可以显示,若可以显示,可显示区域减去该控件所占的区域,并将控件可见属性CanLook设置为true,否则,则设置为false。然后在onLayuot阶段是在判断控件是否可见。就可以完成了。
完整的控件代码
package com.example.testviewgroup;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
public class MyViewGroup extends ViewGroup {
private boolean HorV;//水平还是垂直 true为水平
public MyViewGroup(Context context) {
this(context, null);
}
public MyViewGroup(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.mViewGroup);
HorV = a.getBoolean(R.styleable.mViewGroup_viewGroup_HorV, true);//默认为水平
a.recycle();
}
//新增需求,要对view实现优先级设置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//初始化,对优先级做一个处理。
initPrior();
if (HorV) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
//View v = getChildAt(i);
CustomView v = (CustomView) getChildAt(i);
if (v.getVisibility() != View.GONE && v.getCanseen() && v.getCanseen()) {
int childWidth = v.getMeasuredWidth();
int childHeight = v.getMeasuredHeight();
//开始摆放
v.layout(l, t, l + childWidth, t + childHeight);
//把左边的锚定位置往右移
//如果在垂直方向继续累加l偏移量,那么显示出来的三个子view呈现阶梯状。
l += childWidth;
//垂直方向累计坐标量
// t += childHeight;
}
}
} else {
int count = getChildCount();
for (int i = 0; i < count; i++) {
CustomView v = (CustomView) getChildAt(i);
if (v.getVisibility() != View.GONE && v.getCanseen()) {
int childWidth = v.getMeasuredWidth();
int childHeight = v.getMeasuredHeight();
//开始摆放
v.layout(l, t, l + childWidth, t + childHeight);
//把左边的锚定位置往右移
//如果在垂直方向继续累加l偏移量,那么显示出来的三个子view呈现阶梯状。
//l += childWidth;
//垂直方向累计坐标量
t += childHeight;
}
}
}// else end 也就是垂直的逻辑部分
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (HorV) {
int measuredWidth = 0;
int measuredHeight = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View v = getChildAt(i);
if (v.getVisibility() != View.GONE) {
measureChild(v, widthMeasureSpec, heightMeasureSpec);
measuredHeight = Math.max(measuredHeight, v.getMeasuredHeight());
measuredWidth += v.getMeasuredWidth();
}
}
measuredWidth += getPaddingLeft() + getPaddingRight();
measuredHeight += getPaddingTop() + getPaddingBottom();
//可选
//measuredWidth = Math.max(measuredWidth, getSuggestedMinimumWidth());
//measuredHeight = Math.max(measuredHeight, getSuggestedMinimumHeight());
//为了使用优先级在屏幕里,我们需要将viewGroup的大小设置为屏幕和viewGroup二者的最小值。
DisplayMetrics mDisplayMetrics = getResources().getDisplayMetrics();
int width = mDisplayMetrics.widthPixels;
int height = mDisplayMetrics.heightPixels;
//另外一种set度量值的方法
//setMeasuredDimension(resolveSize(measuredWidth, widthMeasureSpec),resolveSize(measuredHeight, heightMeasureSpec));
setMeasuredDimension(Math.min(measuredWidth, width), Math.min(measuredHeight, height));
} else {
int measuredWidth = 0;
int measuredHeight = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View v = getChildAt(i);
if (v.getVisibility() != View.GONE) {
measureChild(v, widthMeasureSpec, heightMeasureSpec);
measuredWidth = Math.max(measuredWidth, v.getMeasuredWidth());
measuredHeight += v.getMeasuredHeight();
}
}
measuredWidth += getPaddingLeft() + getPaddingRight();
measuredHeight += getPaddingTop() + getPaddingBottom();
//可选
//measuredWidth = Math.max(measuredWidth, getSuggestedMinimumWidth());
//measuredHeight = Math.max(measuredHeight, getSuggestedMinimumHeight());
//另外一种set度量值的方法
//setMeasuredDimension(resolveSize(measuredWidth, widthMeasureSpec),resolveSize(measuredHeight, heightMeasureSpec));
//新增需求,获取activty的大小,若viewgroup大小超过他们,则截断超出的部分。
DisplayMetrics mDisplayMetrics = getResources().getDisplayMetrics();
int width = mDisplayMetrics.widthPixels;
int height = mDisplayMetrics.heightPixels;
setMeasuredDimension(Math.min(measuredWidth, width), Math.min(measuredHeight, height));
}//else end 也就是垂直的逻辑部分
}
//做优先级的判断的代码
private void initPrior() {
if (HorV == true) {
int width = getWidth();
List<CustomView> list = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
CustomView v = (CustomView) getChildAt(i);
list.add(v);
}//以 key:prior优先级 - value:i下标 的形式存储一下。
Collections.sort(list, new Comparator<CustomView>() {
@Override
public int compare(CustomView v1, CustomView v2) {
// 按照学生的年龄进行升序排列
if (v2.getPrior() > v1.getPrior()) {
return 1;
}
if (v1.getPrior() == v2.getPrior()) {
return 0;
}
return -1;
}
});
Iterator iter = list.iterator();
while (iter.hasNext()) {
CustomView v = (CustomView) iter.next();
if (width - v.getMeasuredWidth() >= 0) {
width -= v.getMeasuredWidth();
v.setCanSeen(true);
}//可以放
else {
v.setCanSeen(false);
}
}
}//HorV == true 水平排放的逻辑部分
else {
int height = getHeight();
List<CustomView> list = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
CustomView v = (CustomView) getChildAt(i);
list.add(v);
}//以 key:prior优先级 - value:i下标 的形式存储一下。
Collections.sort(list, new Comparator<CustomView>() {
@Override
public int compare(CustomView v1, CustomView v2) {
// 按照学生的年龄进行升序排列
if (v2.getPrior() > v1.getPrior()) {
return 1;
}
if (v1.getPrior() == v2.getPrior()) {
return 0;
}
return -1;
}
});
Iterator iter = list.iterator();
while (iter.hasNext()) {
CustomView v = (CustomView) iter.next();
if (height - v.getMeasuredHeight() >= 0) {
height -= v.getMeasuredHeight();
v.setCanSeen(true);
}//可以放
else {
v.setCanSeen(false);
}
}
}//HorV == false 垂直排放的逻辑部分
}
}