1/*
2 * Copyright 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.recyclerview.selection;
18
19import static androidx.core.util.Preconditions.checkArgument;
20
21import android.view.MotionEvent;
22import android.view.View;
23
24import androidx.annotation.NonNull;
25import androidx.annotation.Nullable;
26import androidx.recyclerview.widget.GridLayoutManager;
27import androidx.recyclerview.widget.LinearLayoutManager;
28import androidx.recyclerview.widget.RecyclerView;
29
30/**
31 * Provides a means of controlling when and where band selection can be initiated.
32 *
33 * <p>
34 * Two default implementations are provided: {@link EmptyArea}, and {@link NonDraggableArea}.
35 *
36 * @see SelectionTracker.Builder#withBandPredicate(BandPredicate)
37 */
38public abstract class BandPredicate {
39
40    /**
41     * @return true if band selection can be initiated in response to the {@link MotionEvent}.
42     */
43    public abstract boolean canInitiate(MotionEvent e);
44
45    private static boolean hasSupportedLayoutManager(@NonNull RecyclerView recyclerView) {
46        RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
47        return lm instanceof GridLayoutManager
48                || lm instanceof LinearLayoutManager;
49    }
50
51    /**
52     * A BandPredicate that allows initiation of band selection only in areas of RecyclerView
53     * that map to {@link RecyclerView#NO_POSITION}. In most cases, this will be the empty areas
54     * between views.
55     *
56     * <p>
57     * Use this implementation to permit band selection only in empty areas
58     * surrounding view items. But be advised that if there is no empy area around
59     * view items, band selection cannot be initiated.
60     */
61    public static final class EmptyArea extends BandPredicate {
62
63        private final RecyclerView mRecyclerView;
64
65        /**
66         * @param recyclerView the owner RecyclerView
67         */
68        public EmptyArea(@NonNull RecyclerView recyclerView) {
69            checkArgument(recyclerView != null);
70
71            mRecyclerView = recyclerView;
72        }
73
74        @Override
75        public boolean canInitiate(@NonNull MotionEvent e) {
76            if (!hasSupportedLayoutManager(mRecyclerView)
77                    || mRecyclerView.hasPendingAdapterUpdates()) {
78                return false;
79            }
80
81            View itemView = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
82            int position = itemView != null
83                    ? mRecyclerView.getChildAdapterPosition(itemView)
84                    : RecyclerView.NO_POSITION;
85
86            return position == RecyclerView.NO_POSITION;
87        }
88    }
89
90    /**
91     * A BandPredicate that allows initiation of band selection in any area that is not
92     * draggable as determined by consulting
93     * {@link ItemDetailsLookup.ItemDetails#inDragRegion(MotionEvent)}. By default empty
94     * areas (those with a position that maps to {@link RecyclerView#NO_POSITION}
95     * are considered non-draggable.
96     *
97     * <p>
98     * Use this implementation in order to permit band selection in
99     * otherwise empty areas of a View. This is useful especially in
100     * list layouts where there is no empty space surrounding the list items,
101     * and individual list items may contain extra white space (like
102     * in a list of varying length words).
103     *
104     * @see ItemDetailsLookup.ItemDetails#inDragRegion(MotionEvent)
105     */
106    public static final class NonDraggableArea extends BandPredicate {
107
108        private final RecyclerView mRecyclerView;
109        private final ItemDetailsLookup mDetailsLookup;
110
111        /**
112         * Creates a new instance.
113         *
114         * @param recyclerView the owner RecyclerView
115         * @param detailsLookup provides access to item details.
116         */
117        public NonDraggableArea(
118                @NonNull RecyclerView recyclerView, @NonNull ItemDetailsLookup detailsLookup) {
119
120            checkArgument(recyclerView != null);
121            checkArgument(detailsLookup != null);
122
123            mRecyclerView = recyclerView;
124            mDetailsLookup = detailsLookup;
125        }
126
127        @Override
128        public boolean canInitiate(@NonNull MotionEvent e) {
129            if (!hasSupportedLayoutManager(mRecyclerView)
130                    || mRecyclerView.hasPendingAdapterUpdates()) {
131                return false;
132            }
133
134            @Nullable ItemDetailsLookup.ItemDetails details = mDetailsLookup.getItemDetails(e);
135            return (details == null) || !details.inDragRegion(e);
136        }
137    }
138}
139