version:2.8.5
- For more information, see: http://www.cherylgood.cn
Today, we mainly analyze the source code of BaseRecycler view Adapter Helper, which provides viewing with the ability to listen to click events.
public abstract class SimpleClickListener implements RecyclerView.OnItemTouchListener { private GestureDetectorCompat mGestureDetector; private RecyclerView recyclerView; protected BaseQuickAdapter baseQuickAdapter; public static String TAG = "SimpleClickListener"; private boolean mIsPrepressed = false; private boolean mIsShowPress = false; private View mPressedView = null; [@Override](https://my.oschina.net/u/1162528) public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { Log.i(TAG,">>>>onInterceptTouchEvent e"+e.getActionMasked()); if (recyclerView == null) { this.recyclerView = rv; this.baseQuickAdapter = (BaseQuickAdapter) recyclerView.getAdapter(); mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener(recyclerView)); }else if (recyclerView!=rv){ this.recyclerView = rv; this.baseQuickAdapter = (BaseQuickAdapter) recyclerView.getAdapter(); mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener(recyclerView)); } if (!mGestureDetector.onTouchEvent(e) && e.getActionMasked() == MotionEvent.ACTION_UP && mIsShowPress) { if (mPressedView!=null){ BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(mPressedView); if (vh == null ||!isHeaderOrFooterView(vh.getItemViewType())) { mPressedView.setPressed(false); } } mIsShowPress = false; mIsPrepressed = false; } return false; } [@Override](https://my.oschina.net/u/1162528) public void onTouchEvent(RecyclerView rv, MotionEvent e) { Log.i(TAG,">>>>onTouchEvent e"+e.getActionMasked()); mGestureDetector.onTouchEvent(e); } [@Override](https://my.oschina.net/u/1162528) public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { Log.i(TAG,">>>>onRequestDisallowInterceptTouchEvent disallowIntercept"+disallowIntercept); } private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener { private RecyclerView recyclerView; ItemTouchHelperGestureListener(RecyclerView recyclerView) { this.recyclerView = recyclerView; } [@Override](https://my.oschina.net/u/1162528) public boolean onDown(MotionEvent e) { mIsPrepressed = true; mPressedView = recyclerView.findChildViewUnder(e.getX(), e.getY()); super.onDown(e); return false; } [@Override](https://my.oschina.net/u/1162528) public void onShowPress(MotionEvent e) { Log.i(TAG,">>>>onShowPress e"+e); if (mIsPrepressed && mPressedView != null) { // mPressedView.setPressed(true); mIsShowPress = true; } super.onShowPress(e); } @Override public boolean onSingleTapUp(MotionEvent e) { Log.i(TAG,">>>>onSingleTapUp e"+e); if (mIsPrepressed && mPressedView != null) { if (recyclerView.getScrollState()!=RecyclerView.SCROLL_STATE_IDLE){ return false; } final View pressedView = mPressedView; BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(pressedView); if (isHeaderOrFooterPosition(vh.getLayoutPosition())) { return false; } Set<Integer> childClickViewIds = vh.getChildClickViewIds(); Set<Integer> nestViewIds = vh.getNestViews(); if (childClickViewIds != null && childClickViewIds.size() > 0) { for (Integer childClickViewId : childClickViewIds) { View childView = pressedView.findViewById(childClickViewId); if (childView != null) { if (inRangeOfView(childView, e) && childView.isEnabled()) { if (nestViewIds!=null&&nestViewIds.contains(childClickViewId)){ return false; } setPressViewHotSpot(e, childView); childView.setPressed(true); onItemChildClick(baseQuickAdapter, childView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount()); resetPressedView(childView); return true; } else { childView.setPressed(false); } } } setPressViewHotSpot(e,pressedView); mPressedView.setPressed(true); for (Integer childClickViewId : childClickViewIds) { View childView = pressedView.findViewById(childClickViewId); if (childView!=null){ childView.setPressed(false); } } onItemClick(baseQuickAdapter, pressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount()); } else { setPressViewHotSpot(e,pressedView); mPressedView.setPressed(true); if (childClickViewIds != null && childClickViewIds.size() > 0) { for (Integer childClickViewId : childClickViewIds) { View childView = pressedView.findViewById(childClickViewId); if (childView!=null){ childView.setPressed(false); } } } onItemClick(baseQuickAdapter, pressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount()); } resetPressedView(pressedView); } return true; } private void resetPressedView(final View pressedView) { if (pressedView!=null){ pressedView.postDelayed(new Runnable() { @Override public void run() { if (pressedView!=null){ pressedView.setPressed(false); } } }, 100); } mIsPrepressed = false; mPressedView = null; } @Override public void onLongPress(MotionEvent e) { Log.i(TAG,">>>>onLongPress e"+e); boolean isChildLongClick =false; if (recyclerView.getScrollState()!=RecyclerView.SCROLL_STATE_IDLE){ return ; } if (mIsPrepressed && mPressedView != null) { mPressedView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(mPressedView); if (!isHeaderOrFooterPosition(vh.getLayoutPosition())) { Set<Integer> longClickViewIds = vh.getItemChildLongClickViewIds(); Set<Integer> nestViewIds = vh.getNestViews(); if (longClickViewIds != null && longClickViewIds.size() > 0) { for (Integer longClickViewId : longClickViewIds) { View childView = mPressedView.findViewById(longClickViewId); if (inRangeOfView(childView, e) && childView.isEnabled()) { if (nestViewIds!=null&&nestViewIds.contains(longClickViewId)){ isChildLongClick=true; break; } setPressViewHotSpot(e, childView); onItemChildLongClick(baseQuickAdapter, childView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount()); childView.setPressed(true); mIsShowPress = true; isChildLongClick = true; break; } } } if (!isChildLongClick){ onItemLongClick(baseQuickAdapter, mPressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount()); setPressViewHotSpot(e,mPressedView); mPressedView.setPressed(true); if (longClickViewIds != null) { for (Integer longClickViewId : longClickViewIds) { View childView = mPressedView.findViewById(longClickViewId); childView.setPressed(false); } } mIsShowPress = true; } } } } } private void setPressViewHotSpot(final MotionEvent e,final View mPressedView) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { /** * when click Outside the region ,mPressedView is null */ if (mPressedView !=null && mPressedView.getBackground() != null) { mPressedView.getBackground().setHotspot(e.getRawX(), e.getY()-mPressedView.getY()); } } } /** * Callback method to be invoked when an item in this AdapterView has * been clicked. * * @param view The view within the AdapterView that was clicked (this * will be a view provided by the adapter) * @param position The position of the view in the adapter. */ public abstract void onItemClick(BaseQuickAdapter adapter, View view, int position); /** * callback method to be invoked when an item in this view has been * click and held * * @param view The view whihin the AbsListView that was clicked * @param position The position of the view int the adapter * @return true if the callback consumed the long click ,false otherwise */ public abstract void onItemLongClick(BaseQuickAdapter adapter, View view, int position); public abstract void onItemChildClick(BaseQuickAdapter adapter, View view, int position); public abstract void onItemChildLongClick(BaseQuickAdapter adapter, View view, int position); public boolean inRangeOfView(View view, MotionEvent ev) { int[] location = new int[2]; if (view==null||!view.isShown()){ return false; } view.getLocationOnScreen(location); int x = location[0]; int y = location[1]; if (ev.getRawX() < x || ev.getRawX() > (x + view.getWidth()) || ev.getRawY() < y || ev.getRawY() > (y + view.getHeight())) { return false; } return true; } private boolean isHeaderOrFooterPosition(int position) { /** * have a headview and EMPTY_VIEW FOOTER_VIEW LOADING_VIEW */ if (baseQuickAdapter==null){ if (recyclerView!=null){ baseQuickAdapter= (BaseQuickAdapter) recyclerView.getAdapter(); }else { return false; } } int type = baseQuickAdapter.getItemViewType(position); return (type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOADING_VIEW); } private boolean isHeaderOrFooterView(int type) { return (type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOADING_VIEW); }}
Small partners who have used click events in the BVAH framework should be familiar with the following interface classes:
- OnItemChildClickListener adds a click event listener to childView
- OnItemChildLong ClickListener adds a long-click event listener to childView
- OnItemClickListener adds a click event listener to the entire Item
- OnItemLong ClickListener adds a long-click event listener to the entire Item
There are callback interfaces for responses, but what empowers them is the SimpleClickListener class. Are you interested? Let's take a look at the real face of this class:
public abstract class SimpleClickListener implements RecyclerView.OnItemTouchListener {....}
-
First, we find that SimpleClickListener implements RecyclerView.OnItemTouchListener interface OnItemTouchListener interface is a listener provided by recyclerview to listen for item clicks. The source code is as follows:
Allow applications to intercept and process touch events, usually when implementing interactive events that operate recyclerview public static interface OnItemTouchListener { The onInterceptTouchEvent method is called before the touch ing interaction occurs in the recyclerview's child view. public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e); public void onTouchEvent(RecyclerView rv, MotionEvent e); public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept); }
> Note: Click events can be implemented in multiple ways, such as:
- Implementation using addOnItemTouchListener() provided by RecyclerView
- Add Click Event Listener when Creating ItemView
- Implementation in ItemView attach Recycler View
Intra-class field resolution
- Private Gesture Detector Compat mGesture Detector; Gesture Detection Auxiliary Class has better compatibility than Gesture Detector Compat, and the api uses the same.
- private RecyclerView recyclerView; stores recyclerview instance objects, which are later used to obtain adapter s and viewholder s
- protected BaseQuickAdapter baseQuickAdapter;
- private boolean mIsPrepressed = false; controls are marked by Subscripts
- private boolean mIsShowPress = false; control press status identifier
- private View mPressedView = null; clicked control
Next, we analyze the mechanism of event transmission, when touch event occurs: onInterceptTouchEvent was the first to be called.
@Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { Log.i(TAG,">>>>onInterceptTouchEvent e"+e.getActionMasked()); if (recyclerView == null) { this.recyclerView = rv; this.baseQuickAdapter = (BaseQuickAdapter) recyclerView.getAdapter(); mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener(recyclerView)); }else if (recyclerView!=rv){ this.recyclerView = rv; this.baseQuickAdapter = (BaseQuickAdapter) recyclerView.getAdapter(); mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener(recyclerView)); } if (!mGestureDetector.onTouchEvent(e) && e.getActionMasked() == MotionEvent.ACTION_UP && mIsShowPress) { if (mPressedView!=null){ BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(mPressedView); if (vh == null ||!isHeaderOrFooterView(vh.getItemViewType())) { mPressedView.setPressed(false); } } mIsShowPress = false; mIsPrepressed = false; } return false; }
In the source code, two main things are done in the onInterceptTouchEvent method:
- Initialize recyclerView, baseQuickAdapter, mGestureDetector instances
- If the click event is not consumed eventually, the control state that is touch ed into the press state is restored.
Then we can hand the touch event to mGesture Detector in the onTouchEvent method for processing.
@Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { Log.i(TAG,">>>>onTouchEvent e"+e.getActionMasked()); mGestureDetector.onTouchEvent(e); }
Then we look at the ItemTouchHelper GestureListener listener bound to initialize mGestureDetector. The ItemTouchHelper GestureListener listener inherits from the SimpleOnGestureListener class. We can see from the source code of the SimpleOnGestureListener:
public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener, OnContextClickListener {
In fact, it implements a gesture interaction listener inside, but the method body does nothing. The advantage is that we can inherit it without implementing a bunch of methods, which method we need to use, and rewrite the method.
This time we mainly used the following methods:
- onDown triggers when pressed
- Triggered when onShowPress is in press state
- onSingleTapUp Click Event Triggered
- onLongPress Long Click Event press Status Trigger
You can see the benefits of using Gesture Detector Compat.
According to the occurrence of a touch event, down - > showpress - > up That is, press down, show the extrusion effect, loosen and spring up.
@Override public boolean onDown(MotionEvent e) { mIsPrepressed = true; mPressedView = recyclerView.findChildViewUnder(e.getX(), e.getY()); super.onDown(e); return false; }
In down:
- Reset mIsPrepressed to true, currently clicked, not released
-
According to the x and y of e, get the clicked view.
@Override public void onShowPress(MotionEvent e) { if (mIsPrepressed && mPressedView != null) { mIsShowPress = true; } super.onShowPress(e); } In showpress:
- Based on the result of down, set the mIsShowPress field to determine whether the current state is showpress or not.
If it is a stand-alone event, onSingleTapUp (Motion Event e) is triggered. From the source code of onSIngleTapUp, you can see that:
if (mIsPrepressed && mPressedView != null) {
-
If the view in our recyclerview is clicked, the code will be executed later.
if (recyclerView.getScrollState()!=RecyclerView.SCROLL_STATE_IDLE){ return false; }
-
If the user is not in RecyclerView.SCROLL_STATE_IDLE state and returns false directly, the user may be sliding, not clicking.
final View pressedView = mPressedView; BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(pressedView);
if (isHeaderOrFooterPosition(vh.getLayoutPosition())) { return false; }
- Get the viewholder if it's a head view or a tail view, and return false. We don't need to deal with it.
Next comes the key code:
Set<Integer> childClickViewIds = vh.getChildClickViewIds();
- Get ids with click event view added Set<Integer> nestViewIds = vh.getNestViews();
- Get the ids of the view container embedded with recyclerview if (childClickViewIds != null && childClickViewIds.size() > 0) {
- If there is a view that adds click event monitoring
for (Integer childClickViewId : childClickViewIds) { - Start traversing views
View childView = pressedView.findViewById(childClickViewId); -
view with click events added based on ids
if (inRangeOfView(childView, e) && childView.isEnabled()) {
- Judging that view is within the click range, cut isEnable
If you cut isEnable = true within the click range, execute the following code
if (nestViewIds!=null&&nestViewIds.contains(childClickViewId)){ return false; }
- If the recyclerview is embedded, it is not processed, and is handled by the sub-recyclerview.
After filtering layer by layer, all that's left is what you need to do when the view is clicked: If
- setPressViewHotSpot(e, childView); Modify the status of childview
- childView.setPressed(true); set its press to true
- OnItemChildClick (baseQuickAdapter, childView, vh. getLayoutPosition () - baseQuickAdapter. getHeaderLayoutCount ()); callback event onItemChildClick.
- resetPressedView(childView); reset the status of childview
- return true; indicates that the event has been consumed
If not out of click range or isEable=false, then childView.setPressed(false);
The above process deals with the child view click under item. If the item dimension clicks, the following code is executed. The process is basically the same, but the onItemClick method is called back.
setPressViewHotSpot(e,pressedView); mPressedView.setPressed(true); for (Integer childClickViewId : childClickViewIds) { View childView = pressedView.findViewById(childClickViewId); if (childView!=null){ childView.setPressed(false); } } onItemClick(baseQuickAdapter, pressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount()); }
Previously, when there is a child view, if there is no child view, the natural click events are item. The operation is similar to that when there is a child view but the child view is not within the click range or isEable=false.
else { setPressViewHotSpot(e,pressedView); mPressedView.setPressed(true); if (childClickViewIds != null && childClickViewIds.size() > 0) { for (Integer childClickViewId : childClickViewIds) { View childView = pressedView.findViewById(childClickViewId); if (childView!=null){ childView.setPressed(false); } } } onItemClick(baseQuickAdapter, pressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount()); } resetPressedView(pressedView);
Long-click implementations are similar, so there is no duplicate analysis here. If you don't know anything, you can leave a message directly. Summary: Through the use of Gesture Detector Compat, we can simplify our coding volume very well. When it comes to interactive operations, we can use Gesture Detector Compat to assist. The best teacher is to read excellent source code. Welcome to study together and make progress together!