commit
						0372df129e
					
				@ -0,0 +1,983 @@ | 
				
			|||||||
 | 
					/* | 
				
			||||||
 | 
					 * Copyright 2020 Mupceet | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License. | 
				
			||||||
 | 
					 * You may obtain a copy of the License at | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and | 
				
			||||||
 | 
					 * limitations under the License. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package io.legado.app.ui.widget.recycler; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.res.Resources; | 
				
			||||||
 | 
					import android.text.TextUtils; | 
				
			||||||
 | 
					import android.util.DisplayMetrics; | 
				
			||||||
 | 
					import android.util.Log; | 
				
			||||||
 | 
					import android.util.TypedValue; | 
				
			||||||
 | 
					import android.view.MotionEvent; | 
				
			||||||
 | 
					import android.view.View; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.annotation.NonNull; | 
				
			||||||
 | 
					import androidx.annotation.Nullable; | 
				
			||||||
 | 
					import androidx.core.view.ViewCompat; | 
				
			||||||
 | 
					import androidx.recyclerview.widget.GridLayoutManager; | 
				
			||||||
 | 
					import androidx.recyclerview.widget.RecyclerView; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.HashSet; | 
				
			||||||
 | 
					import java.util.Locale; | 
				
			||||||
 | 
					import java.util.Set; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.legado.app.BuildConfig; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** | 
				
			||||||
 | 
					 * @author mupceet | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					public class DragSelectTouchHelper { | 
				
			||||||
 | 
					    private static final String TAG = "DSTH"; | 
				
			||||||
 | 
					    private static final float MAX_HOTSPOT_RATIO = 0.5f; | 
				
			||||||
 | 
					    private static final EdgeType DEFAULT_EDGE_TYPE = EdgeType.INSIDE_EXTEND; | 
				
			||||||
 | 
					    private static final float DEFAULT_HOTSPOT_RATIO = 0.2f; | 
				
			||||||
 | 
					    private static final int DEFAULT_HOTSPOT_OFFSET = 0; | 
				
			||||||
 | 
					    private static final int DEFAULT_MAX_SCROLL_VELOCITY = 10; | 
				
			||||||
 | 
					    private static final int SELECT_STATE_NORMAL = 0x00; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* | 
				
			||||||
 | 
					     *                        !autoChangeMode           +-------------------+     inactiveSelect() | 
				
			||||||
 | 
					     *           +------------------------------------> |                   | <--------------------+ | 
				
			||||||
 | 
					     *           |                                      |      Normal       |                      | | 
				
			||||||
 | 
					     *           |        activeDragSelect(position)    |                   | activeSlideSelect()  | | 
				
			||||||
 | 
					     *           |      +------------------------------ |                   | ----------+          | | 
				
			||||||
 | 
					     *           |      v                               +-------------------+           v          | | 
				
			||||||
 | 
					     *  +-------------------+                              autoChangeMode     +-----------------------+ | 
				
			||||||
 | 
					     *  | Drag From Disable | ----------------------------------------------> |                       | | 
				
			||||||
 | 
					     *  +-------------------+                                                 |                       | | 
				
			||||||
 | 
					     *  |                   |                                                 |                       | | 
				
			||||||
 | 
					     *  |                   | activeDragSelect(position) && allowDragInSlide  |        Slide          | | 
				
			||||||
 | 
					     *  |                   | <---------------------------------------------- |                       | | 
				
			||||||
 | 
					     *  |  Drag From Slide  |                                                 |                       | | 
				
			||||||
 | 
					     *  |                   |                                                 |                       | | 
				
			||||||
 | 
					     *  |                   | ----------------------------------------------> |                       | | 
				
			||||||
 | 
					     *  +-------------------+                                                 +-----------------------+ | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final int SELECT_STATE_SLIDE = 0x01; | 
				
			||||||
 | 
					    private static final int SELECT_STATE_DRAG_FROM_NORMAL = 0x10; | 
				
			||||||
 | 
					    private static final int SELECT_STATE_DRAG_FROM_SLIDE = 0x11; | 
				
			||||||
 | 
					    private final DisplayMetrics mDisplayMetrics; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Start of the slide area. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private float mSlideAreaLeft; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * End of the slide area. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private float mSlideAreaRight; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The hotspot height by the ratio of RecyclerView. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private float mHotspotHeightRatio; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The hotspot height. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private float mHotspotHeight = 0f; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The hotspot offset. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private float mHotspotOffset; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Whether should continue scrolling when move outside top hotspot region. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private boolean mScrollAboveTopRegion; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Whether should continue scrolling when move outside bottom hotspot region. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private boolean mScrollBelowBottomRegion; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The maximum velocity of auto scrolling. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private int mMaximumVelocity; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Whether should auto enter slide mode after drag select finished. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private boolean mShouldAutoChangeState; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Whether can drag selection in slide select mode. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private boolean mIsAllowDragInSlideState; | 
				
			||||||
 | 
					    private RecyclerView mRecyclerView = null; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The coordinate of hotspot area. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private float mTopRegionFrom = -1f; | 
				
			||||||
 | 
					    private float mTopRegionTo = -1f; | 
				
			||||||
 | 
					    private float mBottomRegionFrom = -1f; | 
				
			||||||
 | 
					    private float mBottomRegionTo = -1f; | 
				
			||||||
 | 
					    private final View.OnLayoutChangeListener mOnLayoutChangeListener = new View.OnLayoutChangeListener() { | 
				
			||||||
 | 
					        @Override | 
				
			||||||
 | 
					        public void onLayoutChange(View v, int left, int top, int right, int bottom, | 
				
			||||||
 | 
					                                   int oldLeft, int oldTop, int oldRight, int oldBottom) { | 
				
			||||||
 | 
					            if (oldLeft != left || oldRight != right || oldTop != top || oldBottom != bottom) { | 
				
			||||||
 | 
					                if (v == mRecyclerView) { | 
				
			||||||
 | 
					                    Logger.i("onLayoutChange:new: " | 
				
			||||||
 | 
					                            + left + " " + top + " " + right + " " + bottom); | 
				
			||||||
 | 
					                    Logger.i("onLayoutChange:old: " | 
				
			||||||
 | 
					                            + oldLeft + " " + oldTop + " " + oldRight + " " + oldBottom); | 
				
			||||||
 | 
					                    init(bottom - top); | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The current mode of selection. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private int mSelectState = SELECT_STATE_NORMAL; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Whether is in top hotspot area. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private boolean mIsInTopHotspot = false; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Whether is in bottom hotspot area. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private boolean mIsInBottomHotspot = false; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Indicates automatically scroll. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private boolean mIsScrolling; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The actual speed of the current moment. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private int mScrollDistance = 0; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The reference coordinate for the action start, used to avoid reverse scrolling. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private float mDownY = Float.MIN_VALUE; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The reference coordinates for the last action. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private float mLastX = Float.MIN_VALUE; | 
				
			||||||
 | 
					    private float mLastY = Float.MIN_VALUE; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The selected items position. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private int mStart = RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					    private int mEnd = RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					    private int mLastRealStart = RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					    private int mLastRealEnd = RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					    private int mSlideStateStartPosition = RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					    private boolean mHaveCalledSelectStart = false; | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Developer callback which controls the behavior of DragSelectTouchHelper. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    private Callback mCallback; | 
				
			||||||
 | 
					    private Runnable mScrollRunnable = new Runnable() { | 
				
			||||||
 | 
					        @Override | 
				
			||||||
 | 
					        public void run() { | 
				
			||||||
 | 
					            if (mIsScrolling) { | 
				
			||||||
 | 
					                scrollBy(mScrollDistance); | 
				
			||||||
 | 
					                ViewCompat.postOnAnimation(mRecyclerView, mScrollRunnable); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					    private final RecyclerView.OnItemTouchListener mOnItemTouchListener = new RecyclerView.OnItemTouchListener() { | 
				
			||||||
 | 
					        @Override | 
				
			||||||
 | 
					        public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { | 
				
			||||||
 | 
					            Logger.d("onInterceptTouchEvent: x:" + e.getX() + ",y:" + e.getY() | 
				
			||||||
 | 
					                    + ", " + MotionEvent.actionToString(e.getAction())); | 
				
			||||||
 | 
					            RecyclerView.Adapter<?> adapter = rv.getAdapter(); | 
				
			||||||
 | 
					            if (adapter == null || adapter.getItemCount() == 0) { | 
				
			||||||
 | 
					                return false; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            boolean intercept = false; | 
				
			||||||
 | 
					            int action = e.getAction(); | 
				
			||||||
 | 
					            int actionMask = action & MotionEvent.ACTION_MASK; | 
				
			||||||
 | 
					            // It seems that it's unnecessary to process multiple pointers.
 | 
				
			||||||
 | 
					            switch (actionMask) { | 
				
			||||||
 | 
					                case MotionEvent.ACTION_DOWN: | 
				
			||||||
 | 
					                    mDownY = e.getY(); | 
				
			||||||
 | 
					                    // call the selection start's callback before moving
 | 
				
			||||||
 | 
					                    if (mSelectState == SELECT_STATE_SLIDE && isInSlideArea(e)) { | 
				
			||||||
 | 
					                        mSlideStateStartPosition = getItemPosition(rv, e); | 
				
			||||||
 | 
					                        if (mSlideStateStartPosition != RecyclerView.NO_POSITION) { | 
				
			||||||
 | 
					                            mCallback.onSelectStart(mSlideStateStartPosition); | 
				
			||||||
 | 
					                            mHaveCalledSelectStart = true; | 
				
			||||||
 | 
					                        } | 
				
			||||||
 | 
					                        intercept = true; | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                    break; | 
				
			||||||
 | 
					                case MotionEvent.ACTION_MOVE: | 
				
			||||||
 | 
					                    if (mSelectState == SELECT_STATE_DRAG_FROM_NORMAL | 
				
			||||||
 | 
					                            || mSelectState == SELECT_STATE_DRAG_FROM_SLIDE) { | 
				
			||||||
 | 
					                        Logger.i("onInterceptTouchEvent: drag mode move"); | 
				
			||||||
 | 
					                        intercept = true; | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                    break; | 
				
			||||||
 | 
					                case MotionEvent.ACTION_UP: | 
				
			||||||
 | 
					                    if (mSelectState == SELECT_STATE_DRAG_FROM_NORMAL | 
				
			||||||
 | 
					                            || mSelectState == SELECT_STATE_DRAG_FROM_SLIDE) { | 
				
			||||||
 | 
					                        intercept = true; | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                    // fall through
 | 
				
			||||||
 | 
					                case MotionEvent.ACTION_CANCEL: | 
				
			||||||
 | 
					                    // finger is lifted before moving
 | 
				
			||||||
 | 
					                    if (mSlideStateStartPosition != RecyclerView.NO_POSITION) { | 
				
			||||||
 | 
					                        selectFinished(mSlideStateStartPosition); | 
				
			||||||
 | 
					                        mSlideStateStartPosition = RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                    // selection has triggered
 | 
				
			||||||
 | 
					                    if (mStart != RecyclerView.NO_POSITION) { | 
				
			||||||
 | 
					                        selectFinished(mEnd); | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                    break; | 
				
			||||||
 | 
					                default: | 
				
			||||||
 | 
					                    // do nothing
 | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            // Intercept only when the selection is triggered
 | 
				
			||||||
 | 
					            Logger.d("intercept result: " + intercept); | 
				
			||||||
 | 
					            return intercept; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override | 
				
			||||||
 | 
					        public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { | 
				
			||||||
 | 
					            if (!isActivated()) { | 
				
			||||||
 | 
					                return; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            Logger.d("onTouchEvent: x:" + e.getX() + ",y:" + e.getY() | 
				
			||||||
 | 
					                    + ", " + MotionEvent.actionToString(e.getAction())); | 
				
			||||||
 | 
					            int action = e.getAction(); | 
				
			||||||
 | 
					            int actionMask = action & MotionEvent.ACTION_MASK; | 
				
			||||||
 | 
					            switch (actionMask) { | 
				
			||||||
 | 
					                case MotionEvent.ACTION_MOVE: | 
				
			||||||
 | 
					                    if (mSlideStateStartPosition != RecyclerView.NO_POSITION) { | 
				
			||||||
 | 
					                        selectFirstItem(mSlideStateStartPosition); | 
				
			||||||
 | 
					                        // selection is triggered
 | 
				
			||||||
 | 
					                        mSlideStateStartPosition = RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					                        Logger.i("onTouchEvent: after slide mode down"); | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                    processAutoScroll(e); | 
				
			||||||
 | 
					                    if (!mIsInTopHotspot && !mIsInBottomHotspot) { | 
				
			||||||
 | 
					                        updateSelectedRange(rv, e); | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                    break; | 
				
			||||||
 | 
					                case MotionEvent.ACTION_CANCEL: | 
				
			||||||
 | 
					                case MotionEvent.ACTION_UP: | 
				
			||||||
 | 
					                    selectFinished(mEnd); | 
				
			||||||
 | 
					                    break; | 
				
			||||||
 | 
					                default: | 
				
			||||||
 | 
					                    // do nothing
 | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override | 
				
			||||||
 | 
					        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { | 
				
			||||||
 | 
					            if (disallowIntercept) { | 
				
			||||||
 | 
					                inactiveSelect(); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public DragSelectTouchHelper(@NonNull Callback callback) { | 
				
			||||||
 | 
					        mCallback = callback; | 
				
			||||||
 | 
					        mDisplayMetrics = Resources.getSystem().getDisplayMetrics(); | 
				
			||||||
 | 
					        setHotspotRatio(DEFAULT_HOTSPOT_RATIO); | 
				
			||||||
 | 
					        setHotspotOffset(DEFAULT_HOTSPOT_OFFSET); | 
				
			||||||
 | 
					        setMaximumVelocity(DEFAULT_MAX_SCROLL_VELOCITY); | 
				
			||||||
 | 
					        setEdgeType(DEFAULT_EDGE_TYPE); | 
				
			||||||
 | 
					        setAutoEnterSlideState(false); | 
				
			||||||
 | 
					        setAllowDragInSlideState(false); | 
				
			||||||
 | 
					        setSlideArea(0, 0); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Attaches the DragSelectTouchHelper to the provided RecyclerView. If TouchHelper is already | 
				
			||||||
 | 
					     * attached to a RecyclerView, it will first detach from the previous one. You can call this | 
				
			||||||
 | 
					     * method with {@code null} to detach it from the current RecyclerView. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param recyclerView The RecyclerView instance to which you want to add this helper or | 
				
			||||||
 | 
					     *                     {@code null} if you want to remove DragSelectTouchHelper from the | 
				
			||||||
 | 
					     *                     current RecyclerView. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public void attachToRecyclerView(@Nullable RecyclerView recyclerView) { | 
				
			||||||
 | 
					        if (mRecyclerView == recyclerView) { | 
				
			||||||
 | 
					            return; // nothing to do
 | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        if (mRecyclerView != null) { | 
				
			||||||
 | 
					            mRecyclerView.removeOnItemTouchListener(mOnItemTouchListener); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        mRecyclerView = recyclerView; | 
				
			||||||
 | 
					        if (mRecyclerView != null) { | 
				
			||||||
 | 
					            mRecyclerView.addOnItemTouchListener(mOnItemTouchListener); | 
				
			||||||
 | 
					            mRecyclerView.addOnLayoutChangeListener(mOnLayoutChangeListener); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Activate the slide selection mode. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public void activeSlideSelect() { | 
				
			||||||
 | 
					        activeSelectInternal(RecyclerView.NO_POSITION); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Activate the selection mode with selected item position. Normally called on long press. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param position Indicates the position of selected item. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public void activeDragSelect(int position) { | 
				
			||||||
 | 
					        activeSelectInternal(position); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Exit the selection mode. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public void inactiveSelect() { | 
				
			||||||
 | 
					        if (isActivated()) { | 
				
			||||||
 | 
					            selectFinished(mEnd); | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            selectFinished(RecyclerView.NO_POSITION); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        Logger.logSelectStateChange(mSelectState, SELECT_STATE_NORMAL); | 
				
			||||||
 | 
					        mSelectState = SELECT_STATE_NORMAL; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * To determine whether it is in the selection mode. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @return true if is in the selection mode. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public boolean isActivated() { | 
				
			||||||
 | 
					        return (mSelectState != SELECT_STATE_NORMAL); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Sets hotspot height by ratio of RecyclerView. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param ratio range (0, 0.5). | 
				
			||||||
 | 
					     * @return The select helper, which may used to chain setter calls. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public DragSelectTouchHelper setHotspotRatio(float ratio) { | 
				
			||||||
 | 
					        mHotspotHeightRatio = ratio; | 
				
			||||||
 | 
					        return this; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Sets hotspot height. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param hotspotHeight hotspot height which unit is dp. | 
				
			||||||
 | 
					     * @return The select helper, which may used to chain setter calls. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public DragSelectTouchHelper setHotspotHeight(int hotspotHeight) { | 
				
			||||||
 | 
					        mHotspotHeight = dp2px(hotspotHeight); | 
				
			||||||
 | 
					        return this; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Sets hotspot offset. It don't need to be set if no special requirement. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param hotspotOffset hotspot offset which unit is dp. | 
				
			||||||
 | 
					     * @return The select helper, which may used to chain setter calls. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public DragSelectTouchHelper setHotspotOffset(int hotspotOffset) { | 
				
			||||||
 | 
					        mHotspotOffset = dp2px(hotspotOffset); | 
				
			||||||
 | 
					        return this; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Sets the activation edge type, one of: | 
				
			||||||
 | 
					     * <ul> | 
				
			||||||
 | 
					     * <li>{@link EdgeType#INSIDE} for edges that respond to touches inside | 
				
			||||||
 | 
					     * the bounds of the host view. If touch moves outside the bounds, scrolling | 
				
			||||||
 | 
					     * will stop. | 
				
			||||||
 | 
					     * <li>{@link EdgeType#INSIDE_EXTEND} for inside edges that continued to | 
				
			||||||
 | 
					     * scroll when touch moves outside the bounds of the host view. | 
				
			||||||
 | 
					     * </ul> | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param type The type of edge to use. | 
				
			||||||
 | 
					     * @return The select helper, which may used to chain setter calls. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public DragSelectTouchHelper setEdgeType(EdgeType type) { | 
				
			||||||
 | 
					        switch (type) { | 
				
			||||||
 | 
					            case INSIDE: | 
				
			||||||
 | 
					                mScrollAboveTopRegion = false; | 
				
			||||||
 | 
					                mScrollBelowBottomRegion = false; | 
				
			||||||
 | 
					                break; | 
				
			||||||
 | 
					            case INSIDE_EXTEND: | 
				
			||||||
 | 
					            default: | 
				
			||||||
 | 
					                mScrollAboveTopRegion = true; | 
				
			||||||
 | 
					                mScrollBelowBottomRegion = true; | 
				
			||||||
 | 
					                break; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        return this; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Sets sliding area's start and end, has been considered RTL situation | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param startDp The start of the sliding area | 
				
			||||||
 | 
					     * @param endDp   The end of the sliding area | 
				
			||||||
 | 
					     * @return The select helper, which may used to chain setter calls. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public DragSelectTouchHelper setSlideArea(int startDp, int endDp) { | 
				
			||||||
 | 
					        if (!isRtl()) { | 
				
			||||||
 | 
					            mSlideAreaLeft = dp2px(startDp); | 
				
			||||||
 | 
					            mSlideAreaRight = dp2px(endDp); | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            int displayWidth = mDisplayMetrics.widthPixels; | 
				
			||||||
 | 
					            mSlideAreaLeft = displayWidth - dp2px(endDp); | 
				
			||||||
 | 
					            mSlideAreaRight = displayWidth - dp2px(startDp); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        return this; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Sets the maximum velocity for scrolling | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param velocity maximum velocity | 
				
			||||||
 | 
					     * @return The select helper, which may used to chain setter calls. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public DragSelectTouchHelper setMaximumVelocity(int velocity) { | 
				
			||||||
 | 
					        mMaximumVelocity = (int) (velocity * mDisplayMetrics.density + 0.5f); | 
				
			||||||
 | 
					        return this; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Sets whether should auto enter slide mode after drag select finished. | 
				
			||||||
 | 
					     * It's usefully for LinearLayout RecyclerView. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param autoEnterSlideState should auto enter slide mode | 
				
			||||||
 | 
					     * @return The select helper, which may used to chain setter calls. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public DragSelectTouchHelper setAutoEnterSlideState(boolean autoEnterSlideState) { | 
				
			||||||
 | 
					        mShouldAutoChangeState = autoEnterSlideState; | 
				
			||||||
 | 
					        return this; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Sets whether can drag selection in slide select mode. | 
				
			||||||
 | 
					     * It's usefully for LinearLayout RecyclerView. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param allowDragInSlideState allow drag selection in slide select mode | 
				
			||||||
 | 
					     * @return The select helper, which may used to chain setter calls. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public DragSelectTouchHelper setAllowDragInSlideState(boolean allowDragInSlideState) { | 
				
			||||||
 | 
					        mIsAllowDragInSlideState = allowDragInSlideState; | 
				
			||||||
 | 
					        return this; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void init(int rvHeight) { | 
				
			||||||
 | 
					        if (mHotspotOffset >= rvHeight * MAX_HOTSPOT_RATIO) { | 
				
			||||||
 | 
					            mHotspotOffset = rvHeight * MAX_HOTSPOT_RATIO; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        // The height of hotspot area is not set, using (RV height x ratio)
 | 
				
			||||||
 | 
					        if (mHotspotHeight <= 0) { | 
				
			||||||
 | 
					            if (mHotspotHeightRatio <= 0 || mHotspotHeightRatio >= MAX_HOTSPOT_RATIO) { | 
				
			||||||
 | 
					                mHotspotHeightRatio = DEFAULT_HOTSPOT_RATIO; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            mHotspotHeight = rvHeight * mHotspotHeightRatio; | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            if (mHotspotHeight >= rvHeight * MAX_HOTSPOT_RATIO) { | 
				
			||||||
 | 
					                mHotspotHeight = rvHeight * MAX_HOTSPOT_RATIO; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mTopRegionFrom = mHotspotOffset; | 
				
			||||||
 | 
					        mTopRegionTo = mTopRegionFrom + mHotspotHeight; | 
				
			||||||
 | 
					        mBottomRegionTo = rvHeight - mHotspotOffset; | 
				
			||||||
 | 
					        mBottomRegionFrom = mBottomRegionTo - mHotspotHeight; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (mTopRegionTo > mBottomRegionFrom) { | 
				
			||||||
 | 
					            mTopRegionTo = mBottomRegionFrom = rvHeight >> 1; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Logger.d("Hotspot: [" + mTopRegionFrom + ", " + mTopRegionTo + "], [" | 
				
			||||||
 | 
					                + mBottomRegionFrom + ", " + mBottomRegionTo + "]"); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void activeSelectInternal(int position) { | 
				
			||||||
 | 
					        // We should initialize the hotspot here, because its data may be delayed load
 | 
				
			||||||
 | 
					        if (mRecyclerView != null) { | 
				
			||||||
 | 
					            init(mRecyclerView.getHeight()); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        if (position == RecyclerView.NO_POSITION) { | 
				
			||||||
 | 
					            Logger.logSelectStateChange(mSelectState, SELECT_STATE_SLIDE); | 
				
			||||||
 | 
					            mSelectState = SELECT_STATE_SLIDE; | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            if (!mHaveCalledSelectStart) { | 
				
			||||||
 | 
					                mCallback.onSelectStart(position); | 
				
			||||||
 | 
					                mHaveCalledSelectStart = true; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (mSelectState == SELECT_STATE_SLIDE) { | 
				
			||||||
 | 
					                if (mIsAllowDragInSlideState && selectFirstItem(position)) { | 
				
			||||||
 | 
					                    Logger.logSelectStateChange(mSelectState, SELECT_STATE_DRAG_FROM_SLIDE); | 
				
			||||||
 | 
					                    mSelectState = SELECT_STATE_DRAG_FROM_SLIDE; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } else if (mSelectState == SELECT_STATE_NORMAL) { | 
				
			||||||
 | 
					                if (selectFirstItem(position)) { | 
				
			||||||
 | 
					                    Logger.logSelectStateChange(mSelectState, SELECT_STATE_DRAG_FROM_NORMAL); | 
				
			||||||
 | 
					                    mSelectState = SELECT_STATE_DRAG_FROM_NORMAL; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } else { | 
				
			||||||
 | 
					                Logger.e("activeSelect in unexpected state: " + mSelectState); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private boolean selectFirstItem(int position) { | 
				
			||||||
 | 
					        boolean selectFirstItemSucceed = mCallback.onSelectChange(position, true); | 
				
			||||||
 | 
					        // The drag select feature is only available if the first item is available for selection
 | 
				
			||||||
 | 
					        if (selectFirstItemSucceed) { | 
				
			||||||
 | 
					            mStart = position; | 
				
			||||||
 | 
					            mEnd = position; | 
				
			||||||
 | 
					            mLastRealStart = position; | 
				
			||||||
 | 
					            mLastRealEnd = position; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        return selectFirstItemSucceed; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void updateSelectedRange(RecyclerView rv, MotionEvent e) { | 
				
			||||||
 | 
					        updateSelectedRange(rv, e.getX(), e.getY()); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void updateSelectedRange(RecyclerView rv, float x, float y) { | 
				
			||||||
 | 
					        int position = getItemPosition(rv, x, y); | 
				
			||||||
 | 
					        if (position != RecyclerView.NO_POSITION && mEnd != position) { | 
				
			||||||
 | 
					            mEnd = position; | 
				
			||||||
 | 
					            notifySelectRangeChange(); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void notifySelectRangeChange() { | 
				
			||||||
 | 
					        if (mStart == RecyclerView.NO_POSITION || mEnd == RecyclerView.NO_POSITION) { | 
				
			||||||
 | 
					            return; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int newStart, newEnd; | 
				
			||||||
 | 
					        newStart = Math.min(mStart, mEnd); | 
				
			||||||
 | 
					        newEnd = Math.max(mStart, mEnd); | 
				
			||||||
 | 
					        if (mLastRealStart == RecyclerView.NO_POSITION || mLastRealEnd == RecyclerView.NO_POSITION) { | 
				
			||||||
 | 
					            if (newEnd - newStart == 1) { | 
				
			||||||
 | 
					                notifySelectChange(newStart, newStart, true); | 
				
			||||||
 | 
					            } else { | 
				
			||||||
 | 
					                notifySelectChange(newStart, newEnd, true); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            if (newStart > mLastRealStart) { | 
				
			||||||
 | 
					                notifySelectChange(mLastRealStart, newStart - 1, false); | 
				
			||||||
 | 
					            } else if (newStart < mLastRealStart) { | 
				
			||||||
 | 
					                notifySelectChange(newStart, mLastRealStart - 1, true); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (newEnd > mLastRealEnd) { | 
				
			||||||
 | 
					                notifySelectChange(mLastRealEnd + 1, newEnd, true); | 
				
			||||||
 | 
					            } else if (newEnd < mLastRealEnd) { | 
				
			||||||
 | 
					                notifySelectChange(newEnd + 1, mLastRealEnd, false); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mLastRealStart = newStart; | 
				
			||||||
 | 
					        mLastRealEnd = newEnd; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void notifySelectChange(int start, int end, boolean newState) { | 
				
			||||||
 | 
					        for (int i = start; i <= end; i++) { | 
				
			||||||
 | 
					            mCallback.onSelectChange(i, newState); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void selectFinished(int lastItem) { | 
				
			||||||
 | 
					        if (lastItem != RecyclerView.NO_POSITION) { | 
				
			||||||
 | 
					            mCallback.onSelectEnd(lastItem); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        mStart = RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					        mEnd = RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					        mLastRealStart = RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					        mLastRealEnd = RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					        mHaveCalledSelectStart = false; | 
				
			||||||
 | 
					        mIsInTopHotspot = false; | 
				
			||||||
 | 
					        mIsInBottomHotspot = false; | 
				
			||||||
 | 
					        stopAutoScroll(); | 
				
			||||||
 | 
					        switch (mSelectState) { | 
				
			||||||
 | 
					            case SELECT_STATE_DRAG_FROM_NORMAL: | 
				
			||||||
 | 
					                if (mShouldAutoChangeState) { | 
				
			||||||
 | 
					                    Logger.logSelectStateChange(mSelectState, SELECT_STATE_SLIDE); | 
				
			||||||
 | 
					                    mSelectState = SELECT_STATE_SLIDE; | 
				
			||||||
 | 
					                } else { | 
				
			||||||
 | 
					                    Logger.logSelectStateChange(mSelectState, SELECT_STATE_NORMAL); | 
				
			||||||
 | 
					                    mSelectState = SELECT_STATE_NORMAL; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                break; | 
				
			||||||
 | 
					            case SELECT_STATE_DRAG_FROM_SLIDE: | 
				
			||||||
 | 
					                Logger.logSelectStateChange(mSelectState, SELECT_STATE_SLIDE); | 
				
			||||||
 | 
					                mSelectState = SELECT_STATE_SLIDE; | 
				
			||||||
 | 
					                break; | 
				
			||||||
 | 
					            default: | 
				
			||||||
 | 
					                // doesn't change the selection state
 | 
				
			||||||
 | 
					                break; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Process motion event, according to the location to determine whether to scroll | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private void processAutoScroll(MotionEvent e) { | 
				
			||||||
 | 
					        float y = e.getY(); | 
				
			||||||
 | 
					        if (y >= mTopRegionFrom && y <= mTopRegionTo && y < mDownY) { | 
				
			||||||
 | 
					            mLastX = e.getX(); | 
				
			||||||
 | 
					            mLastY = e.getY(); | 
				
			||||||
 | 
					            float scrollDistanceFactor = (y - mTopRegionTo) / mHotspotHeight; | 
				
			||||||
 | 
					            mScrollDistance = (int) (mMaximumVelocity * scrollDistanceFactor); | 
				
			||||||
 | 
					            if (!mIsInTopHotspot) { | 
				
			||||||
 | 
					                mIsInTopHotspot = true; | 
				
			||||||
 | 
					                startAutoScroll(); | 
				
			||||||
 | 
					                mDownY = mTopRegionTo; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } else if (mScrollAboveTopRegion && y < mTopRegionFrom && mIsInTopHotspot) { | 
				
			||||||
 | 
					            mLastX = e.getX(); | 
				
			||||||
 | 
					            mLastY = mTopRegionFrom; | 
				
			||||||
 | 
					            // Use the maximum speed
 | 
				
			||||||
 | 
					            mScrollDistance = mMaximumVelocity * -1; | 
				
			||||||
 | 
					            startAutoScroll(); | 
				
			||||||
 | 
					        } else if (y >= mBottomRegionFrom && y <= mBottomRegionTo && y > mDownY) { | 
				
			||||||
 | 
					            mLastX = e.getX(); | 
				
			||||||
 | 
					            mLastY = e.getY(); | 
				
			||||||
 | 
					            float scrollDistanceFactor = (y - mBottomRegionFrom) / mHotspotHeight; | 
				
			||||||
 | 
					            mScrollDistance = (int) (mMaximumVelocity * scrollDistanceFactor); | 
				
			||||||
 | 
					            if (!mIsInBottomHotspot) { | 
				
			||||||
 | 
					                mIsInBottomHotspot = true; | 
				
			||||||
 | 
					                startAutoScroll(); | 
				
			||||||
 | 
					                mDownY = mBottomRegionFrom; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } else if (mScrollBelowBottomRegion && y > mBottomRegionTo && mIsInBottomHotspot) { | 
				
			||||||
 | 
					            mLastX = e.getX(); | 
				
			||||||
 | 
					            mLastY = mBottomRegionTo; | 
				
			||||||
 | 
					            // Use the maximum speed
 | 
				
			||||||
 | 
					            mScrollDistance = mMaximumVelocity; | 
				
			||||||
 | 
					            startAutoScroll(); | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            mIsInTopHotspot = false; | 
				
			||||||
 | 
					            mIsInBottomHotspot = false; | 
				
			||||||
 | 
					            mLastX = Float.MIN_VALUE; | 
				
			||||||
 | 
					            mLastY = Float.MIN_VALUE; | 
				
			||||||
 | 
					            stopAutoScroll(); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void startAutoScroll() { | 
				
			||||||
 | 
					        if (!mIsScrolling) { | 
				
			||||||
 | 
					            mIsScrolling = true; | 
				
			||||||
 | 
					            mRecyclerView.removeCallbacks(mScrollRunnable); | 
				
			||||||
 | 
					            ViewCompat.postOnAnimation(mRecyclerView, mScrollRunnable); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void stopAutoScroll() { | 
				
			||||||
 | 
					        if (mIsScrolling) { | 
				
			||||||
 | 
					            mIsScrolling = false; | 
				
			||||||
 | 
					            mRecyclerView.removeCallbacks(mScrollRunnable); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void scrollBy(int distance) { | 
				
			||||||
 | 
					        int scrollDistance; | 
				
			||||||
 | 
					        if (distance > 0) { | 
				
			||||||
 | 
					            scrollDistance = Math.min(distance, mMaximumVelocity); | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            scrollDistance = Math.max(distance, -mMaximumVelocity); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        mRecyclerView.scrollBy(0, scrollDistance); | 
				
			||||||
 | 
					        if (mLastX != Float.MIN_VALUE && mLastY != Float.MIN_VALUE) { | 
				
			||||||
 | 
					            updateSelectedRange(mRecyclerView, mLastX, mLastY); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private int dp2px(float dpVal) { | 
				
			||||||
 | 
					        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, | 
				
			||||||
 | 
					                dpVal, mDisplayMetrics); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private boolean isRtl() { | 
				
			||||||
 | 
					        return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) | 
				
			||||||
 | 
					                == View.LAYOUT_DIRECTION_RTL; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private boolean isInSlideArea(MotionEvent e) { | 
				
			||||||
 | 
					        float x = e.getX(); | 
				
			||||||
 | 
					        return (x > mSlideAreaLeft && x < mSlideAreaRight); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private int getItemPosition(RecyclerView rv, MotionEvent e) { | 
				
			||||||
 | 
					        return getItemPosition(rv, e.getX(), e.getY()); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private int getItemPosition(RecyclerView rv, float x, float y) { | 
				
			||||||
 | 
					        final View v = rv.findChildViewUnder(x, y); | 
				
			||||||
 | 
					        if (v == null) { | 
				
			||||||
 | 
					            RecyclerView.LayoutManager layoutManager = rv.getLayoutManager(); | 
				
			||||||
 | 
					            if (layoutManager instanceof GridLayoutManager) { | 
				
			||||||
 | 
					                int lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition(); | 
				
			||||||
 | 
					                int lastItemPosition = layoutManager.getItemCount() - 1; | 
				
			||||||
 | 
					                if (lastItemPosition == lastVisibleItemPosition) { | 
				
			||||||
 | 
					                    return lastItemPosition; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            return RecyclerView.NO_POSITION; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        return rv.getChildAdapterPosition(v); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Edge type that specifies an activation area starting at the view bounds and extending inward. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public enum EdgeType { | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * After activation begins, moving outside the view bounds will stop scrolling. | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        INSIDE, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * After activation begins, moving outside the view bounds will continue scrolling. | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        INSIDE_EXTEND | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * This class is the contract between DragSelectTouchHelper and your application. It lets you | 
				
			||||||
 | 
					     * update adapter when selection start/end and state changed. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public abstract static class Callback { | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * Called when changing item state. | 
				
			||||||
 | 
					         * | 
				
			||||||
 | 
					         * @param position   this item want to change the state to new state. | 
				
			||||||
 | 
					         * @param isSelected true if the position should be selected, false otherwise. | 
				
			||||||
 | 
					         * @return Whether to set the new state successfully. | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        public abstract boolean onSelectChange(int position, boolean isSelected); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * Called when selection start. | 
				
			||||||
 | 
					         * | 
				
			||||||
 | 
					         * @param start the first selected item. | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        public void onSelectStart(int start) { | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * Called when selection end. | 
				
			||||||
 | 
					         * | 
				
			||||||
 | 
					         * @param end the last selected item. | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        public void onSelectEnd(int end) { | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * An advance Callback which provide 4 useful selection modes {@link Mode}. | 
				
			||||||
 | 
					     * <p> | 
				
			||||||
 | 
					     * Note: Since the state of item may be repeatedly set, in order to improve efficiency, | 
				
			||||||
 | 
					     * please process it in the Adapter | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public abstract static class AdvanceCallback<T> extends Callback { | 
				
			||||||
 | 
					        private Mode mMode; | 
				
			||||||
 | 
					        private Set<T> mOriginalSelection; | 
				
			||||||
 | 
					        private boolean mFirstWasSelected; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * Creates a SimpleCallback with default {@link Mode#SelectAndReverse}# mode. | 
				
			||||||
 | 
					         * | 
				
			||||||
 | 
					         * @see Mode | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        public AdvanceCallback() { | 
				
			||||||
 | 
					            setMode(Mode.SelectAndReverse); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * Creates a SimpleCallback with select mode. | 
				
			||||||
 | 
					         * | 
				
			||||||
 | 
					         * @param mode the initial select mode | 
				
			||||||
 | 
					         * @see Mode | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        public AdvanceCallback(Mode mode) { | 
				
			||||||
 | 
					            setMode(mode); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * Sets the select mode. | 
				
			||||||
 | 
					         * | 
				
			||||||
 | 
					         * @param mode The type of select mode. | 
				
			||||||
 | 
					         * @see Mode | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        public void setMode(Mode mode) { | 
				
			||||||
 | 
					            mMode = mode; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override | 
				
			||||||
 | 
					        public void onSelectStart(int start) { | 
				
			||||||
 | 
					            mOriginalSelection = new HashSet<>(); | 
				
			||||||
 | 
					            Set<T> selected = currentSelectedId(); | 
				
			||||||
 | 
					            if (selected != null) { | 
				
			||||||
 | 
					                mOriginalSelection.addAll(selected); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            mFirstWasSelected = mOriginalSelection.contains(getItemId(start)); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override | 
				
			||||||
 | 
					        public void onSelectEnd(int end) { | 
				
			||||||
 | 
					            mOriginalSelection = null; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override | 
				
			||||||
 | 
					        public boolean onSelectChange(int position, boolean isSelected) { | 
				
			||||||
 | 
					            boolean stateChanged; | 
				
			||||||
 | 
					            switch (mMode) { | 
				
			||||||
 | 
					                case SelectAndKeep: { | 
				
			||||||
 | 
					                    stateChanged = updateSelectState(position, true); | 
				
			||||||
 | 
					                    break; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                case SelectAndReverse: { | 
				
			||||||
 | 
					                    stateChanged = updateSelectState(position, isSelected); | 
				
			||||||
 | 
					                    break; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                case SelectAndUndo: { | 
				
			||||||
 | 
					                    if (isSelected) { | 
				
			||||||
 | 
					                        stateChanged = updateSelectState(position, true); | 
				
			||||||
 | 
					                    } else { | 
				
			||||||
 | 
					                        stateChanged = updateSelectState(position, mOriginalSelection.contains(getItemId(position))); | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                    break; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                case ToggleAndKeep: { | 
				
			||||||
 | 
					                    stateChanged = updateSelectState(position, !mFirstWasSelected); | 
				
			||||||
 | 
					                    break; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                case ToggleAndReverse: { | 
				
			||||||
 | 
					                    if (isSelected) { | 
				
			||||||
 | 
					                        stateChanged = updateSelectState(position, !mFirstWasSelected); | 
				
			||||||
 | 
					                    } else { | 
				
			||||||
 | 
					                        stateChanged = updateSelectState(position, mFirstWasSelected); | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                    break; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                case ToggleAndUndo: { | 
				
			||||||
 | 
					                    if (isSelected) { | 
				
			||||||
 | 
					                        stateChanged = updateSelectState(position, !mFirstWasSelected); | 
				
			||||||
 | 
					                    } else { | 
				
			||||||
 | 
					                        stateChanged = updateSelectState(position, mOriginalSelection.contains(getItemId(position))); | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                    break; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                default: | 
				
			||||||
 | 
					                    // SelectAndReverse Mode
 | 
				
			||||||
 | 
					                    stateChanged = updateSelectState(position, isSelected); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            return stateChanged; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * Get the currently selected items when selecting first item. | 
				
			||||||
 | 
					         * | 
				
			||||||
 | 
					         * @return the currently selected item's id set. | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        public abstract Set<T> currentSelectedId(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * Get the ID of the item. | 
				
			||||||
 | 
					         * | 
				
			||||||
 | 
					         * @param position item position to be judged. | 
				
			||||||
 | 
					         * @return item's identity. | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        public abstract T getItemId(int position); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * Update the selection status of the position. | 
				
			||||||
 | 
					         * | 
				
			||||||
 | 
					         * @param position   the position who's selection state changed. | 
				
			||||||
 | 
					         * @param isSelected true if the position should be selected, false otherwise. | 
				
			||||||
 | 
					         * @return Whether to set the state successfully. | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        public abstract boolean updateSelectState(int position, boolean isSelected); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** | 
				
			||||||
 | 
					         * Different existing selection modes | 
				
			||||||
 | 
					         */ | 
				
			||||||
 | 
					        public enum Mode { | 
				
			||||||
 | 
					            /** | 
				
			||||||
 | 
					             * Selects the first item and applies the same state to each item you go by | 
				
			||||||
 | 
					             * and keep the state on move back | 
				
			||||||
 | 
					             */ | 
				
			||||||
 | 
					            SelectAndKeep, | 
				
			||||||
 | 
					            /** | 
				
			||||||
 | 
					             * Selects the first item and applies the same state to each item you go by | 
				
			||||||
 | 
					             * and applies inverted state on move back | 
				
			||||||
 | 
					             */ | 
				
			||||||
 | 
					            SelectAndReverse, | 
				
			||||||
 | 
					            /** | 
				
			||||||
 | 
					             * Selects the first item and applies the same state to each item you go by | 
				
			||||||
 | 
					             * and reverts to the original state on move back | 
				
			||||||
 | 
					             */ | 
				
			||||||
 | 
					            SelectAndUndo, | 
				
			||||||
 | 
					            /** | 
				
			||||||
 | 
					             * Toggles the first item and applies the same state to each item you go by | 
				
			||||||
 | 
					             * and keep the state on move back | 
				
			||||||
 | 
					             */ | 
				
			||||||
 | 
					            ToggleAndKeep, | 
				
			||||||
 | 
					            /** | 
				
			||||||
 | 
					             * Toggles the first item and applies the same state to each item you go by | 
				
			||||||
 | 
					             * and applies inverted state on move back | 
				
			||||||
 | 
					             */ | 
				
			||||||
 | 
					            ToggleAndReverse, | 
				
			||||||
 | 
					            /** | 
				
			||||||
 | 
					             * Toggles the first item and applies the same state to each item you go by | 
				
			||||||
 | 
					             * and reverts to the original state on move back | 
				
			||||||
 | 
					             */ | 
				
			||||||
 | 
					            ToggleAndUndo, | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static class Logger { | 
				
			||||||
 | 
					        private static boolean DEBUG = BuildConfig.DEBUG; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static void d(String msg) { | 
				
			||||||
 | 
					            if (DEBUG) { | 
				
			||||||
 | 
					                Log.d(TAG, msg); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static void e(String msg) { | 
				
			||||||
 | 
					            Log.e(TAG, msg); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static void i(String msg) { | 
				
			||||||
 | 
					            Log.i(TAG, msg); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static void logSelectStateChange(int before, int after) { | 
				
			||||||
 | 
					            i("Select state changed: " + stateName(before) + " --> " + stateName(after)); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static String stateName(int state) { | 
				
			||||||
 | 
					            switch (state) { | 
				
			||||||
 | 
					                case SELECT_STATE_NORMAL: | 
				
			||||||
 | 
					                    return "NormalState"; | 
				
			||||||
 | 
					                case SELECT_STATE_SLIDE: | 
				
			||||||
 | 
					                    return "SlideState"; | 
				
			||||||
 | 
					                case SELECT_STATE_DRAG_FROM_NORMAL: | 
				
			||||||
 | 
					                    return "DragFromNormal"; | 
				
			||||||
 | 
					                case SELECT_STATE_DRAG_FROM_SLIDE: | 
				
			||||||
 | 
					                    return "DragFromSlide"; | 
				
			||||||
 | 
					                default: | 
				
			||||||
 | 
					                    return "Unknown"; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue