- 需求是圖標(biāo)控件寬度固定,控件內(nèi)展示不全的話症虑,內(nèi)容會(huì)自動(dòng)滾動(dòng)到末尾,然后循環(huán)归薛。
首先想到是TextView的marquee屬性谍憔,如果TextView靚仔能包辦的話那自然極好。具體操作的 話主籍,TextView展示圖片有drawableLeft习贫、加載html、ImageSpan等方式千元,但都無法做到圖片屬性的完全自定義苫昌,況且TextView本身的跑馬燈效果還有些小瑕疵,遂舍棄~
接著是學(xué)習(xí)TextView源碼幸海,企圖在源碼基本找到跑馬燈邏輯
Marquee(TextView v) {
final float density = v.getContext().getResources().getDisplayMetrics().density;
mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
mView = new WeakReference<TextView>(v);
mChoreographer = Choreographer.getInstance();
}
void tick() {
if (mStatus != MARQUEE_RUNNING) {
return;
}
mChoreographer.removeFrameCallback(mTickCallback);
final TextView textView = mView.get();
if (textView != null && (textView.isFocused() || textView.isSelected())) {
long currentMs = mChoreographer.getFrameTime();
long deltaMs = currentMs - mLastAnimationMs;
mLastAnimationMs = currentMs;
float deltaPx = deltaMs * mPixelsPerMs;
mScroll += deltaPx;
if (mScroll > mMaxScroll) {
mScroll = mMaxScroll;
mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
} else {
mChoreographer.postFrameCallback(mTickCallback);
}
textView.invalidate();
}
}
看到 mChoreographer大佬就頭疼祟身,不忍卒讀[doge]
- 最后還是來到最愛的RecyclerView心頭肉環(huán)節(jié)。首先RecyclerView好處理圖片數(shù)據(jù)的加載和
自定義涕烧,剩下的就是怎么動(dòng)起來了月而。用CountDownTimer定時(shí),然后定時(shí)smoothScrollToPosition议纯,想想就很smooth父款,但效果不行,倏一下就滑動(dòng)完了瞻凤。還是老老實(shí)實(shí)用scrollBy吧憨攒,在很短時(shí)間內(nèi)滑動(dòng)很短距離,連起來的話就是連續(xù)光滑的滑動(dòng)了阀参,就像電影一樣肝集,好了,上代碼
private fun initTimer() {
Log.e("initTimer===", "initTimer")
if (timer == null) {
//單個(gè)item寬度
mWidth = ll.width + ll.marginLeft + ll.marginRight//單個(gè)item寬度
//recyclerView寬度
mParentWidth = recyclerView.width - recyclerView.paddingLeft - recyclerView.paddingRight
//如果可以顯示完整蛛壳,返回
if (mData.size * mWidth < mParentWidth) {
return
}
//需要滾動(dòng)的總長(zhǎng)度
var xTotalScroll = mWidth * mData.size - mParentWidth + 50L
//滾動(dòng)間隔
var interval = 50L
//每次滾動(dòng)距離
var step = 20
//總共滾動(dòng)時(shí)間
var time = xTotalScroll / step * interval
timer = object : CountDownTimer(time, interval) {
override fun onFinish() {
Handler().postDelayed({
//重置滾動(dòng)狀態(tài)
xScrolled = 0
recyclerView.scrollToPosition(0)
}, 1000)
realScroll()
}
override fun onTick(p0: Long) {
recyclerView.scrollBy(step, 0)
xScrolled += step
}
}
realScroll()
}
}
這樣就完成跑馬燈邏輯了杏瞻,剩下就是關(guān)于性能優(yōu)化的思考。上面的代碼都是寫在RecyclerView的adapter中的衙荐,所以在adapter的onAttachedToRecyclerView和onDetachedFromRecyclerView中就應(yīng)該有相應(yīng)的回收和初始化工作捞挥。
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
//初始化跑馬燈
recyclerView.post {
initTimer()
}
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
//停止計(jì)時(shí)器
stopWork()
}
相應(yīng)地,在Activity的onResume和onPause狀態(tài)監(jiān)聽中也要做相應(yīng)的工作忧吟,具體實(shí)現(xiàn)是通過adapter繼承LifecycleObserver砌函,面試常問jetpack的lifecycle,不得不說lifecycle就是好用
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
Log.e("onResume===", "onResume")
if (paused) {
recyclerView.post {
initTimer()
}
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
Log.e("onPause===", "onPause")
paused = true
stopWork()
}
對(duì)應(yīng)的,在Activity中注冊(cè)觀察者
lifecycle.addObserver(mAdapter)