前言
继承系统的viewGroup来实现从而达到自己想要的效果。 也可以参考
自定义View基础 - 最易懂的自定义View原理系列
Android自定义View基础:ViewRoot、DecorView & Window的简介
Android自定义View绘制前的准备:DecorView创建 & 显示
自定义View:Measure过程说明之MeasureSpec类详细讲解
Android自定义View:源码解析通过getWidth() 与 getMeasuredWidth()获取宽高的区别
Android:你知道该如何正确获取View坐标位置的方法吗?
Android:为什么view.post()能保证获取到view的宽高?
Android:手把手带你清晰梳理自定义View的工作全流程
Android 自定义View实战系列 :时间轴 用到recycleview的高阶知识
Android自定义View:你需要一个简单好用、含历史搜索记录的搜索框吗?
Android:手把手教你写一个完整的自定义View
Path类的最全面详解 - 自定义View应用系列
Canvas类的最全面详解 - 自定义View应用系列
效果
实现思路
自定义viewGroup控件核心实现方法:onMeasure()、onLayout().
测量流程 onMeasure
-
- 测量自身大小 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- 为每个子View计算测量的限制信息 Mode / Size MeasureSpec.getMode(widthMeasureSpec);
-
- 把上一步确定的限制信息,传递给每一个子View,然后子View开始measure
int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
child.measure(childWidthSpec, childHeightSpec);
- 把上一步确定的限制信息,传递给每一个子View,然后子View开始measure
-
- 获取子View测量完成后的尺寸
-
- ViewGroup根据自身的情况,计算自己的尺寸 涉及到具体场景具体逻辑处理
-
- 保存自身的尺寸 setMeasuredDimension(width,height);
布局流程 onLayout
-
- 遍历子View for
-
- 确定自己的规则
-
- 子View的测量尺寸
-
- left,top,right,bottom
-
- child.layout
Demo1
package com.essence.wanapp;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* 继承系统viewGroup
*/
public class MyViewGroup extends ViewGroup {
//写死偏移量100
private int OFFSET = 100;
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量子布局 里面源码有必要关注
measureChildren(widthMeasureSpec, heightMeasureSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
//最大宽度
width = Math.max(width, i * OFFSET + getChildAt(i).getMeasuredWidth());
//高度
height = height + getChildAt(i).getMeasuredHeight();
}
//设置当前viewgroup宽高
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int top = 0;
int left = 0;
int bottom = 0;
int right = 0;
for (int i = 0; i < childCount; i++) {
View childAt = getChildAt(i);
left = OFFSET * i;
right = left + childAt.getMeasuredWidth();
bottom = top + childAt.getMeasuredHeight();
//子view摆放布局
childAt.layout(left, top, right, bottom);
top = top + childAt.getMeasuredHeight();
}
}
}
Demo2 写法差不多,主要是需要考虑当前viewGroup本身的系统提供的宽高跟测量模式
package com.zero.myviewgroupdemo01;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class MyViewGroup extends ViewGroup {
private static final int OFFSET = 100;//表示缩进的尺寸
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//1. 测量自身
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 2. 为每个子View计算测量的限制信息 Mode / Size
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//3. 把上一步确定的限制信息,传递给每一个子View,然后子View开始measure
//自己的尺寸
int childCount = getChildCount();//子view的个数
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
ViewGroup.LayoutParams lp = child.getLayoutParams();
int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
child.measure(childWidthSpec, childHeightSpec);
}
int width = 0;
int height = 0;
//4. 获取子View测量完成后的尺寸
//5. ViewGroup根据自身的情况,计算自己的尺寸
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
int widthAddOffset = i * OFFSET + child.getMeasuredWidth();
width = Math.max(width, widthAddOffset);//取最大的宽度
}
break;
default:
break;
}
switch (heightMode){
case MeasureSpec.EXACTLY:
height =heightSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
height += child.getMeasuredHeight();
}
break;
default:
break;
}
//6. 保存自身的尺寸
setMeasuredDimension(width,height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 1. 遍历子View for
// 2. 确定自己的规则
// 3. 子View的测量尺寸
// 4. left,top,right,bottom
// 6. child.layout
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
int childCount = getChildCount();
for(int i = 0; i < childCount; i++){
View child = getChildAt(i);
left = i * OFFSET;
right = left + child.getMeasuredWidth();
bottom = top + child.getMeasuredHeight();
child.layout(left,top,right,bottom);
top += child.getMeasuredHeight();//不考虑padding margin gravity xxx
}
}
}