最近看到QQ音乐的歌词每次滑动后都可以滚回到中间位置。觉得甚是神奇,打开开发者模式显示布局,发现歌词部分不是采用 android 控件的写的,应该是前端写的。于是,我想,能不能用 recyclerView 实现这个自动回滚到中间位置呢。
功夫不负有心人,查找了一些资料之后,终于搞定了。
下面由我细细讲来。
目标
点击某个条目,在经过4s无任何操作之后,该条目滚动到中间位置显示。点击后,用户在滑动,等用户不操作后再开始延时。用户多次点击,记最后一次点击位置。
分析
首先先考虑,滚动到指定位置是如何操作的?
//滚动到指定位置
recyclerView.scrollToPosition(position);//平滑滚动到指定位置
recyclerView.smoothScrollToPosition(position);
有没有滚动到制定像素位置呢?
//scrollBy(x, y)这个方法是自己去控制移动的距离,单位是像素,所以在使用scrollBy(x, y)需要自己去计算移动的高度或宽度。
recyclerView.scrollBy(x, y)
可是,问题是滚动到中间位置啊?这个怎么办呢?这样子行不行呢?
mRecyclerView.scrollToPosition(0);
mRecyclerView.scrollBy(0,400);
先滚动到制定位置,在滚动一段距离不就好了?运行发现,这两行代码只执行第一行,第二行无效。
debug 调试看了下,还是没有弄懂,实现太复杂。
那就是说这样是不行的,那有没有其他办法呢?
RecyclerView 有一个滚动监听方法:
mRecyclerView.addOnScrollListener(newRecyclerView.OnScrollListener() {
@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, intnewState) {super.onScrollStateChanged(recyclerView, newState);
}
@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, intdy) {super.onScrolled(recyclerView, dx, dy);
}
});
onScrollStateChanged 方法对应三种状态:静止(SCROLL_STATE_IDLE),拖动滚动(SCROLL_STATE_DRAGGING),滑动(SCROLL_STATE_SETTLING)。
当手动缓慢滑动的时候,会触发:onScrollStateChanged (拖动滚动) --> (n个)onScrolled -->onScrollStateChanged(静止);
当手快速滑动的时候,会触发:onScrollStateChanged (拖动滚动) --> (n个)onScrolled --> onScrollStateChanged (滑动) -->
(n个)onScrolled--> onScrollStateChanged (静止);
有想法了,点击的时候,先运行 scrollToPosition,在 onScrolled 方法里面 运行 scrollBy 方法。写代码,运行,通过。
下面就是中间位置的计算了。
首先计算出 recylerview 的展现高度。
Rect rect = newRect();
mRecyclerView.getGlobalVisibleRect(rect);
reHeight= rect.bottom - rect.top - vHeight;
当运行 scrollToPosition 后,点击条目就会出现在视野当中,这时候,计算出相应的位移即可。需要注意一点的是,当点击条目在视野内的时候,是不会运行 scrollToPosition 方法的。
int top = mRecyclerView.getChildAt(position -firstPosition).getTop();int half = reHeight / 2;
mRecyclerView.scrollBy(0, top - half);
最后就是延时的设定,采用Handler 进行延时。
代码
核心代码如下:
public class MainActivity extendsAppCompatActivity {private static final String TAG = "MainActivity";privateRecyclerView mRecyclerView;privateLinearLayoutManager mLayoutManager;privateRecyclerView.Adapter mAdapter;privateString[] data;privateHandler handler;private boolean isClick = false;private static int vHeight = -1;private static int reHeight = -1;private static int position = 0;private static final int target = 10;private static boolean isMove = false;privateRunnable runnable;
@Overrideprotected voidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler= newHandler();
mRecyclerView=(RecyclerView) findViewById(R.id.my_recycler_view);//创建默认的线性LayoutManager
mLayoutManager = new LinearLayoutManager(this);
mLayoutManager.setAutoMeasureEnabled(true);
mRecyclerView.setLayoutManager(mLayoutManager);//如果可以确定每个item的高度是固定的,设置这个选项可以提高性能
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setNestedScrollingEnabled(false);
data= new String[]{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21"};
runnable= newRunnable() {
@Overridepublic voidrun() {if(isVisible()) {
scrollToMiddle();
}else{
mRecyclerView.scrollToPosition(position);
isMove= true;
isClick= false;
}
}
};
mAdapter = new MyAdapter(data, newMyAdapter.onRecyclerViewItemClick() {
@Overridepublic void onItemClick(View v, intpos) {
Toast.makeText(MainActivity.this, "第" + pos + "行", Toast.LENGTH_SHORT).show();
position=pos;
vHeight=v.getHeight();
Rect rect= newRect();
mRecyclerView.getGlobalVisibleRect(rect);
reHeight= rect.bottom - rect.top -vHeight;//handler.removeCallbacksAndMessages(null);
handler.removeCallbacks(runnable);
handler.postDelayed(runnable,4000);
isClick= true;
}
});
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addOnScrollListener(newRecyclerView.OnScrollListener() {
@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, intnewState) {super.onScrollStateChanged(recyclerView, newState);
Log.d(TAG,"" +newState);if (newState == RecyclerView.SCROLL_STATE_DRAGGING && !isMove) {
handler.removeCallbacks(runnable);
}if (newState ==RecyclerView.SCROLL_STATE_IDLE) {if(isClick) {
handler.postDelayed(runnable,4000);
}
}
}
@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, intdy) {super.onScrolled(recyclerView, dx, dy);if(isMove) {if (vHeight < 0) {
isMove= false;return;
}
scrollToMiddle();
}
}
});public voidscrollToMiddle() {final int firstPosition =mLayoutManager.findFirstVisibleItemPosition();int top = mRecyclerView.getChildAt(position -firstPosition).getTop();
Log.d(TAG," position" + position + " " +top);int half = reHeight / 2;
mRecyclerView.scrollBy(0, top -half);
isMove= false;
}public booleanisVisible() {final int firstPosition =mLayoutManager.findFirstVisibleItemPosition();final int lastPosition =mLayoutManager.findLastVisibleItemPosition();return position <= lastPosition && position >=firstPosition;
}
@Overrideprotected voidonDestroy() {super.onDestroy();
handler.removeCallbacksAndMessages(null);
handler= null;
}
}