Android實(shí)現(xiàn)循環(huán)輪播跑馬燈的效果
先看效果


支持暫停,恢復(fù),view自定義和池化回收復(fù)用。使用上,只需要引入xml,并綁定factory即可,內(nèi)部會(huì)在attach時(shí)自動(dòng)開始
<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的思路,對(duì)view進(jìn)行回收復(fù)用,避免內(nèi)存持續(xù)增長(zhǎng),增大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)
}
}
}創(chuàng)造工廠
這里的思路源于ViewSwitchFactory
interface PoolViewFactory {
fun makeView(layoutInflater: LayoutInflater, parent: ViewGroup): View
fun setAnimator(objectAnimator: ObjectAnimator, width: Int, parentWidth: Int)
/**
* 返回值,代表view是否需要重新測(cè)量
*/
fun setView(view: View): Boolean
}輪詢切換
這里根據(jù)對(duì)動(dòng)畫進(jìn)行初始化,并設(shè)置合適的監(jiān)聽(tīng)。此時(shí)需要獲取當(dāng)view和parent的width,以用于標(biāo)定始末位置,需要注意x軸的正負(fù)方向。animators用于存儲(chǔ)開始的動(dòng)畫,這也是設(shè)計(jì)時(shí)存在的遺留問(wèn)題,因?yàn)橹鲃?dòng)取消所有動(dòng)畫,但view->animator是單向綁定關(guān)系,所以需要保存發(fā)生的動(dòng)畫
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即為默認(rèn)線性插值器
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()
}動(dòng)畫監(jiān)聽(tīng)
當(dāng)動(dòng)畫結(jié)束時(shí),需要對(duì)view進(jìn)行回收,并對(duì)動(dòng)畫移除。取消動(dòng)畫時(shí),需要將view強(qiáng)制歸位
同時(shí),為了方便使用,OnAttachStateChangeListener使得整體動(dòng)畫更加平滑,也避免了view不可見(jiàn)時(shí),動(dòng)畫仍然在持續(xù)執(zhí)行浪費(fèi)資源。當(dāng)然如fragment不可見(jiàn)時(shí)的監(jiān)聽(tīng)需要完善
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()
}對(duì)外能力
fun start() {
if (measuredWidth == 0) {
this.post {
// 如果測(cè)量還未完成,那就等待post后發(fā)起
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
到此這篇關(guān)于Android實(shí)現(xiàn)循環(huán)輪播跑馬燈的效果的文章就介紹到這了,更多相關(guān)Android跑馬燈內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中懸浮窗口的實(shí)現(xiàn)原理實(shí)例分析
這篇文章主要介紹了Android中懸浮窗口的實(shí)現(xiàn)原理,以實(shí)例形式較為詳細(xì)的分析了Android懸浮窗口的原理與具體實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
Android Studio中一套代碼多渠道打包的實(shí)現(xiàn)方法
這篇文章主要介紹了Android Studio中一套代碼多渠道打包的實(shí)現(xiàn)方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-05-05
解析Android游戲中獲取電話狀態(tài)進(jìn)行游戲暫?;蚶^續(xù)的解決方法
本篇文章是對(duì)在Android游戲中獲取電話狀態(tài)進(jìn)行游戲暫停或繼續(xù)的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Android中的webview監(jiān)聽(tīng)每次URL變化實(shí)例
這篇文章主要介紹了Android中的webview監(jiān)聽(tīng)每次URL變化實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
loadavg數(shù)據(jù)異常引發(fā)問(wèn)題起源分析
這篇文章主要為大家介紹了loadavg數(shù)據(jù)異常引發(fā)問(wèn)題起源分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
android自定義組件實(shí)現(xiàn)儀表計(jì)數(shù)盤
這篇文章主要為大家詳細(xì)介紹了android自定義組件實(shí)現(xiàn)儀表計(jì)數(shù)盤,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11
Android編程實(shí)現(xiàn)畫板功能的方法總結(jié)【附源碼下載】
這篇文章主要介紹了Android編程實(shí)現(xiàn)畫板功能的方法,結(jié)合實(shí)例形式總結(jié)分析了Android基于自定義View與Canvas類實(shí)現(xiàn)畫板功能的具體操作步驟與相關(guān)注意事項(xiàng),需要的朋友可以參考下2018-02-02
怎樣刪除android的gallery中的圖片實(shí)例說(shuō)明
長(zhǎng)按gallery中的圖片進(jìn)行刪除該圖片的操作,具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下哈2013-06-06
Android不使用自定義布局情況下實(shí)現(xiàn)自定義通知欄圖標(biāo)的方法
這篇文章主要介紹了Android不使用自定義布局情況下實(shí)現(xiàn)自定義通知欄圖標(biāo)的方法,實(shí)例分析了Android通知欄圖標(biāo)的創(chuàng)建技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-12-12

