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"><Buttonandroid:layout_width="wrap_content"android:layout_height="55dp"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="match_parent"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="人在他在,塔亡人亡"android:layout_gravity="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="match_parent"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="90dp"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="85dp"android:text="人在他在,塔亡人亡"android:layout_gravity="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="45dp"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="人在他在,塔亡人亡"android:layout_gravity="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="75dp"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="60dp"android:text="人在他在,塔亡人亡"android:layout_gravity="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="85dp"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="100dp"android:text="人在他在,塔亡人亡"android:layout_gravity="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="85dp"android:text="人在他在,塔亡人亡"android:layout_gravity="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="人在他在,塔亡人亡"android:layout_gravity="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="85dp"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="人在他在,塔亡人亡"android:layout_gravity="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="65dp"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="100dp"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="75dp"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="人在他在,塔亡人亡"android:layout_gravity="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="80dp"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="人在他在,塔亡人亡"android:layout_gravity="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="300dp"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="65dp"android:text="人在他在,塔亡人亡"android:layout_gravity="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方1" /><Buttonandroid:layout_width="wrap_content"android:layout_height="250dp"android:text="发电房" /><Buttonandroid: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; //每一行的子Viewprivate 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<>();}@Overrideprotected 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_parentlineHeight = 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_parentremeasureChild(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); //每一行的子Viewint 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);}}}}@Overrideprotected 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); //取出这一行的高度//遍历当前行的子Viewint size = lineViews.size();for(int j = 0; j < size; j++){//布局当前行的每一个ViewView 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;}}@Overrideprotected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {return new LayoutParams(p);}@Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {return new LayoutParams(getContext(), attrs);}@Overrideprotected LayoutParams generateDefaultLayoutParams() {return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);}@Overrideprotected 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);}@Overridepublic String toString() {return "LayoutParams{" +"gravity=" + gravity +", bottomMargin=" + bottomMargin +", leftMargin=" + leftMargin +", rightMargin=" + rightMargin +", topMargin=" + topMargin +", height=" + height +", width=" + width +"} ";}}
}

运行效果

Android自定义View之实现流式布局相关推荐

  1. Android 自定义UI 实战 02 流式布局

    Android 自定义UI 实战 02 流式布局-- 自定义ViewGroup 第二章 自定义ViewGroup 流式布局 文章目录 Android 自定义UI 实战 02 流式布局-- 自定义Vie ...

  2. android实现标签功能,Android实现热门标签的流式布局

    一.概述: 在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧(源码下载在下面最后给出) 类似的 ...

  3. android 搜索历史流布局,FlowLayout流式布局实现搜索清空历史记录

    本文实例为大家分享了FlowLayout实现搜索清空历史记录的具体代码,供大家参考,具体内容如下 效果图:点击搜索框将搜索的历史在流式布局中展示出来,清空历史记录就会将历史清空,每次搜索后都存入sp中 ...

  4. 自定义LinearLayout并搭配流式布局,实现商城app商品规格选择View

    一:效果图镇楼 二:整个View的制作过程 继承自LinearLayout 设置为垂直走向 动态摆放每一个属性的title 绘制每一个规格属性,这里使用的是Flowlayout里面添加RadioBut ...

  5. 自定义 FlowLayout流式布局搜索框 加 GreenDao存取搜索记录,使用RecyclerView展示

    输入框布局的shape <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android ...

  6. Android 自定义View合集

    http://blog.csdn.net/u011507982/article/details/51199644 自定义控件学习  https://github.com/GcsSloop/Androi ...

  7. android自定义view流布局,Android控件进阶-自定义流式布局和热门标签控件

    一.概述: 在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧 类似的自定义换行流式布局控件.下 ...

  8. Android自定义组合布局,Android 流式布局 + 自定义组合控件

    自定义组合控件 package yanjupeng.bawei.com.day09.two; import android.content.Context; import android.util.A ...

  9. Android自定义ViewGroup的布局,往往都是从流式布局开始

    前言 前面几篇我们简单的复习了一下自定义 View 的测量与绘制,并且回顾了常见的一些事件的处理方式. 那么如果我们想自定义 ViewGroup 的话,它和自定义View又有什么区别呢?其实我们把 V ...

最新文章

  1. Tensorflow函数——tf.variable_scope()
  2. cdoj 题目简单分类
  3. Spartan-6的I/O时钟缓冲器
  4. 6、函数返回值、this、递归及回调函数
  5. 百度地图标点点击变色_《和平精英》版本爆料第三弹:雪地洞穴开启!组队标点功能升级~...
  6. eclipse xml文件中按没有提示
  7. 亚马逊独霸美国安云计算未来十年订单;英伟达推出首个元宇宙平台;华为云、天翼云会合并吗?...
  8. Cloud一分钟 | 云厂商Zoho域名被禁两小时;京东云公布医疗健康战略
  9. 如何获取filecoin_【IPFSFilecoin】Filecoin矿机厂商排名?如何选择?哪家公司靠谱?...
  10. 阿里最新分享Redis全套学习笔记PDF版,图文并茂,太详细了
  11. 计算机网页制作保存,不使用任何工具轻松保存网页资源的法子
  12. 使用C#或C++语言实现微信自动加好友的功能(附接口SDK及源码)
  13. docker及k8s容器面试精华汇总(一),祝大家顺利通过企业面试!
  14. 盘古石杯电子取证比赛WP
  15. 继勒索病毒“永恒之蓝”445端口被封之后,在公网实现smb文件共享
  16. python基础day-15:time、hash、json
  17. 步进电机stepmotor
  18. 收货地址 (默认收货地址)
  19. Python与MySQL数据库的交互
  20. jsp用Echarts做扇形图

热门文章

  1. 矩阵旋转(顺时针、逆时针、取转置、倒转)
  2. 未找到框架“.NETFramework,Version=v4.6.1”的引用程序集 的解决办法
  3. linux 中 ACPI 电源管理 G 状态、S 状态、D 状态、C 状态、P 状态
  4. 为什么中年男人爱出轨?
  5. Redis(4)Redis事务
  6. matlab 如何查内置函数,关于Matlab的内置函数 ordqz的用法-经管之家官网!
  7. c语言触发器指令,西门子plc置位-复位触发器指令用法举例
  8. GitKraken 7.5.1 无法连接GitHub和GitLab
  9. Chocolatey安装过程中失败,再安装报错的解决办法
  10. 线性调频Z变换(CZT)