RecyclerView is a new UI control proposed after Android 5.0, which can be used to replace the traditional ListView. But RecyclerView will not completely replace ListView, because the use of the two scenarios are not the same. But the emergence of RecyclerView will make a lot of open source projects obsolete, such as horizontal scrolling ListView, horizontal scrolling GridView, waterfall flow control, because RecyclerView can achieve all these functions, This is because RecyclerView decouples all functions, so it has better extensibility compared with ListView. This article focuses on the use of RecyclerView, and ListView contrast.

Using RecyclerView

Specific use is the need to see the corresponding function in the code to achieve it. For AndroidX projects, you can use it directly without introducing dependencies.

Activity_main. XML:

<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="10dp" tools:context=".MainActivity"> <LinearLayout android:orientation="horizontal" Android :layout_width="match_parent" Android: Layout_height ="wrap_content"> <Button android:text=" Button id" android:onClick="onClickAddData" android:layout_width="0dp" android:layout_weight="1" <Button android:text=" horizontal "Android :onClick="onClickHorizontal" android:layout_width="0dp" android:layout_weight="1" android:layout_marginStart="3dp" Android :layout_height="wrap_content"/> <Button android:text=" display "Android :onClick="onClickReverse" android:layout_width="0dp" android:layout_weight="1" android:layout_marginStart="3dp" android:layout_height="wrap_content"/> </LinearLayout> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/btn_linear_layout" Android :onClick="onChangeLayout" Android :layout_width="0dp" Android :layout_weight="1" <Button android:id="@+id/btn_grid_layout" Android :text=" grid layout" android:onClick="onChangeLayout" android:layout_width="0dp" android:layout_weight="1" android:layout_marginStart="3dp" <Button android:id="@+id/btn_staggered_grid_layout" Android :text=" cascading layout" android:onClick="onChangeLayout" android:layout_width="0dp" android:layout_weight="1" android:layout_marginStart="3dp" android:layout_height="wrap_content"/> </LinearLayout> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:layout_width="0dp" Android :layout_weight="1" Android :layout_height="wrap_content" Android :text=" Insert a data" android:onClick="onInsertDataClick"/> <Button android:layout_width="0dp" android:layout_weight="1" Android :layout_height="wrap_content" Android :text=" Delete a data "android:layout_marginStart="3dp" android:onClick="onRemoveDataClick"/> </LinearLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>Copy the code

item.xml

<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:background="#CDDC39" android:layout_margin="4dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:padding="5dp" android:id="@+id/iv" android:layout_width="50dp" android:layout_height="50dp" android:scaleType="fitXY"/> <TextView android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="match_parent" android:textColor="@color/black" android:gravity="center_vertical" android:layout_marginStart="8dp"/> </LinearLayout>Copy the code

MyRecycleViewAdapter.java

/** * public class MyRecycleViewAdapter extends public class MyRecycleViewAdapter extends RecyclerView.Adapter<MyRecycleViewAdapter.MyViewHolder> { private final Context context; private final RecyclerView recyclerView; private List<String> dataSource; private OnItemClickListener listener; public MyRecycleViewAdapter(Context context, RecyclerView recyclerView){ this.context = context; this.recyclerView = recyclerView; this.dataSource = new ArrayList<>(); } public void setDataSource(List<String> dataSource) { this.dataSource = dataSource; notifyDataSetChanged(); } public void setListener(OnItemClickListener listener) { this.listener = listener; } public MyViewHolder onCreateViewHolder(@nonnull ViewGroup parent, int viewType) { return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item, parent, false)); } / / through the ViewHolder binding data @ Override public void onBindViewHolder (@ NonNull MyRecycleViewAdapter. MyViewHolder holder, int position) { holder.imageView.setImageResource(getIcon(position)); holder.textView.setText(dataSource.get(position)); LinearLayout.LayoutParams params; if(StaggeredGridLayoutManager.class.equals(recyclerView.getLayoutManager().getClass())){ int randomHeight = getRandomHeight(); / / in the waterfall flow layout only use random height params = new LinearLayout. LayoutParams (ViewGroup. LayoutParams. MATCH_PARENT, randomHeight < 50? dp2px(context, 50f): randomHeight ); }else{ params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } params.gravity = Gravity.CENTER; holder.textView.setLayoutParams(params); holder.itemView.setOnClickListener(v -> listener.onItemClick(position)); } private int dp2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; Return (int) (dpValue * scale + 0.5f); } @override public int getItemCount() {return dataSource. Size (); } private int getRandomHeight(){return (int)(math.random () * 500); } private int getIcon(int position){switch (position % 5){case 0: return r.rawable.ic_4k; case 1: return R.drawable.ic_5g; case 2: return R.drawable.ic_360; case 3: return R.drawable.ic_adb; case 4: return R.drawable.ic_alarm; default: return 0; }} public void addData (int position) {dataSource. Add (position, "insert data "); notifyItemInserted(position); // Refresh ItemView notifyItemRangeChanged(position, datasource.size () -position); } public void removeData (int position) {dataSource. Remove (position); notifyItemRemoved(position); // Refresh ItemView notifyItemRangeChanged(position, datasource.size () -position); } static class MyViewHolder extends RecyclerView.ViewHolder { ImageView imageView; TextView textView; public MyViewHolder(@NonNull View itemView) { super(itemView); imageView = itemView.findViewById(R.id.iv); textView = itemView.findViewById(R.id.tv); } } interface OnItemClickListener { void onItemClick(int position); }}Copy the code

MainActivity.java

public class MainActivity extends AppCompatActivity { private RecyclerView recyclerView; private MyRecycleViewAdapter adapter; private LinearLayoutManager linearLayoutManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = findViewById(R.id.recycler_view); // Set linearLayout linearLayoutManager = new linearLayoutManager (this); recyclerView.setLayoutManager(linearLayoutManager); adapter = new MyRecycleViewAdapter(this, recyclerView); Adapter. setListener(position -> toast. makeText(mainActivity. this, "" + position +" "data clicked ", Toast.LENGTH_SHORT).show()); recyclerView.setAdapter(adapter); } public void onClickAddData(View view) { List<String> data = new ArrayList<>(); for (int i = 0; i < 30; I ++) {data.add(" "+ I + "); } adapter.setDataSource(data); } public void onClickHorizontal(View view) { linearLayoutManager.setReverseLayout(false); / / aligned ItemView linearLayoutManager. SetOrientation (linearLayoutManager. HORIZONTAL); recyclerView.setLayoutManager(linearLayoutManager); {} public void onClickReverse View (View). / / data reverse display linearLayoutManager setReverseLayout (true); / / data vertically linearLayoutManager setOrientation (linearLayoutManager. VERTICAL); recyclerView.setLayoutManager(linearLayoutManager); } public void onChangeLayout(View view) { switch (view.getId()){ case R.id.btn_linear_layout: LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(linearLayoutManager); break; case R.id.btn_grid_layout: GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2); recyclerView.setLayoutManager(gridLayoutManager); break; case R.id.btn_staggered_grid_layout: StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); recyclerView.setLayoutManager(staggeredGridLayoutManager); break; Public void onInsertDataClick (View v) {adapter.addData(1); Public void onRemoveDataClick (View v) {adapter.removeData(1); }}Copy the code
Layout of the class The effect
LinearLayoutManager Displays items in a vertical or horizontal scrolling list
GridLayoutManager Displays items in a grid
StaggeredGridLayoutManager Displays items in a scattered alignment grid

Code parsing above

RecyclerView use steps

Create Adapter: Create a RecyclerView.Adapter

class (VH is ViewHolder class name), MyRecycleViewAdapter

Create ViewHolder: Create a static internal class of RecyclerView.ViewHolder in MyRecycleViewAdapter. The ViewHolder implementation is almost identical to the ListView ViewHolder implementation.

3, in MyRecycleViewAdapter to achieve three methods:

// Map ItemLayoutId, OnCreateViewHolder (ViewGroup parent, int viewType) onBindViewHolder(VH Holder, Int position) // Number of returned items getItemCount()Copy the code

RecyclerView local refresh

ListView by adapter. NotifyDataSetChanged () implementation ListView update, the update method shortcoming is a global update, namely to redraw each Item View. But in fact, a lot of times, we just update the data of one Item, and the other items don’t need to be redrawn. So in the code above: adapter.adddata (1) and adapter.removedata (1) both use local refreshes:

Public void addData (int position) {dataSource. Add (position, "insert data "); notifyItemInserted(position); // Refresh ItemView notifyItemRangeChanged(position, datasource.size () -position); } public void removeData (int position) {dataSource. Remove (position); notifyItemRemoved(position); // Refresh ItemView notifyItemRangeChanged(position, datasource.size () -position); }Copy the code

If it’s a ListView, it’s a little more complicated to do a partial refresh:

public void updateItemView(ListView listview, int position, Data data){ int firstPos = listview.getFirstVisiblePosition(); int lastPos = listview.getLastVisiblePosition(); // Update when visible, If (position >= firstPos && position <= lastPos){// listView.getChildAT (I) returns the view view of the ith item that is currently visible view = listview.getChildAt(position - firstPos); VH vh = (VH)view.getTag(); vh.text.setText(data.text); }}Copy the code

Click/hold event of Item

interface OnItemClickListener { void onItemClick(int position); } private OnItemClickListener listener; public void setListener(OnItemClickListener listener) { this.listener = listener; }... public void onBindViewHolder(@NonNull MyRecycleViewAdapter.MyViewHolder holder, int position) { ...... holder.itemView.setOnClickListener(v -> listener.onItemClick(position)); holder.itemView.setOnLongClickListener(v -> { listener.onItemLongClick(position); return false; }); }Copy the code

Other instructions

1. Dp unit to PX unit:

private int dp2px(Context context, float dpValue) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (int) (dpValue * scale + 0.5f);
}
Copy the code

Android :scaleType=”center”: keep the size of the original image and display it in the center of the ImageView. When the size of the original image is greater than the size of the ImageView, the excess is truncated.

Android :scaleType=”center_inside”: For normal display of the original image, if the size of the original image is larger than the size of the ImageView, scale down the width and height of the original image to center in the ImageView. If the size of the original image is smaller than the size of the ImageView, the image is not centered.

android:scaleType=”center_crop”: For the purpose of filling the ImageView with the original image, if the size of the original image is larger than the size of the ImageView, it will be scaled down and displayed in the center of the ImageView as center_inside. If the size of the original image is smaller than the size of the ImageView, scale up the width and height of the original image to fill the ImageView center display.

Android :scaleType=”matrix”: do not change the size of the original image. Draw from the upper left corner of the ImageView.

Androd :scaleType=”fit_xy”: Displays the image in ImageView at the specified size, stretches the image, does not maintain the original scale, fills the ImageView.

Android :scaleType=”fit_start”: Scale the original image to the height of the ImageView and display it in the start (front/top) of the ImageView.

Android :sacleType=”fit_center”: Scale the original image to the height of the ImageView and display it in the ImageView center (middle/center display).

Android :scaleType=”fit_end”: Scale the original image to the height of the ImageView and display it at the end (back/tail/bottom) of the ImageView.

Compare ListView and RecyclerView

AddHeaderView (), addFooterView(), addHeaderView(), addFooterView()

2. You can use the “Android: Divider” to set custom dividing lines.

3, through the setOnItemClickListener () and setOnItemLongClickListener () can easily set the click event and long press event.

These functions in RecyclerView have no direct interface, although it is very simple to achieve but still want to achieve their own, so ListView used to achieve a simple display function is simpler.

RecyclerView advantages: 1, the default has realized the reuse of View, recycling mechanism is more perfect.

2. Partial refresh is supported by default.

3. Easy to realize the animation effect of adding item and deleting item.

4, easy to achieve drag, side slide delete and other functions.

5, RecyclerView is a plug-in implementation, the decoupling of various functions, so that the expansibility is better.

Analysis of recovery mechanism

ListView collection mechanism

In order to ensure the reuse of Item View, ListView implements a set of recycling mechanism. The recycle mechanism is RecycleBin, which realizes two levels of cache:

View[] mActiveViews: Cache a View on the screen. Views in this cache do not need to call getView().

ArrayList[] mScrapViews: ArrayList[] mScrapViews: Each Item Type is a list of views that are lost due to rolling, and are passed to getView() as arguments. Next, we analyze how ListView interacts with RecycleBin through source code. In fact, ListView and RecyclerView layout process is much the same, ListView layout function is layoutChildren(), the implementation is as follows:

Void layoutChildren(){// 1. MActiveViews if(dataChanged) {for (int I = scrapheap // RecyclePool RecyclePool 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); } }else { recycleBin.fillActiveViews(childCount, firstPosition); } // 2. Fill switch(){case LAYOUT_XXX: fillXxx(); break; case LAYOUT_XXX: fillXxx(); break; } / / 3. Recycling of excess activeView mRecycler. ScrapActiveViews (); }Copy the code

FillXxx () implements the filling of the Item View. This method internally calls makeAndAddView(), which is implemented as follows:

View makeAndAddView(){ if(! mDataChanged) { child = mRecycler.getActiveView(position); if (child ! = null) { return child; } } child = obtainView(position, mIsScrap); return child; }Copy the code

GetActiveView () gets the appropriate View from mActiveViews. If it gets the View, it returns it directly without calling obtainView(). You do not need to call getView().

ObtainView () gets the appropriate View from mScrapViews and passes it to getView() as a parameter.

View obtainView(int position){ final View scrapView = mRecycler.getScrapView(position); Final View child = adapter. getView(position, scrapView, this); }Copy the code

Getposition (Position) gets the Item Type from mScrapViews, and returns null if the Item Type is not available. The concrete implementation is as follows:

class RecycleBin{ private View[] mActiveViews; Private ArrayList<View>[] mScrapViews; // Each item type corresponds to an ArrayList private int mViewTypeCount; Private ArrayList<View> mCurrentScrap; // mScrapViews[0] View getScrapView(int position) { final int whichScrap = mAdapter.getItemViewType(position); if(whichScrap < 0) { return null; } if(mViewTypeCount == 1) { return retrieveFromScrap(mCurrentScrap, position); }else if (whichScrap < mScrapViews.length) { return retrieveFromScrap(mScrapViews[whichScrap], position); } return null; } private View retrieveFromScrap(ArrayList<View> scrapViews, int position){ int size = scrapViews.size(); if(size > 0){ return scrapView.remove(scrapViews.size() - 1); }else{return null; }}}Copy the code

RecyclerView recycling mechanism

RecyclerView and ListView recycle mechanism is very similar, but ListView recycles by View as unit, and Recycles by ViewHolder as unit. Recycler is a RecyclerView implementation that recycles four levels of caching:

MAttachedScrap: ViewHolder cached on the screen. MCachedViews: Cache off-screen ViewHolder. The default value is 2. ListView calls getView() for all off-screen caches. MViewCacheExtensions: Requires user customization and is not implemented by default. MRecyclerPool: Cache pool shared by multiple RecyclerViews.

The main concern is the getViewForPosition() method, so here’s how to implement it:

View getViewForPosition(int position, Boolean dryRun) {if (holder = = null) {/ / from mAttachedScrap mCachedViews get ViewHolder holder = getScrapViewForPosition(position,INVALID,dryRun); Bind} final int type = madapter.getitemViewType (offsetPosition); If (adapter.hasstableids ()) {// Default to false holder = getScrapViewForId(adapter.getitemId (offsetPosition), type, dryRun); } if(holder == null && mViewCacheExtension ! = null){ final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type); if(view ! = null){ holder = getChildViewHolder(view); } } if(holder == null){ holder = getRecycledViewPool().getRecycledView(type); } the if (holder = = null) {/ / does not cache, create holder. = mAdapter createViewHolder (RecyclerView. This type); Call onCreateViewHolder()} if(! holder.isBound() || holder.needsUpdate() || holder.isInvalid()){ mAdapter.bindViewHolder(holder, offsetPosition); } return holder.itemView; }Copy the code

From the above implementation, we can see that the ViewHolder can be reused from mAttachedScrap, mCachedViews, mViewCacheExtension, and mRecyclerPool. If the ViewHolder is taken from mAttachedScrap or mCachedViews, onBindViewHolder() is not called, MAttachedScrap and mCachedViews are also known as Scrap Heap; OnBindViewHolder () is called if a ViewHolder is retrieved from mViewCacheExtension or mRecyclerPool.

The realization principle of RecyclerView local refresh is also based on RecyclerView recycling mechanism, that is, ViewHolder that can be reused directly does not call onBindViewHolder().

The resources

1, strong and flexible RecyclerView Adapter: github.com/CymChad/Bas…

RecyclerView Ins and outs-Google I_O 2016