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

简介: Android 中自定义ViewGroup实现流式布局的效果

前言:

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

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

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

下面来实现流式布局,定义一个类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>

运行后,效果如下:


目录
相关文章
|
5月前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
470 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
27天前
|
存储 消息中间件 人工智能
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
76 11
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
|
27天前
|
XML 存储 Java
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
61 3
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
|
5月前
|
Android开发
Android自定义view之利用PathEffect实现动态效果
本文介绍如何在Android自定义View中利用`PathEffect`实现动态效果。通过改变偏移量,结合`PathEffect`的子类(如`CornerPathEffect`、`DashPathEffect`、`PathDashPathEffect`等)实现路径绘制的动态变化。文章详细解析了各子类的功能与参数,并通过案例代码展示了如何使用`ComposePathEffect`组合效果,以及通过修改偏移量实现动画。最终效果为一个菱形图案沿路径运动,源码附于文末供参考。
|
5月前
|
Android开发 开发者
Android自定义view之利用drawArc方法实现动态效果
本文介绍了如何通过Android自定义View实现动态效果,重点使用`drawArc`方法完成圆弧动画。首先通过`onSizeChanged`进行测量,初始化画笔属性,设置圆弧相关参数。核心思路是不断改变圆弧扫过角度`sweepAngle`,并调用`invalidate()`刷新View以实现动态旋转效果。最后附上完整代码与效果图,帮助开发者快速理解并实践这一动画实现方式。
143 0
|
5月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
|
5月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
234 65
Android自定义view之网易云推荐歌单界面
|
5月前
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
573 84
|
5月前
|
Android开发 开发者
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
本文详细介绍了如何通过自定义 `attrs.xml` 文件实现 Android 自定义 View 的属性配置。以一个包含 TextView 和 ImageView 的 DemoView 为例,讲解了如何使用自定义属性动态改变文字内容和控制图片显示隐藏。同时,通过设置布尔值和点击事件,实现了图片状态的切换功能。代码中展示了如何在构造函数中解析自定义属性,并通过方法 `setSetting0n` 和 `setbackeguang` 实现功能逻辑的优化与封装。此示例帮助开发者更好地理解自定义 View 的开发流程与 attrs.xml 的实际应用。
105 2
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
|
5月前
|
前端开发 Android开发 UED
讲讲Android为自定义view提供的SurfaceView
本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。
118 3

热门文章

最新文章