Andorid Group Item top hover + interactive synchronization
Summary of demand
The top of the grouped data in some of the pages of the project needs to be hovered over, and the views that are hovered need to be operationally synchronized with views with the same layout in the ItemView, that is, synchronized with each other. The specified child already has a parent. You must call removeView() on The child’s parent first. Meaning the same View object cannot have two parents. Instead of simply adding the same View object to two parent objects, we need to find another way.
Program selection:
SitckyScrollView hover: Do not support list reuse, main thread will stall, pass.
② Overlay a View on the top of the ListView, regenerate the View that needs to hover, and synchronize the hover View with the original View. For listView sliding and group hovering, this scheme is too much work and not feasible.
③NestedScrollingChild and NestedScrollingParent schemes do not support grouping, not suitable.
④recyclerView + ItemDecoration + view.draw (canvas) + motionEvent.offlocation () : feasible.
Why did I choose the fourth option? Main reasons are as follows: first of all, we operate is a list of the display and control of hovering View moves must want to know the top of the Item information, RecyclerView. ItemDecoration can well solve the problem. RecyclerView, visible position and visible View in RecyclerView.Adapter can be easily obtained in ItemDecoration, so that it is easy to obtain the View that needs to hover. Second, in the ItemDecoration#onDrawView() method we can draw the View we want to hover over. ItemDecoration helps us to draw the hovering View easily. We only need to deal with the synchronization between the real View and the state of the hovering View. How to achieve state synchronization will be explained later.
ItemDecoration
Here is the implementation of ItemDecoration. It is an interface, and the functions of various internal methods are shown in the figure below:
As mentioned above, the time to draw the View should be in the onDrawOver method.
How about the top View
Let’s start with the code,
// Get the topmost ItemView
View adapterView = parent.getChildAt(0);
if(adapterView ! =null) {
// Get the View to draw. Here we need to draw a title and a Header for NewCHLayoutUnScroll.
// Get the height of the two views. We need their height to hover the View
View title = adapterView.findViewById(R.id.title);
int titleHeight = 0;
if(title ! =null && View.VISIBLE == title.getVisibility()) {
titleHeight = title.getMeasuredHeight();
}
int saveCount = canvas.save();
// Set the total offset using the height we obtained above
stickyViewHeight = titleHeight;
if (adapterView.getBottom() < stickyViewHeight) {
offsetY = stickyViewHeight - adapterView.getBottom();
canvas.translate(0, -offsetY);
}
/ / render the View
if(title ! =null) {
title.draw(canvas);
isTitleDrawed = true;
}
canvas.restoreToCount(saveCount);
} else{}Copy the code
The following code is explained:
RecyclerView#getChildren; RecyclerView#getChildren; RecyclerView#getChildAt(int index); View adapterView = parent. GetChildAt (0)
We need to draw the View at the top of the View is a child of the top ItemView, according to view.findViewById (ID) to get the View to draw.
3. In order to realize the effect of synchronous sliding up and down of hovering View in vertical RecyclerView, we need to find the critical value of hovering View display complete and incomplete, as shown in the figure below:
As analyzed in the above figure, when drawing a hover View, we can determine the offset drawn by the hover View according to the height of the hover View and the size of the uppermost ItemView.getBottom(), so that the hover View can slide along RecyclerView at an appropriate time.
Synchronizes state changes in ItemView to hover View.
The synchronization of state changes here includes the left and right swiping of the list in the itemView and the hovering View status update triggered by the title click event in the itemView. It’s easy to implement, just call the following line of code when updating the state in the ItemView:
mRecyclerView.invalidateItemDecorations();
Copy the code
RecyclerView# invalidateItemDecorations () method will cause ItemDecoration re-paint, onDrawOver method will call again, so the hovering View also will be redrawn, It’s going to match the title of the top ItemView.
Synchronize the events of the hover View to the top ItemView.
This step is the trickiest. The problem can be interpreted as how to synchronize the events of the Canvas drawn View to the painted View. The hover View drawn first is not a real View, and its event is passed to RecyclerView by default. Even if the event is directly intercepted in RecyclerView, how to deal with it is also a problem, because it is difficult to locate the actual location of the MotionEvent. At this point, we can take a page from StickyScrollView and use it to draw a hover View. The core code is as follows:
StickyScrollView#onTouchEvent
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(redirectTouchesToStickyView) { ev.offsetLocation(0, ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView))); }...return super.onTouchEvent(ev);
}
Copy the code
The core code is ev.offsetLocation().
MotionEvent#offsetLocation
/**
* Adjust this event's location. * @param deltaX Amount to add to the current X coordinate of the event. * @param deltaY Amount to add to the current Y coordinate of the event. */ public final void offsetLocation(float deltaX, float deltaY) { if (deltaX ! = 0.0 f | | deltaY! = 0.0f) {nativeOffsetLocation(mNativePtr, deltaX, deltaY); }}Copy the code
This method offsets the action location of the MotionEvent by a certain amount, which means that the event will be passed to another location. In addition, in the ViewGroup event distribution source, is also through MotionEvent#offsetLocation(offsetX, offsetY) to handle the event.
Based on the above analysis, we can pass the MotionEvent of the hover View to the real View region through MotionEvent#offsetLocation(offsetX, offsetY) method. The only thing we need to do is to calculate the value of offsetY.
One more thing to note: Where do we get the MotionEvent and how do we get the recyclerView.item event? Look here, Passing MotionEvents from RecyclerView. OnItemTouchListener to GestureDetectorCompat, Add OnItemTouchListener to recyclerView, and then use OnItemTouchListener#onInterceptTouchEvent to get the event; Once we get the event, we need to use gesture-related classes to process the event. The specific code is as follows:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.myfrag, container, false);
detector = new GestureDetectorCompat(getActivity(), new RecyclerViewOnGestureListener());
recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview);
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.addOnItemTouchListener(this);
adapter = new MyAdapter(myData));
recyclerView.setAdapter(adapter);
return rootView;
}
private class RecyclerViewOnGestureListener extends SimpleOnGestureListener {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
View view = recyclerView.findChildViewUnder(e.getX(), e.getY());
int position = recyclerView.getChildPosition(view);
// handle single tap
return super.onSingleTapConfirmed(e);
}
public void onLongPress(MotionEvent e) {
View view = recyclerView.findChildViewUnder(e.getX(), e.getY());
int position = recyclerView.getChildPosition(view);
// handle long press
super.onLongPress(e);
}
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
detector.onTouchEvent(e);
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
Copy the code
Ok, the above analysis of how to achieve ios-like group hover effect, to solve the problem of the train of thought is described, here is a summary:
Reference:
1. In-depth understanding of ItemDecoration
2. Inspiration
3, recyclerView event processing
4. Gesture detection