Android RecyclerView 代碼控制scroll時不能平緩滑動解決方案
作者:圣光啊那個敵人值得一戰(zhàn)
參考文章:http://blog.csdn.net/a86261566/article/details/50906456
參考文章作者:AndroidArenas
因為現(xiàn)在所在的公司的Android產(chǎn)品是發(fā)布在自己生產(chǎn)的設(shè)備上的,所以有時候會碰到比較奇葩的需求,比如周五就有一個,因為現(xiàn)在在做的是一款考勤軟件(其實早做好了給別人了但是里面有好多bug),而且它,不!帶!觸!摸!屏!(不要問我是怎么實現(xiàn)界面跳轉(zhuǎn)的,它帶物理鍵。。。我能怎么辦,我也很絕望啊)其中的課表在某一個區(qū)域顯示不全,所以讓它在自動滾動,本來當(dāng)時就是簡單的一秒走一個item,但是吧。。周五讓產(chǎn)品經(jīng)理挑刺了(話說我做這個項目的時候我可從來沒見過這公司神出鬼沒的產(chǎn)品經(jīng)理,就連產(chǎn)品經(jīng)理是誰都是各種小道消息,簡直服氣)"這個課表滾動的效果不好,一下走一格跟屎一樣!"
好吧,確實跟屎一樣,我自己仔細(xì)瞅了瞅也覺得這么不是回事,那就改唄?;厝スの凰妓髁肆艘幌?其實就是百度來著),發(fā)現(xiàn)RecycleView滾動起來好像有點(diǎn)不按套路出牌,順著smoothScrollToPosition方法點(diǎn)進(jìn)去看了下,它里面長這樣:
public void smoothScrollToPosition(int position) {
if (mLayoutFrozen) {
return;
}
if (mLayout == null) {
Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " +
"Call setLayoutManager with a non-null argument.");
return;
}
mLayout.smoothScrollToPosition(this, mState, position);
}
可以看見它的最后調(diào)用了mlayout這個類的smoothScrollToPosition方法,而點(diǎn)一下這個mlayout發(fā)現(xiàn)它的類型是LayoutManager的,可以,那除了我們設(shè)給它的LayoutManager沒其他的了,馬上去看了下LinearLayoutManager里面的對應(yīng)方法
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
LinearSmoothScroller linearSmoothScroller =
new LinearSmoothScroller(recyclerView.getContext());
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
來大家,我們看這個方法,他首先new了一個LinearSmoothScroller 類,點(diǎn)進(jìn)去看注釋,可以,沒看懂兄弟,那我們點(diǎn)進(jìn)構(gòu)造函數(shù)看看
public LinearSmoothScroller(Context context) {
MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
}
兄弟們!不覺得返回的這個變量名很可疑嗎!再點(diǎn)擊去看看。。
/**
* Calculates the scroll speed.
*
* @param displayMetrics DisplayMetrics to be used for real dimension calculations
* @return The time (in ms) it should take for each pixel. For instance, if returned value is
* 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds.
*/
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
來我們看這注釋,恩,計算滾動速度 參數(shù)為dpi,返回參數(shù)為1px滾動所需的時間(ms)
哎~這波就很nice了各位,這就意味著我們只需要重寫smoothScrollToPosition這個方法,然后在里面再重寫calculateSpeedPerPixel這個方法就可以大概理論上動態(tài)改變顯示位置時的滾動速度了,我們首先繼承LinearLayoutManager類,然后重寫其中的smoothScrollToPosition方法,例子如下:
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, final int position) {
LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 5;//返回滾過1px需要多少ms
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
然后將我們重寫的好的LayoutManager設(shè)到RecyclerView上,試驗了一下,滾動速度確實的變慢了,但是!我的需求并不只是僅僅變慢而已,我那是課表啊各位!所以我得知道它什么時候滾動到了最底部好讓我返回第一列重新滾動。
恩。。。。我是相信Google的開發(fā)者的,所以我相信他們絕對留下了接口來讓我知道滾動什么停止了的,回到剛才的smoothScrollToPosition方法,可以看見最后一行的startSmoothScroll方法,套路不變,點(diǎn)進(jìn)去
public void startSmoothScroll(SmoothScroller smoothScroller) {
if (mSmoothScroller != null && smoothScroller != mSmoothScroller
&& mSmoothScroller.isRunning()) {
mSmoothScroller.stop();
}
mSmoothScroller = smoothScroller;
mSmoothScroller.start(mRecyclerView, this);
}
恩,這里在判斷smoothScroller的實例在和以前不一樣以及其在滾動時會讓其立馬停止?jié)L動,也就是調(diào)了一下stop方法,我們再進(jìn)stop方法看看?
final protected void stop() {
if (!mRunning) {
return;
}
onStop();
mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION;
mTargetView = null;
mTargetPosition = RecyclerView.NO_POSITION;
mPendingInitialRun = false;
mRunning = false;
// trigger a cleanup
mLayoutManager.onSmoothScrollerStopped(this);
// clear references to avoid any potential leak by a custom smooth scroller
mLayoutManager = null;
mRecyclerView = null;
}
恩。。道理我都懂,這里各種初始化各種配置完了,但是第一行就調(diào)了個onStop什么意思?點(diǎn)進(jìn)去
/**
* Called when smooth scroller is stopped. This is a good place to cleanup your state etc.
* @see #stop()
*/
abstract protected void onStop();
你看各位,我就說他們肯定留接口了吧?
這樣我們就能知道什么時候滾動停止了,完整例子如下
/**
* Created by lip on 2017/2/24.
* 控制recycler滾動速度manager
*/
public class ControlRvSpeedLinearLayoutManager extends LinearLayoutManager {
public static final String NORMAL = "normal";
public static final String SLOW = "slow";
public static final String EXTREMELY_SLOW = "extremelySlow";
/**
* 滾動完成回調(diào)
*/
private StopScrollCallBack outStopScrollCallBack;
/**
* 滾動速度
*/
private String speed;
/**
*
* @param context 上下文
* @param stopScrollCallBack 滾動完成回調(diào)
* @param speed 滾動速度
*/
public ControlRvSpeedLinearLayoutManager(Context context, StopScrollCallBack stopScrollCallBack,String speed) {
super(context);
this.outStopScrollCallBack = stopScrollCallBack;
this.speed = speed;
}
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, final int position) {
LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
Log.i("滾動demo","======="+displayMetrics.densityDpi);
float ms = 1f;
switch (speed){
case NORMAL:
ms = 1f;
break;
case SLOW:
ms = 5f;
break;
case EXTREMELY_SLOW:
ms = 10f;
break;
}
return ms;//返回滾過1px需要多少ms
}
@Override
protected void onStop() {
super.onStop();
outStopScrollCallBack.scrollStop(position);
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
public interface StopScrollCallBack {
void scrollStop(int position);
}
}
這里我們傳入了一個接口回調(diào)StopScrollCallBack 好讓外面來實現(xiàn)具體在停止時的操作,使用方法如下:
BaseRecyclerAdapter<String> recyclerAdapter = new BaseRecyclerAdapter<String>(MainActivity.this, strings) {
@Override
public int getItemLayoutId(int viewType) {
return R.layout.text_rv_item_layout;
}
@Override
public void bindData(RecyclerView.ViewHolder holder, int position, String item) {
RecyclerViewHolder rv = (RecyclerViewHolder) holder;
rv.setText(R.id.tv_text, item);
}
};
ControlRvSpeedLinearLayoutManager controlRvSpeedLinearLayoutManager = new ControlRvSpeedLinearLayoutManager(MainActivity.this, new ControlRvSpeedLinearLayoutManager.StopScrollCallBack() {
@Override
public void scrollStop(final int position) {
handler.sendEmptyMessageDelayed(0,1000);
}
},ControlRvSpeedLinearLayoutManager.EXTREMELY_SLOW);
rvTextList.setLayoutManager(controlRvSpeedLinearLayoutManager);
rvTextList.addItemDecoration(new SpaceItemDecoration(20));
rvTextList.setAdapter(recyclerAdapter);
demo效果如下:

但是啊各位,不得不說一個坑,在回調(diào)回來的瞬間,不要立即進(jìn)行任何的讓其滾動的操作,因為各位看這一段代碼
public void startSmoothScroll(SmoothScroller smoothScroller) {
if (mSmoothScroller != null && smoothScroller != mSmoothScroller
&& mSmoothScroller.isRunning()) {
mSmoothScroller.stop();
}
mSmoothScroller = smoothScroller;
mSmoothScroller.start(mRecyclerView, this);
}
在座的各位,看見沒,它會在判斷其還在滾動的時候調(diào)用stop,而走我們回調(diào)的一瞬間它判斷就是在滾動,然后就會不停的走stop,馬上就拋stackoverflow了,所以各位。。。來個handler吧。。。。
具體demo在git上 https://github.com/LIPKKKK/RecyclerViewSmoothScrollDemo
感興趣的可以去看一下,謝謝支持