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