Android實(shí)現(xiàn)橫向無(wú)限循環(huán)滾動(dòng)的單行彈幕效果
本期將帶領(lǐng)大家實(shí)現(xiàn)一個(gè)這樣的效果,支持無(wú)限循環(huán)的單行彈幕效果。
實(shí)現(xiàn)思路分析
要實(shí)現(xiàn)上面的效果,我們先拆分下實(shí)現(xiàn)要素:
1、彈幕布局是從屏幕的右側(cè)向左側(cè)滾動(dòng),單個(gè)彈幕之間的間距是固定的(設(shè)計(jì)要求)
2、彈幕要支持無(wú)限滾動(dòng),出于性能要求,如果不在屏幕內(nèi)的,應(yīng)該移除,不能無(wú)限追加到內(nèi)存里面。
拆分完需求要素之后,針對(duì)上面的需求要素,做一下思路解答:
1、對(duì)于滾動(dòng)和超出屏幕后移除,可以使用動(dòng)畫(huà)來(lái)實(shí)現(xiàn),動(dòng)畫(huà)從屏幕右邊開(kāi)始移動(dòng)到屏幕左邊,監(jiān)聽(tīng)如果已經(jīng)動(dòng)畫(huà)結(jié)束,則remove掉布局。
2、無(wú)限循環(huán)效果,可以使用兩個(gè)鏈表實(shí)現(xiàn),一個(gè)保存加入到屏幕的彈幕數(shù)據(jù)(A),另一個(gè)保存未添加到屏幕的彈幕數(shù)據(jù)(B)。讓進(jìn)入屏幕前將布局從B中poll出來(lái),添加到A中。反之,屏幕移除的時(shí)候從A中poll出來(lái),添加到B中。
代碼實(shí)現(xiàn)
首先創(chuàng)建出來(lái)一個(gè)彈幕數(shù)據(jù)對(duì)象類(lèi)
data class Danmu( //頭像 var headerUrl: String? = null, //昵稱(chēng) var userName: String? = null, //信息 var info: String? = null, )
要被使用的彈幕itemView
class DanmuItemView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : LinearLayoutCompat(context, attrs, defStyleAttr) { private var danmuItemView: TextView? = null var danmu: Danmu? = null init { LayoutInflater.from(context).inflate(R.layout.danmu_item, this, true) danmuItemView = findViewById(R.id.tvDanmuItem) } fun setDanmuEntity(danmu: Danmu) { this.danmu = danmu danmuItemView?.text = "我是一個(gè)彈幕~~~~~哈哈哈哈哈哈" + danmu.userName measure(0, 0) } }
接下來(lái)就是彈幕布局的容器類(lèi),用來(lái)控制動(dòng)畫(huà)和數(shù)據(jù)交替。注意代碼中有很有用的注釋
class DanmuView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr) { private var mWidth = 0 //為展示在屏幕上的彈幕數(shù)據(jù) private val mDanMuList = LinkedList<Danmu>() //屏幕中展示的彈幕數(shù)據(jù) private val mVisibleDanMuList = LinkedList<Danmu>() //判斷是否在運(yùn)行 private val mIsRunning = AtomicBoolean(false) override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) mWidth = measuredWidth } /** * 添加彈幕數(shù)據(jù) */ fun enqueueDanMuList(danMuList: ArrayList<Danmu>) { 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 { //這里是用來(lái)處理布局的交替工作,前面分析有說(shuō)明 mVisibleDanMuList.add(this) createDanMuItemView(this) } } } }) } else { if (mIsRunning.get().not()) { mDanMuList.poll()?.apply { //這里是用來(lái)處理布局的交替工作,前面分析有說(shuō)明 mVisibleDanMuList.add(this) createDanMuItemView(this) } } } } private fun startDanMuAnimate(danMuItemView: DanmuItemView) { var isInit = false danMuItemView.animate() //注意這邊設(shè)置的便宜量是容器布局的寬度+彈幕item布局的寬度,這樣就確保滾動(dòng)值剛好是從屏幕右側(cè)外到屏幕左側(cè)外 .translationXBy((-(mWidth + danMuItemView.measuredWidth)).toFloat()) .setDuration(6000) .setInterpolator(LinearInterpolator()) .setUpdateListener { val danMuTranslateX = (mWidth + danMuItemView.measuredWidth) * (it.animatedValue as Float) //這里是關(guān)鍵,用來(lái)確保每個(gè)item布局的間距一致,判斷如果滾動(dòng)進(jìn)入屏幕的距離剛好是自身+20dp,也就是剛好空出來(lái)了20dp之后,緊接著下一個(gè)彈幕布局開(kāi)始添加并動(dòng)起來(lái)。 if (danMuTranslateX >= danMuItemView.measuredWidth + Utils.convertDpToPixel(20F) && isInit.not()) { isInit = true mDanMuList.poll()?.apply { mVisibleDanMuList.add(this) createDanMuItemView(this) } } } .setListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { if (mIsRunning.get().not()) { mIsRunning.set(true) } //很重要,在動(dòng)畫(huà)結(jié)束,也就是布局從屏幕移除之后,切記從布局中移除掉, //并且進(jìn)行一波數(shù)據(jù)交替,方便實(shí)現(xiàn)無(wú)線循環(huán) danMuItemView.danmu?.let { mVisibleDanMuList.remove(it) mDanMuList.add(it) } removeView(danMuItemView) } }).start() } private fun createDanMuItemView(danMu: Danmu) { val danMuItemView = DanmuItemView(context).apply { setDanmuEntity(danMu) } //這里將布局添加之后,默認(rèn)便宜到屏幕右側(cè)出屏幕,造成布局總是從右👉移動(dòng)到👈左的效果。 val param = LayoutParams(danMuItemView.measuredWidth, danMuItemView.measuredHeight) param.gravity = Gravity.CENTER_VERTICAL param.leftMargin = mWidth startDanMuAnimate(danMuItemView) addView(danMuItemView, param) } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- android開(kāi)發(fā)之橫向滾動(dòng)/豎向滾動(dòng)的ListView(固定列頭)
- Android程序開(kāi)發(fā)之ListView實(shí)現(xiàn)橫向滾動(dòng)(帶表頭與固定列)
- 詳解Android使GridView橫向水平滾動(dòng)的實(shí)現(xiàn)方式
- Android使用GridView實(shí)現(xiàn)橫向滾動(dòng)效果
- Android開(kāi)發(fā)實(shí)現(xiàn)橫向列表GridView橫向滾動(dòng)的方法【附源碼下載】
- Android GridView實(shí)現(xiàn)橫向列表水平滾動(dòng)
- Android自定義ViewGroup實(shí)現(xiàn)可滾動(dòng)的橫向布局(2)
- Android實(shí)現(xiàn)自定義的彈幕效果
- 實(shí)例解析如何在Android應(yīng)用中實(shí)現(xiàn)彈幕動(dòng)畫(huà)效果
- Android 實(shí)現(xiàn)仿網(wǎng)絡(luò)直播彈幕功能詳解及實(shí)例
相關(guān)文章
Android開(kāi)發(fā)實(shí)現(xiàn)判斷通知欄是否打開(kāi)及前往設(shè)置頁(yè)面的方法
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)判斷通知欄是否打開(kāi)及前往設(shè)置頁(yè)面的方法,涉及Android通知欄的打開(kāi)、判斷、設(shè)置等相關(guān)操作技巧,需要的朋友可以參考下2018-01-01Android自定義textview實(shí)現(xiàn)豎直滾動(dòng)跑馬燈效果
這篇文章主要為大家詳細(xì)介紹了Android自定義textview實(shí)現(xiàn)豎直滾動(dòng)跑馬燈效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Adapter實(shí)現(xiàn)ListView帶多選框等狀態(tài)的自定義控件的注意事項(xiàng)
Android本身為L(zhǎng)istView提供了幾個(gè)方便的Adapter,比如ArrayAdapter、SimpleCurrentAdapter等等,接下來(lái)介紹自定義Adapter實(shí)現(xiàn)ListView帶多選框等狀態(tài)控件的注意事項(xiàng),感興趣的朋友可以詳細(xì)了解下,或許對(duì)你有所幫助2013-01-01android studio3.0.1無(wú)法啟動(dòng)Gradle守護(hù)進(jìn)程的解決方法
這篇文章主要為大家詳細(xì)介紹了android studio3.0.1無(wú)法啟動(dòng)Gradle守護(hù)進(jìn)程的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08Android 5秒學(xué)會(huì)使用手勢(shì)解鎖功能
本文講述的是一個(gè)手勢(shì)解鎖的庫(kù),可以定制顯示隱藏宮格點(diǎn)、路徑、并且?guī)в行【艑m格顯示圖,和震動(dòng)!讓你學(xué)會(huì)使用這個(gè)簡(jiǎn)單,高效的庫(kù),好了,具體內(nèi)容詳情大家通過(guò)本文學(xué)習(xí)吧2017-12-12Android實(shí)現(xiàn)語(yǔ)音數(shù)據(jù)實(shí)時(shí)采集、播放
這篇文章主要介紹了android實(shí)現(xiàn)語(yǔ)音數(shù)據(jù)實(shí)時(shí)采集、播放的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12Android項(xiàng)目開(kāi)發(fā) 教你實(shí)現(xiàn)Periscope點(diǎn)贊效果
這篇文章主要為大家分享了Android項(xiàng)目開(kāi)發(fā),一步一步教你實(shí)現(xiàn)Periscope點(diǎn)贊效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-12-12淺談Android Studio 3.0 工具新特性的使用 Android Profiler 、Device File
這篇文章主要介紹了淺談Android Studio 3.0 工具新特性的使用 Android Profiler 、Device File Explorer的相關(guān)資料,需要的朋友可以參考下2017-11-11Android實(shí)現(xiàn)3D推拉門(mén)式滑動(dòng)菜單源碼解析
這篇文章主要為大家詳細(xì)解析了Android實(shí)現(xiàn)3D推拉門(mén)式滑動(dòng)菜單源碼以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。2017-11-11