前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android 自定义View之展开收起的Layout

Android 自定义View之展开收起的Layout

作者头像
yechaoa
发布于 2022-06-10 06:51:48
发布于 2022-06-10 06:51:48
1.3K01
代码可运行
举报
文章被收录于专栏:移动开发专栏移动开发专栏
运行总次数:1
代码可运行

效果

分析

效果图来看,点击事件触发view的展开收起,并在收起状态下保留了第一个子view显示,这个展开收起其实就是view的高度变化,所以只要控制好高度,就能很简单的实现这个效果。

步骤

  • 1.初始化参数 设置方向等
  • 2.根据动画执行进度计算高度

初始化

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class ExpandLinearLayout : LinearLayout {

    //是否展开,默认展开
    private var isOpen = true

    //第一个子view的高度,即收起保留高度
    private var firstChildHeight = 0

    //所有子view高度,即总高度
    private var allChildHeight = 0

    /**
     * 动画值改变的时候 请求重新布局
     */
    private var animPercent: Float = 0f
    
    constructor(context: Context) : super(context) {
        initView()
    }

    constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
        initView()
    }

    constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(
        context,
        attributeSet,
        defStyleAttr
    ) {
        initView()
    }

    private fun initView() {
        //横向的话 稍加修改计算宽度即可
        orientation = VERTICAL

        animPercent = 1f
        isOpen = true
    }

}

定义一个类ExpandLinearLayout ,继承自LinearLayout,当然也可以是其他的view。

然后重写构造方法,并在构造方法里面调用initView方法。

initView方法中,我们对一些参数进行初始化操作,比如方向、默认展开。

计算高度

ok,这个就是重点了。

因为只是view本身高度的变化,我们只需要重写onMeasure去计算高度即可。

来看onMeasure

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        //重置高度
        allChildHeight = 0
        firstChildHeight = 0

        if (childCount > 0) {

            //遍历计算高度
            for (index in 0 until childCount) {
                //这个地方实际使用中除了measuredHeight,以及margin等,也要计算在内
                if (index == 0) {
                    firstChildHeight = getChildAt(index).measuredHeight
                    +getChildAt(index).marginTop + getChildAt(index).marginBottom
                    +this.paddingTop + this.paddingBottom
                }
                //实际使用时或包括padding等
                allChildHeight += getChildAt(index).measuredHeight + getChildAt(index).marginTop + getChildAt(index).marginBottom

                //最后一条的时候 加上当前view自身的padding
                if (index == childCount - 1) {
                    allChildHeight += this.paddingTop + this.paddingBottom
                }
            }

            // 根据是否展开设置高度
            if (isOpen) {
                setMeasuredDimension(
                    widthMeasureSpec,
                    firstChildHeight + ((allChildHeight - firstChildHeight) * animPercent).toInt()
                )
            } else {
                setMeasuredDimension(
                    widthMeasureSpec,
                    allChildHeight - ((allChildHeight - firstChildHeight) * animPercent).toInt()
                )
            }
        }
    }

onMeasure里面也是分了两个步骤的:

  • 遍历计算高度
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//遍历计算高度
for (index in 0 until childCount) {
    //这个地方实际使用中除了measuredHeight,以及margin等,也要计算在内
    if (index == 0) {
        firstChildHeight = getChildAt(index).measuredHeight
        +getChildAt(index).marginTop + getChildAt(index).marginBottom
        +this.paddingTop + this.paddingBottom
    }
    //实际使用时或包括padding等
    allChildHeight += getChildAt(index).measuredHeight + getChildAt(index).marginTop + getChildAt(index).marginBottom
    //最后一条的时候 加上当前view自身的padding
    if (index == childCount - 1) {
        allChildHeight += this.paddingTop + this.paddingBottom
    }
}

来看第一个if判断,记录了第一个子view的高度,这里需要注意,除了measuredHeightmargin也要算上,而且父view的内边距padding也要加上,因为如果父view的padding很大的话,收起时view可能会显示不出来的。

然后就是总高度的计算,道理同上。

来看最后一个if判断,同样总高度计算完之后也要加上父view的上下padding,才是完整的高度。

第一个判断可以理解为收起状态的高度,第二个判断可以理解为展开状态的高度。

  • 展开收起逻辑
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 根据是否展开设置高度
if (isOpen) {
    setMeasuredDimension(
        widthMeasureSpec,
        firstChildHeight + ((allChildHeight - firstChildHeight) * animPercent).toInt()
    )
} else {
    setMeasuredDimension(
        widthMeasureSpec,
        allChildHeight - ((allChildHeight - firstChildHeight) * animPercent).toInt()
    )
}

因为第一个子view是保留显示的,所以在计算的时候都需要减去第一个子view的高度,就是剩余高度

剩余高度可以很简单的计算出来,但是如何在显示的时候不突兀呢。

这里加一个动画,根据动画的执行进度来计算。

展开:第一个子view的高度 + 剩余高度 × 0到1的Float动画值 收起:总高度 - 剩余高度 × 0到1的Float动画值

author:yechaoa

动画

写一个方法控制展开收起,并在展开收起的时候执行动画。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    fun toggle(): Boolean {
        isOpen = !isOpen
        startAnim()
        return isOpen
    }
    
    /**
     * 执行动画的时候 更改 animPercent 属性的值 即从0-1
     */
    @SuppressLint("AnimatorKeep")
    private fun startAnim() {
        //ofFloat,of xxxX 根据参数类型来确定
        //1,动画对象,即当前view。2.动画属性名。3,起始值。4,目标值。
        val animator = ObjectAnimator.ofFloat(this, "animPercent", 0f, 1f)
        animator.duration = 500
        animator.start()
    }

并修改我们的动画参数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /**
     * 动画值改变的时候 请求重新布局
     */
    private var animPercent: Float = 0f
        set(value) {
            field = value
            requestLayout()
        }

set value的时候调用requestLayout(),重新执行onMeasure

调用

  • xml
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    <com.yechaoa.customviews.expand.ExpandLinearLayout
        android:id="@+id/ell"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#f5f5f5"
        android:orientation="vertical"
        android:padding="10dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@string/app_name"
            android:textColor="@android:color/holo_red_dark"
            android:textSize="20sp" />

        ...

    </com.yechaoa.customviews.expand.ExpandLinearLayout>
  • 代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        ll_btn.setOnClickListener {
            val toggle = ell.toggle()
            tv_tip.text = if (toggle) "收起" else "展开"
        }

扩展

  • 横向:计算高度变成计算宽度即可
  • 高度:可以根据xml自定义属性来控制保留高度

总结

总的来说,效果还是比较实用的,难度系数也不高,可以根据扩展自己去进一步完善。

Github

https://github.com/yechaoa/CustomViews

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-02-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Android 自定义View之随机数验证码(仿写鸿洋)
本文面向自定义view新手,但是希望你最好有一定的理论知识,或基础概念,有的地方可能会一笔带过并不会细讲,细讲篇幅就太长了。
yechaoa
2022/06/10
3450
Android 自定义View之随机数验证码(仿写鸿洋)
Android自定义View——布局Layout
之前写了一篇文章介绍自定义View,主要是介绍了自定义View绘制相关的操作。 这里主要是介绍自定义View另一个重要的关键——布局Layout。
艳龙
2021/12/16
1.4K0
Android自定义View——布局Layout
从自定义ViewGroup看layout作用
这次感冒可耽误我太多时间了,中间断断续续去了几趟医院和诊所,终于差不多好了,于是心里又暗暗下定决定,一定要好好养身体(可能过两天又忘了?) 总之大家也都多注意身体吧,身体垮了啥也干不了。 废话不多说,
码上积木
2021/07/01
5690
Android - 居中的FlowLayout
前言 因为需求的原因,需要去使用流式布局,但是这次我们的需求,和我之前的见到的流式布局不太一样。因为我们的是居中显示的流式布局。这时候,就得自己去自定义了。 老规矩,先看图。 这里说一下,我的实现思路
code_horse
2018/07/02
1.4K0
Kotlin 第一弹:自定义 ViewGroup 实现流式标签控件
上周 Google I/O 大会的召开,宣布了 Kotlin 语言正式成为了官方开发语言。一时间 Android 开发者的圈子炸开了锅,各种关于 Kotlin 的资料介绍也如雨后春笋不断的冒出。
Frank909
2019/01/14
1.5K0
android自定义View处理padding和wrap_content和自定义属性
在onMeasure方法中指定一个默认的宽和高,在设置wrap_content属性时设置此默认的宽和高就可以了: setMeasuredDimension() 方法px
tea9
2022/09/08
7930
Android开发之漫漫长途 番外篇——自定义View的各种姿势1
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。
LoveWFan
2018/08/07
7900
Android开发之漫漫长途 番外篇——自定义View的各种姿势1
常用的自定义View例子一(流布式布局)
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/51765428
程序员徐公
2018/09/18
5260
常用的自定义View例子一(流布式布局)
笔记51 | Android自定义View(二)
地址 http://blog.csdn.net/xiangyong_1521/article/details/78804104 http://www.jianshu.com/p/c84693096e41 ---- 自定义ViewGroup 自定义View的过程很简单,就那几步,可自定义ViewGroup可就没那么简单啦~,因为它不仅要管好自己的,还要兼顾它的子View。我们都知道ViewGroup是个View容器,它装纳child View并且负责把child View放入指定的位置。我们假象一下,如果是让
项勇
2018/06/19
5450
Android自定义View-入门(明白自定义View和自定义ViewGroup)
主要用来测量布局,其参数 widthMeasureSpec 和 heightMeasureSpec 包含宽和高的信息和测量模式。
Petterp
2022/02/09
1.2K0
Android自定义View-入门(明白自定义View和自定义ViewGroup)
layout怎么布局的?viewGroup和view的layout方法又有什么不同?
关于layout,很多朋友知道它是负责布局的,那么具体是怎么布局的?viewGroup和view的layout方法又有什么不同?一起来看看吧。
Android技术干货分享
2021/05/19
9480
layout怎么布局的?viewGroup和view的layout方法又有什么不同?
Android进阶之绘制-自定义View完全掌握(四)
前面的案例中我们都是使用系统的一些控件通过组合的方式来生成我们自定义的控件,自定义控件的实现还可以通过自定义类继承View来完成。从该篇博客开始,我们通过自定义类继承View来实现一些我们自定义的控件。 我们通过一个案例来学习,现在来实现这样一个效果。
wangweijun
2020/01/20
5500
高级 UI 成长之路 (三) 理解 View 工作原理并带你入自定义 View 门
该篇分为上下结构,上部分主要讲解 View 的工作原理,下部分主要以案例的形式讲解自定义 View。
做个快乐的码农
2021/11/16
8660
高级 UI 成长之路 (三) 理解 View 工作原理并带你入自定义 View 门
自定义View二篇,如何自定义一个规范的ViewGroup
在自定义View开篇,必须跨过的一道坎儿 中,我们介绍了自定义View的几种方式,以及如何实现一个规范的自定义View,上文中也说了,实现一个规范的自定义ViewGroup是一件比较困难的事情,因为要考虑的情况包含 本身的padding以及子view的margin 与 本身wrap_content 问题。
黄林晴
2020/02/15
4940
android-自定义控件-自定义ViewGroup
项目地址-->书中自定义 view 汇总:https://github.com/FishInWater-1999/DesighViewText
圆号本昊
2021/09/24
4080
教你搞定Android自定义ViewGroup
我们知道ViewGroup就是View的容器类,我们经常用的LinearLayout,RelativeLayout等都是ViewGroup的子类,因为ViewGroup有很多子View,所以它的整个绘制过程相对于View会复杂一点,但是还是三个步骤measure,layout,draw,我们一次说明。
三好码农
2018/09/11
9090
教你搞定Android自定义ViewGroup
自定义View Measure过程 - 最易懂的自定义View原理系列(2)
测量规格(MeasureSpec) = 测量模式(mode) + 测量大小(size)
Carson.Ho
2019/02/22
9430
自定义FlowLayout,android flowLayout实现
我想大家在开发过程中都碰到过这样的需求,类似标签展示,要展示如上图效果,这里面的数据不确定每项字数,有的非常长,有的很短,数据动态填充。
再见孙悟空_
2023/02/10
3570
自定义FlowLayout,android flowLayout实现
Android开发之自定义View(一)
Android常见的自定义控件有三种方式: 继承View 继承原有的控件,在原有控件的基础上进行修改 重新拼装组合 今天先来简单说一说第一种也是最复杂的一种~~ 剩下的下次再说~~ 继承View,重写onDraw方法,但是注意采用这种方式需要自己在代码中来支持熟悉的wrap_content、padding属性。 1、想好需要自定义的属性,在values下创建一个attrs.xml,这里我就演示一个简单的颜色,自定义一个属性集合,命名为CustomView,有一个格式为color的属性custom_colo
YungFan
2018/04/24
7380
Android开发之自定义View(一)
Carson带你学Android:手把手带你深入学习自定义View Measure过程
具体请看文章:Android自定义View基础:MeasureSpec类到底是什么?
Carson.Ho
2022/03/24
3350
Carson带你学Android:手把手带你深入学习自定义View Measure过程
推荐阅读
相关推荐
Android 自定义View之随机数验证码(仿写鸿洋)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档