Android實現(xiàn)無限循環(huán)滾動彈幕的代碼示例
,文中通過代碼示例講解的非常詳細,對大家實現(xiàn)循環(huán)彈幕有一定的幫助,需要的朋友可以參考下
先上效果圖
要實現(xiàn)上面的效果,我們先拆分下實現(xiàn)要素:
- 1、彈幕布局是從屏幕的右側(cè)向左側(cè)滾動,單個彈幕之間的間距是固定的,并且滾動速度需要勻速
- 2、彈幕要支持無限滾動,出于性能要求,如果不在屏幕內(nèi)的,應該移除,不能無限追加到內(nèi)存里面。
思路
1、對于滾動和超出屏幕后移除,可以使用動畫來實現(xiàn),動畫從屏幕右邊開始移動到屏幕左邊,監(jiān)聽如果已經(jīng)動畫結(jié)束,則remove掉布局。
2、無限循環(huán)效果,可以使用兩個鏈表實現(xiàn),一個保存加入到屏幕的彈幕數(shù)據(jù)(A),另一個保存未添加到屏幕的彈幕數(shù)據(jù)(B)。讓進入屏幕前將布局從B中poll出來,添加到A中。反之,屏幕移除的時候從A中poll出來,添加到B中。
實現(xiàn)
1.無線滾動 需要兩個鏈表 mDanMuList初始包含所有彈幕數(shù)據(jù) mVisibleDanMuList每次取出mDanMuList第一條數(shù)據(jù) 填充到彈幕 直到彈幕動畫結(jié)束后,再重新添加到mDanMuList里
//為展示在屏幕上的彈幕數(shù)據(jù) private val mDanMuList = LinkedList<BulletChatDetail>() //屏幕中展示的彈幕數(shù)據(jù) private val mVisibleDanMuList = LinkedList<BulletChatDetail>() fun enqueueDanMuList(danMuList: List<BulletChatDetail>?) { danMuList?.forEach { if (this.mDanMuList.contains(it).not()) { this.mDanMuList.add(it) } } if (mWidth == 0) { viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { mWidth = measuredWidth viewTreeObserver.removeOnGlobalLayoutListener(this) if (mIsRunning.get().not()) { mDanMuList.poll()?.apply { //這里是用來處理布局的交替工作,前面分析有說明 mVisibleDanMuList.add(this) createDanMuItemView(this) } } } }) } else { if (mIsRunning.get().not()) { mDanMuList.poll()?.apply { //這里是用來處理布局的交替工作,前面分析有說明 mVisibleDanMuList.add(this) createDanMuItemView(this) } } } }
2.添加動畫 每個彈幕默認都是頭像加描述組成 所以自定義一個DanMuItemView 內(nèi)部就一個填充數(shù)據(jù)的方法
class DanMuItemView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : LinearLayoutCompat(context, attrs, defStyleAttr) { var danMu: BulletChatDetail? = null private val mBinding: DanmuItemBinding init { mBinding = DanmuItemBinding.inflate( LayoutInflater.from(context), this, true ) } fun setDanMuEntity(danmu: BulletChatDetail?) { this.danMu = danmu mBinding.tvDanMuItem.text = danmu?.bulletChat mBinding.ivDanMuItem.setCircleImageUrl(danmu?.userLogo?.toCompleteImageUrl(3)) measure(0, 0) } }
3.監(jiān)聽viewtree樹,當布局初始化完成后,創(chuàng)建彈幕view,并開啟動畫 DanMuItemView的位置 默認在-danMuItemView.measuredWidth 負彈幕view的寬度
private fun createDanMuItemView(DanMu: BulletChatDetail) { val danMuItemView = DanMuItemView(context).apply { setDanMuEntity(DanMu) } //這里將布局添加之后,默認便宜到屏幕右側(cè)出屏幕,造成布局總是從右??移動到??左的效果。 val param = LayoutParams(danMuItemView.measuredWidth, danMuItemView.measuredHeight) param.gravity = Gravity.CENTER_VERTICAL param.leftMargin = mWidth startDanMuAnimate(danMuItemView) addView(danMuItemView, param) }
4 .開啟動畫 view默認有動畫的方法 我們已知view移動的距離為 屏幕寬度+彈幕view 需要勻速120.0f // 你想要的目標速度,單位是像素每秒 所以時間得動態(tài)計算 并且我們知道每個彈幕間ui的間距 所以監(jiān)聽彈幕view勻速動畫所在的位置 當滾動進入屏幕的距離剛好是自身+間距的20dp時,就開啟第二個彈幕item的動畫 這樣就實現(xiàn)了無限彈幕
val targetSpeedPxPerSecond = 120.0f // 你想要的目標速度,單位是像素每秒 val totalTranslateX = ((mWidth + danMuItemView.measuredWidth)).toFloat() val duration = (totalTranslateX / targetSpeedPxPerSecond * 1000).toLong() // 轉(zhuǎn)換為毫秒 var isInit = false danMuItemView.animate() //注意這邊設置的便宜量是容器布局的寬度+彈幕item布局的寬度,這樣就確保滾動值剛好是從屏幕右側(cè)外到屏幕左側(cè)外 .translationXBy(-totalTranslateX) .setDuration(duration) .setInterpolator(LinearInterpolator()) .setUpdateListener { val danMuTranslateX =(mWidth + danMuItemView.measuredWidth) * (it.animatedValue as Float) //這里是關鍵,用來確保每個item布局的間距一致,判斷如果滾動進入屏幕的距離剛好是自身+20dp,也就是剛好空出來了20dp之后,緊接著下一個彈幕布局開始添加并動起來。 if (danMuTranslateX >= danMuItemView.measuredWidth + DensityUtils.dp2px(15f) && isInit.not()) { isInit = true if (mDanMuList.size == 0) { mHasStopRun = true }else{ mHasStopRun = false mDanMuList.poll()?.apply { mVisibleDanMuList.add(this) createDanMuItemView(this) } } } } .setListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { super.onAnimationEnd(animation) if (mIsRunning.get().not()) { mIsRunning.set(true) } //很重要,在動畫結(jié)束,也就是布局從屏幕移除之后,切記從布局中移除掉, //并且進行一波數(shù)據(jù)交替,方便實現(xiàn)無線循環(huán) danMuItemView.danMu?.let { mVisibleDanMuList.remove(it) mDanMuList.add(it) } YzLog.e("mHasStopRun->>>>${mHasStopRun}") if (mHasStopRun) { createDanMu() }else{ removeView(danMuItemView) } } }).start()
當數(shù)據(jù)只有一兩個的時候,需要添加一個變量來控制 比如如果彈幕數(shù)據(jù)源只有兩條數(shù)據(jù),當兩條彈幕更好在同一屏幕出現(xiàn)此時彈幕數(shù)據(jù)源為空,可見數(shù)據(jù)源為兩條,當?shù)诙l彈幕滾動到符合添加第三條數(shù)據(jù)的位置時,此時數(shù)據(jù)源已經(jīng)沒有數(shù)據(jù)了,需要將參數(shù)置為true,等到第二條數(shù)據(jù)動畫結(jié)束時,重新走添加彈幕邏輯。具體事項在上面代碼已經(jīng)展示
以上就是Android實現(xiàn)無限循環(huán)滾動彈幕的代碼示例的詳細內(nèi)容,更多關于Android無限循環(huán)滾動彈幕的資料請關注腳本之家其它相關文章!
相關文章
基于Android實現(xiàn)跳轉(zhuǎn)到WiFi開關設置頁的詳細步驟
在Android應用開發(fā)中,有時候需要引導用戶到特定的系統(tǒng)設置頁面,例如Wi-Fi開關設置頁,可以通過隱式Intent來實現(xiàn)這一功能,以下是詳細的步驟以及相關的Kotlin代碼示例,需要的朋友可以參考下2024-09-09Android巧用DecorView實現(xiàn)對話框功能
本篇文章主要介紹了Android巧用DecorView實現(xiàn)對話框功能,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04Flutter路由跳轉(zhuǎn)參數(shù)處理技巧詳解
這篇文章主要為大家介紹了Flutter路由跳轉(zhuǎn)參數(shù)處理技巧示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08Android非XML形式動態(tài)生成、調(diào)用頁面的方法
這篇文章主要介紹了Android非XML形式動態(tài)生成、調(diào)用頁面的方法,涉及Android構(gòu)建頁面的相關技巧,需要的朋友可以參考下2015-04-04