一、前言
之前一篇博客使用 ViewPager 实现轮播图《Android ViewPager 实现循环轮播图》,但是 ViewPager 有个天生的缺陷是 View 无法重用,此外 ViewPager 的滑动过程会频繁 requestLayout,尽管可以通过 addViewInLayout 和 removeViewInLayout 配合 PagerAdapter 的 startUpdate 和 finishUpdate 可以减少重绘,但在 ListView 和 RecyclerView 中仍然达不到最好的效果。因此,使用一种新的方式十分必要。
二、代码实现
我们这里主要需要处理几个代码
2.1 onTouch事件处理
第一个是事件拦截和处理,手指按下时停止播放,手指抬起时恢复播放
scss
@Override
public boolean onTouchEvent(MotionEvent e) {
int action = e.getAction();
if(action==MotionEvent.ACTION_DOWN){
lastIsPlayState = isPlaying;
if(lastIsPlayState){
stopPlay();
}
}else if(action==MotionEvent.ACTION_UP || action==MotionEvent.ACTION_CANCEL){
if(lastIsPlayState){
startPlay();
}
}
return super.onTouchEvent(e);
}
2.2 fling事件处理
fling可以理解为瞬间滑动,其中核心的两个因素是velocityX和velocityY, 用来测算可以匀减速运动多少距离。
为什么要处理这个事件呢?主要还是担心一下滑动超过一个页面,主要操作为
滑动慢的时候判断是否超过了屏幕一半,超过的情况滑动到下一个Page,否者返回去
滑动很快的话直接到下个页面
另外,Flinger事件被拦截,那就意味着RecyclerView中的ViewFlinger不能处理Fling效果。
2.3 SRCOLL_STATE_IDEL 状态处理
偶然会遇到微小的滑动,但是这个我们在onTouch中处理可能影响RecyclerView的一些内部机制,因此需要对SCROLL_SATTE_IDEL 做补偿处理,防止出现IDLE状态后页面没有正确归位。
2.4 全部代码
RecyclerPagerView
scss
public class RecyclerPagerView extends RecyclerView implements Handler.Callback {
private static final long TASK_TIMEOUT = 3000;
public OnPageChangeListener onPageChangeListener;
private final Handler mRecyclerHandler;
private final int MSG_PLAY_NEXT = 112233;
private volatile boolean isPlaying = false;
private boolean lastIsPlayState = false;
private int realPosition = -1;
public RecyclerPagerView(Context context) {
this(context,null);
}
public RecyclerPagerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public RecyclerPagerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mRecyclerHandler = new Handler(Looper.getMainLooper(),this);
}
public void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
this.onPageChangeListener = onPageChangeListener;
if(this.onPageChangeListener!=null){
addOnScrollListener(this.onPageChangeListener);
int currentItem = getCurrentItem();
this.onPageChangeListener.onPageSelection(currentItem);
}
}
public int getCurrentItem(){
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
return linearLayoutManager.findFirstVisibleItemPosition();
}
public void setCurrentItem(int position,boolean isAnimate){
Adapter adapter = getAdapter();
if(adapter==null || adapter.getItemCount()<=position){
return;
}
if(!isAnimate)
{
scrollToPosition(position);
}else {
smoothScrollToPosition(position);
}
}
public void setCurrentItem(int position ){
setCurrentItem(position,true);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
int action = e.getAction();
if(action==MotionEvent.ACTION_DOWN){
lastIsPlayState = isPlaying;
if(lastIsPlayState){
stopPlay();
}
}else if(action==MotionEvent.ACTION_UP || action==MotionEvent.ACTION_CANCEL){
if(lastIsPlayState){
startPlay();
}
}
return super.onTouchEvent(e);
}
@Override
public boolean fling(int velocityX, int velocityY) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
int screenWidth = getWidth();
// views on the screen
int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);
// distance we need to scroll
int leftMargin = (screenWidth - lastView.getWidth()) / 2;
int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
int leftEdge = lastView.getLeft();
int rightEdge = firstView.getRight();
int scrollDistanceLeft = leftEdge - leftMargin;
int scrollDistanceRight = rightMargin - rightEdge;
int targetPosition;
if (Math.abs(velocityX) < 1500) {
// 滑动慢的时候判断是否超过了屏幕一半,超过的情况滑动到下一个Page,否者返回去
if (leftEdge > screenWidth / 2) {
// go to next page
smoothScrollBy(-scrollDistanceRight, 0);
targetPosition = firstVisibleItemPosition;
} else if (rightEdge < screenWidth / 2) {
// go to next page
smoothScrollBy(scrollDistanceLeft, 0);
targetPosition = firstVisibleItemPosition+1;
} else {
// stay at current page
if (velocityX > 0) {
smoothScrollBy(-scrollDistanceRight, 0);
} else {
smoothScrollBy(scrollDistanceLeft, 0);
}
targetPosition = firstVisibleItemPosition;
}
} else {
// 滑动很快的话直接到下个页面
if (velocityX > 0) {
smoothScrollBy(scrollDistanceLeft, 0);
targetPosition = firstVisibleItemPosition+1;
} else {
smoothScrollBy(-scrollDistanceRight, 0);
targetPosition = firstVisibleItemPosition;
}
}
Log.e("RecyclerPagerView","nextPage="+targetPosition);
if(this.onPageChangeListener!=null){
realPosition = targetPosition;
this.onPageChangeListener.onPageSelection(targetPosition);
}
return true;
}
@Override
public void onScrollStateChanged(final int state) {
super.onScrollStateChanged(state);
if (state == SCROLL_STATE_IDLE) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
int screenWidth = getWidth();
int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);
// distance we need to scroll
int leftMargin = (screenWidth - lastView.getWidth()) / 2;
int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
int leftEdge = lastView.getLeft();
int rightEdge = firstView.getRight();
int scrollDistanceLeft = leftEdge - leftMargin;
int scrollDistanceRight = rightMargin - rightEdge;
int targetPosition = -1;
if (leftEdge > screenWidth / 2) {
smoothScrollBy(-scrollDistanceRight, 0);
targetPosition = firstVisibleItemPosition+1;
} else if (rightEdge < screenWidth / 2) {
smoothScrollBy(scrollDistanceLeft, 0);
targetPosition = lastVisibleItemPosition;
}else{
targetPosition = firstVisibleItemPosition;
}
if(this.onPageChangeListener!=null){
realPosition = targetPosition;
this.onPageChangeListener.onPageSelection(targetPosition);
}
}
}
@Override
public boolean handleMessage(Message msg) {
int what = msg.what;
switch (what){
case MSG_PLAY_NEXT:
showNextPage();
break;
}
return false;
}
private void showNextPage() {
if(!isPlaying){
return;
}
if(!canRecyclePlaying()){
isPlaying = false;
return;
}
Adapter adapter = getAdapter();
int currentItem = getCurrentItem();
if(adapter!=null && adapter.getItemCount()>0) {
if (currentItem == NO_POSITION ) {
setCurrentItem(0);
}else {
setCurrentItem(currentItem+1);
}
}
mRecyclerHandler.sendEmptyMessageDelayed(MSG_PLAY_NEXT,TASK_TIMEOUT);
}
public void startPlay(){
if(isPlaying){
stopPlay();
}
if (!canRecyclePlaying()){
isPlaying = false;
return;
}
isPlaying = true;
mRecyclerHandler.sendEmptyMessageDelayed(MSG_PLAY_NEXT,TASK_TIMEOUT);
}
@Override
public void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
if(canRecyclePlaying()){
if(realPosition==-1){
realPosition = 1000;
if(adapter.getItemCount()==3000){
realPosition = realPosition - 1;
}
}
setCurrentItem(realPosition,false);
}
}
private boolean canRecyclePlaying() {
Adapter adapter = getAdapter();
if(adapter==null || adapter.getItemCount()<1) return false;
return true;
}
private void stopPlay() {
isPlaying = false;
mRecyclerHandler.removeMessages(MSG_PLAY_NEXT);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if(lastIsPlayState){
startPlay();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
lastIsPlayState = isPlaying;
stopPlay();
}
public static abstract class OnPageChangeListener extends RecyclerView.OnScrollListener{
public abstract void onPageSelection(int position);
}
}
Adapter+Holder 适配器
该类的作用主要用于约束和快速接入
ini
public class QuickViewHolder extends RecyclerView.ViewHolder{
private SparseArray<View> mViews;
private View mConvertView;
private QuickViewHolder(View v){
super(v);
mConvertView = v;
mViews = new SparseArray<>();
}
public static QuickViewHolder get(ViewGroup parent, int layoutId){
View convertView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
return new QuickViewHolder(convertView);
}
public <T extends View> T getView(int id){
View v = mViews.get(id);
if(v == null){
v = mConvertView.findViewById(id);
mViews.put(id, v);
}
return (T)v;
}
}
BannerAdapter 实现,该类继承 QuickAdapter
scala
public static abstract class BaseQuickAdapter<VH extends QuickViewHolder,DM extends Serializable> extends RecyclerPagerView.Adapter{
private List<DM> dataset;
public BaseQuickAdapter(List<DM> dataset){
this.dataset = dataset;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return QuickViewHolder.get(parent,getLayoutId(viewType));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
convert((VH)holder,getItem(position),position);
}
public abstract void convert(VH holder, DM dataModel, int position);
public abstract int getLayoutId(int viewType);
public DM getItem(int position) {
if(dataset==null) return null;
if(position<0 || position>=getItemCount()) {
return null;
}
return dataset.get(position%dataset.size());
}
@Override
public int getItemCount() {
if(dataset==null || dataset.isEmpty()) return 0;
return dataset.size()*1000;
}
}
public static class BannerAdapter extends BaseQuickAdapter {
public BannerAdapter(List<Item> dataset) {
super(dataset);
}
@Override
public void convert(QuickViewHolder holder, Serializable dataModel, int position) {
if(!(dataModel instanceof Item)) return;
TextView textView = holder.getView(R.id.text);
textView.setText(((Item) dataModel).getTitle());
ImageView imageView = holder.getView(R.id.image);
imageView.setImageDrawable(((Item) dataModel).getImageResource());
}
@Override
public int getLayoutId(int viewType) {
return R.layout.item_banner;
}
}
三、使用
使用过程难度不大,依照我们实现的接口
3.1 监听器
这里的监听器主要坚挺滑动到哪个位置了,方便获取当前卡片
arduino
public static class PagerChangeListener extends RecyclerPagerView.OnPageChangeListener {
private TextView tipTextView;
private int size;
public PagerChangeListener(TextView tipTextView,int size) {
this.tipTextView = tipTextView;
this.size = size;
}
@Override
public void onPageSelection(int position) {
tipTextView.setText((position%size+1)+"/"+size);
}
}
2.1 数据集,展示卡片
csharp
public static class Item implements Serializable {
Drawable drawable;
String title;
public Item(int color, String s) {
this.drawable = new ColorDrawable(color);
this.title = s;
}
public Drawable getImageResource() {
return drawable;
}
public String getTitle() {
return title;
}
public static List<Item> getSampleData(){
int[] colors = {
0xffDC143C,
0xff00BFFF,
0xffA52A2A
};
List<Item> lst = new ArrayList<>();
for (int i=0;i<colors.length;i++) {
Item item = new Item(colors[i%colors.length],"第"+(i%colors.length+1)+"张图");
lst.add(item);
}
return lst;
}
}
四、总结
我们实现了很多RecyclerView相关的东西,基本上都是内部处理,因此,后续的文章可能会重点转向LayoutManager,就目前而言,RecyclerView相比约束布局来说,还不是全家桶,没有太臃肿的逻辑,总体上感官不差,