Android进阶技术分享之——自定义 View 系列实战篇-ViewGroup(内含自定义View 宝藏图)

Posted 学习Android的第1024天

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android进阶技术分享之——自定义 View 系列实战篇-ViewGroup(内含自定义View 宝藏图)相关的知识,希望对你有一定的参考价值。

自定义 ViewGroup 其实也不复杂,但是要对子 View 的 margin 属性支持,就还需要花点精力。

下面自己写了一个自定义的 FlowLayout,支持了本身的 padding 属性的同时,也支持子 View 的 margin 属性。基本注释都已尽可能详尽地写在代码中。

先上效果图

兄弟们,上代码

import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.core.view.children
import kotlin.math.max

/**
 *
 * @Author: QCoder
 * @CreateDate: 2021/12/6
 * @Description: 流式布局,当一行放不下后,换行放
 * 支持了本身的 Padding 属性,以及子 View 的 margin 属性
 * @Email: 526416248@qq.com
 */
class QFlowLayout(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs) 

    //通过矩阵记录每个子 View margin 后的具体位置
    private val childrenBounds = mutableListOf<Rect>()
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) 
        //获取 QFlowLayout 的宽高的期望值 (XSize) 和 测量模式(XMode),其中 X 代表宽或高,下面同义。
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        // QFlowLayout 的实际宽度
        var selfWidth = 0
        // QFlowLayout 的实际高度
        var selfHeight = 0
        //当行的宽度(当行已有宽度,由子 View 和 margin,padding 属性累加起来的)
        var currentLineWidth = 0
        //当行的高度
        var currentLineHeight = 0

        //遍历测量子 View
        for (child in children) 
            //判断,若 visibility == GONE ,即不可见又不占位置的时候,跳过测量
            if (child.visibility == GONE) continue
            //测量子 View。child 当前的子 View;XMeasureSpec 是QFlowLayout 对子 View 的期望
            measureChild(child, widthMeasureSpec, heightMeasureSpec)

            //获取到子 View 的 layout属性。
            val lp = child.layoutParams as MarginLayoutParams
            val childWidth = child.measuredWidth + lp.leftMargin + lp.rightMargin
            val childHeight = child.measuredHeight + lp.topMargin + lp.bottomMargin
            //判断是否需要换行,true 需要换行;false 不需要换行
            if (currentLineWidth + childWidth > widthSize - paddingLeft - paddingRight) 

                //将当前宽度和原先的宽度对比后,重置宽度为当前子 View 宽度
                selfWidth = max(selfWidth, currentLineWidth)
                currentLineWidth = childWidth


                selfHeight += currentLineHeight
                currentLineHeight = childHeight
                childrenBounds.add(
                    //因为需要换行,所以当前的子 View 是在新的一行,那么
                    // left 左边界 = 当前子View 的 leftMargin + QFlowLayout 的 paddingLeft
                    // top 上边界 = 累计的高度 selfHeight + 当前子View 的 topMargin + QFlowLayout 的 paddingTop
                    // right 右边界 = 当前子View 的宽度 + left
                    // bottom 下边界 = top + 当前子View 的高度
                    Rect(
                        lp.leftMargin + paddingLeft, //left
                        selfHeight + lp.topMargin + paddingTop, //top
                        child.measuredWidth + lp.leftMargin + paddingLeft, //right
                        selfHeight + lp.topMargin + paddingTop + child.measuredHeight //bottom
                    )
                )
             else 
                //因为不需要换行,所以当前的子 View 在当行的接轨上去,那么
                // left 左边界 = 当行宽度 currentLineWidth + 当前子View 的 leftMargin + QFlowLayout 的 paddingLeft
                // top 上边界 = 累计的高度 selfHeight + 当前子View 的 topMargin + QFlowLayout 的 paddingTop
                // right 右边界 = 当行宽度 currentLineWidth + left
                // bottom 下边界 = top + 当前子View 的高度
                childrenBounds.add(
                    Rect(
                        currentLineWidth + lp.leftMargin + paddingLeft,//left
                        selfHeight + lp.topMargin + paddingTop,//top
                        child.measuredWidth + currentLineWidth + lp.leftMargin + paddingLeft, //right
                        selfHeight + lp.topMargin + paddingTop + child.measuredHeight//bottom
                    )
                )

                //不需要换行,所以当前行的宽度 = 原来的宽度 + 当前子 View 的宽度
                currentLineWidth += childWidth
                //行的高度,我们只需要知道最高的那就行
                currentLineHeight = max(currentLineHeight, childHeight)
            

        
        selfWidth = max(selfWidth, currentLineWidth) + paddingRight + paddingLeft
        selfHeight += currentLineHeight + paddingTop + paddingBottom
        setMeasuredDimension(
            if (widthMode == MeasureSpec.EXACTLY) widthSize else selfWidth,
            if (heightMode == MeasureSpec.EXACTLY) heightSize else selfHeight
        )
    

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) 
        for ((index, child) in children.withIndex()) 
            val childBounds = childrenBounds[index]
            child.layout(
                childBounds.left,
                childBounds.top,
                childBounds.right,
                childBounds.bottom
            )
        
    

    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams 
        return MarginLayoutParams(context, attrs)
    

    override fun generateLayoutParams(p: LayoutParams): LayoutParams 
        return MarginLayoutParams(p)
    

    override fun generateDefaultLayoutParams(): LayoutParams 
        return MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
    

 

希望大家觉得有用,可以点个赞。你们的肯定,是我写博客的不竭动力源泉~

最后,附上一个最近我整理的有关自定义 View 的知识网络结构图

iub-1649470539071)]


希望大家觉得有用,可以点个赞。你们的肯定,是我写博客的不竭动力源泉~

最后,附上一个最近我整理的有关自定义 View 的知识网络结构图

最后

有小伙伴私信问Compose的问题,好不好用啊,现在要不要学啊?

其实答案很简单,自从谷歌2019年公布了声明式UI框架Jetpack Compose后,两年多的时间,各种大力宣传,和大量资源的倾斜,API功能都趋于稳定了。

至于好不好用,各种用过的同行都是持肯定态度的。优势大概就是这四点:

强大的工具和直观的Kotlin API
简化并加速了Android上的UI开发
可以帮助开发者用更少更直观的代码创建View
有更强大的功能,以及还能提高开发速度

这么大的优势,毋庸置疑,肯定是要学的嘛,而且越快掌握越好。别等刀架到脖子上了,才去练金钟罩。

至于怎么快速上手,可以给大家免费分享一份**《Jetpack Compose 完全开发手册》**,手把手教大家从入门到精通。

第一章 初识 Jetpack Compose

  • 为什么我们需要一个新的UI 工具?

  • Jetpack Compose的着重点

    加速开发
    强大的UI工具
    直观的Kotlin API

  • API 设计

  • Compose API 的原则
    一切都是函数
    顶层函数(Top-level function)
    组合优于继承
    信任单一来源

  • 深入了解Compose
    Core
    Foundation
    Material

  • 插槽API

第二章 Jetpack Compose构建Android UI

  • Android Jetpack Compose 最全上手指南
    Jetpack Compose 环境准备和Hello World
    布局
    使用Material design 设计
    Compose 布局实时预览
    ……

  • 深入详解 Jetpack Compose | 优化 UI 构建
    Compose 所解决的问题
    Composable 函数剖析
    声明式 UI
    组合 vs 继承
    封装
    重组
    ……

  • 深入详解 Jetpack Compose | 实现原理
    @Composable 注解意味着什么?
    执行模式
    Positional Memoization (位置记忆化)
    存储参数
    重组
    ……

第三章 Jetpack Compose 项目实战演练(附Demo)

  • Jetpack Compose应用1
    开始前的准备
    创建DEMO
    遇到的问题

  • Jetpack Compose应用2
  • Jetpack Compose应用做一个倒计时器
    数据结构
    倒计时功能
    状态模式
    Compose 布局
    绘制时钟

  • 用Jetpack Compose写一个玩安卓App
    准备工作
    引入依赖
    新建 Activity
    创建 Compose
    PlayTheme
    画页面
    底部导航栏
    管理状态
    添加页面

  • 用Compose Android 写一个天气应用
    开篇
    画页面
    画背景
    画内容
    ……

  • 用Compose快速打造一个“电影App”
    成品
    实现方案
    实战
    不足
    ……

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

有需要的话可以点下面二维码免费领取↓↓↓

以上是关于Android进阶技术分享之——自定义 View 系列实战篇-ViewGroup(内含自定义View 宝藏图)的主要内容,如果未能解决你的问题,请参考以下文章

Android进阶之绘制-自定义View完全掌握

Android进阶之自定义View实战九宫格手势解锁实现

Android进阶之绘制-自定义View完全掌握

Android进阶之绘制-自定义View完全掌握

Android进阶之绘制-自定义View完全掌握

Android高手进阶教程之----Android 中自定义View的应用.