Recycleview realizes unlimited automatic carousel

  • 2021-12-04 19:50:21
  • OfStack

Overview

In my opinion, there are two ways for RecycleView to realize infinite repeated sliding of specific data

1. Modify the reuse mechanism of adpter to reuse data indefinitely
2. Return data length in adpter returns the maximum value of Integer

Because the first method can realize the infinite repetition of data, but the data bits still have no change, so it is impossible to carousel down one bit when automatically jumping to the end, so here I use the second method to realize automatic carousel

Briefly describe the reuse mechanism of modifying adpter

Let's take the linear LinearLayoutManager as an example. We only need to reconstruct LinearLayoutManager and do some hands and feet when drawing it.


package com.li.liproject.recycle;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;

/**
 * @author  Version: 1.0
 *  Creation date: 2020/4/14 14
 *  Description: 
 */
public class ScrollSpeedLinearLayoutManger extends LinearLayoutManager {
 public ScrollSpeedLinearLayoutManger(Context context) {
  super(context);
 }

 public ScrollSpeedLinearLayoutManger(Context context, int orientation, boolean reverseLayout) {
  super(context, orientation, reverseLayout);
 }

 public ScrollSpeedLinearLayoutManger(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
  super(context, attrs, defStyleAttr, defStyleRes);
 }

 @Override
 public RecyclerView.LayoutParams generateDefaultLayoutParams() {
  return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
 }

 // 1  In RecyclerView When initialized, it is called twice. 
// 2  When calling adapter.notifyDataSetChanged() Is called when the. 
// 3  When calling setAdapter Replace Adapter Hour , Will be called. 
// 4  In RecyclerView It is also called when the animation is executed. 
 @Override
 public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
  Log.d("TAG","onLayoutChildren ");
  if (getItemCount() == 0){
   detachAndScrapAttachedViews(recycler);
   return;
  }
  //state.isPreLayout() Is to support animation 
  if (getItemCount() == 0 && state.isPreLayout()){
   return;
  }
  // Will the current Recycler In view Remove all of them and put them in the scrap cache , After that, priority is given to reusing the view
  detachAndScrapAttachedViews(recycler);

  int actualHeight = 0;
  for (int i = 0 ;i < getItemCount() ; i++){
   View scrap = recycler.getViewForPosition(i);
   addView(scrap);
   measureChildWithMargins(scrap,0,0);
   int width = getDecoratedMeasuredWidth(scrap);
   int height = getDecoratedMeasuredHeight(scrap);
   layoutDecorated(scrap,0,actualHeight,width,actualHeight+height);
   actualHeight+=height;
   // If it goes beyond the interface, it will not be drawn , Nor is it add It's over 
   if (actualHeight > getHeight()){
    break;
   }
  }
 }

 @Override
 public boolean canScrollVertically() {
  return true;
 }


 @Override
 public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
  Log.d("feifeifei","getChildCount() " + getChildCount() + " recycler.getScrapList().size() " + recycler.getScrapList().size());

  // When the interface scrolls down, ,dy Is positive , When scrolling up, dy Is negative 

  // Padding 
  fill(dy,recycler,state);
  // Scroll 
  offsetChildrenVertical(dy*-1);

  // Recycle the that has left the interface 
  recycleOut(dy,recycler,state);

  return dy;
 }

 private void fill(int dy, RecyclerView.Recycler recycler, RecyclerView.State state){
  // Scroll down 
  if (dy > 0){
   // Fill in the bottom first 
   View lastView = getChildAt(getChildCount() -1);
   int lastPos = getPosition(lastView);
   if (lastView.getBottom() - dy < getHeight()){
    View scrap;
    if (lastPos == getItemCount() -1){
     scrap = recycler.getViewForPosition(0);
    }else {
     scrap = recycler.getViewForPosition(lastPos+1);
    }
    addView(scrap);
    measureChildWithMargins(scrap,0,0);
    int width = getDecoratedMeasuredWidth(scrap);
    int height = getDecoratedMeasuredHeight(scrap);
    layoutDecorated(scrap,0,lastView.getBottom(),width,lastView.getBottom()+height);
   }
  }else {
   // Scroll up 
   // Now fill the top 
   View firstView = getChildAt(0);
   int layoutPostion = getPosition(firstView);

   if (firstView.getTop() >= 0 ){
    View scrap ;
    if (layoutPostion == 0){
     scrap = recycler.getViewForPosition(getItemCount()-1);
    }else {
     scrap = recycler.getViewForPosition(layoutPostion -1);
    }
    addView(scrap,0);
    measureChildWithMargins(scrap,0,0);
    int width = getDecoratedMeasuredWidth(scrap);
    int height = getDecoratedMeasuredHeight(scrap);
    layoutDecorated(scrap,0,firstView.getTop() - height,width,firstView.getTop());
   }
  }
 }

 private void recycleOut(int dy, RecyclerView.Recycler recycler, RecyclerView.State state){
  for (int i = 0 ; i <getChildCount() ;i++){
   View view = getChildAt(i);
   if (dy >0){
    if (view.getBottom()-dy <0){
     Log.d("feifeifei","recycleOut " + i);
     removeAndRecycleView(view,recycler);
    }
   }else {
    if (view.getTop()-dy > getHeight()){
     Log.d("feifeifei","recycleOut " + i);
     removeAndRecycleView(view,recycler);
    }
   }
  }
 }

 @Override
 public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
  RecyclerView.SmoothScroller smoothScroller = new CenterSmoothScroller(recyclerView.getContext());
  smoothScroller.setTargetPosition(position);
  startSmoothScroll(smoothScroller);
 }

 private class CenterSmoothScroller extends LinearSmoothScroller {
  public CenterSmoothScroller(Context context) {
   super(context);
  }
  protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
   return 0.2f;
  }
 }
}

This is probably how the grid needs to be rewritten because the calculation will be different. Here is a simple description of 1

Get down to business

Implementation of Adpter Adapter

There is no difference in writing. Only getItemCount and onBindViewHolder with 1 should be processed as follows


public class AdAuditorAdapter extends RecyclerView.Adapter<AdAuditorAdapter.MyViewHolder> {
 private Context mContext;
 private List<String> mData;

 public AdAuditorAdapter(Context mContext, List<String> mData) {
  this.mContext = mContext;
  this.mData = mData;
 }

 @NonNull
 @Override
 public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
  MyViewHolder holder = new MyViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_adauditor, viewGroup, false));
  return holder;
 }

 @Override
 public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, @SuppressLint("RecyclerView") int i) {
  Log.e("HHHHHHHHH", "onBindViewHolder: -->"+i );
  // Take the remainder or the index will be out of bounds 
  myViewHolder.tv_1.setText(mData.get(i%mData.size()));
  myViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    listener.onClick(v, i%mData.size(), 3);
   }
  });

 }


 @Override
 public int getItemCount() {
 // Return adpter Maximum value 
  return Integer.MAX_VALUE;
 }

 public void update(List<String> list) {
  this.mData = list;
  notifyDataSetChanged();
 }

 class MyViewHolder extends RecyclerView.ViewHolder {
  TextView tv_1;


  public MyViewHolder(@NonNull View itemView) {
   super(itemView);

   tv_1 = itemView.findViewById(R.id.tv_1); //ID

  }
 }

 public MyClickListener getListener() {
  return listener;
 }

 public void setMyClickListener(MyClickListener listener) {
  this.listener = listener;
 }

 MyClickListener listener;

 public interface MyClickListener {
  void onClick(View view, int position, int type);
 }

 public List<String> getData() {
  return mData;
 }
}

Implementation of activity

1 basic implementation

1.1 Add false data and write click events
1.2 Delay message mRecyclerView. smoothScrollToPosition (position) with handler; Move to the specified position
1.3 Click Stop Moving

2 Effect optimization

2.1 Add uniform damping effect
2.2 Realize infinite carousel considering the situation that the value exceeds the maximum value of Integer
2.3 Clicking recycleview while carousel will stop carousel, and clicking again will execute click event (optimized to click stop and execute click event)
The damping effect is to reduce the sliding rate

Let's do this


package com.li.liproject.recycle;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;

/**
 * @author  Version: 1.0
 *  Creation date: 2020/4/14 14
 *  Description: 
 */
public class ScrollSpeedGridLayoutManager1 extends GridLayoutManager {


 public ScrollSpeedGridLayoutManager1(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
  super(context, attrs, defStyleAttr, defStyleRes);
 }

 public ScrollSpeedGridLayoutManager1(Context context, int spanCount) {
  super(context, spanCount);
 }

 public ScrollSpeedGridLayoutManager1(Context context, int spanCount, int orientation, boolean reverseLayout) {
  super(context, spanCount, orientation, reverseLayout);
 }


 @Override
 public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
  RecyclerView.SmoothScroller smoothScroller = new CenterSmoothScroller(recyclerView.getContext());
  smoothScroller.setTargetPosition(position);
  startSmoothScroll(smoothScroller);
 }

 private class CenterSmoothScroller extends LinearSmoothScroller {
  public CenterSmoothScroller(Context context) {
   super(context);
  }
  protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
   return 10f;// Sliding rate problem 
  }
 }
}

activity full code


package com.li.liproject.recycle;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.li.liproject.MainActivity;
import com.li.liproject.R;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * @author  Version: 1.0
 *  Creation date: 2020/4/14 14
 *  Description: 
 */
public class RecycleViewActivity extends AppCompatActivity {
 static RecyclerView rv_1;
 private static int HANDLER_MSG = 0x0011;
 private static int HANDLER_LONG_MSG = 0x0021;
 static int position = 0;
 static int addNum = 3;
 @SuppressLint("HandlerLeak")
 private static Handler handler = new Handler() {
  @Override
  public void handleMessage(@NonNull Message msg) {
   if (msg.what == HANDLER_MSG) {
   // 9 The realization rate of Gongge effect is the same 
    if (addNum==3){
     position = position + addNum;
     addNum = 6;
    }else {
     position = position + addNum;
     addNum = 3;
    }
    Log.e("TAG", "handleMessage: -->" + position);
    smoothMoveToPosition(rv_1, position >= 0 ? position : 0);
    if (position==Integer.MAX_VALUE/2){
     // Click or exceed 2 Divide Integer.MAX_VALU Reset adpter
     LongAutoMove();
    }else {
     AutoMove();
    }

   }else if (msg.what==HANDLER_LONG_MSG){
    position = 0;
    addNum = 3;
    Log.e("TAG", "handleMessage: -->" + position);
    smoothMoveToPosition(rv_1, 0);
    AutoMove();

   }
  }


 };

 private static AdAuditorAdapter adAuditorAdapter;
 static List<String> strings;
 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_recycle);
  rv_1 = findViewById(R.id.rv_1);
  strings = new ArrayList<>();
  for (int i = 0; i < 50; i++) {
   strings.add(i + "");
  }
  adAuditorAdapter = new AdAuditorAdapter(this, strings);
  adAuditorAdapter.setMyClickListener(new AdAuditorAdapter.MyClickListener() {
   @Override
   public void onClick(View view, int position, int type) {
    Toast.makeText(RecycleViewActivity.this, adAuditorAdapter.getData().get(position), Toast.LENGTH_SHORT).show();
    StopMove();
   }
  });
  GridLayoutManager layoutManager = new ScrollSpeedGridLayoutManager1(this,3,GridLayoutManager.HORIZONTAL, false);
  rv_1.setLayoutManager(layoutManager);
  rv_1.setAdapter(adAuditorAdapter);
  rv_1.addOnScrollListener(new RecyclerView.OnScrollListener() {
   @Override
   public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    super.onScrollStateChanged(recyclerView, newState);
//    if (mShouldScroll && RecyclerView.SCROLL_STATE_IDLE == newState) {
//     mShouldScroll = false;
//     smoothMoveToPosition(recyclerView, mToPosition);
//    }
    Log.e("TAG", "onScrollStateChanged11111111: -->" + newState);
    if (newState == 1) {
//     RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(recyclerView.getRootView());
     recyclerView.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
       Toast.makeText(RecycleViewActivity.this, adAuditorAdapter.getData().get(position)+"........", Toast.LENGTH_SHORT).show();
      }
     });
     StopMove();
     LongAutoMove();
    }
   }

//   @Override
//   public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
//    super.onScrolled(recyclerView, dx, dy);
//    Log.e("TAG", "onScrolled: dx=" +dx +" dy="+dy );
//   }
  });
  rv_1.setOnTouchListener(new View.OnTouchListener() {
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    if (event.getAction()==MotionEvent.ACTION_DOWN){
     // Monitoring click location found view Implement click events 
     View childView = rv_1.findChildViewUnder(event.getX(), event.getY());
     Log.e("GGGGGGGGGGGGGGGGG", "onTouch: -->"+rv_1.getChildLayoutPosition(childView));
     adAuditorAdapter.getListener().onClick(v, rv_1.getChildLayoutPosition(childView),1);
    }
    return false;
   }
  });
  AutoMove();
 }


 private static void AutoMove() {
  handler.removeMessages(HANDLER_MSG);
  handler.sendEmptyMessageDelayed(HANDLER_MSG, 2000);
 }

 private static void LongAutoMove() {
  if (handler.hasMessages(HANDLER_MSG)) {
   handler.removeMessages(HANDLER_LONG_MSG);
  }
  handler.sendEmptyMessageDelayed(HANDLER_LONG_MSG, 5000);
 }

 public static void StopMove() {
  if (handler.hasMessages(HANDLER_MSG)) {
   handler.removeMessages(HANDLER_MSG);
  }
 }


 // Whether the target item is last 1 After visible items 
 private static boolean mShouldScroll;
 // Record the location of the target item 
 private static int mToPosition;

 /**
  *  Slide to the specified position 
  */
 private static void smoothMoveToPosition(RecyclerView mRecyclerView, final int position) {
  if (position==0){
   mRecyclerView.setAdapter(adAuditorAdapter);
  }
   mRecyclerView.smoothScrollToPosition(position);
   mToPosition = position;
   mShouldScroll = true;
 }

 @Override
 protected void onDestroy() {
  super.onDestroy();
  if (handler!=null){
   handler.removeCallbacksAndMessages(null);
   handler= null;
  }
  if (adAuditorAdapter!=null) {
   adAuditorAdapter= null;
  }
 }
}

Basic realization of automatic carousel effect

Here's Demo only written about the effect there are a lot of things need to be optimized under 1, in order to get the project to use


Related articles: