preface

Today, we often use a requirement in development, that is the hover effect, what is the hover effect directly look at the picture:

Is RecyclerView grouping, when the group head at the top of the need to suspend, this such as in the selection of address, address book grouping are useful, in the usual development we are generally directly with wheels, today we will explore how to achieve this effect.

The realization of this effect, I have seen several libraries, in fact, the principle is similar, or use DslAdapter today, mainly to understand its principle, after understanding the principle of wheel is very simple.

The body of the

In DslAdapter, the hover Item implementation class is in HoverItemDecoration. First, it inherits from ItemDecoration.

[juejin.cn/post/700762… open source library source code learning -DslAdapter side drop and drag function)

Here it is mentioned again, mainly three methods, respectively is to set the interval, draw before RecyclerView and draw after RecyclerView, the following figure is easier to understand

See here, is it possible to guess how to realize the hover effect, yes, is to use the onDrawOver function, draw a hover View above RecyclerView, the original RecyclerView to cover up, to look like the effect of suspension.

Look directly at the picture:

Then scroll up RecyclerView, then group 1 will be replaced by group 2:

The requirement here is to have a topping effect of group 1, and the hover View can only draw one, to know when group 2 is RecyclerView, and when the onDrawOver method draws.

Understand the principle, very close to implementation, really very close.

onDrawOver

Since the hover View is drawn by onDrawOver, let’s take a closer look at this function:

public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
        @NonNull State state) {
    onDrawOver(c, parent);
}
Copy the code
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. Params: C -- Canvas to draw into parent -- RecyclerView this ItemDecoration is drawing into state -- The current state of RecyclerView.Copy the code

This method has a lot of callback time, as long as itemView is drawn or scrolling, this method will call back, so it is easy to do, you can get recyclerView state in real time.

The specific implementation

The first step is to find the hover View to draw, and the second step is to draw the hover View, so there are also two steps in the onDrawOver method:

// Override fun onDrawOver(canvas: canvas, parent: RecyclerView, state: RecyclerView.State) { Log.i(TAG, "onDrawOver: On RecyclerView drawing modification ") if (state. IsPreLayout | | state. WillRunSimpleAnimations ()) {return} / / find hover the View CheckHoverDecoration (parent) // HoverOlder is not empty, draw hoverViewHolder? .let { if (! hoverDecorationRect.isEmpty) { hoverCallback? .apply { if (enableTouchEvent && enableDrawableState) { addHoverView(it.itemView) } drawOverDecoration.invoke(canvas, paint, it, hoverDecorationRect) } } } }Copy the code

But the process is very complicated, so let me slow down.

1, obtain the first ViewHolder displayed by RecyclerView.

By means:

Private fun firstChildViewHolder(parent: RecyclerView, childIndex: Int): RecyclerView.ViewHolder? { if (parent.childCount > childIndex) { return parent.findContainingViewHolder(parent.getChildAt(childIndex)) } return null }Copy the code
FirstChildViewHolder (parent, 0)Copy the code

Note that this is the first ViewHolder displayed, not the first ViewHolder in the RecyclerView data set. It does not matter if the RecyclerView is suspended after the RecyclerView is drawn, for example:

The position of firstChildViewHolder is 0 -> 1 ->2, with many callbacks.

/ / of the first visible said the ViewHolder pos firstChildAdapterPositionCopy the code

The ViewHolder is the ViewHolder that needs to be hovered. The ViewHolder is the ViewHolder that needs to be hovered.

If the first visible ViewHolder does not have a hover set, look ahead to find the nearest ViewHolder, as shown below:

In both cases, the same hover ViewHolder needs to be drawn so that the code is not stuck. For details, please refer to the HoverItemDecoration class of the source library.

If group 1 is already hovering, one of the things that happens when you scroll up is that group 2 will run under group 1 and slide up again, and group 1 will be pushed up.

Note that group 1 is now created by onDrawOver in RecyclerView, not the RecyclerView Item, so you need to create your own logic and animation:

Find the next ViewHolder to hover over. If each ViewHolder is the same height, the View to hover over is the View next to the first ViewHolder visible:

For example, if the ViewHolder height is the same, when the first ViewHolder height is 1, the next ViewHolder height is 1, and the next ViewHolder height is 2, then the ViewHolder height should be 2, but when the ViewHolder height is different, this may not be the case, for example:

In this case, the first ViewHolder to be hovered is still 1, but the next ViewHolder to be hovered is 3 instead of 2, and the ViewHolder to be hovered is moved up according to the ViewHolder of 3. Therefore, the algorithm here is to calculate the next ViewHolder to be hovered according to the height drawn. The code is as follows:

ViewHolder fun findNextDecoration(parent: RecyclerView, Adapter: RecyclerView.Adapter<*>, decorationHeight: Int, offsetIndex: Int = 1 ): RecyclerView.ViewHolder? { var result: RecyclerView.ViewHolder? = null if (hoverCallback ! = null) { val callback: HoverCallback = hoverCallback!! Val childIndex = findNextChildIndex(offsetIndex) if (childIndex! = RecyclerView.NO_POSITION) { val childViewHolder = firstChildViewHolder(parent, childIndex) if (childViewHolder ! = null) { if (callback.haveHoverDecoration.invoke( adapter, ChildViewHolder. AdapterPosition)) {/ / if the next item with line result = childViewHolder} else {/ / line if not (childViewHolder. ItemView. Bottom < decorationHeight) {/ / the height of the item, there is no line is so high, continue to search result = findNextDecoration (the parent, adapter, decorationHeight, offsetIndex + 1 ) } else { } } } } } return result }Copy the code

4. The GIF in step 3 will be pulled up, and then the pull down will bring out the previous group. Here, in fact, to reverse the thinking, when the pull down, the first ViewHolder that is currently visible is the ViewHolder, then its next ViewHolder will be next to it, so a set of code can handle it.

5. From the above steps we can get the View we need to hover and the Rect we need to display the hover View (this will change when we are jacked) and draw the ViewHolder:

hoverViewHolder? .let { if (! HoverDecorationRect. IsEmpty) {Log. I (TAG, "onDrawOver: add a hover ViewHolder") hoverCallback? .apply {if (enableTouchEvent && enableDrawableState) {addHoverView(it.itemView)} // draw decoration drawOverDecoration.invoke(canvas, paint, it, hoverDecorationRect) } } }Copy the code

Touch event Handling

Through the above steps, we can draw the suspension of RecyclerView on the hover View, of course, the specific implementation to consider the details of much more than this, or that sentence, here only provide ideas and solutions, see the specific implementation of the code.

The hover effect can be realized up to now, but there is a problem, that is, click hover View. Although this is the same as the ViewHolder of RecyclerView, it is not the ViewHolder of RecyclerView, so it cannot respond to click events, as shown in the figure:

You’ll notice that when group 1 is drawn onDrawOver, it won’t be able to click, fold and fold won’t work anymore, so you have to handle the touch event.

The addItemTouchListener method is used to generate touch events in RecyclerView before changing them to RecyclerView.

addOnItemTouchListener(itemTouchListener)
Copy the code
Override fun onInterceptTouchEvent(recyclerView: recyclerView, Event: MotionEvent): Boolean {val action = event.actionmasked if (action == motionEvent.action_down) {//Rect contains the isDownInHoverItem method = hoverDecorationRect.contains(event.x.toInt(), event.y.toInt()) } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { isDownInHoverItem } // When the click point is on the hover View, If (isDownInHoverItem) {//L.i("onInterceptTouchEvent:$Event ") log. I ("onInterceptTouchEvent:$event") $isDownInHoverItem ") onTouchEvent(recyclerView, event)} return isDownInHoverItem}Copy the code
override fun onTouchEvent(recyclerView: RecyclerView, event: MotionEvent) { Log.i(TAG, "onTouchEvent: eventAction = ${event.actionMasked}") if (isDownInHoverItem) { Log.i(TAG, "onTouchEvent: Handle touch events on HoverItem ") hoverViewHolder? .apply { if (hoverCallback? .enableDrawableState == true) { if (event.actionMasked == MotionEvent.ACTION_DOWN) { Log.i(TAG, "onTouchEvent: Up ") recyclerView. PostDelayed (cancelEvent, 160 l)} else {recyclerView. RemoveCallbacks (cancelEvent)} / / must call dispatchTouchEvent, otherwise the ViewGroup inside View, I (TAG, "onTouchEvent: ItemView distribute events ") itemView. DispatchTouchEvent (event) if (itemView is ViewGroup) {if ((itemView as ViewGroup).onInterceptTouchEvent(event)) { itemView.onTouchEvent(event) } } else { itemView.onTouchEvent(event) } } else  { Log.i(TAG, "onTouchEvent: Drawable is not activated ") // Drawable state is not activated, you need to manually control the touch event, If (event.actionmasked == motionEvent.action_up) {log. I (TAG, "onTouchEvent: ItemView handles UP events by itself ") itemView.performClick()}}}}}Copy the code

When a touch event is in the Rect range of the dangling View, the touch event is intercepted and processed. Since we created the dangling View by creating a ViewHolder, we can directly retrieve the itemView and distribute the event.

conclusion

This chapter mainly introduces the hover effect, which is quite simple when used in ordinary times, but the implementation of the principle is quite complicated. You need to understand the use of ItemDecoration properly, and other requirements can be quickly realized later.

Here is just the analysis of the principle, the specific implementation of the source code, a lot of details, no further details.

Haven’t read the previous article can jump to see, basic RecyclerView several common operations are said again:

# Open Source Library source learning -DslAdapter side drop and drag features

# Open source library source learning -DslAdapter sideslip feature