自定义流式布局具体流程:

  1. 自定义属性:声明、设置、解析获取自定义值

    1. attr.xml中声明
<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:gravity"/></declare-styleable>
</resources>

​ 2. 自定义属性:引入命名空间

xmlns:app="http://schemas.android.com/apk/res-auto"

​ 3. 解析:构造方法中

TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout);
try {gravity = a.getInt(R.styleable.FlowLayout_Layout_android_layout_gravity, -1);
}finally {//释放a.recycle();
}

重写generateLayoutParams、generateDefaultLayoutParams、checkLayoutParams

  1. 测量:在onMeasure MeasureSpce.AT_MOST/EXACTLY

    1. 自身的宽高/child的宽高
    2. FlowLayout本身宽高AT_MOST
    3. layout_width:wrap_content:给默认值,宽度最大
    4. AT_MOST: 所有的行数的累加的高度
    5. 子view:layout_height wrap_content child layout_height match_parent

​ 这一行里面高度最大的

  1. 布局:在onLayout方法里根据自己的规则确定child的位置

  2. 绘制:onDraw

  3. 处理LayoutParams

  4. 触摸反馈:滑动事件

    1. 通过view的ScrollBy和ScrollTo方法滑动
    2. 通过动画给view添加位移效果实现滑动
    3. 通过改变view的layoutParams 让view重新布局

具体代码:

package com.edu.cdut.rxjavatest;import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.OverScroller;
import android.widget.Scroller;import androidx.core.view.ViewConfigurationCompat;import java.util.ArrayList;
import java.util.List;public class FlowLayout extends ViewGroup {private static final String TAG = FlowLayout.class.getSimpleName();private List<View> lineViews;//每一行的子viewprivate List<List<View>> views;//所有的行, 一行一行的存private List<Integer> heights;//每一行的高度private boolean scrollable = false;private int measureHeight;//本身的测量高度private int realHeight;//内容的高度private int mTouchSlop;//判断是不是一次滑动private float mLastInterceptX = 0;private float mLastInterceptY = 0;private float mLastY = 0;private OverScroller mScroller;private VelocityTracker mVelocityTracker;private int mMaxVelocity;private int mMinVelocity;public FlowLayout(Context context) {super(context);}public FlowLayout(Context context, AttributeSet attrs) {super(context, attrs);}public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);ViewConfiguration configuration = ViewConfiguration.get(context);mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);mScroller = new OverScroller(context);}public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}private void initVelocityTrackerIfNotExists() {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}}private void recycleVelocityTrackerIfNotExists() {if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}}private void init(){views = new ArrayList<>();lineViews = new ArrayList<>();heights = new ArrayList<>();}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {boolean intercepted = false;float xIntercept = ev.getX();float yIntercept = ev.getY();switch (ev.getAction()){case MotionEvent.ACTION_DOWN:intercepted = false;mLastInterceptY = yIntercept;mLastInterceptX = xIntercept;break;case MotionEvent.ACTION_MOVE:float dx = xIntercept - mLastInterceptX;float dy = yIntercept - mLastInterceptY;if (Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > mTouchSlop){intercepted = true;}else {intercepted = false;}break;case MotionEvent.ACTION_UP:intercepted = false;break;}mLastInterceptY = yIntercept;mLastInterceptX = xIntercept;return intercepted;}@Overridepublic boolean onTouchEvent(MotionEvent event) {if (!scrollable) {return super.onTouchEvent(event);}initVelocityTrackerIfNotExists();mVelocityTracker.addMovement(event);float curY = event.getY();switch (event.getAction()){case MotionEvent.ACTION_DOWN:if (mScroller.isFinished()) {mScroller.abortAnimation();}mLastY = curY;break;case MotionEvent.ACTION_MOVE:float dy = mLastY - curY;//本次滑动距离int oldScrollY = getScrollY();//已经偏移的距离
//                int scrollY = oldScrollY + (int) dy;//本次需要偏移的距离 = 之前已经偏移的距离 + 本次手势滑动的距离
//                if (scrollY < 0) {//                    scrollY = 0;
//                }
//                if (scrollY > realHeight - measureHeight) {//                    scrollY = realHeight - measureHeight;
//                }
//                scrollTo(0, scrollY);mScroller.startScroll(0, mScroller.getFinalY(), 0, (int) dy);//mCurrY = oldScrollY = dy * scaleinvalidate();//触发computeScrollmLastY = curY;break;case MotionEvent.ACTION_UP:final VelocityTracker velocityTracker = mVelocityTracker;velocityTracker.computeCurrentVelocity(1000, mMaxVelocity);int initialVelocity = (int) velocityTracker.getYVelocity();if (Math.abs(initialVelocity) > mMinVelocity) {fling(-initialVelocity);}else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,(realHeight - measureHeight))) {postInvalidateOnAnimation();}break;}return super.onTouchEvent(event);}private void fling(int velocityY) {if (getChildCount() > 0) {int height = measureHeight;int bottom = realHeight;mScroller.fling(getScrollX(), getScrollY(),0, velocityY, 0, 0, 0,Math.max(0, bottom - height), 0, height / 2);postInvalidateOnAnimation();}}@Overridepublic void computeScroll() {super.computeScroll();if (mScroller.computeScrollOffset()) {//mCurrY = oldScrollY = dy * scale
//            int scrollY = oldScrollY + (int) dy;//本次需要偏移的距离 = 之前已经偏移的距离 + 本次手势滑动的距离
//            if (scrollY < 0) {//                scrollY = 0;
//            }
//            if (scrollY > realHeight - measureHeight) {//                scrollY = realHeight - measureHeight;
//            }
//            scrollTo(0, scrollY);scrollTo(0, mScroller.getCurrY());postInvalidate();}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//1. 测量自身super.onMeasure(widthMeasureSpec, heightMeasureSpec);//2. 为每一个子view计算测量的限制信息 Mode / Sizeint widthMode = MeasureSpec.getMode(widthMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);measureHeight = heightSize;//当前行的宽高int lineWidth = 0;//子view的宽度之和int lineHeight = 0;//子view的高度中的最大值//整个流式布局的int flowLayoutWidth = 0;//所有行宽度中的最大值int flowLayoutHeight = 0;//所有行的高度的累加//初始化参数列表init();//遍历所有的子view,对子view测量,分配到具体的行int childCount = getChildCount();for (int i = 0; i < childCount; i++) {View child = 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) {if (lineViews.size() == 1 && lineViews.get(0).getLayoutParams().height == LayoutParams.MATCH_PARENT) {lineHeight = Utils.dp2px(150);}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){//height == match_parent不处理lineHeight = Math.max(childHeight, lineHeight);}if (i == childCount - 1) {flowLayoutHeight += lineHeight;flowLayoutWidth = Math.max(flowLayoutWidth, lineWidth);heights.add(lineHeight);views.add(lineViews);}}remeasureChild(widthMeasureSpec, heightMeasureSpec);if (heightMode == MeasureSpec.EXACTLY) {flowLayoutHeight = Math.max(heightSize, flowLayoutHeight);}realHeight = flowLayoutHeight;scrollable = realHeight > measureHeight;//FlowLayout最终宽高setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : flowLayoutWidth,(heightMode == MeasureSpec.EXACTLY) ? heightSize : flowLayoutHeight);}//重新测量一次layout 当height == match_parentprivate void remeasureChild(int widthMeasureSpec, int heightMeasureSpec){int lineSize = views.size();for (int i = 0; i < lineSize; i++) {int lineHeight = heights.get(i);//每一行的高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, lp.height);child.measure(childWidthSpec, childHeightSpec);}}}}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int childCount = views.size();int curX = 0;int curY = 0;for (int i = 0; i < childCount; i++) {List<View> lineView  = views.get(i);int lineHeight = heights.get(i);//遍历当前行的子viewint size = lineView.size();for (int j = 0; j < size; j++) {//布局当前行的每一个viewView child = lineView.get(j);int left = curX;int top = curY;int right = left + child.getMeasuredWidth();int bottom = top + child.getMeasuredHeight();child.layout(left, top, right, bottom);//确定下一个view的left值curX += child.getMeasuredWidth();}curY += lineHeight;curX = 0;}}//对传入的lp进行转化--xml@Overridepublic ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {return super.generateLayoutParams(attrs);}//对传入的lp进行转化--自定义@Overrideprotected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {return super.generateLayoutParams(p);}//生成默认lp@Overrideprotected ViewGroup.LayoutParams generateDefaultLayoutParams() {return super.generateDefaultLayoutParams();}//检查lp是否合法@Overrideprotected boolean checkLayoutParams(ViewGroup.LayoutParams p) {return super.checkLayoutParams(p);}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(MarginLayoutParams source) {super(source);}public LayoutParams(ViewGroup.LayoutParams source) {super(source);}}
}

自定义流式布局的代码实现相关推荐

  1. Adnroid 自定义流式布局

    一般常见的流式布局由两种,一种是横向的个数固定,列表按照竖向进行排列.另一种是横向先排,横向排满之后再竖向排列.而本框架实现是以第二种方式进行处理. 那么这个框架到底该如何使用呢? 首先引入资源 这里 ...

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

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

  3. Android自定义流式布局-FlowLayout

    很多App在搜索页时都有对热门标签的一个排列,而Android官方没有对应的布局控件,所以自己写了一个流式布局(也称标签布局)--FlowLayout. 为了大家使用的方便,没有添加自定义属性,所以需 ...

  4. android自定义流式布局思路,Android 自定义控件基础-流式布局

    什么是流式布局?其实我们在平时遇到过,只是有可能叫不出它的名字. 如图: 如上图,就是一个流式布局的样式. &esmp;这里,将记录一下怎么实现这个功能.其实实现这个功能的方法,就是自定义Vi ...

  5. Jetpack Compose 自定义流式布局

    效果: @Composable fun StaggeredGrid(modifier: Modifier = Modifier,content: @Composable () -> Unit ) ...

  6. 自定义流式布局View

    //或者说直接for for (int i = 0; i < 20; i++) {             TextView txt = new TextView(this);          ...

  7. 自定义View----滑动刻度尺与流式布局 实例(四)

    2019独角兽企业重金招聘Python工程师标准>>> 近在系统学习自定义View这一块的知识,前面几篇基本都是理论知识,这篇博客着重从实战来加强对自定义View的理解与运用.实现的 ...

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

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

  9. Android自定义控件之流式布局

    效果图: 一.首先创建我 们的自定义流式布局 public class FlowLayoutView extends ViewGroup {public FlowLayoutView(Context ...

  10. 流式布局案例——京东网页

    文章目录 前言 一.京东移动端页面 二.制作步骤 1.页面整体框架 2.注意要点 总结 前言 在之前学了关于移动端的基础知识,接下来我们就需要运用这些知识来进行实际应用. 上才艺!看案例! 一.京东移 ...

最新文章

  1. maven package和install
  2. linux七大功能,值得Linux向其他系统借鉴的七大功能特性
  3. colab如何通过<>来直接加入相对应的代码段呢?模块化代码操作,真好
  4. 2.4-yum工具详解
  5. c++ string详解 assign
  6. 【BIEE】12_查看BIEE的物理SQL
  7. 3.企业安全建设指南(金融行业安全架构与技术实践) --- 安全规划
  8. GreenPlum数据库调研及架构介绍
  9. 《数据库系统基础教程》读书笔记——第一章 数据库系统世界
  10. matlab配置VLFeat
  11. NX/UG二次开发简单干涉
  12. FDTD超表面仿真详细教程,几何相位,共振相位,传播相位
  13. 修改计算机桌面壁纸影响计算机考试吗,计算机考试
  14. 转发: Feign报错feign.RetryableException: too many bytes written executing
  15. spring4 系列一 概览
  16. 蜡烛图plotly_可视化神器Plotly(5)---参数详解
  17. pinia 配置教程
  18. 福布斯:为什么区块链有利于竞争
  19. NFT 改变社会的 5 种方式
  20. 云计算应用(二):云计算“虚拟化“概述

热门文章

  1. Coroutine协成
  2. matlab中nc值是什么意思,科学网-.nc数据读取详细资料matlab2010a及后面的版本-张凌的博文...
  3. 读书笔记_稻盛和夫《心》
  4. openpyxl 打开大文件很慢_解决python执行较大excel文件openpyxl慢问题
  5. 卡内基梅隆计算机硕士录取案例,学子喜获计算机牛校卡内基梅隆录取
  6. 单元格内容分列多行_excel拆分单元格内容 excel单元格拆分多行
  7. oracle IMO史上最详细使用文档
  8. CSS 实现水晶按钮特效 - 来自 www.codesc.net
  9. mysql基础学习--day7
  10. 关于Handling Unit SAP包装