Android RecyclerView 代码控制scroll时不能平缓滑动解决方案
作者:圣光啊那个敌人值得一战
参考文章:http://blog.csdn.net/a86261566/article/details/50906456
参考文章作者:AndroidArenas
因为现在所在的公司的Android产品是发布在自己生产的设备上的,所以有时候会碰到比较奇葩的需求,比如周五就有一个,因为现在在做的是一款考勤软件(其实早做好了给别人了但是里面有好多bug),而且它,不!带!触!摸!屏!(不要问我是怎么实现界面跳转的,它带物理键。。。我能怎么办,我也很绝望啊)其中的课表在某一个区域显示不全,所以让它在自动滚动,本来当时就是简单的一秒走一个item,但是吧。。周五让产品经理挑刺了(话说我做这个项目的时候我可从来没见过这公司神出鬼没的产品经理,就连产品经理是谁都是各种小道消息,简直服气)"这个课表滚动的效果不好,一下走一格跟屎一样!"
好吧,确实跟屎一样,我自己仔细瞅了瞅也觉得这么不是回事,那就改呗。回去工位思索了了一下(其实就是百度来着),发现RecycleView滚动起来好像有点不按套路出牌,顺着smoothScrollToPosition方法点进去看了下,它里面长这样:
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);
}
可以看见它的最后调用了mlayout这个类的smoothScrollToPosition方法,而点一下这个mlayout发现它的类型是LayoutManager的,可以,那除了我们设给它的LayoutManager没其他的了,马上去看了下LinearLayoutManager里面的对应方法
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
LinearSmoothScroller linearSmoothScroller =
new LinearSmoothScroller(recyclerView.getContext());
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
来大家,我们看这个方法,他首先new了一个LinearSmoothScroller 类,点进去看注释,可以,没看懂兄弟,那我们点进构造函数看看
public LinearSmoothScroller(Context context) {
MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
}
兄弟们!不觉得返回的这个变量名很可疑吗!再点击去看看。。
/**
* 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;
}
来我们看这注释,恩,计算滚动速度 参数为dpi,返回参数为1px滚动所需的时间(ms)
哎~这波就很nice了各位,这就意味着我们只需要重写smoothScrollToPosition这个方法,然后在里面再重写calculateSpeedPerPixel这个方法就可以大概理论上动态改变显示位置时的滚动速度了,我们首先继承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设到RecyclerView上,试验了一下,滚动速度确实的变慢了,但是!我的需求并不只是仅仅变慢而已,我那是课表啊各位!所以我得知道它什么时候滚动到了最底部好让我返回第一列重新滚动。
恩。。。。我是相信Google的开发者的,所以我相信他们绝对留下了接口来让我知道滚动什么停止了的,回到刚才的smoothScrollToPosition方法,可以看见最后一行的startSmoothScroll方法,套路不变,点进去
public void startSmoothScroll(SmoothScroller smoothScroller) {
if (mSmoothScroller != null && smoothScroller != mSmoothScroller
&& mSmoothScroller.isRunning()) {
mSmoothScroller.stop();
}
mSmoothScroller = smoothScroller;
mSmoothScroller.start(mRecyclerView, this);
}
恩,这里在判断smoothScroller的实例在和以前不一样以及其在滚动时会让其立马停止滚动,也就是调了一下stop方法,我们再进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;
}
恩。。道理我都懂,这里各种初始化各种配置完了,但是第一行就调了个onStop什么意思?点进去
/**
* 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";
/**
* 滚动完成回调
*/
private StopScrollCallBack outStopScrollCallBack;
/**
* 滚动速度
*/
private String speed;
/**
*
* @param context 上下文
* @param stopScrollCallBack 滚动完成回调
* @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);
}
}
这里我们传入了一个接口回调StopScrollCallBack 好让外面来实现具体在停止时的操作,使用方法如下:
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效果如下:
但是啊各位,不得不说一个坑,在回调回来的瞬间,不要立即进行任何的让其滚动的操作,因为各位看这一段代码
public void startSmoothScroll(SmoothScroller smoothScroller) {
if (mSmoothScroller != null && smoothScroller != mSmoothScroller
&& mSmoothScroller.isRunning()) {
mSmoothScroller.stop();
}
mSmoothScroller = smoothScroller;
mSmoothScroller.start(mRecyclerView, this);
}
在座的各位,看见没,它会在判断其还在滚动的时候调用stop,而走我们回调的一瞬间它判断就是在滚动,然后就会不停的走stop,马上就抛stackoverflow了,所以各位。。。来个handler吧。。。。
具体demo在git上 https://github.com/LIPKKKK/RecyclerViewSmoothScrollDemo
感兴趣的可以去看一下,谢谢支持