package com.netease.nim.uikit.common.ui.recyclerview.listener; import android.os.Build; import android.support.v4.view.GestureDetectorCompat; import android.support.v7.widget.RecyclerView; import android.view.GestureDetector; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import com.netease.nim.uikit.common.ui.recyclerview.adapter.IRecyclerView; import com.netease.nim.uikit.common.ui.recyclerview.holder.BaseViewHolder; import java.util.Iterator; import java.util.Set; import static com.netease.nim.uikit.common.ui.recyclerview.adapter.IRecyclerView.FETCHING_VIEW; /** * This can be useful for applications that wish to implement various forms of click and longclick and childView click * manipulation of item views within the RecyclerView. SimpleClickListener may intercept * a touch interaction already in progress even if the SimpleClickListener is already handling that * gesture stream itself for the purposes of scrolling. * * @see RecyclerView.OnItemTouchListener */ public abstract class SimpleClickListener<T extends IRecyclerView> implements RecyclerView.OnItemTouchListener { private GestureDetectorCompat mGestureDetector; private RecyclerView recyclerView; private Set<Integer> childClickViewIds; private Set<Integer> longClickViewIds; protected T baseAdapter; public static String TAG = "SimpleClickListener"; private boolean mIsPrepressed = false; private boolean mIsShowPress = false; private View mPressedView = null; private boolean shouldDetectGesture = true; private int longClickDelta = 200; @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { if (!shouldDetectGesture()) { return false; // 拦截手势检测 } // 手势检测 if (recyclerView == null) { this.recyclerView = rv; this.baseAdapter = (T) 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 || vh.getItemViewType() != IRecyclerView.LOADING_VIEW || vh.getItemViewType() != FETCHING_VIEW) { mPressedView.setPressed(false); } mPressedView = null; } mIsShowPress = false; mIsPrepressed = false; } return false; } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { if (!shouldDetectGesture()) { return; } mGestureDetector.onTouchEvent(e); } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener { private RecyclerView recyclerView; public ItemTouchHelperGestureListener(RecyclerView recyclerView) { this.recyclerView = recyclerView; } @Override public boolean onDown(MotionEvent e) { if (!shouldDetectGesture()) { return false; } mIsPrepressed = true; mPressedView = recyclerView.findChildViewUnder(e.getX(), e.getY()); super.onDown(e); return false; } @Override public void onShowPress(MotionEvent e) { if (!shouldDetectGesture()) { return; } if (mIsPrepressed && mPressedView != null) { // mPressedView.setPressed(true); mIsShowPress = true; } super.onShowPress(e); } @Override public boolean onSingleTapUp(MotionEvent e) { if (!shouldDetectGesture()) { return false; } if (mIsPrepressed && mPressedView != null) { final View pressedView = mPressedView; BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(pressedView); if (isHeaderOrFooterPosition(vh.getLayoutPosition())) { return false; } childClickViewIds = vh.getChildClickViewIds(); if (childClickViewIds != null && childClickViewIds.size() > 0) { for (Iterator it = childClickViewIds.iterator(); it.hasNext(); ) { View childView = pressedView.findViewById((Integer) it.next()); if (inRangeOfView(childView, e) && childView.isEnabled()) { setPressViewHotSpot(e, childView); childView.setPressed(true); onItemChildClick(baseAdapter, childView, vh.getLayoutPosition() - baseAdapter.getHeaderLayoutCount()); resetPressedView(childView); return true; } else { childView.setPressed(false); } } setPressViewHotSpot(e, pressedView); mPressedView.setPressed(true); for (Iterator it = childClickViewIds.iterator(); it.hasNext(); ) { View childView = pressedView.findViewById((Integer) it.next()); childView.setPressed(false); } onItemClick(baseAdapter, pressedView, vh.getLayoutPosition() - baseAdapter.getHeaderLayoutCount()); } else { setPressViewHotSpot(e, pressedView); mPressedView.setPressed(true); for (Iterator it = childClickViewIds.iterator(); it.hasNext(); ) { View childView = pressedView.findViewById((Integer) it.next()); childView.setPressed(false); } onItemClick(baseAdapter, pressedView, vh.getLayoutPosition() - baseAdapter.getHeaderLayoutCount()); } resetPressedView(pressedView); } return true; } @Override public void onLongPress(MotionEvent e) { if (!shouldDetectGesture()) { return; } boolean isChildLongClick = false; if (mIsPrepressed && mPressedView != null) { mPressedView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(mPressedView); if (!isHeaderOrFooterPosition(vh.getLayoutPosition())) { longClickViewIds = vh.getItemChildLongClickViewIds(); if (longClickViewIds != null && longClickViewIds.size() > 0) { for (Iterator it = longClickViewIds.iterator(); it.hasNext(); ) { View childView = mPressedView.findViewById((Integer) it.next()); if (inRangeOfView(childView, e) && childView.isEnabled()) { setPressViewHotSpot(e, childView); onItemChildLongClick(baseAdapter, childView, vh.getLayoutPosition() - baseAdapter.getHeaderLayoutCount()); childView.setPressed(true); mIsShowPress = true; isChildLongClick = true; break; } } } if (!isChildLongClick) { onItemLongClick(baseAdapter, mPressedView, vh.getLayoutPosition() - baseAdapter.getHeaderLayoutCount()); setPressViewHotSpot(e, mPressedView); mPressedView.setPressed(true); for (Iterator it = longClickViewIds.iterator(); it.hasNext(); ) { View childView = mPressedView.findViewById((Integer) it.next()); childView.setPressed(false); } mIsShowPress = true; } } } } private final void resetPressedView(final View pressedView) { if (pressedView != null) { pressedView.postDelayed(new Runnable() { @Override public void run() { if (pressedView != null) { pressedView.setPressed(false); } } }, longClickDelta); } mIsPrepressed = false; mPressedView = null; } } 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(T 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(T adapter, View view, int position); public abstract void onItemChildClick(T adapter, View view, int position); public abstract void onItemChildLongClick(T adapter, View view, int position); public boolean inRangeOfView(View view, MotionEvent ev) { int[] location = new int[2]; if (view.getVisibility() != View.VISIBLE) { 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 (baseAdapter == null) { if (recyclerView != null) { baseAdapter = (T) recyclerView.getAdapter(); } else { return false; } } int type = baseAdapter.getItemViewType(position); return (type == IRecyclerView.EMPTY_VIEW || type == IRecyclerView.HEADER_VIEW || type == IRecyclerView.FOOTER_VIEW || type == IRecyclerView.LOADING_VIEW || type == FETCHING_VIEW); } public void setShouldDetectGesture(boolean shouldDetectGesture) { this.shouldDetectGesture = shouldDetectGesture; } private boolean shouldDetectGesture() { if (!shouldDetectGesture) { mIsPrepressed = false; mPressedView = null; } return shouldDetectGesture; } public void setLongClickDelta(int longClickDelta) { if (longClickDelta <= 0 || longClickDelta > 2000) { longClickDelta = 200; } this.longClickDelta = longClickDelta; } }