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 com.example.android.supportv7.widget.selection.simple;
18
19import android.content.Context;
20import android.os.Bundle;
21import android.util.Log;
22import android.view.Menu;
23import android.view.MenuItem;
24import android.view.MotionEvent;
25import android.widget.Toast;
26
27import androidx.annotation.CallSuper;
28import androidx.appcompat.app.AppCompatActivity;
29import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
30import androidx.recyclerview.selection.ItemKeyProvider;
31import androidx.recyclerview.selection.SelectionPredicates;
32import androidx.recyclerview.selection.SelectionTracker;
33import androidx.recyclerview.selection.SelectionTracker.SelectionObserver;
34import androidx.recyclerview.selection.StableIdKeyProvider;
35import androidx.recyclerview.selection.StorageStrategy;
36import androidx.recyclerview.widget.GridLayoutManager;
37import androidx.recyclerview.widget.RecyclerView;
38
39import com.example.android.supportv7.R;
40
41/**
42 * ContentPager demo activity.
43 */
44public class SimpleSelectionDemoActivity extends AppCompatActivity {
45
46    private static final String TAG = "SelectionDemos";
47    private static final String EXTRA_COLUMN_COUNT = "demo-column-count";
48
49    private SimpleSelectionDemoAdapter mAdapter;
50    private SelectionTracker<Long> mSelectionTracker;
51
52    private GridLayoutManager mLayout;
53    private int mColumnCount = 1;  // This will get updated when layout changes.
54
55    @Override
56    protected void onCreate(Bundle savedInstanceState) {
57        super.onCreate(savedInstanceState);
58
59        setContentView(R.layout.selection_demo_layout);
60        RecyclerView recView = (RecyclerView) findViewById(R.id.list);
61
62        // keyProvider depends on mAdapter.setHasStableIds(true).
63        ItemKeyProvider<Long> keyProvider = new StableIdKeyProvider(recView);
64
65        mLayout = new GridLayoutManager(this, mColumnCount);
66        recView.setLayoutManager(mLayout);
67
68        mAdapter = new SimpleSelectionDemoAdapter(this, keyProvider);
69        // The adapter is paired with a key provider that supports
70        // the native RecyclerView stableId. For this to work correctly
71        // the adapter must report that it supports stable ids.
72        mAdapter.setHasStableIds(true);
73
74        recView.setAdapter(mAdapter);
75
76        SelectionTracker.Builder<Long> builder = new SelectionTracker.Builder<>(
77                "simple-demo",
78                recView,
79                keyProvider,
80                new DemoDetailsLookup(recView),
81                StorageStrategy.createLongStorage());
82
83        // Override default behaviors and build in multi select mode.
84        // Call .withSelectionPredicate(SelectionTracker.SelectionPredicate.SINGLE_ANYTHING)
85        // for single selection mode.
86        mSelectionTracker = builder
87                .withSelectionPredicate(SelectionPredicates.createSelectAnything())
88                .withOnDragInitiatedListener(new OnDragInitiatedListener(this))
89                .withOnContextClickListener(new OnContextClickListener(this))
90                .withOnItemActivatedListener(new OnItemActivatedListener(this))
91                .withFocusDelegate(new FocusDelegate(this))
92                .withBandOverlay(R.drawable.selection_demo_band_overlay)
93                .build();
94
95        // Lazily bind SelectionTracker. Allows us to defer initialization of the
96        // SelectionTracker dependency until after the adapter is created.
97        mAdapter.bindSelectionHelper(mSelectionTracker);
98
99        // TODO: Glue selection to ActionMode, since that'll be a common practice.
100        mSelectionTracker.addObserver(
101                new SelectionObserver<Long>() {
102                    @Override
103                    public void onSelectionChanged() {
104                        Log.i(TAG, "Selection changed to: " + mSelectionTracker.getSelection());
105                    }
106                });
107
108        // Restore selection from saved state.
109        updateFromSavedState(savedInstanceState);
110    }
111
112    @Override
113    protected void onSaveInstanceState(Bundle state) {
114        super.onSaveInstanceState(state);
115        mSelectionTracker.onSaveInstanceState(state);
116        state.putInt(EXTRA_COLUMN_COUNT, mColumnCount);
117    }
118
119    private void updateFromSavedState(Bundle state) {
120        mSelectionTracker.onRestoreInstanceState(state);
121
122        if (state != null) {
123            if (state.containsKey(EXTRA_COLUMN_COUNT)) {
124                mColumnCount = state.getInt(EXTRA_COLUMN_COUNT);
125                mLayout.setSpanCount(mColumnCount);
126            }
127        }
128    }
129
130    @Override
131    public boolean onCreateOptionsMenu(Menu menu) {
132        boolean showMenu = super.onCreateOptionsMenu(menu);
133        getMenuInflater().inflate(R.menu.selection_demo_actions, menu);
134        return showMenu;
135    }
136
137    @Override
138    @CallSuper
139    public boolean onPrepareOptionsMenu(Menu menu) {
140        super.onPrepareOptionsMenu(menu);
141        menu.findItem(R.id.option_menu_add_column).setEnabled(mColumnCount <= 3);
142        menu.findItem(R.id.option_menu_remove_column).setEnabled(mColumnCount > 1);
143        return true;
144    }
145
146    @Override
147    public boolean onOptionsItemSelected(MenuItem item) {
148        switch (item.getItemId()) {
149            case R.id.option_menu_add_column:
150                // TODO: Add columns
151                mLayout.setSpanCount(++mColumnCount);
152                return true;
153
154            case R.id.option_menu_remove_column:
155                mLayout.setSpanCount(--mColumnCount);
156                return true;
157
158            default:
159                return super.onOptionsItemSelected(item);
160        }
161    }
162
163    @Override
164    public void onBackPressed() {
165        if (mSelectionTracker.clearSelection()) {
166            return;
167        } else {
168            super.onBackPressed();
169        }
170    }
171
172    private static void toast(Context context, String msg) {
173        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
174    }
175
176    @Override
177    protected void onDestroy() {
178        mSelectionTracker.clearSelection();
179        super.onDestroy();
180    }
181
182    @Override
183    protected void onStart() {
184        super.onStart();
185        mAdapter.loadData();
186    }
187
188    // Implementation of MouseInputHandler.Callbacks allows handling
189    // of higher level events, like onActivated.
190    private static final class OnItemActivatedListener implements
191            androidx.recyclerview.selection.OnItemActivatedListener<Long> {
192
193        private final Context mContext;
194
195        OnItemActivatedListener(Context context) {
196            mContext = context;
197        }
198
199        @Override
200        public boolean onItemActivated(ItemDetails<Long> item, MotionEvent e) {
201            toast(mContext, "Activate item: " + item.getSelectionKey());
202            return true;
203        }
204    }
205
206    private static final class FocusDelegate extends
207            androidx.recyclerview.selection.FocusDelegate<Long> {
208
209        private final Context mContext;
210
211        private FocusDelegate(Context context) {
212            mContext = context;
213        }
214
215        @Override
216        public void focusItem(ItemDetails<Long> item) {
217            toast(mContext, "Focused item: " + item.getSelectionKey());
218        }
219
220        @Override
221        public boolean hasFocusedItem() {
222            return false;
223        }
224
225        @Override
226        public int getFocusedPosition() {
227            return 0;
228        }
229
230        @Override
231        public void clearFocus() {
232            toast(mContext, "Cleared focus.");
233        }
234    }
235
236    // Implementation of MouseInputHandler.Callbacks allows handling
237    // of higher level events, like onActivated.
238    private static final class OnContextClickListener implements
239            androidx.recyclerview.selection.OnContextClickListener {
240
241        private final Context mContext;
242
243        OnContextClickListener(Context context) {
244            mContext = context;
245        }
246
247        @Override
248        public boolean onContextClick(MotionEvent e) {
249            toast(mContext, "Context click received.");
250            return true;
251        }
252    };
253
254    private static final class OnDragInitiatedListener implements
255            androidx.recyclerview.selection.OnDragInitiatedListener {
256
257        private final Context mContext;
258
259        private OnDragInitiatedListener(Context context) {
260            mContext = context;
261        }
262
263        public boolean onDragInitiated(MotionEvent e) {
264            toast(mContext, "onDragInitiated received.");
265            return true;
266        }
267    }
268}
269