Android实现循环轮播跑马灯的效果
先看效果
支持暂停,恢复,view自定义和池化回收复用。使用上,只需要引入xml,并绑定factory即可,内部会在attach时自动开始
<MarqueeAnimalView
android:id="@+id/marqueeView"
android:layout_width="200dp"
android:layout_height="30dp"
android:background="@color/color_yellow" />
val list = mutableListOf("我是跑马灯1", "我不是跑马灯", "你猜我是不是跑马灯")
var position = 0
view.marqueeView.setFactory(object : PoolViewFactory {
override fun makeView(layoutInflater: LayoutInflater, parent: ViewGroup): View {
val view = TextView(this@ViewActivity)
view.setPadding(0, 0, 20.dp(), 0)
view.textSize = 12f
view.setTextColor(ResourceUtil.getColor(R.color.white))
return view
}
override fun setAnimator(objectAnimator: ObjectAnimator, width: Int, parentWidth: Int) {
objectAnimator.duration = (parentWidth + width) * 5L
}
override fun setView(view: View): Boolean {
(view as? TextView)?.text = list[position++ % list.size]
return true
}
})
池化思路
参考Message的思路,对view进行回收复用,避免内存持续增长,增大GC压力
private fun obtain(): View? {
synchronized(sPoolSync) {
if (!isAttachedToWindow) {
return null
}
if (queue.isNotEmpty()) {
return queue.poll()
}
}
return factory?.makeView(layoutInflater, this@MarqueeAnimalView)?.apply {
addView(this, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
}
private fun recycle(view: View) {
synchronized(sPoolSync) {
if (queue.size < MAX_POOL_SIZE) {
queue.offer(view)
}
}
}
创造工厂
这里的思路源于ViewSwitchFactory
interface PoolViewFactory {
fun makeView(layoutInflater: LayoutInflater, parent: ViewGroup): View
fun setAnimator(objectAnimator: ObjectAnimator, width: Int, parentWidth: Int)
fun setView(view: View): Boolean
}
轮询切换
这里根据对动画进行初始化,并设置合适的监听。此时需要获取当view和parent的width,以用于标定始末位置,需要注意x轴的正负方向。animators
用于存储开始的动画,这也是设计时存在的遗留问题,因为主动取消所有动画,但view->animator是单向绑定关系,所以需要保存发生的动画
private val animators = hashMapOf<String, ObjectAnimator>()
private fun next(view: View?) {
view ?: return
if (factory?.setView(view) == true) {
view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
}
val width = view.measuredWidth
val parentWidth = measuredWidth
val targetValue = parentWidth - width
val animator = ObjectAnimator.ofFloat(view, PROPERTY_NAME, parentWidth.toFloat(), -width.toFloat()).apply {
// null即为默认线性插值器
interpolator = null
addUpdateListener(
RecyclerAnimatorUpdateListener(targetValue) {
next(obtain())
removeUpdateListener(it)
}
)
addListener(this@MarqueeAnimalView)
factory?.setAnimator(this, width, parentWidth)
}
animators["${view.hashCode()}-${animator.hashCode()}"] = animator
animator.start()
}
动画监听
当动画结束时,需要对view进行回收,并对动画移除。取消动画时,需要将view强制归位
同时,为了方便使用,OnAttachStateChangeListener
使得整体动画更加平滑,也避免了view不可见时,动画仍然在持续执行浪费资源。当然如fragment不可见时的监听需要完善
override fun onAnimationEnd(animation: Animator?) {
(animation as? ObjectAnimator)?.let { animator ->
(animator.target as? View)?.let { view ->
animators.remove("${view.hashCode()}-${animator.hashCode()}")
recycle(view)
}
// target释放
animator.target = null
}
}
override fun onAnimationCancel(animation: Animator?) {
(animation as? ObjectAnimator)?.let { animator ->
(animator.target as? View)?.let { view ->
view.translationX = measuredWidth.toFloat()
}
}
}
override fun onViewAttachedToWindow(v: View?) {
if (animators.isNotEmpty()) {
resume()
} else {
start()
}
}
override fun onViewDetachedFromWindow(v: View?) {
pause()
}
对外能力
fun start() {
if (measuredWidth == 0) {
this.post {
// 如果测量还未完成,那就等待post后发起
next(obtain())
}
return
}
next(obtain())
}
fun stop() {
val it = animators.values.iterator()
while (it.hasNext()) {
val i = it.next()
it.remove()
i.cancel()
}
}
fun pause() {
for (i in animators.values) {
i.pause()
}
}
fun resume() {
for (i in animators.values) {
i.resume()
}
}
完整代码
欢迎支持,搜索MarqueeAnimalView即可 github.com/wjf-962464/Self_Demo.git
到此这篇关于Android实现循环轮播跑马灯的效果的文章就介绍到这了,更多相关Android跑马灯内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341