博主前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住也分享一下给大家,
👉​​​点击跳转到网站​

前言:

自定义View与自定义ViewGroup的区别:

  1. 自定义View:在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View。这个是控件。
  2. 自定义ViewGroup:一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout。这个是组件。

自定义View的绘制流程图如下:

Android 中自定义ViewGroup实现流式布局的效果_ViewGroup

下面来实现流式布局,定义一个类FlowLayout继承自ViewGroup,重写里面的相关方法,实现流式布局的效果。

public class FlowLayout extends ViewGroup {
//每个item横向间距
private int mHorizontalSpacing = dp2px(16);
//每个item竖向间距
private int mVerticalSpacing = dp2px(8);

//记录所有的行,一行一行的存储,用于Layout
private List<List<View>> allLines;
//记录每一行的行高,用于Layout
List<Integer> lineHeights = new ArrayList<>();

//new FlowLayout(传入上下文)
//在代码中创建组件时会调用该构造方法
//比如创建一个按钮:Button btn=new Button(this),
// this 是指当前的 Activity,Activity 是 Context 的子类
public FlowLayout(Context context) {
super(context);
}

//在xml中使用 在xml转化为java代码的时候,通过反射调用有两个参数的构造函数
//在 layout 布局文件中使用时调
//用,参数 attrs 表示当前配置中的属性集合,例如在要 layout.xml 中定义一个按钮:
//<Button
// android:layout_width = "match_parent"
// android:layout_height = "wrap_content"
//android:text ="OK"/>
// Android 会调用第二个构造方法 Inflate 出 Button 对象
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

//代码中有不同的主题style,调用有三个参数的构造函数
//该方法不会自动调用的,当我们在 Theme 中定义了 Style 属性时通常在第二个
//构造方法中手动调用。
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

//进行集合的初始化
private void initMeasureParams() {
if (allLines != null) {
allLines.clear();
} else {
allLines = new ArrayList<>();
}
if (lineHeights != null) {
lineHeights.clear();
} else {
lineHeights = new ArrayList<>();
}
}

//度量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//每次进入onMeasure()方法中 都需要将全局变量重新初始化
initMeasureParams();
//度量子View的宽度和高度
int childCount = getChildCount();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();

//ViewGroup解析的宽度
int selfWidth = MeasureSpec.getSize(widthMeasureSpec);
//ViewGroup解析的高度
int selfHeight = MeasureSpec.getSize(heightMeasureSpec);

//保存一行中的所有View
List<View> lineViews = new ArrayList<>();

//记录这一行已经使用了多宽的size
int lineWidthUsed = 0;
//一行的行高
int lineHeight = 0;
//measure过程中,子View要求的父ViewGroup的宽
int parentNeededWidth = 0;
//measure过程中,子View要求的父ViewGroup的高
int parentNeededHeight = 0;

for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
LayoutParams childLP = childView.getLayoutParams();
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingBottom + paddingTop, childLP.height);
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

//获取子View的宽高
int childMeasuredWidth = childView.getMeasuredWidth();
int childMeasuredHeight = childView.getMeasuredHeight();

//通过宽度来判断是否需要换行,通过换行后的每行的行高来获取
//整个ViewGroup的行高
if (childMeasuredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
allLines.add(lineViews);
lineHeights.add(lineHeight);

//一旦换行,我们就可以判断当前列需要的宽和高了,所以此时需要记录下来
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
lineViews = new ArrayList<>();
lineWidthUsed = 0;
lineHeight = 0;
}

//view是分行layout的,所以要记录每一行有哪些View,这样可以方便布局
lineViews.add(childView);
//每行都会有自己的宽和高
lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;
lineHeight = Math.max(lineHeight, childMeasuredHeight);
}

//根据子View的度量结果,来重新度量自己的ViewGroup
//作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来度量
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;
int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;

//度量自己的宽度和高度
setMeasuredDimension(realWidth, realHeight);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//获取Layout中的行数
int lineCount = allLines.size();
int curL = getPaddingLeft();
int curT = getPaddingTop();
for (int i = 0; i < lineCount; i++) {
List<View> lineViews = allLines.get(i);
int lineHeight = lineHeights.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View view = lineViews.get(j);
int left = curL;
int top = curT;
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
view.layout(left, top, right, bottom);
curL = right + mHorizontalSpacing;
}
curL = getPaddingLeft();
curT = lineHeight + curT + mVerticalSpacing;
}
}

//dp转为px
public static int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
}
}

之后我们在xml中,进行引用,就可以了。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ViewGroupActivity">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="搜索历史"
android:textColor="@color/black"
android:textSize="17sp" />

<com.example.animationtest.view.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="水果味孕妇奶粉"
android:textColor="@color/black" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="儿童洗衣机"
android:textColor="@color/black" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="洗衣机全自动"
android:textColor="@color/black" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="手机"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="儿童汽车可坐人111111111111111111"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="抽真空收纳袋"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="儿童滑板车"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="稳定器 电容"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="儿童洗衣机"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="衣服"
android:textColor="@color/black"
android:textSize="16sp" />


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="运动鞋"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="手表"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="水果味孕妇奶粉"
android:textColor="@color/black" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="儿童洗衣机"
android:textColor="@color/black" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="洗衣机全自动"
android:textColor="@color/black" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="手机"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="抽真空收纳袋"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="儿童滑板车"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="稳定器 电容"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="儿童洗衣机"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="衣服"
android:textColor="@color/black"
android:textSize="16sp" />


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="运动鞋"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="手表"
android:textColor="@color/black"
android:textSize="16sp" />
</com.example.animationtest.view.FlowLayout>

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="搜索发现"
android:textColor="@color/black"
android:textSize="17sp" />

<com.example.animationtest.view.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="水果味孕妇奶粉"
android:textColor="@color/black" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="儿童洗衣机"
android:textColor="@color/black" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="洗衣机全自动"
android:textColor="@color/black" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="手机"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="儿童汽车可坐人111111111111111111"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="抽真空收纳袋"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="儿童滑板车"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="稳定器 电容"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="儿童洗衣机"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="衣服"
android:textColor="@color/black"
android:textSize="16sp" />


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="运动鞋"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tv_back_color"
android:text="手表"
android:textColor="@color/black"
android:textSize="16sp" />

</com.example.animationtest.view.FlowLayout>
</LinearLayout>

运行后,效果如下:

Android 中自定义ViewGroup实现流式布局的效果_ViewGroup_02