写点什么

Android 技术分享| 开源 Demo any 自习室布局架构

发布于: 3 小时前

需求


分析

  • 布局分为横竖屏

  • 涉及到视频窗口的大小、位置切换


通过观察需求原型图可得知,横竖屏切换可以简单分成 7 块区域


  • 4 个视频窗口

  • 1 个 title,显示「XX 号房间」

  • 1 个 ViewGroup,放置「头像,头像,头像,N 个观众」

  • 另 1 个 ViewGroup,放置聊天窗口相关


横竖屏切一开始我的思路是完全不使用系统的横竖屏切换,使用 rotation 来切换横竖屏,切换过程添加一个转换动画,如下图所示:



<p style="color: #999999; font-size: 12px; text-align: center;"> (请无视中间那个小眼睛) </p>


但因为横屏之后依然有聊天功能,不调用系统的旋转,输入法依然还是竖屏的形式弹出来的。暂时没有查到解决办法。无奈改为使用系统的横竖屏切换,切换时通知 ViewGroup 重新计算测量、布局子 View。

实现

首先我们复写onMeasure方法,遍历子 View 测量,指定为我们计算后的宽高,Mode 设置为 EXACTLY。


override fun onMeasure(widthSpace: Int, heightSpace: Int) {    val width = MeasureSpec.getSize(widthMeasureSpec)    val height = MeasureSpec.getSize(heightMeasureSpec)
isVertical = height > width
if (!isVertical) { videosWidth = (width * 0.548f).toInt() horizontalSmallVideoWidth = (videosWidth / 3.0f).toInt() horizontalSmallVideoHeight = (horizontalSmallVideoWidth * 0.6803f).toInt() topicViewHeight = height - horizontalSmallVideoHeight - videoViewSpacing }
for (i in 0 until childCount) { val child = getChildAt(i)
val location = if (child.tag == null) { val location = createAndCalcCoordinates(child, i, width, height) if (location.fromLeft == 0 && location.fromRight == 0) location.run { fromLeft = toLeft fromTop = toTop fromRight = toRight fromBottom = toBottom } location } else { child.tag as ViewLocation }
child.tag = location child.measure( MeasureSpec.makeMeasureSpec(location.right - location.left, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(location.bottom - location.top, MeasureSpec.EXACTLY) ) }
setMeasuredDimension( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) )}
复制代码


其中 ViewLocalion 对象存储了 View 四个边的位置(left、top、right、bottom)。首次加载时创建 ViewLocation 对象,并根据横竖屏计算每个 View 的坐标,最终对象会存储到 View 的 tag 中。再次触发onMeasure时不会重新计算布局,而是沿用之前的坐标数据。


计算子 View 位置的函数只会在横竖屏切换、用户点击切换视频位置或大小时调用。


onLayout中遍历子 View,通知布局并传入对应的四边位置即可。


override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {    for (i in 0 until childCount) {        val child = getChildAt(i)        val location = child.tag as ViewLocation
child.layout(location.left, location.top, location.right, location.bottom) }}
复制代码


计算子 View 位置的算法可以点击这里查看,就不贴出来了。有一个需要注意的细节是:更新 View 坐标(除横竖屏切换)时,实际更改的是目标坐标,而非当前坐标,这样做的目的是为了实现动画效果。


当前坐标指的是在onMeasureonLayout中使用的变量:left、top、right、bottom。目标坐标表示动画执行完毕后 View 的位置。


执行动画与坐标计算充分解耦,无论想新增怎样的动画(平移、缩放),只需要根据需求计算好左、上、右、下的位置,发送给动画执行的 Runnable 即可。


如切换大屏 View 的计算:


fun toEquallyDividedVideos() {    if (isAnimRunning) {        return    }    if (!isVertical/* || !isSmallMode*/) {        return    }    isAnimRunning = true    isSmallMode = false    topicIndex = -1
val videoWidth = measuredWidth.shr(1) val videoHeight = (videoWidth.toFloat() * 0.6882f).toInt()
val arr = arrayOfNulls<ViewLocation>(childCount) for (i in 0 until childCount) { val location = getChildLocationAndResetFromLocations(i) arr[i] = location
val remainder = (i % 2) when (i) { in 0..3 -> location.run { toLeft = remainder * videoWidth + remainder * videoViewSpacing.shr(1) toTop = (if (i >= 2) i / 2 else 0) * (videoViewSpacing + videoHeight) + titleHeight toRight = toLeft + videoWidth - ((i + 1) % 2) * videoViewSpacing.shr(1) toBottom = toTop + videoHeight } 5 -> location.run { toTop = titleHeight + videoHeight.shl(1) + videoViewSpacing toBottom = toTop + titleHeight } 6 -> location.run { toTop = titleHeight.shl(1) + videoHeight.shl(1) + videoViewSpacing } } }
post(AnimRunnable(arr.map { it!! }.toTypedArray(), mDuration = 200L, isRotation = false))}
复制代码

最终效果


发布于: 3 小时前阅读数: 2
用户头像

实时交互,万物互联! 2020.08.10 加入

实时交互,万物互联,全球实时互动云服务商领跑者!

评论

发布
暂无评论
Android技术分享| 开源Demo any自习室布局架构