二种显示方式:
- 网络主播全屏幕,别的游人飘浮在右边。下边通称尺寸屏模式。
- 每个人等分显示屏。下边通称等分模式。
剖析
- 较多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 = 0
for (i in 1 until childCount) if (i != position) (getChildAt(i).tag as ViewLayoutInfo).run {
toLeft = measuredWidth - maxWidgetPadding - smallViewWidth
toTop = defMultipleVideosTopPadding index * smallViewHeight index * maxWidgetPadding
toRight = measuredWidth - maxWidgetPadding
toBottom = toTop smallViewHeight
index
}
} else {
var posOffset = 0
var pos = 0
if (childCount == 4) {
posOffset = 2
pos
(getChildAt(0).tag as ViewLayoutInfo).run {
toLeft = measuredWidth.shr(1) - multiViewWidth.shr(1)
toTop = defMultipleVideosTopPadding
toRight = measuredWidth.shr(1) multiViewWidth.shr(1)
toBottom = defMultipleVideosTopPadding multiViewHeight
}
}
for (i in pos until childCount) if (i != position) {
val topFloor = posOffset / 2
val leftFloor = posOffset % 2
(getChildAt(i).tag as ViewLayoutInfo).run {
toLeft = leftFloor * measuredWidth.shr(1) leftFloor * multipleWidgetPadding
toTop = topFloor * multiViewHeight topFloor * multipleWidgetPadding defMultipleVideosTopPadding
toRight = toLeft multiViewWidth
toBottom = 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 = 9L
override fun run() {
var progress = processing / duration
if (progress > 1.0f) {
progress = 1.0f
}
for (viewInfo in viewInfoList) {
if (viewInfo.isAlpha) {
viewInfo.progress = progress
} else viewInfo.run {
val diffLeft = (toLeft - originalLeft) * progress
val diffTop = (toTop - originalTop) * progress
val diffRight = (toRight - originalRight) * progress
val diffBottom = (toBottom - originalBottom) * progress left = originalLeft diffLeft
top = originalTop diffTop
right = originalRight diffRight
bottom = originalBottom diffBottom
}
}
requestLayout()
if (progress < 1.0f) {
if (progress > 0.8f) {
var offset = ((progress - 0.7f) / 0.25f)
if (offset > 1.0f)
offset = 1.0f
processing = 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.0f
duration = 0.0f
originalLeft = left.toInt()
originalTop = top.toInt()
originalRight = right.toInt()
originalBottom = bottom.toInt()
isAlpha = false
isConverted = false
}
}
animRunning = false
processing = duration
if (!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.0f
processing = 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)
return
for (i in 0 until childCount) {
val child = getChildAt(i)
val layoutInfo = child.tag as ViewLayoutInfo
child.layout(
layoutInfo.left.toInt(),
layoutInfo.top.toInt(),
layoutInfo.right.toInt(),
layoutInfo.bottom.toInt()
)
if (layoutInfo.isAlpha) {
val progress = if (layoutInfo.isConverted)
1.0f - layoutInfo.progress
else
layoutInfo.progress
child.alpha = progress
}
}
}
4.界定行高有关的自变量,供简易的订制改动
/**
* @param multipleWidgetPadding : 等分方式载入
* @param maxWidgetPadding : 尺寸屏合理布局载入
* @param defMultipleVideosTopPadding : 间距顶端变距
*/
private var multipleWidgetPadding = 0
private var maxWidgetPadding = 0
private var defMultipleVideosTopPadding = 0
init {
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 ViewLayoutInfo
child.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.0
if (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 。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。