1. RecyclerView overview
Starting with Android 5.0, Google has introduced a new control for displaying large amounts of data, RecylerView, which can be used to replace the traditional ListView, more powerful and flexible.
Changing an item in the ListView and then refreshing the list will return to the top, and RecyclerView can keep the original sliding position unchanged.
When using RecyclerView, the following contents may be involved:
- To control how its items are arranged, use the Layout Manager
LayoutManager
- To create an adapter, use the
RecyclerView.Adapter
- To control the spacing between items, use the
RecyclerView.ItemDecoration
- To control the addition and deletion of items, use the
RecyclerView.ItemAnimator
- CardView extension
FrameLayout
Class to display the in-card information in a consistent way across the platform. CardView widgets can have shadows and rounded corners.
The above functions mainly involve the following methods:
mRecyclerView = findView(R.id.id_recyclerview);
// Set the layout manager
mRecyclerView.setLayoutManager(layout);
/ / set the adapter
mRecyclerView.setAdapter(adapter)
// Set the animation to add and remove Item
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
// Add a divider
mRecyclerView.addItemDecoration(new DividerItemDecoration(
getActivity(), DividerItemDecoration.HORIZONTAL_LIST));
Copy the code
As shown above, if you want to use RecyclerView, you must specify an Adapter and LayoutManager
2. RecyclerView use
2.1 Usage Process
RecyclerView is defined in the support library. If you want to use this control, you need to add the corresponding dependent library in the project build.gradle. In the app/build.gradle file, add the current version to the Dependencies closure:
implementation 'com. Android. Support: recyclerview - v7:28.0.0'
Copy the code
After synchronization, you can expand the use of RecyclerView, which is similar to the three elements of ListView.
In the concrete demonstration of RecyclerView before the use of the left to understand its Adapter: RecyclerView.adapter
2.2 RecyclerView. Adapter
The adapter is an abstract class and supports generics
public abstract static class Adapter<VH extends RecyclerView.ViewHolder> {... }Copy the code
If you create an Adapter that inherits RecyclerView.Adapter, you need to override three methods:
onCreateViewHolder()
onBindViewHolder()
getItemCount()
After you specify a generic, methods 1 and 2 change based on the generic.
When creating an adapter, you typically define a ViewHolder internal class that inherits RecyclerView.Viewholder. The specified generic type is the ViewHolder, within which view controls on each list can be defined and initialized in onCreateViewHolder
onCreateViewHolder()
The sample
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
Copy the code
This method is used to create viewholders. Multiple Viewholders can be created based on the itemType required. The getItemViewType(int Position) method is used to create multiple ItemTypes
onBindViewHolder()
The sample
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
Copy the code
This method essentially binds the Layout view control to the component property in the ViewHolder.
getItemCount()
The sample
@Override
public int getItemCount(a) {
return mFruitList.size();
}
Copy the code
The return value of this method is the number of actual RecyclerView items. In some cases, when adding a HeaderView or FooterView, you need to consider this return value
Introduced the three methods of RecyclerView.Adapter, now a simple example of the use of RecyclerView
- Add RecyclerView control to XML
<?xml version="1.0" encoding="utf-8"? >
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#ffff0000"
android:dividerHeight="10dp" />
</RelativeLayout>
Copy the code
- Create RecyclerView to display the interface XML layout
<?xml version="1.0" encoding="utf-8"? >
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="10dp" />
</LinearLayout>
Copy the code
- Defining data classes
public class Fruit {
private String name;
private int imageId;
public Fruit(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName(a) {
return name;
}
public int getImageId(a) {
returnimageId; }}Copy the code
- Create a new adapter and bind it to RecyclerView
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View view) {
super(view); fruitImage = (ImageView) view.findViewById(R.id.fruit_image); fruitName = (TextView) view.findViewById(R.id.fruit_name); }}public FruitAdapter(List<Fruit> fruitList) {
mFruitList = fruitList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount(a) {
returnmFruitList.size(); }}Copy the code
- Using RecyclerView
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
private void initFruits(a) {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit(getRandomLengthName("Apple"), R.drawable.apple_pic);
fruitList.add(apple);
Fruit banana = new Fruit(getRandomLengthName("Banana"), R.drawable.banana_pic);
fruitList.add(banana);
Fruit orange = new Fruit(getRandomLengthName("Orange"), R.drawable.orange_pic);
fruitList.add(orange);
Fruit watermelon = new Fruit(getRandomLengthName("Watermelon"), R.drawable.watermelon_pic);
fruitList.add(watermelon);
Fruit pear = new Fruit(getRandomLengthName("Pear"), R.drawable.pear_pic);
fruitList.add(pear);
Fruit grape = new Fruit(getRandomLengthName("Grape"), R.drawable.grape_pic);
fruitList.add(grape);
Fruit pineapple = new Fruit(getRandomLengthName("Pineapple"), R.drawable.pineapple_pic);
fruitList.add(pineapple);
Fruit strawberry = new Fruit(getRandomLengthName("Strawberry"), R.drawable.strawberry_pic);
fruitList.add(strawberry);
Fruit cherry = new Fruit(getRandomLengthName("Cherry"), R.drawable.cherry_pic);
fruitList.add(cherry);
Fruit mango = new Fruit(getRandomLengthName("Mango"), R.drawable.mango_pic); fruitList.add(mango); }}private String getRandomLengthName(String name) {
Random random = new Random();
int length = random.nextInt(20) + 1;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++){
builder.append(name);
}
returnbuilder.toString(); }}Copy the code
In addition, besides LinearLayoutManager provides GridLayoutManager and StaggeredGridLayoutManager both built-in layout arrangement. The former implements a grid layout and the latter can be used to implement a waterfall flow layout.
Remark:
In onCreateViewHolder(), the mapping Layout must be
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
Copy the code
And can’t be
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, null);
Copy the code
2.3 Universal Adapter
Create RecyclerView Adapter, found that the implementation process is much the same, you can create a universal Adapter, quickly create Adapter.
The following gives a simple version of universal adapter, learn RecyclerView other parts of the content in the completed version.
public abstract class QuickAdapter<T> extends RecyclerView.Adapter<QuickAdapter.VH>{
private List<T> mDatas;
public QuickAdapter(List<T> datas){
this.mDatas = datas;
}
public abstract int getLayoutId(int viewType);
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
return VH.get(parent,getLayoutId(viewType));
}
@Override
public void onBindViewHolder(VH holder, int position) {
convert(holder, mDatas.get(position), position);
}
@Override
public int getItemCount(a) {
return mDatas.size();
}
public abstract void convert(VH holder, T data, int position);
static class VH extends RecyclerView.ViewHolder{...}
}
Copy the code
The quickAdapter. VH is implemented as follows:
static class VH extends RecyclerView.ViewHolder{
private SparseArray<View> mViews;
private View mConvertView;
private VH(View v){
super(v);
mConvertView = v;
mViews = new SparseArray<>();
}
public static VH get(ViewGroup parent, int layoutId){
View convertView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
return new VH(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;
}
public void setText(int id, String value){ TextView view = getView(id); view.setText(value); }}Copy the code
Among them:
- GetLayoutId (viewType) returns the layout ID based on the viewType
- Convert () does the specific Bind operation
After setting up the universal Adapter, if you want to create an Adapter, simply:
mAdapter = new QuickAdapter<Model>(data) {
@Override
public int getLayoutId(int viewType) {
switch(viewType){
case TYPE_1:
return R.layout.item_1;
case TYPE_2:
returnR.layout.item_2; }}public int getItemViewType(int position) {
if(position % 2= =0) {return TYPE_1;
} else{
returnTYPE_2; }}@Override
public void convert(VH holder, Model data, int position) {
int type = getItemViewType(position);
switch(type){
case TYPE_1:
holder.setText(R.id.text, data.text);
break;
case TYPE_2:
holder.setImage(R.id.image, data.image);
break; }}};Copy the code
3. Customize and use LayoutManager
3.1 an overview of the
If Adapter is responsible for providing views, LayoutManger is responsible for their placement in RecyclerView and recycling strategies when they are not visible in the window.
RecyclerView provides the layout manager:
LinearLayoutManager
Displays items in a vertical or horizontal scrolling listGridLayoutManager
Displays items in a grid.StaggeredGridLayoutManager
Displays items in a scattered alignment grid.
LinearLayoutManager LinearLayoutManager LinearLayoutManager LinearLayoutManager
onLayoutChildren()
: Entrance method of RecyclerView layout.fill()
: Responsible for filling RecyclerView.scrollVerticallyBy()
: Swipe a certain distance based on the movement of your finger and call fill() to fill.canScrollVertically()
orcanScrollHorizontally()
: Determines whether vertical or horizontal sliding is supported.
The core implementation of onLayoutChildren() is as follows:
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
detachAndScrapAttachedViews(recycler); // Move all Item Views to the Recycler Scrap Heap or Recycle Pool
fill(recycler, mLayoutState, state, false); // Populate all Item Views now
}
Copy the code
An important concept of RecyclerView is to divide the Recycle station into Scrap Heap and Recycle Pool, where Scrap Heap elements can be reused directly without calling onBindViewHolder(). DetachAndScrapAttachedViews () according to the situation, the original Item View into the Scrap Heap or Recycle Pool, so as to promote efficiency in the reuse
Fill () calls layoutChunk() over and over again until the remaining space is filled. The core implementation of layoutChunk() is as follows:
public void layoutChunk(a) {
View view = layoutState.next(recycler); // Call getViewForPosition()
addView(view); / / to join the View
measureChildWithMargins(view, 0.0); // Calculate the size of the View
layoutDecoratedWithMargins(view, left, top, right, bottom); / / the View layout
}
Copy the code
Where next() calls getViewForPosition(currentPosition), which can retrieve the RecyclerView from RecyclerView.
Before I show you how to customize LayoutManager, let me introduce a common API
-
recycler.getViewForPosition(position)
-
Get the View at position.
-
getPosition(View view)
-
Gets the position of the view.
-
measureChildWithMargins(View child, int widthUsed, int heightUsed)
-
Measure the width and height of the view, including margins.
-
layoutDecoratedWithMargins(View child, int left, int top, int right,int bottom)
-
Display child in RecyclerView, left, top, right, bottom specifies the display area.
-
detachView(View child)
-
Temporarily recycle the view.
-
attachView(View child)
-
DetachView (View Child) retrieve the View from detachView.
-
detachAndScrapAttachedViews(RecyclerView.Recycler recycler)
-
Use the specified Recycler to temporarily remove all added views.
-
detachAndScrapView(View child, RecyclerView.Recycler recycler)
-
Use designated Recycler temporary recycling view.
-
removeAndRecycleAllViews(RecyclerView.Recycler recycler)
-
Remove all views and recycle them.
-
removeAndRecycleView(View child, RecyclerView.Recycler recycler)
-
Remove the specified view and recycle the recycler.
-
offsetChildrenHorizontal(int dx)
-
Move all views horizontally, also offsetChildrenVertical(int dy)
3.2 Simple Example
Here we show a simple example of the core methods you need to implement to customize a LayoutManager.
- Start by generating a class, such as CustomLayoutManager, derived from LayoutManager
public class CustomLayoutManager extends LayoutManager {
@Override
public LayoutParams generateDefaultLayoutParams(a) {
return newRecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT); }}Copy the code
Derived from LayoutManager, forcing us to generate generateDefaultLayoutParams () method, namely RecyclerView Item layout parameters. No special requirements, let the subitem determine its width and height
- add
onLayoutChild()
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// Define the offset in the vertical direction
int offsetY = 0;
for (int i = 0; i < getItemCount(); i++) {
View view = recycler.getViewForPosition(i);
addView(view);
measureChildWithMargins(view, 0.0);
int width = getDecoratedMeasuredWidth(view);
int height = getDecoratedMeasuredHeight(view);
layoutDecorated(view, 0, offsetY, width, offsetY + height); offsetY += height; }}Copy the code
Two things are done here:
-
Load in all views corresponding to items
-
Put all items where they belong
This method is the main entry point to the LayoutManager and is called when the view needs to initialize the layout (again when adapter data changes or adapter replacement).
In general, in this method you need to complete the following main steps:
- Check the current offset position of all attached views after the scroll event ends.
- Determine whether you need to add a new view to fill the blank created by scrolling. And get views from Recycler.
- Determines whether the current view is no longer displayed. Remove them and place them in Recycler.
- Check whether the remaining views need to be cleaned. These changes may require you to modify the view’s subindexes to better align with their adapter locations.
This example is simple, just by measureChildWithMargins(View, 0, 0); Function to measure the view and through getDecoratedMeasuredWidth get measured width (view), it is important to note by getDecoratedMeasuredWidth (view) is the total width of the item + decoration. If you just want to get the measured width of the view, use View.getMeasuredWidth ()
Then through layoutDecorated(); The function places each item in its corresponding position. The left and right positions of each item are the same, starting from x=0 to the left, except that the point of y needs to be calculated. So there is a variable offsetY that adds up the heights of all items before the current Item.
- Add slide effect
@Override
public boolean canScrollVertically(a) {
return true;
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
// Shift the item inside the container
offsetChildrenVertical(-dy);
return dy;
}
Copy the code
Pass in canScrollVertically() return true; Enable vertical scrolling in LayoutManager. The distance dy for each roll is then received in scrollVerticallyBy().
Where dy represents the displacement of each finger slide on the screen:
- When you slide your finger up,dy is greater than 0
- When you slide your finger up,dy is less than 0
So obviously the finger slides up, the subitem moves up, and we subtract dy, and it makes sense to move the Item by -dy. You can move all items offsetChildrenVertical()
- Adding exception Detection
The above code implements scrolling, but Item can continue to scroll when it reaches the top or bottom, and it needs to determine whether it reaches the top or bottom
We’re at the top
You just add up all the dy’s, and if it’s less than 0, you’re at the top and you’re not moving anymore
private int mSumDy = 0;
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
int travel = dy;
// If you slide to the top
if (mSumDy + dy < 0) {
travel = -mSumDy;
}
mSumDy += travel;
// Shift the item inside the container
offsetChildrenVertical(-travel);
return dy;
}
Copy the code
Judge to the end
The way to determine the bottom is to subtract the height of the last screen from the total height, which is the offset to the bottom. If it is greater than this offset, it exceeds the bottom.
Firstly, the total height of all items should be obtained. In onLayoutChildren, all items are measured and each Item is arranged. The total height of all items can be obtained by adding the heights of all items in onLayoutChildren().
private int mTotalHeight = 0;
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// Define the offset in the vertical direction
int offsetY = 0;
for (int i = 0; i < getItemCount(); i++) {
View view = recycler.getViewForPosition(i);
addView(view);
measureChildWithMargins(view, 0.0);
int width = getDecoratedMeasuredWidth(view);
int height = getDecoratedMeasuredHeight(view);
layoutDecorated(view, 0, offsetY, width, offsetY + height);
offsetY += height;
}
// If the height of all subviews and the RecyclerView height does not fill,
// Set the height to RecyclerView height
mTotalHeight = Math.max(offsetY, getVerticalSpace());
}
private int getVerticalSpace(a) {
return getHeight() - getPaddingBottom() - getPaddingTop();
}
Copy the code
GetVerticalSpace () can obtain the real height of RecyclerView used to display item. SrcollVerticallyBy: srcollVerticallyBy:
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
int travel = dy;
// If you slide to the top
if (mSumDy + dy < 0) {
travel = -mSumDy;
} else if (mSumDy + dy > mTotalHeight - getVerticalSpace()) {
travel = mTotalHeight - getVerticalSpace() - mSumDy;
}
mSumDy += travel;
// Shift the item inside the container
offsetChildrenVertical(-travel);
return dy;
}
Copy the code
MSumDy + dy represents the current moving distance, mTotalheight-getVerticalSpace () represents the total scrolling distance when sliding to the bottom;
3.3 LayoutManager additional features
LayoutManager provides some auxiliary methods to operate decoration (ItemDecoration will be described in detail later) :
- with
getDecoratedLeft()
Get the left edge of the child view instead of child.getLeft(). - with
getDecoratedTop()
Get the top edge of the subview instead of getTop(). - with
getDecoratedRight()
Gets the right edge of the subview instead of getRight(). - with
getDecoratedBottom()
Get the bottom edge of the subview instead of getBottom(). - use
measureChild()
或measureChildWithMargins()
Replace child.measure() with new views from Recycler. - use
layoutDecorated()
Replace child.layout() with new views from Recycler. - use
getDecoratedMeasuredWidth()
或getDecoratedMeasuredHeight()
Gets the measurements of the child view instead of Child.getMeasuredWidth () or Child.getMeasuredHeight ()
3.3.1 Data set changes
LayoutManager is responsible for updating views in the layout when notifyDataSetChanged() triggers the update operation of recyclerView.Adapter. At this point, onLayoutChildren() is called again. Doing this requires us to determine in the onLayoutChildre() method whether the current state is generating a new view or if the view changes during adapter updates.
.
3.3.2 rainfall distribution on 10-12 onAdapterChanged ()
This method provides another place to reset the layout. Setting up a new Adapter triggers this event.
Example:
@Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
//Completely scrap the existing layout
removeAllViews();
}
Copy the code
Removing the view triggers a new layout process, and when onLayoutChildren() is called again, our code will perform the layout process of creating the new view, since there is no subview of attched now.
3.3.3 insensitive to the Position
One important feature is adding the ability to scroll to a specific location to LayoutManager. It can be animated or not
-
scrollToPosition()
When layout sets the current location to the first visible Item, it calls scrollToPosition() of RecyclerView.
Example:
@Override
public void scrollToPosition(int position) {
if (position >= getItemCount()) {
Log.e(TAG, "Cannot scroll to "+position+", item count is "+getItemCount());
return;
}
//Ignore current scroll offset, snap to top-left
mForceClearOffsets = true;
//Set requested position as first visible
mFirstVisiblePosition = position;
//Trigger a new view layout
requestLayout();
}
Copy the code
smoothScrollToPosition()
In the case of with animation, need in the method to create a RecyclerView. SmoothScroller instance, before the method returns the request startSmoothScroll () to start the animation
RecyclerView. SmoothScroller abstract class, is to provide the API contains four methods:
-
onStart()
Triggered when the slide animation starts
-
onStop()
Triggered when the slide animation stops
-
onSeekTargetStep()
Called repeatedly when scroller searches the target view, this method is responsible for reading the supplied dx/dy and then updating the distance that should be moved in both directions.
-
onTragetFound()
Only called once after the target view is attached. This is the last place to animate the target view to the exact location.
4. ItemDecoration
RecyclerView does not support divider properties, we can customize the divider.
RecyclerView add cent secant method is: mRecyclerView addItemDecoration (), the method of parameter to RecyclerView ItemDecoration, this class is an abstract class
Take a look at the source code:
public abstract static class ItemDecoration {
public ItemDecoration(a) {}public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDraw(c, parent);
}
/ * *@deprecated* /
@Deprecated
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) {}public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDrawOver(c, parent);
}
/ * *@deprecated* /
@Deprecated
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent) {}/ * *@deprecated* /
@Deprecated
public void getItemOffsets(@NonNull Rect outRect, int itemPosition, @NonNull RecyclerView parent) {
outRect.set(0.0.0.0);
}
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.getItemOffsets(outRect, ((RecyclerView.LayoutParams)view.getLayoutParams()).getViewLayoutPosition(), parent); }}Copy the code
To add decoration, the class’s onDraw() and onDrawOver() methods are called
onDraw
Method precedes drawChildrenonDrawOver
After drawChildren, we usually choose one of them to duplicate.getItemOffsets
Set an offset for each item, mainly for drawing decorators, and set the line width and height
Simple example :(here LayoutManager is LinearLayoutManager)
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if(orientation ! = HORIZONTAL_LIST && orientation ! = VERTICAL_LIST) {throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else{ drawHorizontal(c, parent); }}public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final intbottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); }}public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final intright = left + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); }}@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0.0.0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0.0, mDivider.getIntrinsicWidth(), 0); }}}Copy the code
The implementation class as you can see by reading the theme from the android system. The state Richard armitage TTR event. ListDivider as the dividing line between the Item, and support the horizontal and vertical
Then add a sentence to the original code:
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,
DividerItemDecoration.VERTICAL_LIST));
Copy the code
4.1 ItemDecoration parsing
-
The preceding code divider is the default. You can find the usage of this property in the theme. XML file.
<! -- Application theme. --> <style name="AppTheme" parent="AppBaseTheme"> <item name="android:listDivider">@drawable/divider_bg</item> </style> Copy the code
You can write a Drawable of your own, for example:
<?xml version="1.0" encoding="utf-8"? > <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <gradient android:centerColor="#ff00ff00" android:endColor="#ff0000ff" android:startColor="#ffff0000" android:type="linear" /> <size android:height="4dp"/> </shape> Copy the code
-
GetItemOffsets () and onDraw() must be customized to use ItemDecoration.
ItemDecoration.
Item decoration
GetItemOffsets () sets the offset around the item (that is, the width of the decoration area), while onDraw() is the real decoration callback, allowing you to draw on the decoration area.
As can be seen from the previous ItemDecoration source code, there are three main methods:
OnDraw() onDrawOver() getItemOffsets() Copy the code
The role of these three methods can be easily understood in the following figure
Where green represents content and red represents decoration:
- Figure 1 shows
getItemOffsets()
To achieve a similar padding effect - Figure 2 shows
onDraw()
, to achieve similar background rendering effect, content on - Figure 3 said
onDrawOver()
, can be drawn on top of content, overwriting content
Suppose we want to implement a line divider for a linear list:
- When the linear list is horizontal, the dividing line is vertical; When a linear list is vertical, the dividing line is horizontal
- When drawing a vertical line, we need to offset the width of a line to the right of the item. When drawing a horizontal dividing line, you need to offset the height of a line below item
/** ** line */ @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); if (orientation == RecyclerView.HORIZONTAL) { drawVertical(c, parent, state); } else if(orientation == RecyclerView.VERTICAL) { drawHorizontal(c, parent, state); }}/** * Sets the offset around the entry */ @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); if (orientation == RecyclerView.HORIZONTAL) { // Draw a vertical line outRect.set(0.0, mDivider.getIntrinsicWidth(), 0); } else if (orientation == RecyclerView.VERTICAL) { // Draw a horizontal line outRect.set(0.0.0, mDivider.getIntrinsicHeight()); }}Copy the code
GetItemOffsets () is relative to each item, that is, each item will offset the same decorative area, but onDraw is different, it is relative to Canvas, in popular terms, it is to find the position of the line to draw
/** * Load the system's own divider in the constructor (the one used by ListView) */ public MyDecorationOne(Context context, int orientation) { this.orientation = orientation; int[] attrs = new int[]{android.R.attr.listDivider}; TypedArray a = context.obtainStyledAttributes(attrs); mDivider = a.getDrawable(0); a.recycle(); } /** * draw a vertical divider */ private void drawVertical(Canvas c, RecyclerView parent, RecyclerView.State state) { int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int left = child.getRight() + params.rightMargin; int top = child.getTop() - params.topMargin; int right = left + mDivider.getIntrinsicWidth(); intbottom = child.getBottom() + params.bottomMargin; mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); }}/** * draw a horizontal divider */ private void drawHorizontal(Canvas c, RecyclerView parent, RecyclerView.State state) { int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int left = child.getLeft() - params.leftMargin; int top = child.getBottom() + params.bottomMargin; int right = child.getRight() + params.rightMargin; intbottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); }}Copy the code
- Figure 1 shows
There are three main methods for a simple example ItemDecoration
Below is the interface without adding any ItemDecoration:
-
The expected results are as follows:
From the effect diagram to realize the secant line effect. Based on the above knowledge, just use the getItemOffset() method to empty a certain height control below the Item, and then use onDraw() to draw the space
public class SimpleDividerDecoration extends RecyclerView.ItemDecoration { private int dividerHeight; private Paint dividerPaint; public SimpleDividerDecoration(Context context) { dividerPaint = new Paint(); dividerPaint.setColor(context.getResources().getColor(R.color.colorAccent)); dividerHeight = context.getResources().getDimensionPixelSize(R.dimen.divider_height); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); outRect.bottom = dividerHeight; // A space is left under item } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { int childCount = parent.getChildCount(); int left = parent.getPaddingLeft(); int right = parent.getWidth() - parent.getPaddingRight(); / / traverse the item for (int i = 0; i < childCount - 1; i++) { View view = parent.getChildAt(i); float top = view.getBottom(); floatbottom = view.getBottom() + dividerHeight; c.drawRect(left, top, right, bottom, dividerPaint); }}}Copy the code
-
The expected effect is as shown below:
This effect is similar to that of an item label, which can be overlaid on top of the content by onDrawOver()
public class LeftAndRightTagDecoration extends RecyclerView.ItemDecoration { private int tagWidth; private Paint leftPaint; private Paint rightPaint; public LeftAndRightTagDecoration(Context context) { leftPaint = new Paint(); leftPaint.setColor(context.getResources().getColor(R.color.colorAccent)); rightPaint = new Paint(); rightPaint.setColor(context.getResources().getColor(R.color.colorPrimary)); tagWidth = context.getResources().getDimensionPixelSize(R.dimen.tag_width); } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { View child = parent.getChildAt(i); int pos = parent.getChildAdapterPosition(child); boolean isLeft = pos % 2= =0; if (isLeft) { float left = child.getLeft(); float right = left + tagWidth; float top = child.getTop(); float bottom = child.getBottom(); c.drawRect(left, top, right, bottom, leftPaint); } else { float right = child.getRight(); float left = right - tagWidth; float top = child.getTop(); floatbottom = child.getBottom(); c.drawRect(left, top, right, bottom, rightPaint); }}}}Copy the code
ItemDecoration effects can be stacked
recyclerView.addItemDecoration(new LeftAndRightTagDecoration(this)); recyclerView.addItemDecoration(new SimpleDividerDecoration(this)); Copy the code
When the above two item decorations are added to RecyclerView at the same time, the effect is as follows:
-
The expected effect is as shown below:
The effect is similar to mobile phone address book grouping. This effect is that some items are separated and some are not.
-
Define an interface to call back to the Activity to group data and get initial letters
public interface DecorationCallback { long getGroupId(int position); String getGroupFirstLine(int position); } Copy the code
-
Implement ItemDecoration
public class SectionDecoration extends RecyclerView.ItemDecoration { private static final String TAG = "SectionDecoration"; private DecorationCallback callback; // Callback interface private TextPaint textPaint; private Paint paint; private int topGap; private Paint.FontMetrics fontMetrics; / / initialization public SectionDecoration(Context context, DecorationCallback decorationCallback) { Resources res = context.getResources(); this.callback = decorationCallback; paint = new Paint(); paint.setColor(res.getColor(R.color.colorAccent)); textPaint = new TextPaint(); textPaint.setTypeface(Typeface.DEFAULT_BOLD); textPaint.setAntiAlias(true); textPaint.setTextSize(80); textPaint.setColor(Color.BLACK); textPaint.getFontMetrics(fontMetrics); textPaint.setTextAlign(Paint.Align.LEFT); fontMetrics = new Paint.FontMetrics(); topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);//32dp } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); int pos = parent.getChildAdapterPosition(view); Log.i(TAG, "getItemOffsets:" + pos); long groupId = callback.getGroupId(pos); if (groupId < 0) return; if (pos == 0 || isFirstInGroup(pos)) {// Add the padding to the first group outRect.top = topGap; } else { outRect.top = 0; }}@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); int left = parent.getPaddingLeft(); int right = parent.getWidth() - parent.getPaddingRight(); int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { View view = parent.getChildAt(i); int position = parent.getChildAdapterPosition(view); long groupId = callback.getGroupId(position); if (groupId < 0) return; String textLine = callback.getGroupFirstLine(position).toUpperCase(); // Determine whether to draw a dividing line if (position == 0 || isFirstInGroup(position)) { float top = view.getTop() - topGap; float bottom = view.getTop(); c.drawRect(left, top, right, bottom, paint);// Draw red rectangle c.drawText(textLine, left, bottom, textPaint);// Draw text}}}private boolean isFirstInGroup(int pos) { if (pos == 0) { return true; } else { long prevGroupId = callback.getGroupId(pos - 1); long groupId = callback.getGroupId(pos); return prevGroupId != groupId; } } public interface DecorationCallback { long getGroupId(int position); String getGroupFirstLine(int position); }}Copy the code
-
Used in an Activity
recyclerView.addItemDecoration(new SectionDecoration(this.new SectionDecoration.DecorationCallback() { @Override public long getGroupId(int position) { return Character.toUpperCase(dataList.get(position).getName().charAt(0)); } @Override public String getGroupFirstLine(int position) { return dataList.get(position).getName().substring(0.1).toUpperCase(); }}));Copy the code
-
-
The expected results are as follows:
This effect is called sticky head.
If the Header does not move, it must be drawn above the contents of the Item. You need to override onDrawOver(), as described above
public class PinnedSectionDecoration extends RecyclerView.ItemDecoration { private static final String TAG = "PinnedSectionDecoration"; private DecorationCallback callback; private TextPaint textPaint; private Paint paint; private int topGap; private Paint.FontMetrics fontMetrics; public PinnedSectionDecoration(Context context, DecorationCallback decorationCallback) { Resources res = context.getResources(); this.callback = decorationCallback; paint = new Paint(); paint.setColor(res.getColor(R.color.colorAccent)); textPaint = new TextPaint(); textPaint.setTypeface(Typeface.DEFAULT_BOLD); textPaint.setAntiAlias(true); textPaint.setTextSize(80); textPaint.setColor(Color.BLACK); textPaint.getFontMetrics(fontMetrics); textPaint.setTextAlign(Paint.Align.LEFT); fontMetrics = new Paint.FontMetrics(); topGap = res.getDimensionPixelSize(R.dimen.sectioned_top); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); int pos = parent.getChildAdapterPosition(view); long groupId = callback.getGroupId(pos); if (groupId < 0) return; if (pos == 0 || isFirstInGroup(pos)) { outRect.top = topGap; } else { outRect.top = 0; }}@Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); int itemCount = state.getItemCount(); int childCount = parent.getChildCount(); int left = parent.getPaddingLeft(); int right = parent.getWidth() - parent.getPaddingRight(); float lineHeight = textPaint.getTextSize() + fontMetrics.descent; long preGroupId, groupId = -1; for (int i = 0; i < childCount; i++) { View view = parent.getChildAt(i); int position = parent.getChildAdapterPosition(view); preGroupId = groupId; groupId = callback.getGroupId(position); if (groupId < 0 || groupId == preGroupId) continue; String textLine = callback.getGroupFirstLine(position).toUpperCase(); if (TextUtils.isEmpty(textLine)) continue; int viewBottom = view.getBottom(); float textY = Math.max(topGap, view.getTop()); if (position + 1 < itemCount) { // The next one is different from the current one long nextGroupId = callback.getGroupId(position + 1); if(nextGroupId ! = groupId && viewBottom < textY ) {// The last view in the group enters the headertextY = viewBottom; } } c.drawRect(left, textY - topGap, right, textY, paint); c.drawText(textLine, left, textY, textPaint); }}}Copy the code
5. Item Animator
6. The Item
RecyclerView does not provide setOnItemClickListener() interface like ListView by default, we need to modify the implementation.
6.1 General Implementation
Add OnItemClickListener interface and setOnItemClickListener interface to the Adapter and add click listener for each Item.
Code examples:
- Customize Adapter Adapter, add item click interface, set click events
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
private LayoutInflater mInflater;
private Context mContext;
private List<String> mDatas;
private int[] imgIds;
private OnItemClickListener mOnItemClickListener;
public MyAdapter(Context context, List<String> datas, int[] imgIds) {
this.mContext = context;
this.mDatas = datas;
this.imgIds = imgIds;
mInflater = LayoutInflater.from(context);
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_single_textview, parent, false);
MyViewHolder viewHolder = new MyViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
holder.textView.setText(mDatas.get(position));
holder.imageView.setBackgroundResource(imgIds[ position % imgIds.length]);
// item click
if(mOnItemClickListener ! =null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) { mOnItemClickListener.onItemClick(holder.itemView, position); }});// item long click
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
mOnItemClickListener.onItemLongClick(holder.itemView, position);
return true; }}); }}@Override
public int getItemCount(a) {
return mDatas.size();
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.mOnItemClickListener = listener;
}
public void addData(int pos) {
mDatas.add(pos, "Add one");
notifyItemInserted(pos);
}
public void deleteData(int pos) {
mDatas.remove(pos);
notifyItemRemoved(pos);
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position); }}class MyViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
TextView textView;
public MyViewHolder(View itemView) {
super(itemView); textView = itemView.findViewById(R.id.textView); imageView = itemView.findViewById(R.id.imageView); }}Copy the code
- Modify the MainActivity
mAdapter = new MyAdapter(this, mDatas, imgIds);
recyclerView.setAdapter(mAdapter);
// Set RecyclerView layout management
LinearLayoutManager manager = new LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(manager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this."clicked " + position,
Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this."long clicked "+ position, Toast.LENGTH_SHORT).show(); }}); }Copy the code