ItemDecoration
RecyclerView. ItemDecoration function is used to draw the most common RecyclerView divider between the Item. But if you think it can only be used to draw dividing lines, think again. Here with the demand of a specific project to talk about RecyclerView. ItemDecoration advanced usage.
The specific requirements are shown above. Firstly, it must be a RecyclerView on the whole, and then see how item is realized. Items can be divided into left and right sides, the right side is easy to implement, the key is the left side of the timeline is a bit tricky. It can be noticed from the figure that the ongoing item icon is relatively large, just aligned with the title above and below; Item ICONS in other states are centered with respect to title. So how do WE draw the timeline for item in this state?
Ideas:
-
Divided by icon, there is a short line above and a long line below. In such a drawing, the top short line depends on the previous item, and the bottom long line depends on the current item. That is, when you draw the current item, you also need to know information about the previous item.
Implementation is a little tricky, and it’s not elegant.
-
On the left side of the shaft to RecyclerView. ItemDecoration implementation, RecyclerView only need according to the conventional writing shows some of the information on the right. This implementation is elegant.
After comprehensive consideration, the second scheme is adopted. Before post implementation code, first to know about the RecyclerView. ItemDecoration some methods of class.
RecyclerView.ItemDecoration
RecyclerView ItemDecoration class is an abstract class, all can override method only three groups:
RecyclerView.ItemDecoration
/** * An ItemDecoration allows the application to add a special drawing and layout offset * to specific item views from the adapter's data set. This can be useful for drawing dividers * between items, highlights, visual grouping boundaries and more. * * <p>All ItemDecorations are drawn in the order they were added, before the item * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()} * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, * RecyclerView.State)}.</p> */ public abstract static class ItemDecoration { /** * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. * Any content drawn by this method will be drawn before the item views are drawn, * and will thus appear underneath the views. * * @param c Canvas to draw into * @param parent RecyclerView this ItemDecoration is drawing into * @param state The current state of RecyclerView */ public void onDraw(Canvas c, RecyclerView parent, State state) { onDraw(c, parent); } /** * @deprecated * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)} */ @Deprecated public void onDraw(Canvas c, RecyclerView parent) { } /** * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. * Any content drawn by this method will be drawn after the item views are drawn * and will thus appear over the views. * * @param c Canvas to draw into * @param parent RecyclerView this ItemDecoration is drawing into * @param state The current state of RecyclerView. */ public void onDrawOver(Canvas c, RecyclerView parent, State state) { onDrawOver(c, parent); } /** * @deprecated * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)} */ @Deprecated public void onDrawOver(Canvas c, RecyclerView parent) { } /** * @deprecated * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)} */ @Deprecated public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { outRect.set(0, 0, 0, 0); } /** * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies * the number of pixels that the item view should be inset by, similar to padding or margin. * The default implementation sets the bounds of outRect to 0 and returns. * * <p> * If this ItemDecoration does not affect the positioning of item views, it should set * all four fields of <code>outRect</code> (left, top, right, bottom) to zero * before returning. * * <p> * If you need to access Adapter for additional data, you can call * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the * View. * * @param outRect Rect to receive the output. * @param view The child view to decorate * @param parent RecyclerView this ItemDecoration is decorating * @param state The current state of RecyclerView. */ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent); }}Copy the code
The above code is the declaration of the ItemDecoration class. You can see that there are only three methods that need override — onDraw, onDrawOver, and getItemOffsets.
There are still some very useful explanations in the comments:
-
All item decorations are drawn in the order in which they are added
-
TemDecoration. OnDraw can draw any decoration to RecyclerView by Canvas
This method is called before the item Views are drawn, so the drawn content is displayed below the views
-
ItemDecoration. OnDrawOver through Canvas draw any decoration on RecyclerView
This method is called after the item Views are drawn, so the drawn content is displayed on top of the views
-
ItemDecoration. GetItemOffsets item can be set by setting outRect value to view an inset of (and padding or margin)
The default implementation of outRect is set and returns 0
-
If you need to visit the Adapter for more information, you can call RecyclerView. GetChildAdapterPosition (View) to get the Adapter location of the View
Implement UI effects
After understanding the related knowledge of ItemDecoration, let’s take a look at the writing of the timeline at the beginning:
class CertProgressItemDecoration : RecyclerView. ItemDecoration () {/ * * doing subscript, Values passed in the background start from 1 */ var activeStepIndex = -1 /** Paint except the dashed line */ private val Paint = Paint() /** Dashed line related */ private val dashPath = Path() private val dashPaint = Paint() init { paint.strokeWidth = SizeUtils.dp2px(1F).toFloat() paint.color = Color.WHITE paint.isAntiAlias = true dashPaint.strokeWidth = SizeUtils.dp2px(1F).toFloat() dashPaint.style = Paint.Style.STROKE dashPaint.isAntiAlias = true dashPaint.pathEffect = DashPathEffect(floatArrayOf(SizeUtils.dp2px(6F).toFloat(), SizeUtils.dp2px(3F).toFloat()), 0F) } override fun getItemOffsets( outRect: Rect? , view: View? , parent: RecyclerView? , state: RecyclerView.State? ) {// The left part of item is 68dp away from the left, so the left part of the timeline is set to 68dp. .set(SizeUtils.dp2px(68F), 0, 0, 0) } override fun onDrawOver(c: Canvas? , parent: RecyclerView? , state: RecyclerView.State?) {} override fun onDraw(c: Canvas? , parent: RecyclerView? , state: RecyclerView.State?) { parent ? : return c ? : Return for (I in 0 until parent.childcount) {val child = parent.getChildat (I) val left = 0F val top = Child.top.tofloat () val right = sizeutils.dp2px (68F).tofloat () val bottom = child.bottom.tofloat () // Draw the background // Because the color is not set in the whole RecyclerView, the color is drawn by item // But we set the inset on the left side of item View, Paint. Color = color.white c.rawrect (left, top, right, bottom, Int int int int int int int int int int int int int int int int int int int int int int int int int int int int int int int int int Parent. childCount // val centerX = (right-left) / 2 // Val centerX = (right-left) // Doing status icon = 22dp val bPartTop Sizeutils.dp2px (11F) + top val lastDataIndex = dataIndex - 1 if (dataIndex! = 0) { if (lastDataIndex < activeStepIndex) { paint.color = parent.context.getColorCompact(R.color.colorAccent) c.drawLine(centerX, top, centerX, bPartTop, paint) } else if (lastDataIndex ! = dataCount - 1) { dashPaint.color = parent.context.getColorCompact(R.color.colorTimeLineUndo) dashPath.reset() dashPath.moveTo(centerX, top) dashPath.lineTo(centerX, bPartTop) c.drawPath(dashPath, DashPaint)}} // The bottom line of the current item if (dataIndex < activeStepIndex) {paint.color = parent.context.getColorCompact(R.color.colorAccent) c.drawLine(centerX, bPartTop, centerX, bottom, paint) } else if (dataIndex ! = dataCount - 1) { dashPaint.color = parent.context.getColorCompact(R.color.colorTimeLineUndo) dashPath.reset() dashPath.moveTo(centerX, bPartTop) dashPath.lineTo(centerX, bottom) c.drawPath(dashPath, DashPaint)} val bitmap = when {dataIndex < activeStepIndex -> BitmapFactory.decodeResource(parent.context.resources, R.drawable.ic_timeline_tick_n) dataIndex == activeStepIndex -> BitmapFactory.decodeResource(parent.context.resources, R.drawable.ic_timeline_ing_n) else -> BitmapFactory.decodeResource(parent.context.resources, R.drawable.ic_timeline_unfinished_n) } val bitmapX = centerX - (bitmap.width shr 1) val bitmapY = bPartTop - (bitmap.height shr 1) c.drawBitmap(bitmap, bitmapX, bitmapY, paint) } } }Copy the code
Note that if the RecyclerView has a header or footer, you need to convert the subscript correctly.
The timeline of the code after you’ve written, just call RecyclerView. AddItemDecoration (ItemDecoration) method can be added.
Drag and drop sort and slide delete
Effect of video
The main process is as follows:
- Define drag and drop operation, slide delete interface
- The Adapter interface that implements the first step, which is mainly used to perform operations on data
- Custom Callback implementation. Android support. V7. Widget. Helper. ItemTouchHelper. The Callback, the Callback is the key to achieve drag and drop, sliding delete RecyclerView
- Create ItemTouchHelper and attachToRecyclerView
1. Define interfaces for dragging and sliding operations
Public interface IDragSwipe {/** * two items swap positions * @param fromPosition * @param toPosition */ void onItemSwapped(int fromPosition, int toPosition); /** * Delete Item * @param position Position of the Item to be deleted */ void onItemDeleted(int position); /** * @position position of Item */ void onItemDone(int position); }Copy the code
There are three methods defined: swap, delete, and tag completion
2. Adapter Implements the interface
public class TodoTaskAdapter extends BaseQuickAdapter<TodoTask, BaseViewHolder> implements IDragSwipe { ... @Override public void onItemSwapped(int fromPosition, int toPosition) { Collections.swap(getData(), fromPosition, toPosition); notifyItemMoved(fromPosition, toPosition); } @Override public void onItemDeleted(int position) { Toast.makeText(mContext, "onItemDeleted", Toast.LENGTH_SHORT).show(); mData.remove(position); notifyItemRemoved(position); } @Override public void onItemDone(int position) { Toast.makeText(mContext, "onItemDone", Toast.LENGTH_SHORT).show(); mData.remove(position); notifyItemRemoved(position); }}Copy the code
As shown above
- Swap the data at the corresponding location using collections. swap, and notify that the data has changed (notifyItemMoved and notifyItemRemoved) with a default animation effect.
- Deletion and tag completion are simply the removal of data and notification of update.
3. Customize the Callback class to call the exchange and delete operations of Adapter
public class DragSwipeCallback extends android.support.v7.widget.helper.ItemTouchHelper.Callback { /** */ private IDragSwipe mAdapter; Public DragSwipeCallback(IDragSwipe adapter){// Insert IDragSwipe mAdapter = adapter; } @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView. ViewHolder ViewHolder) {/ / determine the direction of the drag and drop, sliding support int dragFlags = ItemTouchHelper. UP | ItemTouchHelper. DOWN; int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; return makeMovementFlags(dragFlags, swipeFlags); } @Override public boolean isLongPressDragEnabled() { return true; } @Override public boolean isItemViewSwipeEnabled() { return true; } @override public Boolean change (RecyclerView RecyclerView, RecyclerView.ViewHolder ViewHolder, RecyclerView.ViewHolder target) { mAdapter.onItemSwapped(viewHolder.getAdapterPosition(), target.getAdapterPosition()); return true; } @override public void onSwiped(recyclerView. ViewHolder ViewHolder, int direction) { switch (direction) { case ItemTouchHelper.END: / / the START - > END tag complete event mAdapter. OnItemDone (viewHolder. GetAdapterPosition ()); break; Case ItemTouchHelper. START: / / END - > START delete events mAdapter. OnItemDeleted (viewHolder. GetAdapterPosition ()); break; default: ActionState is only ACTION_STATE_DRAG or ACTION_STATE_SWIPE */ @override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { switch (actionState) { case ItemTouchHelper.ACTION_STATE_DRAG: // Drag, if isCurrentlyActive, translationZ, Otherwise the reset viewHolder. ItemView. SetTranslationZ (SizeUtils dp2px (isCurrentlyActive? 4:0)); super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); break; ACTION_STATE_SWIPE: Case ItemTouchHelper.ACTION_STATE_SWIPE: view rootView = viewholder. itemView; View contentView = rootView.findViewById(R.id.ll_content_root); View actionView = rootView.findViewById(R.id.ll_action_root); ImageView doneImageView = actionView.findViewById(R.id.iv_task_done); View actionSpaceView = actionView.findViewById(R.id.view_action_space); ImageView deleteImageView = actionView.findViewById(R.id.iv_task_delete); if (dX < 0) { deleteImageView.setImageResource(R.drawable.ic_delete_white_24dp); deleteImageView.setBackgroundResource(R.color.ffff4081); doneImageView.setImageDrawable(null); doneImageView.setBackgroundResource(R.color.ffff4081); actionSpaceView.setBackgroundResource(R.color.ffff4081); } else { doneImageView.setImageResource(R.drawable.ic_done_white_24dp); doneImageView.setBackgroundResource(R.color.ff53c4ac); deleteImageView.setImageDrawable(null); deleteImageView.setBackgroundResource(R.color.ff53c4ac); actionSpaceView.setBackgroundResource(R.color.ff53c4ac); } contentView.setTranslationX(dX); break; default: }} /** * create a usm user by creating a usm user by creating a usm user by creating a usm user recyclerView, RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); View rootView = viewHolder.itemView; View contentView = rootView.findViewById(R.id.ll_content_root); contentView.setTranslationX(0); }}Copy the code
4. Create ItemTouchHelper and attachToRecyclerView
Create the TodoTaskAdapter, inject it into the DragSwipeCallback class, then inject ItemTouchHelper, and finally attachToRecyclerView
mTodoTaskAdapter = new TodoTaskAdapter(queryTaskFromDB());
ItemTouchHelper.Callback callback = new DragSwipeCallback(mTodoTaskAdapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mTodoTaskAdapter);
touchHelper.attachToRecyclerView(mRecyclerView);
Copy the code