Android自定义View之实现流式布局
- 流式布局
把子控件从左到右摆放,如果一行放不下,自动放到下一行 - 自定义布局流程
1. 自定义属性:声明,设置,解析获取自定义值 在attr.xml中声明
2. 测量:在onMeasure 方法测量自身的宽高和child的宽高
3. 布局:在onLayout方法里面根据自己的规则来确定children的位置
4. 绘制:onDraw
5. 处理layoutParams
6. 触摸反馈:滑动事件 - 代码实现
属性定义
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FlowLayout">
<attr name="android:gravity"/>
<attr name="android:horizontalSpacing" format="dimension |reference"/>
</declare-styleable>
<declare-styleable name="FlowLayout_Layout">
<attr name="android:layout_gravity"/>
</declare-styleable>
</resources>
- 布局文件
<?xml version="1.0" encoding="utf-8"?>
<!--<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"-->
<!--xmlns:app="http://schemas.android.com/apk/res-auto"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="wrap_content"-->
<!--xmlns:tools="http://schemas.android.com/tools">-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<com.example.as.proj.myviewgroupdemo2.FlowLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="55dp"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="90dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="45dp"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="75dp"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="60dp"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="100dp"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="65dp"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="100dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="75dp"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="80dp"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="300dp"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="65dp"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方1" />
<Button
android:layout_width="wrap_content"
android:layout_height="250dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是结束" />
</com.example.as.proj.myviewgroupdemo2.FlowLayout>
<!--</ScrollView>-->
</LinearLayout>
自定义View
package com.example.as.proj.myviewgroupdemo2;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
public class FlowLayout extends ViewGroup {
public static final String TAG = "Zero";
private List<View> lineViews; //每一行的子View
private List<List<View>> views; //所有的行,一行一行的存储
private List<Integer> heights; //每一行的高度
public FlowLayout(Context context) {
this(context, null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private void init(){
views = new ArrayList<>();
lineViews = new ArrayList<>();
heights = new ArrayList<>();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//记录当前行的宽和高
int lineWidth = 0; //宽度是当前行子View的宽度之和
int lineHeight = 0; //高度是当前行所有子View中高度的最大值
//整个流式布局的宽度和高度
int flowlayoutWidth = 0; //所有行中宽度的最大值
int flowlayoutHeight = 0; //所有行高度的累加
//初始化参数列表
init();
//遍历所有子View,对子View进行测量,分配到具体的行
int childCount = this.getChildCount();
for(int i = 0; i < childCount; i++){
View child = this.getChildAt(i);
//测量子View 获取当前子View的测量宽高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
//获取到当前子View的测量的宽高
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
LayoutParams lp = (LayoutParams) child.getLayoutParams();
//看下当前的行的剩余宽度是否可以容纳下一个子View
//如果放不下,换行 保存当前行所有的子View,累加行高,当前的宽度 高度 置零
if(lineWidth + childWidth > widthSize){
//换行
views.add(lineViews);
lineViews = new ArrayList<>();// 创建新的一行
flowlayoutWidth = Math.max(flowlayoutWidth, lineWidth);
flowlayoutHeight += lineHeight;
heights.add(lineHeight);
lineWidth = 0;
lineHeight = 0;
}
lineViews.add(child);
lineWidth += childWidth;
if(lp.height != LayoutParams.MATCH_PARENT){
//暂时先不要处理layout_height=match_parent
lineHeight = Math.max(lineHeight, childHeight);
}
Log.i(TAG, "onMeasure: " + lineHeight);
if(i == childCount - 1){
//最后一行
flowlayoutHeight += lineHeight;
flowlayoutWidth = Math.max(flowlayoutWidth, lineWidth);
heights.add(lineHeight);
views.add(lineViews);
}
}
//重新测量一次layout_height = match_parent
remeasureChild(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : flowlayoutWidth,
heightMode == MeasureSpec.EXACTLY ? heightSize : flowlayoutHeight);
}
private void remeasureChild(int widthMeasureSpec, int heightMeasureSpec){
int lineSize = views.size();
for(int i = 0; i < lineSize; i++){
int lineHeight = heights.get(i); //每一行行高
Log.i(TAG, "remeasureChile: " + lineHeight);
List<View> lineViews = views.get(i); //每一行的子View
int size = lineViews.size();
for(int j = 0; j < size; j++){
View child = lineViews.get(j);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if(lp.height == LayoutParams.MATCH_PARENT){
int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lineHeight);
child.measure(childWidthSpec, childHeightSpec);
}
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int lineCount = views.size();
int currX = 0;
int currY = 0;
for (int i = 0; i < lineCount; i++){
//大循环,所有子View一行一行的布局
List<View> lineViews = views.get(i); //取出一行、
int lineHeight = heights.get(i); //取出这一行的高度
//遍历当前行的子View
int size = lineViews.size();
for(int j = 0; j < size; j++){
//布局当前行的每一个View
View child = lineViews.get(j);
int left = currX;
int top = currY;
int right = left + child.getMeasuredWidth();
int bottom = top + child.getMeasuredHeight();
child.layout(left, top, right, bottom);
currX += child.getMeasuredWidth();
}
currY += lineHeight;
currX = 0;
}
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return super.checkLayoutParams(p) && p instanceof LayoutParams;
}
public static class LayoutParams extends MarginLayoutParams{
public int gravity = -1;
public LayoutParams(Context c, AttributeSet attrs){
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout);
try{
gravity = a.getInt(R.styleable.FlowLayout_Layout_android_layout_gravity, -1);
}finally {
a.recycle();
}
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
@Override
public String toString() {
return "LayoutParams{" +
"gravity=" + gravity +
", bottomMargin=" + bottomMargin +
", leftMargin=" + leftMargin +
", rightMargin=" + rightMargin +
", topMargin=" + topMargin +
", height=" + height +
", width=" + width +
"} ";
}
}
}