Android实现循环轮播跑马灯的效果
作者:Calculus_小王
这篇文章主要介绍了为大家详细介绍了如何通过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) /** * 返回值,代表view是否需要重新测量 */ 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跑马灯内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!