| /* |
| * Copyright 2017 The Android Open Source Project |
| * |
| * 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 androidx.recyclerview.selection; |
| |
| import static android.support.v4.util.Preconditions.checkArgument; |
| import static android.support.v7.widget.RecyclerView.NO_POSITION; |
| |
| import static androidx.recyclerview.selection.Shared.DEBUG; |
| |
| import android.support.annotation.IntDef; |
| import android.util.Log; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| |
| /** |
| * Class providing support for managing range selections. |
| */ |
| final class Range { |
| |
| static final int TYPE_PRIMARY = 0; |
| |
| /** |
| * "Provisional" selection represents a overlay on the primary selection. A provisional |
| * selection maybe be eventually added to the primary selection, or it may be abandoned. |
| * |
| * <p>E.g. BandSelectionHelper creates a provisional selection while a user is actively |
| * selecting items with a band. GestureSelectionHelper creates a provisional selection |
| * while a user is active selecting via gesture. |
| * |
| * <p>Provisionally selected items are considered to be selected in |
| * {@link Selection#contains(String)} and related methods. A provisional may be abandoned or |
| * merged into the promary selection. |
| * |
| * <p>A provisional selection may intersect with the primary selection, however clearing the |
| * provisional selection will not affect the primary selection where the two may intersect. |
| */ |
| static final int TYPE_PROVISIONAL = 1; |
| @IntDef({ |
| TYPE_PRIMARY, |
| TYPE_PROVISIONAL |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| @interface RangeType {} |
| |
| private static final String TAG = "Range"; |
| |
| private final Callbacks mCallbacks; |
| private final int mBegin; |
| private int mEnd = NO_POSITION; |
| |
| /** |
| * Creates a new range anchored at {@code position}. |
| * |
| * @param position |
| * @param callbacks |
| */ |
| Range(int position, Callbacks callbacks) { |
| mBegin = position; |
| mCallbacks = callbacks; |
| if (DEBUG) Log.d(TAG, "Creating new Range anchored @ " + position); |
| } |
| |
| void extendRange(int position, @RangeType int type) { |
| checkArgument(position != NO_POSITION, "Position cannot be NO_POSITION."); |
| |
| if (mEnd == NO_POSITION || mEnd == mBegin) { |
| // Reset mEnd so it can be established in establishRange. |
| mEnd = NO_POSITION; |
| establishRange(position, type); |
| } else { |
| reviseRange(position, type); |
| } |
| } |
| |
| private void establishRange(int position, @RangeType int type) { |
| checkArgument(mEnd == NO_POSITION, "End has already been set."); |
| |
| mEnd = position; |
| |
| if (position > mBegin) { |
| if (DEBUG) log(type, "Establishing initial range at @ " + position); |
| updateRange(mBegin + 1, position, true, type); |
| } else if (position < mBegin) { |
| if (DEBUG) log(type, "Establishing initial range at @ " + position); |
| updateRange(position, mBegin - 1, true, type); |
| } |
| } |
| |
| private void reviseRange(int position, @RangeType int type) { |
| checkArgument(mEnd != NO_POSITION, "End must already be set."); |
| checkArgument(mBegin != mEnd, "Beging and end point to same position."); |
| |
| if (position == mEnd) { |
| if (DEBUG) log(type, "Ignoring no-op revision for range @ " + position); |
| } |
| |
| if (mEnd > mBegin) { |
| reviseAscending(position, type); |
| } else if (mEnd < mBegin) { |
| reviseDescending(position, type); |
| } |
| // the "else" case is covered by checkState at beginning of method. |
| |
| mEnd = position; |
| } |
| |
| /** |
| * Updates an existing ascending selection. |
| */ |
| private void reviseAscending(int position, @RangeType int type) { |
| if (DEBUG) log(type, "*ascending* Revising range @ " + position); |
| |
| if (position < mEnd) { |
| if (position < mBegin) { |
| updateRange(mBegin + 1, mEnd, false, type); |
| updateRange(position, mBegin - 1, true, type); |
| } else { |
| updateRange(position + 1, mEnd, false, type); |
| } |
| } else if (position > mEnd) { // Extending the range... |
| updateRange(mEnd + 1, position, true, type); |
| } |
| } |
| |
| private void reviseDescending(int position, @RangeType int type) { |
| if (DEBUG) log(type, "*descending* Revising range @ " + position); |
| |
| if (position > mEnd) { |
| if (position > mBegin) { |
| updateRange(mEnd, mBegin - 1, false, type); |
| updateRange(mBegin + 1, position, true, type); |
| } else { |
| updateRange(mEnd, position - 1, false, type); |
| } |
| } else if (position < mEnd) { // Extending the range... |
| updateRange(position, mEnd - 1, true, type); |
| } |
| } |
| |
| /** |
| * Try to set selection state for all elements in range. Not that callbacks can cancel |
| * selection of specific items, so some or even all items may not reflect the desired state |
| * after the update is complete. |
| * |
| * @param begin Adapter position for range start (inclusive). |
| * @param end Adapter position for range end (inclusive). |
| * @param selected New selection state. |
| */ |
| private void updateRange( |
| int begin, int end, boolean selected, @RangeType int type) { |
| mCallbacks.updateForRange(begin, end, selected, type); |
| } |
| |
| @Override |
| public String toString() { |
| return "Range{begin=" + mBegin + ", end=" + mEnd + "}"; |
| } |
| |
| private void log(@RangeType int type, String message) { |
| String opType = type == TYPE_PRIMARY ? "PRIMARY" : "PROVISIONAL"; |
| Log.d(TAG, String.valueOf(this) + ": " + message + " (" + opType + ")"); |
| } |
| |
| /* |
| * @see {@link DefaultSelectionHelper#updateForRange(int, int , boolean, int)}. |
| */ |
| abstract static class Callbacks { |
| abstract void updateForRange( |
| int begin, int end, boolean selected, @RangeType int type); |
| } |
| } |