SnackbarManager.java revision b7f9224b1495db47eb8fd813b5912250e900770a
1b7f9224b1495db47eb8fd813b5912250e900770aChris Banes/* 2b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Copyright (C) 2015 The Android Open Source Project 3b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * 4b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 5b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * you may not use this file except in compliance with the License. 6b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * You may obtain a copy of the License at 7b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * 8b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * http://www.apache.org/licenses/LICENSE-2.0 9b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * 10b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Unless required by applicable law or agreed to in writing, software 11b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * distributed under the License is distributed on an "AS IS" BASIS, 12b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * See the License for the specific language governing permissions and 14b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * limitations under the License. 15b7f9224b1495db47eb8fd813b5912250e900770aChris Banes */ 16b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 17b7f9224b1495db47eb8fd813b5912250e900770aChris Banespackage android.support.design.widget; 18b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 19b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport android.os.Handler; 20b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport android.os.Looper; 21b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport android.os.Message; 22b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 23b7f9224b1495db47eb8fd813b5912250e900770aChris Banes/** 24b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Manages {@link Snackbar}s. 25b7f9224b1495db47eb8fd813b5912250e900770aChris Banes */ 26b7f9224b1495db47eb8fd813b5912250e900770aChris Banesclass SnackbarManager { 27b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 28b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static final int MSG_TIMEOUT = 0; 29b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 30b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static final int SHORT_DURATION_MS = 1500; 31b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static final int LONG_DURATION_MS = 2750; 32b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 33b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static SnackbarManager sSnackbarManager; 34b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 35b7f9224b1495db47eb8fd813b5912250e900770aChris Banes static SnackbarManager getInstance() { 36b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (sSnackbarManager == null) { 37b7f9224b1495db47eb8fd813b5912250e900770aChris Banes sSnackbarManager = new SnackbarManager(); 38b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 39b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return sSnackbarManager; 40b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 41b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 42b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private final Object mLock; 43b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private final Handler mHandler; 44b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 45b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private SnackbarRecord mCurrentSnackbar; 46b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private SnackbarRecord mNextSnackbar; 47b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 48b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private SnackbarManager() { 49b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mLock = new Object(); 50b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { 51b7f9224b1495db47eb8fd813b5912250e900770aChris Banes @Override 52b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public boolean handleMessage(Message message) { 53b7f9224b1495db47eb8fd813b5912250e900770aChris Banes switch (message.what) { 54b7f9224b1495db47eb8fd813b5912250e900770aChris Banes case MSG_TIMEOUT: 55b7f9224b1495db47eb8fd813b5912250e900770aChris Banes handleTimeout((SnackbarRecord) message.obj); 56b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return true; 57b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 58b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return false; 59b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 60b7f9224b1495db47eb8fd813b5912250e900770aChris Banes }); 61b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 62b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 63b7f9224b1495db47eb8fd813b5912250e900770aChris Banes interface Callback { 64b7f9224b1495db47eb8fd813b5912250e900770aChris Banes void show(); 65b7f9224b1495db47eb8fd813b5912250e900770aChris Banes void dismiss(); 66b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 67b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 68b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public void show(int duration, Callback callback) { 69b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 70b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (isCurrentSnackbar(callback)) { 71b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // Means that the callback is already in the queue. We'll just update the duration 72b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mCurrentSnackbar.duration = duration; 73b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 74b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // If this is the Snackbar currently being shown, call re-schedule it's 75b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // timeout 76b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mHandler.removeCallbacksAndMessages(mCurrentSnackbar); 77b7f9224b1495db47eb8fd813b5912250e900770aChris Banes scheduleTimeoutLocked(mCurrentSnackbar); 78b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return; 79b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } else if (isNextSnackbar(callback)) { 80b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // We'll just update the duration 81b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mNextSnackbar.duration = duration; 82b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } else { 83b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // Else, we need to create a new record and queue it 84b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mNextSnackbar = new SnackbarRecord(duration, callback); 85b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 86b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 87b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mCurrentSnackbar != null) { 88b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // If the new Snackbar isn't in position 0, we'll cancel the current one and wait 89b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // in line 90b7f9224b1495db47eb8fd813b5912250e900770aChris Banes cancelSnackbarLocked(mCurrentSnackbar); 91b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } else { 92b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // Otherwise, just show it now 93b7f9224b1495db47eb8fd813b5912250e900770aChris Banes showNextSnackbarLocked(); 94b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 95b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 96b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 97b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 98b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public void dismiss(Callback callback) { 99b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 100b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (isCurrentSnackbar(callback)) { 101b7f9224b1495db47eb8fd813b5912250e900770aChris Banes cancelSnackbarLocked(mCurrentSnackbar); 102b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 103b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (isNextSnackbar(callback)) { 104b7f9224b1495db47eb8fd813b5912250e900770aChris Banes cancelSnackbarLocked(mNextSnackbar); 105b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 106b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 107b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 108b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 109b7f9224b1495db47eb8fd813b5912250e900770aChris Banes /** 110b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Should be called when a Snackbar is no longer displayed. This is after any exit 111b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * animation has finished. 112b7f9224b1495db47eb8fd813b5912250e900770aChris Banes */ 113b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public void onDismissed(Callback callback) { 114b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 115b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (isCurrentSnackbar(callback)) { 116b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // If the callback is from a Snackbar currently show, remove it and show a new one 117b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mCurrentSnackbar = null; 118b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mNextSnackbar != null) { 119b7f9224b1495db47eb8fd813b5912250e900770aChris Banes showNextSnackbarLocked(); 120b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 121b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 122b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 123b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 124b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 125b7f9224b1495db47eb8fd813b5912250e900770aChris Banes /** 126b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Should be called when a Snackbar is being shown. This is after any entrance animation has 127b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * finished. 128b7f9224b1495db47eb8fd813b5912250e900770aChris Banes */ 129b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public void onShown(Callback callback) { 130b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 131b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (isCurrentSnackbar(callback)) { 132b7f9224b1495db47eb8fd813b5912250e900770aChris Banes scheduleTimeoutLocked(mCurrentSnackbar); 133b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 134b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 135b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 136b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 137b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public void cancelTimeout(Callback callback) { 138b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 139b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (isCurrentSnackbar(callback)) { 140b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mHandler.removeCallbacksAndMessages(mCurrentSnackbar); 141b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 142b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 143b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 144b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 145b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public void restoreTimeout(Callback callback) { 146b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 147b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (isCurrentSnackbar(callback)) { 148b7f9224b1495db47eb8fd813b5912250e900770aChris Banes scheduleTimeoutLocked(mCurrentSnackbar); 149b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 150b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 151b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 152b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 153b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static class SnackbarRecord { 154b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private Callback callback; 155b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private int duration; 156b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 157b7f9224b1495db47eb8fd813b5912250e900770aChris Banes SnackbarRecord(int duration, Callback callback) { 158b7f9224b1495db47eb8fd813b5912250e900770aChris Banes this.callback = callback; 159b7f9224b1495db47eb8fd813b5912250e900770aChris Banes this.duration = duration; 160b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 161b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 162b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 163b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private void showNextSnackbarLocked() { 164b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mNextSnackbar != null) { 165b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mCurrentSnackbar = mNextSnackbar; 166b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mCurrentSnackbar.callback.show(); 167b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mNextSnackbar = null; 168b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 169b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 170b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 171b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private void cancelSnackbarLocked(SnackbarRecord record) { 172b7f9224b1495db47eb8fd813b5912250e900770aChris Banes record.callback.dismiss(); 173b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 174b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 175b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private boolean isCurrentSnackbar(Callback callback) { 176b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return mCurrentSnackbar != null && mCurrentSnackbar.callback == callback; 177b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 178b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 179b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private boolean isNextSnackbar(Callback callback) { 180b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return mNextSnackbar != null && mNextSnackbar.callback == callback; 181b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 182b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 183b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private void scheduleTimeoutLocked(SnackbarRecord r) { 184b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mHandler.removeCallbacksAndMessages(r); 185b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r), 186b7f9224b1495db47eb8fd813b5912250e900770aChris Banes r.duration == Snackbar.LENGTH_LONG 187b7f9224b1495db47eb8fd813b5912250e900770aChris Banes ? LONG_DURATION_MS 188b7f9224b1495db47eb8fd813b5912250e900770aChris Banes : SHORT_DURATION_MS); 189b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 190b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 191b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private void handleTimeout(SnackbarRecord record) { 192b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 193b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mCurrentSnackbar == record || mNextSnackbar == record) { 194b7f9224b1495db47eb8fd813b5912250e900770aChris Banes cancelSnackbarLocked(record); 195b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 196b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 197b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 198b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 199b7f9224b1495db47eb8fd813b5912250e900770aChris Banes} 200