公司法
当前位置: 首页 法律大全

自定义viewgroup的方法有什么(自定义viewgroup教程)

时间:2023-06-12 作者: 小编 阅读量: 1 栏目名: 法律大全

下面简称大小屏模式。分析最多4人连麦,明确这点方便定制坐标算法。自定义的ViewGroup最好分别提供等分模式和大小屏模式的边距设置接口,便于修改。和一个根据现有ChildView的数量计算两种布局模式下,每个View摆放位置的函数。这里只附上addVideoView中的计算部分,只需稍微修改即可适用add、remove和toggle。可根据不同的需求写不同的实现,最终符合定义的数据模型即可。

两种显示方式:

  1. 主播全屏,其他游客悬浮在右侧。下面简称大小屏模式。
  2. 所有人等分屏幕。下面简称等分模式。

分析

  • 最多4人连麦,明确这点方便定制坐标算法。
  • 自定义的 ViewGroup 最好分别提供等分模式和大小屏模式的边距设置接口,便于修改。
  • SDK 自己管理了 TextureView 的绘制和测量,所以 ViewGroup 需要复写 onMeasure 方法以通知 TextureView 测量和绘制。
  • 一个计算 0.0f ~ 1.0f 逐渐减速的函数,给动画过程做支撑。
  • 一个记录坐标的数据模型。和一个根据现有 Child View 的数量计算两种布局模式下,每个 View 摆放位置的函数。

实现

1.定义坐标数据模型

private data class ViewLayoutInfo(var originalLeft: Int = 0,// original开头的为动画开始前的起始值var originalTop: Int = 0,var originalRight: Int = 0,var originalBottom: Int = 0,var left: Float = 0.0f,// 无前缀的为动画过程中的临时值var top: Float = 0.0f,var right: Float = 0.0f,var bottom: Float = 0.0f,var toLeft: Int = 0,// to开头的为动画目标值var toTop: Int = 0,var toRight: Int = 0,var toBottom: Int = 0,var progress: Float = 0.0f,// 进度 0.0f ~ 1.0f,用于控制 Alpha 动画var isAlpha: Boolean = false,// 透明动画,新添加的执行此动画var isConverted: Boolean = false,// 控制 progress 反转的标记var waitingDestroy: Boolean = false,// 结束后销毁 View 的标记var pos: Int = 0// 记录自己索引,以便销毁) {init {left = originalLeft.toFloat()top = originalTop.toFloat()right = originalRight.toFloat()bottom = originalBottom.toFloat()}}

以上,记录了执行动画和销毁View所需的数据。(于源码中第352行)

2.计算不同展示模式下View坐标的函数

if (layoutTopicMode) {var index = 0for (i in 1 until childCount) if (i != position) (getChildAt(i).tag as ViewLayoutInfo).run {toLeft = measuredWidth - maxWidgetPadding - smallViewWidthtoTop = defMultipleVideosTopPadding + index * smallViewHeight + index * maxWidgetPaddingtoRight = measuredWidth - maxWidgetPaddingtoBottom = toTop + smallViewHeightindex++}} else {var posOffset = 0var pos = 0if (childCount == 4) {posOffset = 2pos++(getChildAt(0).tag as ViewLayoutInfo).run {toLeft = measuredWidth.shr(1) - multiViewWidth.shr(1)toTop = defMultipleVideosTopPaddingtoRight = measuredWidth.shr(1) + multiViewWidth.shr(1)toBottom = defMultipleVideosTopPadding + multiViewHeight}}for (i in pos until childCount) if (i != position) {val topFloor = posOffset / 2val leftFloor = posOffset % 2(getChildAt(i).tag as ViewLayoutInfo).run {toLeft = leftFloor * measuredWidth.shr(1) + leftFloor * multipleWidgetPaddingtoTop = topFloor * multiViewHeight + topFloor * multipleWidgetPadding + defMultipleVideosTopPaddingtoRight = toLeft + multiViewWidthtoBottom = toTop + multiViewHeight}posOffset++}}post(AnimThread((0 until childCount).map { getChildAt(it).tag as ViewLayoutInfo }.toTypedArray()))

Demo源码中的add、remove、toggle方法重复代码过多,未来得及优化。这里只附上 addVideoView 中的计算部分(于源代码中第141行),只需稍微修改即可适用add、remove和toggle。(也可参考 CDNLiveVM 中的 calcPosition 方法,为经过优化的版本)layoutTopicMode = true 时,为大小屏模式。

由于是定制算法,只能适用这一种布局,故不写注释。只需明确一点,此方法最终目的是为了计算出每个View当前应该出现的位置,保存到上面定义的数据模型中并开启动画(最后一行 post AnimThread 为开启动画的代码,我这里是通过 post 一个线程来更新每一帧)。

可根据不同的需求写不同的实现,最终符合定义的数据模型即可。

3.逐渐减速的算法,使动画效果看起来更自然。

private inner class AnimThread(private val viewInfoList: Array<ViewLayoutInfo>,private var duration: Float = 180.0f,private var processing: Float = 0.0f) : Runnable {private val waitingTime = 9Loverride fun run() {var progress = processing / durationif (progress > 1.0f) {progress = 1.0f} for (viewInfo in viewInfoList) {if (viewInfo.isAlpha) {viewInfo.progress = progress} else viewInfo.run {val diffLeft = (toLeft - originalLeft) * progressval diffTop = (toTop - originalTop) * progressval diffRight = (toRight - originalRight) * progressval diffBottom = (toBottom - originalBottom) * progressleft = originalLeft + diffLefttop = originalTop + diffTopright = originalRight + diffRightbottom = originalBottom + diffBottom}}requestLayout() if (progress < 1.0f) {if (progress > 0.8f) {var offset = ((progress - 0.7f) / 0.25f)if (offset > 1.0f)offset = 1.0fprocessing += waitingTime - waitingTime * progress * 0.95f * offset} else {processing += waitingTime}postDelayed(this@AnimThread, waitingTime)} else {for (viewInfo in viewInfoList) {if (viewInfo.waitingDestroy) {removeViewAt(viewInfo.pos)} else viewInfo.run {processing = 0.0fduration = 0.0foriginalLeft = left.toInt()originalTop = top.toInt()originalRight = right.toInt()originalBottom = bottom.toInt()isAlpha = falseisConverted = false}}animRunning = falseprocessing = durationif (!taskLink.isEmpty()) {invokeLinkedTask()// 此方法执行正在等待中的任务,从源码中能看到,remove、add等函数需要依次执行,前一个动画未执行完毕就进行下一个动画可能会导致不可预知的错误。}}}}

上述代码除了提供减速算法,还一并更新了对应View数据模型的中间值,也就是模型定义种的 left, top, right, bottom 。

通过减速算法提供的进度值,乘以目标坐标与起始坐标的间距,得出中间值。

逐渐减速的算法关键代码为:

if (progress > 0.8f) {var offset = ((progress - 0.7f) / 0.25f)if (offset > 1.0f)offset = 1.0fprocessing += waitingTime - waitingTime * progress * 0.95f * offset} else {processing += waitingTime}

这个算法实现的有缺陷,因为它直接修改了进度时间,大概率会导致执行完毕的时间与设置的预期时间(如设置200ms执行完毕,实际可能超过200ms)不符。文末我会提供一个优化的减速算法。

变量 waitingTime 表示等待多久执行下一帧动画。用每秒1000ms计算即可,如果目标为60刷新率的动画,设置为1000 / 60 = 16.66667即可(近似值)。

计算并存储每个 View 的中间值后,调用 requestLayout() 通知系统的 onMeasure 和 onLayout 方法,重新摆放 View 。

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {if (childCount == 0)returnfor (i in 0 until childCount) {val child = getChildAt(i)val layoutInfo = child.tag as ViewLayoutInfochild.layout(layoutInfo.left.toInt(),layoutInfo.top.toInt(),layoutInfo.right.toInt(),layoutInfo.bottom.toInt())if (layoutInfo.isAlpha) {val progress = if (layoutInfo.isConverted)1.0f - layoutInfo.progresselselayoutInfo.progresschild.alpha = progress}}}

4.定义边距相关的变量,供简单的定制修改

/** * @param multipleWidgetPadding : 等分模式读取 * @param maxWidgetPadding : 大小屏布局读取 * @param defMultipleVideosTopPadding : 距离顶部变距 */private var multipleWidgetPadding = 0private var maxWidgetPadding = 0private var defMultipleVideosTopPadding = 0init {viewTreeObserver.addOnGlobalLayoutListener(this)attrs?.let {val typedArray = resources.obtainAttributes(it, R.styleable.AnyVideoGroup)multipleWidgetPadding = typedArray.getDimensionPixelOffset(R.styleable.AnyVideoGroup_between23viewsPadding, 0)maxWidgetPadding = typedArray.getDimensionPixelOffset(R.styleable.AnyVideoGroup_at4smallViewsPadding, 0)defMultipleVideosTopPadding = typedArray.getDimensionPixelOffset(R.styleable.AnyVideoGroup_defMultipleVideosTopPadding, 0)layoutTopicMode = typedArray.getBoolean(R.styleable.AnyVideoGroup_initTopicMode, layoutTopicMode)typedArray.recycle()}}

取名时对这三个变量的职责定义,与编写逻辑时的定义有出入,所以有点词不达意,需参考注释。

由于这只是定制化的变量,并不重要,可根据业务逻辑自行随意修改。

5.复写 onMeasure 方法,这里主要是通知 TextureView 更新大小。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {val widthSize = MeasureSpec.getSize(widthMeasureSpec)val heightSize = MeasureSpec.getSize(heightMeasureSpec)multiViewWidth = widthSize.shr(1)multiViewHeight = (multiViewWidth.toFloat() * 1.33334f).toInt()smallViewWidth = (widthSize * 0.3125f).toInt()smallViewHeight = (smallViewWidth.toFloat() * 1.33334f).toInt()for (i in 0 until childCount) {val child = getChildAt(i)val info = child.tag as ViewLayoutInfochild.measure(MeasureSpec.makeMeasureSpec((info.right - info.left).toInt(), MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec((info.bottom - info.top).toInt(), MeasureSpec.EXACTLY))}setMeasuredDimension(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY))}

总结

1.明确数据模型,一般情况下记录起始上下左右坐标、目标上下左右坐标、和进度百分比就足够了。

2.根据需求明确动画算法,这里补充一下优化的减速算法:

factor = 1.0if (factor == 1.0)(1.0 - (1.0 - x) * (1.0 - x))else(1.0 - pow((1.0 - x), 2 * factor))// x = time.

3.根据算法计算出来的值更新 layout 布局即可。

此类 ViewGroup 实现简单方便,只涉及到几个基本系统API。如不想写 onMeasure 方法可继承 FrameLayout 等已写好 onMeasure 实现的 ViewGroup 。

    推荐阅读
  • 怎样烧红烧肉(家常红烧肉的做法)

    怎样烧红烧肉原料:精品五花肉、炖肉料包、葱、冰糖、茶叶。五花肉切条放入凉水中撇去血沫。焯水定型;捞出后晾凉切一样大的方块。锅中放少许油倒入白砂糖炒糖色。糖色的气泡由大变小迅速关火,倒入开水。加少许绍酒,加开水烧,熟得快,加入茶叶水,可以去腥味。改回炒锅大火,放冰糖,使汁粘稠即可出锅,香葱段点缀。

  • 2022杭州径山茶圣节时间、地点、活动一览

    最终集齐所有铜币的游客可至“大宋钱庄”兑换神秘礼物。今来茶韵生活01、陆羽说论坛为进一步挖掘径山茶宴有关历史文化,本届茶圣节特邀请茶学专家交流讨论如何更好保护和传承国家非物质文化遗产。为打造文化传播年,第二十一届中国茶圣节以春迎、夏凉、秋韵、冬福四大主题贯穿全年。

  • 《重生之门》给罗队发短信的人身份

    但是通过前文,不难推测应该是庄文杰发给罗队的短信,只是没有暴露自己的身份。罗坚来到青檀假日酒店排查,没有发现任何异常,庄文杰和许正清乔装改扮随后赶来,他们一出现就被人盯上,庄文杰和许正清来到地下停车场,庄文杰巧妙引开那些人,混进游客中进入酒店。这件事情把十二年前的洛神案串联起来了。

  • 爱情名著哪个好看(随侃名著佳作第6期)

    言下之意,他主动向周晓白提出分手。钟跃民成为一个军人,上了战场,并且是在战斗中受伤,被送到战地医疗帐篷内救治。而周晓白和钟跃民在时隔十多年后的相遇一刻,也是被编剧以及导演,安排得相当的特别,并不是那种悲情欲绝又或者是感动无比的相遇时刻。

  • 板栗可以保存多久 板栗怎么能保存时间长

    如果是晒干的板栗可以存放3-4个月,生板栗在常温下合理贮存可以存放1-2个月,煮熟的栗子大概可以放一周,熟板栗放冰箱冷冻能保存30天左右,熟板栗放冰箱冷藏保存可以存放5天。

  • 贾宝玉与红楼梦的关系(贾宝玉的春梦到底在暗示什么)

    贾宝玉与红楼梦的关系?要知道,贾琏这个人极其好色,而且好的就是熟女,那么从这个曲折的描述中,我们可以推断出,秦可卿应该是那种熟女中的极品。这个问题在书中得不到直接的答案,因为在后面的文章中,秦可卿一共只出现三个镜头:介绍弟弟秦钟与贾宝玉相见,秦可卿病后王熙凤带贾宝玉去探病,秦可卿临死前在梦里向王熙凤交代后事。

  • 简单又好看的剪纸适合儿童(孩子能学会的幼儿简单剪纸教程)

    接下来我们就一起去研究一下吧!简单又好看的剪纸适合儿童幼儿园的孩子经常要做各种各样的手工,通过做手工,提高孩子的审美能力,锻炼孩子的动手能力,培养孩子的专注力和耐心,让孩子更聪明。用蓝天白云绿色的草地,太阳、小兔子和小蘑菇,可以贴出一幅画,也可以用这个画面编出一个小故事,带孩子度过愉快的亲子时光。欢迎关注,学习更多幼儿小手工。

  • 摩尔庄园钓鲤鱼的最佳方法(摩尔庄园钓鲤鱼的有什么最佳方法)

    以下内容希望对你有帮助!摩尔庄园钓鲤鱼的最佳方法工具/原料:华为手机、安卓系统、摩尔庄园游戏。进入游戏后操纵游戏角色进行移动了。去商店购买钓鱼的诱饵。来到池塘边进行的钓鱼。等待的水面出现波动即可钓到鲤鱼了。

  • 国外的懒人产品(歪国产品咖在用哪些可爱的小工具)

    quotes=trueUsabilityHub我通常使用UsabilityHub来帮助确定设计方案。

  • 一年四季水果时间表(一年四季的时令水果是什么)

    3月(春季):枇杷、红香蕉、樱桃、杨桃、番荔枝、青枣、甘果蔗、草莓、番石榴、牛奶蕉、柑桔、观赏南瓜、果桑、鹤首瓜。12月(冬季):樱桃、番茄、红香蕉、鸡蛋果、木瓜、草莓、百香果、杨桃、无花果、番石榴、牛奶蕉、鹤首瓜、观赏南瓜、果蔗、台湾青枣、黑提子、人心果、柠檬、菠萝、油梨、柑橘、橙子。