- Android自定义控件高级进阶与精彩实例
- 启舰
- 2919字
- 2021-03-04 18:45:27
2.5 折叠布局实战(二)——折叠菜单
在2.4节中,我们已经初步了解了实现折叠菜单的原理,而在本节中,我们将实现两方面内容。首先,根据实现原理生成继承自ViewGroup的控件,让用户可以自定义布局;然后,为该控件添加手势交互,以实现响应手势的折叠菜单。
2.5.1 使用ViewGroup实现折叠效果
2.5.1.1 技术选型
一般而言,对于需要展示自身的控件,会继承自View类的控件,比如ImageView、TextView等。但若我们需要用户自定义布局内部控件,则需要继承自ViewGroup类的控件,比如LinearLayout、FrameLayout等。
另外,对于继承自ViewGroup类的控件,除非一些需要自定义布局的需求外(比如实现FlowLayout等),一般都不直接继承自ViewGroup,而是继承自它的子控件,比如LinearLayout等,因为ViewGroup中没有onLayout,所以如果继承自ViewGroup的话,我们需要自己实现onLayout,这有点麻烦。而当继承自类似LinearLayout这类ViewGroup的子控件时,onLayout已经实现好了,只关注我们自己要实现的功能即可,不必关注布局问题。
很显然,在这里我们关注的不是如何布局,而且如何在绘制子控制时实现折叠效果。因此,我们可以直接继承自LinearLayout等子控件。
如果将原本继承自View的效果迁移到继承自ViewGroup,则需要改动的位置如下。
●extends View需要改为extends LinearLayout。
●不存在Bitmap,绘制高度需要使用整个控件的高度。
●在ViewGroup及其子类中,绘制时调用的是dispatchDraw,而不是onDraw。
下面根据这几点变化,重新梳理一下代码。
2.5.1.2 整改init函数
在继承自View时,我们所有的初始化操作都放在init函数中,但在继承自ViewGroup时,由于没有Bitmap,则在初始化时无法获取相关的高度和宽度,这时我们必须延后处理,所以我们将其他不依赖宽度和高度的变量还放在init函数中,仅将依赖的变量先移出来。
此时的init函数代码如下:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_276.jpg?sign=1739558471-5YRzIbXboqxNpWVeeyrFIylS5W26RM1p-0-3dc452ec045ad18a61cc16c184b27f29)
然后,把其他原来与Bitmap宽度和高度相关的变量全部都放在另一个函数中待用:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_277.jpg?sign=1739558471-mC0K6LcGKvDQkTKGhGNQVasOQLT527dN-0-fb5bfb14f93d2d20cb97ffa3e2f58439)
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_278.jpg?sign=1739558471-oQCgAz5ieQfgfvm2S5v4W5GZx4eON2kb-0-7a7f72d9c94269ab9c98c1d86ae750b9)
可以看到,在这个函数的开头有使用:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_279.jpg?sign=1739558471-khCjeVP9humcm1sKZZMg39t5rIFgLpBW-0-44d8af5cddf2581f868d2c97ee79a667)
也就是使用整个ViewGroup的宽度和高度来代替原来mBitmap的宽度和高度,在代码中将原来所有的mBitmap.getWidth都替换为mWidth,所有的mBitmap.getHeight都替换为mHeight。其他代码逻辑没有变化。
那么问题就来了,新建的updateFold函数放在哪里呢?因为我们需要利用getMeasuredWidth和getMeasuredHeight,所以必须将其放在onMeasure之后的生命周期函数内,一般放在onLayout函数中:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_280.jpg?sign=1739558471-oSVBYIWN9MXVtbzgfczUorXAzkZdAXnz-0-ad4e420301ae82afb3d2a97a74e0b7a1)
2.5.1.3 整改dispatchDraw函数
在ViewGroup的绘制过程中,肯定会调用的绘图函数是dispatchDraw,此时不一定会调用onDraw函数。在dispatchDraw函数的整改中,只是将原来的canvas.drawBitmap函数改为super.dispatchDraw(canvas);,这样就实现了在操作完Canvas后绘制子控件的视图,代码如下:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_281.jpg?sign=1739558471-5ZqVoxOYJrb3mG7NtG7CF3wVLEZ539Ah-0-99e0e00a92b5b1e19bc798372f556b81)
我们在使用这个自定义控件时,如果单纯地包裹一个显示图的ImageView:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_282.jpg?sign=1739558471-yzJ2NfuslgjRwKhp5OC65meFIfJ5EM8A-0-4f2080bae9a7479c75f89da3c7a7b393)
此时的效果如图2-52所示。
从图2-52可以看到,图顶部显示了折叠效果,但底部是怎么回事呢?怎么还这么平整?假如我们拿图2-52与前面的效果图(见图2-51)进行对比,如图2-53所示。
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_283.jpg?sign=1739558471-LaTc6PqEOMvPWkXlQgqNDb8wEMufxr80-0-81ba0f57e1e632d69aa24c7d0b1f1aca)
图2-52
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_284.jpg?sign=1739558471-Dcljz6FfjAKVNxAxzu2qTE3ZHHlljnFc-0-8c43dcf76f887fdf9d6faf27f559d01c)
扫码查看彩色图
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_285.jpg?sign=1739558471-UAQE7Zn72ZiotbB8Z8AU1cmmQPOAxUXi-0-df3ca2142e464ed820862807e4a57c5a)
图2-53
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_286.jpg?sign=1739558471-kgHuqnN9n2PdCK1pjnH03Or6TZLbkfWH-0-9d533261bcd0825917fc8af5101417d5)
扫码查看彩色图
很明显可以看到,底部平齐是因为布局的高度问题,底部的折叠效果被截掉了。这是为什么呢?
仔细分析上面的布局代码,可以看出,PolyToPolySample4View的layout_height的值是wrap_content,而它的content是ImageView,其高度就是图2-53右图中绿框部分的高度。很显然,底部的折叠效果会被截掉。
2.5.1.4 截掉问题修复
那么怎么解决底部折叠效果被截掉的问题呢?有两种方法可以解决这个问题。
第一种方法:增加PolyToPolySample4View的测量高度。即在测量结果的基础上,增加depth的高度,这种方法需要重新执行onMeasure,相对比较麻烦。第二种方法:只需要我们将底部往上缩一缩,在PolyToPolySample4View测量高度不变的情况下,通过变形改变底部最低点的位置,使最低点位置处于测量范围内,也就是说底部整体向上缩了depth高度,如图2-54所示。
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_287.jpg?sign=1739558471-mbycymjOw0iy6hvxvSTOupOk3r9yQtDO-0-442d8f8f2ff2a911bb6ddc4c65fe8b42)
图2-54
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_288.jpg?sign=1739558471-X4i8Py6ssVOqs54YMas3Ba7jxbRRcMVI-0-8c15825606fc6d26b09a46ba79a112ba)
扫码查看彩色图
因此,我们需要修改dst数组:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_289.jpg?sign=1739558471-9BizpMp3cVq3Q4bTHzqjUEnECmIAm3Yw-0-f319572086e19882c9e136e0104fc5c6)
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_290.jpg?sign=1739558471-aiVCAQ5ealuAlp1JjCRXVdCzzCAF93XX-0-f01ba3afdacf1cb8654d75fada36b795)
用//注释掉原来的dst数组内容,可以看到,改变前后的区别在于原来的mHeight+depth被替换为mHeight,表示最大高度是mHeight,原来的mHeight被替换为mHeight-depth,以显示折叠效果。这样修改了以后,整个控件的最低点位置就保持在了mHeight处,也就不会出现底部折叠效果被截掉的问题了。此时的效果如图2-55所示。
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_291.jpg?sign=1739558471-fVOeovG350cHcet9ARmnpxoKVqGmhNmQ-0-8b041cd4537f8055611da59bbcd1b7f7)
图2-55
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_292.jpg?sign=1739558471-UEXMvBwTCTERKkeEV7hw12n33CucqfE6-0-623ab96f636ecb785b90382c3560671a)
扫码查看彩色图
2.5.1.5 测试成果
我们将包裹的ImageView改为其他布局,再来看看效果:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_293.jpg?sign=1739558471-kwky5MusDQuEyde9W1gzwsZWgPgleyL0-0-1dedf22eda9b9da92a627a581a38513f)
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_294.jpg?sign=1739558471-UV3O3ljCh5iUvRy35mcMlivZINMn20Zs-0-bcc010bbcb976e4bbcf9d7b08c1dbeec)
效果如图2-56所示。
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_295.jpg?sign=1739558471-P4RgigegoE3UIAqScTXJfhDRqXPgMItI-0-a96000d98391020ecd523262f20d12fd)
图2-56
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_296.jpg?sign=1739558471-uGkEBR1Pz0a6uZwQggbT7LwuDYP2ITWg-0-18e19c40fe6fb39ffcf77a78b69157ce)
扫码查看动态图
从图2-56可以看到,在更改了子控件之后,整个布局自然变更了折叠效果,而且其中的子控件本身的功能依然可用。这就是继承自ViewGroup的好处。
2.5.2 实现折叠菜单
在理解了原理之后,下面就开始着手实现折叠菜单的效果。
2.5.2.1 使用PolyToPolySample6View动态改变宽度
首先,因为在前面的例子中我们都将整个菜单的宽度设定为原宽度的0.8倍,所以在我们要实现动态更新菜单的宽度时,需要增加一个接口,以动态设置菜单的宽度:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_297.jpg?sign=1739558471-gm9LNtvt5FkmVspAamFfRrBAXusz5wYC-0-90d192f77a395bc2f691866197546bc5)
这里新增了一个setFactor函数,可以动态设置缩放变量mFactor的值。设置以后,调用updateFold函数更新各种变量,然后调用invalidate函数重绘整个ViewGroup。
2.5.2.2 实现抽屉菜单控件
那么问题来了,怎么实现抽屉效果呢?在Android Support包中,Google为我们提供了一个官方的抽屉组件:DrawerLayout。这里先大概讲解一下,如果有不理解它的用法的读者,可以先学习此控件的使用方法后再回来学习本节内容。
因此,继承自DrawerLayout来自定义一个抽屉容器,将原来DrawerLayout的菜单布局转移到PolyToPolySample6View中,这样就可以将DrawerLayout的菜单折叠起来了。
相关代码如下,先列出完整代码,然后逐步讲解:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_298.jpg?sign=1739558471-O1OrLIcAHcpKAasfdtRgit06GijzA32n-0-32c4eb5a6a783955d3cf2e0fe7ce4ec2)
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_299.jpg?sign=1739558471-lADJrjZ7mF55wyfRLx0zD1VSeZLAbfPu-0-f626cc87afc36eab7c0f069772e068a5)
我们需要将DrawerLayout的菜单布局转移到PolyToPolySample6View中,需要在View已经生成但还没有显示出来的这个阶段实现。在ViewGroup的生命周期函数中,onFinishInflate和onAttachedToWindow都符合条件,这里将处理代码写在onAttachedToWindow中。
这里主要分为3个步骤。
(1)在onAttachedToWindow中轮询所有的子View,并找到菜单View。我们知道,在使用DrawerLayout时,如果layout_gravity的值是left、right的View,那么这个View肯定是菜单View。函数isDrawerView就是利用Gravity是不是left、right来判断是否是菜单的。
(2)如果是菜单View,则将它加入PolyToPolySample6View中。在将该子View加入PolyToPolySample6View中时,需要注意两点。
●先调用remove函数再调用add函数。
●新增PolyToPolySample6View时,需要使用该子View的布局参数。因为我们已经在子View的布局参数中提前定义了layout_gravity的值,所以DrawerLayout只需要识别它来确定它是否是菜单即可,如果是才会有菜单效果。
(3)设置抽屉滑动监听,当抽屉滑动时,实时地在onDrawerSlide中设置菜单的缩放比例。
2.5.2.3 使用自定义的抽屉组件FoldDrawerLayout
在使用抽屉组件时,因为它本质上是DrawerLayout,所以只需要遵循DrawerLayout的使用方法即可,只需要在菜单View上明确标注它的layout_gravity属性。这里为了方便,将TextView作为菜单项。代码如下(activity_fold_principle6.xml):
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_300.jpg?sign=1739558471-JGweC7faXHEvFLULV782Lhokw6WEnPJv-0-9a5a915a917089658f01d4cc47d16a99)
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_301.jpg?sign=1739558471-h5MBMW8EoK5DO3KFLrlnVn74v5Cw2YCB-0-516b0bd1885df4a5f719d10fa722630e)
然后在MainActivity中使用这个布局即可:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_302.jpg?sign=1739558471-CTyX5csqLN1XA2R6atlLocAL1kgbEG6B-0-c6f92ab38894a98495895ffe04366f7e)
效果如图2-57所示。
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_303.jpg?sign=1739558471-zWBcRw4uDDhzzcWwskR20zHtHzd91RST-0-d55fb7e00f64cf446b2cd94907597441)
图2-57
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_304.jpg?sign=1739558471-uo2L9E6NYnar9hsQiz8cy8u71B4QRth1-0-a4cede5504a7473a522e1ebacbc0bcfd)
扫码查看动态效果图
2.5.2.4 完整实现折叠菜单效果
在前面的效果图中,大概实现了折叠菜单效果,但很明显有一个问题,这就是当手指拖动的时候,折叠菜单并不紧跟手指变化,而是出现了延后现象,比如图2-58中的白点是手指位置,而此时的折叠菜单右侧边在手指距离屏幕左边一半的位置,这是怎么回事呢?
我们知道,一般而言,滑动菜单展开的右侧边位置应该就是手指的位置,这里之所以会出现两个位置不一致的情况,是因为我们在显示折叠菜单时,根据菜单的原始宽度进行了缩放,缩放系数就是mFactor。
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_305.jpg?sign=1739558471-RoRyETtkZzv5kdleicv4AUEnrcXVivRf-0-4bd10eb662df2dd3cec290b963943912)
图2-58
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_306.jpg?sign=1739558471-3ekeN3E04ohioHsWweeQaMal6zeapzTN-0-38fcf029f9897f10e09b7f25d3418df2)
扫码查看彩色图
但缩放后布局时,仍是以(0,0)点为坐标系原点进行布局的,这就导致看起来菜单右侧边与手指有一定的距离,原理如图2-59所示。
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_307.jpg?sign=1739558471-Ap6loygLS072jQWImAmOdArvEsEIho0Y-0-75f678eb94b565bd12dc0a224501d1d9)
图2-59
解决这个问题的办法也比较简单,只需要让缩放后的菜单靠右布局即可,原理如图2-60所示。
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_308.jpg?sign=1739558471-1Ud6rnZbNdxvVx0BouYx5pNibscuBEnS-0-f4129d8df9393140c7d11aa359c76157)
图2-60
因为折叠菜单跟随手指移动的最大距离就是整个菜单宽度,所以右侧菜单缩小后的大小是mFactor×mWidth(mWidth是整个菜单的宽度),左侧空出来的距离是(1-mFacotr)×mWidth。
这样我们只需要对dst数组进行修改,整个折叠菜单向右移(1-mFacotr)×mWidth即可:
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_309.jpg?sign=1739558471-5UXCoBiSqk2dNFat8Vs66wVpXAERnT0x-0-ecb0ee6eef4e664cd621a0875b0d9859)
修改后的代码效果如图2-61所示。
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_310.jpg?sign=1739558471-lIaqMG9MDYyV85XTHk3alvABQe35zghL-0-32f9a964782f1a3243540bfab2de61a8)
图2-61
![img](https://epubservercos.yuewen.com/0CBE40/19391577408683706/epubprivate/OEBPS/Images/txt002_311.jpg?sign=1739558471-8k8vFy7LoK3lDkokEfVcXwjjULlGMGYg-0-454f2ab5449238160922b9b8c6f2c6d2)
扫码查看彩色图
到这里,有关位置矩阵的所有知识就讲解完成了。单纯理解位置矩阵有一定的难度,使用起来更困难,但位置矩阵的应用范围比较广,在自定义控件中经常会用到,所以如果不懂这个知识点的话,可能会读不明白一些代码,因此大家还是应该尽量学会和掌握它。