|
|
@@ -44,438 +44,487 @@ import java.util.concurrent.LinkedBlockingQueue;
|
|
|
* Manages the lifecycle of {@link Crouton}s.
|
|
|
*/
|
|
|
final class Manager extends Handler {
|
|
|
- private static final class Messages {
|
|
|
- private Messages() { /* no-op */ }
|
|
|
+ private static final class Messages {
|
|
|
+ private Messages() { /* no-op */ }
|
|
|
+
|
|
|
+ public static final int DISPLAY_CROUTON = 0xc2007;
|
|
|
+ public static final int DISPLAY_CROUTON_BOTTOM = 0xc2017;
|
|
|
+ public static final int ADD_CROUTON_TO_VIEW = 0xc20074dd;
|
|
|
+ public static final int ADD_CROUTON_TO_VIEW_BOTTOM = 0xc20075dd;
|
|
|
+ public static final int REMOVE_CROUTON = 0xc2007de1;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Manager INSTANCE;
|
|
|
|
|
|
- public static final int DISPLAY_CROUTON = 0xc2007;
|
|
|
- public static final int ADD_CROUTON_TO_VIEW = 0xc20074dd;
|
|
|
- public static final int REMOVE_CROUTON = 0xc2007de1;
|
|
|
- }
|
|
|
+ private final Queue<Crouton> croutonQueue;
|
|
|
|
|
|
- private static Manager INSTANCE;
|
|
|
+ private Manager() {
|
|
|
+ croutonQueue = new LinkedBlockingQueue<Crouton>();
|
|
|
+ }
|
|
|
|
|
|
- private final Queue<Crouton> croutonQueue;
|
|
|
+ /**
|
|
|
+ * @return The currently used instance of the {@link Manager}.
|
|
|
+ */
|
|
|
+ static synchronized Manager getInstance() {
|
|
|
+ if (null == INSTANCE) {
|
|
|
+ INSTANCE = new Manager();
|
|
|
+ }
|
|
|
|
|
|
- private Manager() {
|
|
|
- croutonQueue = new LinkedBlockingQueue<Crouton>();
|
|
|
- }
|
|
|
+ return INSTANCE;
|
|
|
+ }
|
|
|
|
|
|
- /**
|
|
|
- * @return The currently used instance of the {@link Manager}.
|
|
|
- */
|
|
|
- static synchronized Manager getInstance() {
|
|
|
- if (null == INSTANCE) {
|
|
|
- INSTANCE = new Manager();
|
|
|
+ /**
|
|
|
+ * Inserts a {@link Crouton} to be displayed.
|
|
|
+ *
|
|
|
+ * @param crouton The {@link Crouton} to be displayed.
|
|
|
+ */
|
|
|
+ void add(Crouton crouton) {
|
|
|
+ croutonQueue.add(crouton);
|
|
|
+ displayCrouton(false);
|
|
|
}
|
|
|
|
|
|
- return INSTANCE;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Inserts a {@link Crouton} to be displayed.
|
|
|
- *
|
|
|
- * @param crouton
|
|
|
- * The {@link Crouton} to be displayed.
|
|
|
- */
|
|
|
- void add(Crouton crouton) {
|
|
|
- croutonQueue.add(crouton);
|
|
|
- displayCrouton();
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Displays the next {@link Crouton} within the queue.
|
|
|
- */
|
|
|
- private void displayCrouton() {
|
|
|
- if (croutonQueue.isEmpty()) {
|
|
|
- return;
|
|
|
+ void add(Crouton crouton, boolean isBottom) {
|
|
|
+ croutonQueue.add(crouton);
|
|
|
+ displayCrouton(isBottom);
|
|
|
}
|
|
|
|
|
|
- // First peek whether the Crouton has an activity.
|
|
|
- final Crouton currentCrouton = croutonQueue.peek();
|
|
|
+ /**
|
|
|
+ * Displays the next {@link Crouton} within the queue.
|
|
|
+ */
|
|
|
+ private void displayCrouton(boolean isBottom) {
|
|
|
+ if (croutonQueue.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- // If the activity is null we poll the Crouton off the queue.
|
|
|
- if (null == currentCrouton.getActivity()) {
|
|
|
- croutonQueue.poll();
|
|
|
+ // First peek whether the Crouton has an activity.
|
|
|
+ final Crouton currentCrouton = croutonQueue.peek();
|
|
|
+
|
|
|
+ // If the activity is null we poll the Crouton off the queue.
|
|
|
+ if (null == currentCrouton.getActivity()) {
|
|
|
+ croutonQueue.poll();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!currentCrouton.isShowing()) {
|
|
|
+ // Display the Crouton
|
|
|
+ if (isBottom) {
|
|
|
+ sendMessage(currentCrouton, Messages.ADD_CROUTON_TO_VIEW_BOTTOM);
|
|
|
+ } else {
|
|
|
+ sendMessage(currentCrouton, Messages.ADD_CROUTON_TO_VIEW);
|
|
|
+ }
|
|
|
+ if (null != currentCrouton.getLifecycleCallback()) {
|
|
|
+ currentCrouton.getLifecycleCallback().onDisplayed();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (isBottom) {
|
|
|
+ sendMessageDelayed(currentCrouton, Messages.DISPLAY_CROUTON_BOTTOM, calculateCroutonDuration(currentCrouton));
|
|
|
+ } else {
|
|
|
+ sendMessageDelayed(currentCrouton, Messages.DISPLAY_CROUTON, calculateCroutonDuration(currentCrouton));
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- if (!currentCrouton.isShowing()) {
|
|
|
- // Display the Crouton
|
|
|
- sendMessage(currentCrouton, Messages.ADD_CROUTON_TO_VIEW);
|
|
|
- if (null != currentCrouton.getLifecycleCallback()) {
|
|
|
- currentCrouton.getLifecycleCallback().onDisplayed();
|
|
|
- }
|
|
|
- } else {
|
|
|
- sendMessageDelayed(currentCrouton, Messages.DISPLAY_CROUTON, calculateCroutonDuration(currentCrouton));
|
|
|
+ private long calculateCroutonDuration(Crouton crouton) {
|
|
|
+ long croutonDuration = crouton.getConfiguration().durationInMilliseconds;
|
|
|
+ croutonDuration += crouton.getInAnimation().getDuration();
|
|
|
+ croutonDuration += crouton.getOutAnimation().getDuration();
|
|
|
+ return croutonDuration;
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- private long calculateCroutonDuration(Crouton crouton) {
|
|
|
- long croutonDuration = crouton.getConfiguration().durationInMilliseconds;
|
|
|
- croutonDuration += crouton.getInAnimation().getDuration();
|
|
|
- croutonDuration += crouton.getOutAnimation().getDuration();
|
|
|
- return croutonDuration;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Sends a {@link Crouton} within a {@link Message}.
|
|
|
- *
|
|
|
- * @param crouton
|
|
|
- * The {@link Crouton} that should be sent.
|
|
|
- * @param messageId
|
|
|
- * The {@link Message} id.
|
|
|
- */
|
|
|
- private void sendMessage(Crouton crouton, final int messageId) {
|
|
|
- final Message message = obtainMessage(messageId);
|
|
|
- message.obj = crouton;
|
|
|
- sendMessage(message);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Sends a {@link Crouton} within a delayed {@link Message}.
|
|
|
- *
|
|
|
- * @param crouton
|
|
|
- * The {@link Crouton} that should be sent.
|
|
|
- * @param messageId
|
|
|
- * The {@link Message} id.
|
|
|
- * @param delay
|
|
|
- * The delay in milliseconds.
|
|
|
- */
|
|
|
- private void sendMessageDelayed(Crouton crouton, final int messageId, final long delay) {
|
|
|
- Message message = obtainMessage(messageId);
|
|
|
- message.obj = crouton;
|
|
|
- sendMessageDelayed(message, delay);
|
|
|
- }
|
|
|
-
|
|
|
- /*
|
|
|
- * (non-Javadoc)
|
|
|
- *
|
|
|
- * @see android.os.Handler#handleMessage(android.os.Message)
|
|
|
- */
|
|
|
- @Override
|
|
|
- public void handleMessage(Message message) {
|
|
|
- final Crouton crouton = (Crouton) message.obj;
|
|
|
- if (null == crouton) {
|
|
|
- return;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sends a {@link Crouton} within a {@link Message}.
|
|
|
+ *
|
|
|
+ * @param crouton The {@link Crouton} that should be sent.
|
|
|
+ * @param messageId The {@link Message} id.
|
|
|
+ */
|
|
|
+ private void sendMessage(Crouton crouton, final int messageId) {
|
|
|
+ final Message message = obtainMessage(messageId);
|
|
|
+ message.obj = crouton;
|
|
|
+ sendMessage(message);
|
|
|
}
|
|
|
- switch (message.what) {
|
|
|
- case Messages.DISPLAY_CROUTON: {
|
|
|
- displayCrouton();
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- case Messages.ADD_CROUTON_TO_VIEW: {
|
|
|
- addCroutonToView(crouton);
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- case Messages.REMOVE_CROUTON: {
|
|
|
- removeCrouton(crouton);
|
|
|
- if (null != crouton.getLifecycleCallback()) {
|
|
|
- crouton.getLifecycleCallback().onRemoved();
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
|
|
|
- default: {
|
|
|
- super.handleMessage(message);
|
|
|
- break;
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * Sends a {@link Crouton} within a delayed {@link Message}.
|
|
|
+ *
|
|
|
+ * @param crouton The {@link Crouton} that should be sent.
|
|
|
+ * @param messageId The {@link Message} id.
|
|
|
+ * @param delay The delay in milliseconds.
|
|
|
+ */
|
|
|
+ private void sendMessageDelayed(Crouton crouton, final int messageId, final long delay) {
|
|
|
+ Message message = obtainMessage(messageId);
|
|
|
+ message.obj = crouton;
|
|
|
+ sendMessageDelayed(message, delay);
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Adds a {@link Crouton} to the {@link ViewParent} of it's {@link Activity}.
|
|
|
- *
|
|
|
- * @param crouton
|
|
|
- * The {@link Crouton} that should be added.
|
|
|
- */
|
|
|
- private void addCroutonToView(final Crouton crouton) {
|
|
|
- // don't add if it is already showing
|
|
|
- if (crouton.isShowing()) {
|
|
|
- return;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * (non-Javadoc)
|
|
|
+ *
|
|
|
+ * @see android.os.Handler#handleMessage(android.os.Message)
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void handleMessage(Message message) {
|
|
|
+ final Crouton crouton = (Crouton) message.obj;
|
|
|
+ if (null == crouton) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ switch (message.what) {
|
|
|
+ case Messages.DISPLAY_CROUTON: {
|
|
|
+ displayCrouton(false);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case Messages.DISPLAY_CROUTON_BOTTOM: {
|
|
|
+ displayCrouton(true);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case Messages.ADD_CROUTON_TO_VIEW: {
|
|
|
+ addCroutonToView(crouton, false);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case Messages.ADD_CROUTON_TO_VIEW_BOTTOM: {
|
|
|
+ addCroutonToView(crouton, true);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case Messages.REMOVE_CROUTON: {
|
|
|
+ removeCrouton(crouton);
|
|
|
+ if (null != crouton.getLifecycleCallback()) {
|
|
|
+ crouton.getLifecycleCallback().onRemoved();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ default: {
|
|
|
+ super.handleMessage(message);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- final View croutonView = crouton.getView();
|
|
|
- if (null == croutonView.getParent()) {
|
|
|
- ViewGroup.LayoutParams params = croutonView.getLayoutParams();
|
|
|
- if (null == params) {
|
|
|
- params =
|
|
|
- new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
|
- }
|
|
|
- // display Crouton in ViewGroup if it has been supplied
|
|
|
- if (null != crouton.getViewGroup()) {
|
|
|
- final ViewGroup croutonViewGroup = crouton.getViewGroup();
|
|
|
- if (shouldAddViewWithoutPosition(croutonViewGroup)) {
|
|
|
- croutonViewGroup.addView(croutonView, params);
|
|
|
- } else {
|
|
|
- croutonViewGroup.addView(croutonView, 0, params);
|
|
|
+ /**
|
|
|
+ * Adds a {@link Crouton} to the {@link ViewParent} of it's {@link Activity}.
|
|
|
+ *
|
|
|
+ * @param crouton The {@link Crouton} that should be added.
|
|
|
+ */
|
|
|
+ private void addCroutonToView(final Crouton crouton, boolean isBottom) {
|
|
|
+ // don't add if it is already showing
|
|
|
+ if (crouton.isShowing()) {
|
|
|
+ return;
|
|
|
}
|
|
|
- } else {
|
|
|
- Activity activity = crouton.getActivity();
|
|
|
- if (null == activity || activity.isFinishing()) {
|
|
|
- return;
|
|
|
+
|
|
|
+ final View croutonView = crouton.getView();
|
|
|
+ if (null == croutonView.getParent()) {
|
|
|
+ ViewGroup.LayoutParams params = croutonView.getLayoutParams();
|
|
|
+ if (null == params) {
|
|
|
+ params =
|
|
|
+ new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
|
+ }
|
|
|
+ // display Crouton in ViewGroup if it has been supplied
|
|
|
+
|
|
|
+ if (null != crouton.getViewGroup()) {
|
|
|
+ final ViewGroup croutonViewGroup = crouton.getViewGroup();
|
|
|
+ if (isBottom) {
|
|
|
+ Activity activity = crouton.getActivity();
|
|
|
+ if (null == activity || activity.isFinishing()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ handleTranslucentActionBar((ViewGroup.MarginLayoutParams) params, activity, croutonViewGroup);
|
|
|
+ handleActionBarOverlay((ViewGroup.MarginLayoutParams) params, activity, croutonViewGroup);
|
|
|
+
|
|
|
+ activity.addContentView(croutonView, params);
|
|
|
+ } else {
|
|
|
+ if (shouldAddViewWithoutPosition(croutonViewGroup)) {
|
|
|
+ croutonViewGroup.addView(croutonView, params);
|
|
|
+ } else {
|
|
|
+ croutonViewGroup.addView(croutonView, 0, params);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Activity activity = crouton.getActivity();
|
|
|
+ if (null == activity || activity.isFinishing()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ handleTranslucentActionBar((ViewGroup.MarginLayoutParams) params, activity);
|
|
|
+ handleActionBarOverlay((ViewGroup.MarginLayoutParams) params, activity);
|
|
|
+
|
|
|
+ activity.addContentView(croutonView, params);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ croutonView.requestLayout(); // This is needed so the animation can use the measured with/height
|
|
|
+ ViewTreeObserver observer = croutonView.getViewTreeObserver();
|
|
|
+ if (null != observer) {
|
|
|
+ observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
|
|
+ @Override
|
|
|
+ @TargetApi(16)
|
|
|
+ public void onGlobalLayout() {
|
|
|
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
|
|
+ croutonView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
|
|
+ } else {
|
|
|
+ croutonView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (crouton.getInAnimation() != null) {
|
|
|
+ croutonView.startAnimation(crouton.getInAnimation());
|
|
|
+ announceForAccessibilityCompat(crouton.getActivity(), crouton.getText());
|
|
|
+ if (Configuration.DURATION_INFINITE != crouton.getConfiguration().durationInMilliseconds) {
|
|
|
+ sendMessageDelayed(crouton, Messages.REMOVE_CROUTON,
|
|
|
+ crouton.getConfiguration().durationInMilliseconds + crouton.getInAnimation().getDuration());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
- handleTranslucentActionBar((ViewGroup.MarginLayoutParams) params, activity);
|
|
|
- handleActionBarOverlay((ViewGroup.MarginLayoutParams) params, activity);
|
|
|
+ }
|
|
|
|
|
|
- activity.addContentView(croutonView, params);
|
|
|
- }
|
|
|
+ private boolean shouldAddViewWithoutPosition(ViewGroup croutonViewGroup) {
|
|
|
+ return croutonViewGroup instanceof FrameLayout || croutonViewGroup instanceof AdapterView ||
|
|
|
+ croutonViewGroup instanceof RelativeLayout;
|
|
|
}
|
|
|
|
|
|
- croutonView.requestLayout(); // This is needed so the animation can use the measured with/height
|
|
|
- ViewTreeObserver observer = croutonView.getViewTreeObserver();
|
|
|
- if (null != observer) {
|
|
|
- observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
|
|
- @Override
|
|
|
- @TargetApi(16)
|
|
|
- public void onGlobalLayout() {
|
|
|
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
|
|
- croutonView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
|
|
- } else {
|
|
|
- croutonView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
|
|
- }
|
|
|
-
|
|
|
- if(crouton.getInAnimation() != null) {
|
|
|
- croutonView.startAnimation(crouton.getInAnimation());
|
|
|
- announceForAccessibilityCompat(crouton.getActivity(), crouton.getText());
|
|
|
- if (Configuration.DURATION_INFINITE != crouton.getConfiguration().durationInMilliseconds) {
|
|
|
- sendMessageDelayed(crouton, Messages.REMOVE_CROUTON,
|
|
|
- crouton.getConfiguration().durationInMilliseconds + crouton.getInAnimation().getDuration());
|
|
|
+ @TargetApi(19)
|
|
|
+ private void handleTranslucentActionBar(ViewGroup.MarginLayoutParams params, Activity activity) {
|
|
|
+ // Translucent status is only available as of Android 4.4 Kit Kat.
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
|
+ final int flags = activity.getWindow().getAttributes().flags;
|
|
|
+ final int translucentStatusFlag = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
|
|
|
+ if ((flags & translucentStatusFlag) == translucentStatusFlag) {
|
|
|
+ setActionBarMargin(params, activity);
|
|
|
}
|
|
|
- }
|
|
|
}
|
|
|
- });
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- private boolean shouldAddViewWithoutPosition(ViewGroup croutonViewGroup) {
|
|
|
- return croutonViewGroup instanceof FrameLayout || croutonViewGroup instanceof AdapterView ||
|
|
|
- croutonViewGroup instanceof RelativeLayout;
|
|
|
- }
|
|
|
-
|
|
|
- @TargetApi(19)
|
|
|
- private void handleTranslucentActionBar(ViewGroup.MarginLayoutParams params, Activity activity) {
|
|
|
- // Translucent status is only available as of Android 4.4 Kit Kat.
|
|
|
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
|
- final int flags = activity.getWindow().getAttributes().flags;
|
|
|
- final int translucentStatusFlag = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
|
|
|
- if ((flags & translucentStatusFlag) == translucentStatusFlag) {
|
|
|
- setActionBarMargin(params, activity);
|
|
|
- }
|
|
|
+
|
|
|
+ @TargetApi(11)
|
|
|
+ private void handleActionBarOverlay(ViewGroup.MarginLayoutParams params, Activity activity) {
|
|
|
+ // ActionBar overlay is only available as of Android 3.0 Honeycomb.
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
|
|
+ final boolean flags = activity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
|
|
|
+ if (flags) {
|
|
|
+ setActionBarMargin(params, activity);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- @TargetApi(11)
|
|
|
- private void handleActionBarOverlay(ViewGroup.MarginLayoutParams params, Activity activity) {
|
|
|
- // ActionBar overlay is only available as of Android 3.0 Honeycomb.
|
|
|
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
|
|
- final boolean flags = activity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
|
|
|
- if (flags) {
|
|
|
- setActionBarMargin(params, activity);
|
|
|
- }
|
|
|
+
|
|
|
+ @TargetApi(19)
|
|
|
+ private void handleTranslucentActionBar(ViewGroup.MarginLayoutParams params, Activity activity, View view) {
|
|
|
+ // Translucent status is only available as of Android 4.4 Kit Kat.
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
|
+ setActionBarMargin(params, activity, view);
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- private void setActionBarMargin(ViewGroup.MarginLayoutParams params, Activity activity) {
|
|
|
- final int actionBarContainerId = Resources.getSystem().getIdentifier("action_bar_container", "id", "android");
|
|
|
- final View actionBarContainer = activity.findViewById(actionBarContainerId);
|
|
|
- // The action bar is present: the app is using a Holo theme.
|
|
|
- if (null != actionBarContainer) {
|
|
|
- params.topMargin = actionBarContainer.getBottom();
|
|
|
+
|
|
|
+ @TargetApi(11)
|
|
|
+ private void handleActionBarOverlay(ViewGroup.MarginLayoutParams params, Activity activity, View view) {
|
|
|
+ // ActionBar overlay is only available as of Android 3.0 Honeycomb.
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
|
|
+ setActionBarMargin(params, activity, view);
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Removes the {@link Crouton}'s view after it's display
|
|
|
- * durationInMilliseconds.
|
|
|
- *
|
|
|
- * @param crouton
|
|
|
- * The {@link Crouton} added to a {@link ViewGroup} and should be
|
|
|
- * removed.
|
|
|
- */
|
|
|
- protected void removeCrouton(Crouton crouton) {
|
|
|
- // If the crouton hasn't been displayed yet a `Crouton.hide()` will fail to hide
|
|
|
- // it since the DISPLAY message might still be in the queue. Remove all messages
|
|
|
- // for this crouton.
|
|
|
- removeAllMessagesForCrouton(crouton);
|
|
|
-
|
|
|
- View croutonView = crouton.getView();
|
|
|
- ViewGroup croutonParentView = (ViewGroup) croutonView.getParent();
|
|
|
-
|
|
|
- if (null != croutonParentView) {
|
|
|
- croutonView.startAnimation(crouton.getOutAnimation());
|
|
|
-
|
|
|
- // Remove the Crouton from the queue.
|
|
|
- Crouton removed = croutonQueue.poll();
|
|
|
-
|
|
|
- // Remove the crouton from the view's parent.
|
|
|
- croutonParentView.removeView(croutonView);
|
|
|
- if (null != removed) {
|
|
|
- removed.detachActivity();
|
|
|
- removed.detachViewGroup();
|
|
|
- if (null != removed.getLifecycleCallback()) {
|
|
|
- removed.getLifecycleCallback().onRemoved();
|
|
|
+
|
|
|
+ private void setActionBarMargin(ViewGroup.MarginLayoutParams params, Activity activity) {
|
|
|
+ final int actionBarContainerId = Resources.getSystem().getIdentifier("action_bar_container", "id", "android");
|
|
|
+ final View actionBarContainer = activity.findViewById(actionBarContainerId);
|
|
|
+ // The action bar is present: the app is using a Holo theme.
|
|
|
+ if (null != actionBarContainer) {
|
|
|
+ params.topMargin = actionBarContainer.getBottom();
|
|
|
}
|
|
|
- removed.detachLifecycleCallback();
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- // Send a message to display the next crouton but delay it by the out
|
|
|
- // animation duration to make sure it finishes
|
|
|
- sendMessageDelayed(crouton, Messages.DISPLAY_CROUTON, crouton.getOutAnimation().getDuration());
|
|
|
+ private void setActionBarMargin(ViewGroup.MarginLayoutParams params, Activity activity, View container) {
|
|
|
+ if (null != container) {
|
|
|
+ params.topMargin = container.getBottom();
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Removes a {@link Crouton} immediately, even when it's currently being
|
|
|
- * displayed.
|
|
|
- *
|
|
|
- * @param crouton
|
|
|
- * The {@link Crouton} that should be removed.
|
|
|
- */
|
|
|
- void removeCroutonImmediately(Crouton crouton) {
|
|
|
- // if Crouton has already been displayed then it may not be in the queue (because it was popped).
|
|
|
- // This ensures the displayed Crouton is removed from its parent immediately, whether another instance
|
|
|
- // of it exists in the queue or not.
|
|
|
- // Note: crouton.isShowing() is false here even if it really is showing, as croutonView object in
|
|
|
- // Crouton seems to be out of sync with reality!
|
|
|
- if (null != crouton.getActivity() && null != crouton.getView() && null != crouton.getView().getParent()) {
|
|
|
- ((ViewGroup) crouton.getView().getParent()).removeView(crouton.getView());
|
|
|
-
|
|
|
- // remove any messages pending for the crouton
|
|
|
- removeAllMessagesForCrouton(crouton);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes the {@link Crouton}'s view after it's display
|
|
|
+ * durationInMilliseconds.
|
|
|
+ *
|
|
|
+ * @param crouton The {@link Crouton} added to a {@link ViewGroup} and should be
|
|
|
+ * removed.
|
|
|
+ */
|
|
|
+ protected void removeCrouton(Crouton crouton) {
|
|
|
+ // If the crouton hasn't been displayed yet a `Crouton.hide()` will fail to hide
|
|
|
+ // it since the DISPLAY message might still be in the queue. Remove all messages
|
|
|
+ // for this crouton.
|
|
|
+ removeAllMessagesForCrouton(crouton);
|
|
|
+
|
|
|
+ View croutonView = crouton.getView();
|
|
|
+ ViewGroup croutonParentView = (ViewGroup) croutonView.getParent();
|
|
|
+
|
|
|
+ if (null != croutonParentView) {
|
|
|
+ croutonView.startAnimation(crouton.getOutAnimation());
|
|
|
+
|
|
|
+ // Remove the Crouton from the queue.
|
|
|
+ Crouton removed = croutonQueue.poll();
|
|
|
+
|
|
|
+ // Remove the crouton from the view's parent.
|
|
|
+ croutonParentView.removeView(croutonView);
|
|
|
+ if (null != removed) {
|
|
|
+ removed.detachActivity();
|
|
|
+ removed.detachViewGroup();
|
|
|
+ if (null != removed.getLifecycleCallback()) {
|
|
|
+ removed.getLifecycleCallback().onRemoved();
|
|
|
+ }
|
|
|
+ removed.detachLifecycleCallback();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Send a message to display the next crouton but delay it by the out
|
|
|
+ // animation duration to make sure it finishes
|
|
|
+ sendMessageDelayed(crouton, Messages.DISPLAY_CROUTON, crouton.getOutAnimation().getDuration());
|
|
|
+ }
|
|
|
}
|
|
|
- // remove any matching croutons from queue
|
|
|
- final Iterator<Crouton> croutonIterator = croutonQueue.iterator();
|
|
|
- while (croutonIterator.hasNext()) {
|
|
|
- final Crouton c = croutonIterator.next();
|
|
|
- if (c.equals(crouton) && (null != c.getActivity())) {
|
|
|
- // remove the crouton from the content view
|
|
|
- removeCroutonFromViewParent(crouton);
|
|
|
-
|
|
|
- // remove any messages pending for the crouton
|
|
|
- removeAllMessagesForCrouton(c);
|
|
|
-
|
|
|
- // remove the crouton from the queue
|
|
|
- croutonIterator.remove();
|
|
|
-
|
|
|
- // we have found our crouton so just break
|
|
|
- break;
|
|
|
- }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes a {@link Crouton} immediately, even when it's currently being
|
|
|
+ * displayed.
|
|
|
+ *
|
|
|
+ * @param crouton The {@link Crouton} that should be removed.
|
|
|
+ */
|
|
|
+ void removeCroutonImmediately(Crouton crouton) {
|
|
|
+ // if Crouton has already been displayed then it may not be in the queue (because it was popped).
|
|
|
+ // This ensures the displayed Crouton is removed from its parent immediately, whether another instance
|
|
|
+ // of it exists in the queue or not.
|
|
|
+ // Note: crouton.isShowing() is false here even if it really is showing, as croutonView object in
|
|
|
+ // Crouton seems to be out of sync with reality!
|
|
|
+ if (null != crouton.getActivity() && null != crouton.getView() && null != crouton.getView().getParent()) {
|
|
|
+ ((ViewGroup) crouton.getView().getParent()).removeView(crouton.getView());
|
|
|
+
|
|
|
+ // remove any messages pending for the crouton
|
|
|
+ removeAllMessagesForCrouton(crouton);
|
|
|
+ }
|
|
|
+ // remove any matching croutons from queue
|
|
|
+ final Iterator<Crouton> croutonIterator = croutonQueue.iterator();
|
|
|
+ while (croutonIterator.hasNext()) {
|
|
|
+ final Crouton c = croutonIterator.next();
|
|
|
+ if (c.equals(crouton) && (null != c.getActivity())) {
|
|
|
+ // remove the crouton from the content view
|
|
|
+ removeCroutonFromViewParent(crouton);
|
|
|
+
|
|
|
+ // remove any messages pending for the crouton
|
|
|
+ removeAllMessagesForCrouton(c);
|
|
|
+
|
|
|
+ // remove the crouton from the queue
|
|
|
+ croutonIterator.remove();
|
|
|
+
|
|
|
+ // we have found our crouton so just break
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Removes all {@link Crouton}s from the queue.
|
|
|
- */
|
|
|
- void clearCroutonQueue() {
|
|
|
- removeAllMessages();
|
|
|
-
|
|
|
- // remove any views that may already have been added to the activity's
|
|
|
- // content view
|
|
|
- for (Crouton crouton : croutonQueue) {
|
|
|
- removeCroutonFromViewParent(crouton);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes all {@link Crouton}s from the queue.
|
|
|
+ */
|
|
|
+ void clearCroutonQueue() {
|
|
|
+ removeAllMessages();
|
|
|
+
|
|
|
+ // remove any views that may already have been added to the activity's
|
|
|
+ // content view
|
|
|
+ for (Crouton crouton : croutonQueue) {
|
|
|
+ removeCroutonFromViewParent(crouton);
|
|
|
+ }
|
|
|
+ croutonQueue.clear();
|
|
|
}
|
|
|
- croutonQueue.clear();
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Removes all {@link Crouton}s for the provided activity. This will remove
|
|
|
- * crouton from {@link Activity}s content view immediately.
|
|
|
- */
|
|
|
- void clearCroutonsForActivity(Activity activity) {
|
|
|
- Iterator<Crouton> croutonIterator = croutonQueue.iterator();
|
|
|
- while (croutonIterator.hasNext()) {
|
|
|
- Crouton crouton = croutonIterator.next();
|
|
|
- if ((null != crouton.getActivity()) && crouton.getActivity().equals(activity)) {
|
|
|
- // remove the crouton from the content view
|
|
|
- removeCroutonFromViewParent(crouton);
|
|
|
|
|
|
- removeAllMessagesForCrouton(crouton);
|
|
|
+ /**
|
|
|
+ * Removes all {@link Crouton}s for the provided activity. This will remove
|
|
|
+ * crouton from {@link Activity}s content view immediately.
|
|
|
+ */
|
|
|
+ void clearCroutonsForActivity(Activity activity) {
|
|
|
+ Iterator<Crouton> croutonIterator = croutonQueue.iterator();
|
|
|
+ while (croutonIterator.hasNext()) {
|
|
|
+ Crouton crouton = croutonIterator.next();
|
|
|
+ if ((null != crouton.getActivity()) && crouton.getActivity().equals(activity)) {
|
|
|
+ // remove the crouton from the content view
|
|
|
+ removeCroutonFromViewParent(crouton);
|
|
|
+
|
|
|
+ removeAllMessagesForCrouton(crouton);
|
|
|
+
|
|
|
+ // remove the crouton from the queue
|
|
|
+ croutonIterator.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void removeCroutonFromViewParent(Crouton crouton) {
|
|
|
+ if (crouton.isShowing()) {
|
|
|
+ ViewGroup parent = (ViewGroup) crouton.getView().getParent();
|
|
|
+ if (null != parent) {
|
|
|
+ parent.removeView(crouton.getView());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void removeAllMessages() {
|
|
|
+ removeMessages(Messages.ADD_CROUTON_TO_VIEW);
|
|
|
+ removeMessages(Messages.DISPLAY_CROUTON);
|
|
|
+ removeMessages(Messages.REMOVE_CROUTON);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void removeAllMessagesForCrouton(Crouton crouton) {
|
|
|
+ removeMessages(Messages.ADD_CROUTON_TO_VIEW, crouton);
|
|
|
+ removeMessages(Messages.DISPLAY_CROUTON, crouton);
|
|
|
+ removeMessages(Messages.REMOVE_CROUTON, crouton);
|
|
|
|
|
|
- // remove the crouton from the queue
|
|
|
- croutonIterator.remove();
|
|
|
- }
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- private void removeCroutonFromViewParent(Crouton crouton) {
|
|
|
- if (crouton.isShowing()) {
|
|
|
- ViewGroup parent = (ViewGroup) crouton.getView().getParent();
|
|
|
- if (null != parent) {
|
|
|
- parent.removeView(crouton.getView());
|
|
|
- }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generates and dispatches an SDK-specific spoken announcement.
|
|
|
+ * <p>
|
|
|
+ * For backwards compatibility, we're constructing an event from scratch
|
|
|
+ * using the appropriate event type. If your application only targets SDK
|
|
|
+ * 16+, you can just call View.announceForAccessibility(CharSequence).
|
|
|
+ * </p>
|
|
|
+ * <p/>
|
|
|
+ * note: AccessibilityManager is only available from API lvl 4.
|
|
|
+ * <p/>
|
|
|
+ * Adapted from https://http://eyes-free.googlecode.com/files/accessibility_codelab_demos_v2_src.zip
|
|
|
+ * via https://github.com/coreform/android-formidable-validation
|
|
|
+ *
|
|
|
+ * @param context Used to get {@link AccessibilityManager}
|
|
|
+ * @param text The text to announce.
|
|
|
+ */
|
|
|
+ public static void announceForAccessibilityCompat(Context context, CharSequence text) {
|
|
|
+ if (Build.VERSION.SDK_INT >= 4) {
|
|
|
+ AccessibilityManager accessibilityManager = null;
|
|
|
+ if (null != context) {
|
|
|
+ accessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
|
|
+ }
|
|
|
+ if (null == accessibilityManager || !accessibilityManager.isEnabled()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Prior to SDK 16, announcements could only be made through FOCUSED
|
|
|
+ // events. Jelly Bean (SDK 16) added support for speaking text verbatim
|
|
|
+ // using the ANNOUNCEMENT event type.
|
|
|
+ final int eventType;
|
|
|
+ if (Build.VERSION.SDK_INT < 16) {
|
|
|
+ eventType = AccessibilityEvent.TYPE_VIEW_FOCUSED;
|
|
|
+ } else {
|
|
|
+ eventType = AccessibilityEvent.TYPE_ANNOUNCEMENT;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Construct an accessibility event with the minimum recommended
|
|
|
+ // attributes. An event without a class name or package may be dropped.
|
|
|
+ final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
|
|
|
+ event.getText().add(text);
|
|
|
+ event.setClassName(Manager.class.getName());
|
|
|
+ event.setPackageName(context.getPackageName());
|
|
|
+
|
|
|
+ // Sends the event directly through the accessibility manager. If your
|
|
|
+ // application only targets SDK 14+, you should just call
|
|
|
+ // getParent().requestSendAccessibilityEvent(this, event);
|
|
|
+ accessibilityManager.sendAccessibilityEvent(event);
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- private void removeAllMessages() {
|
|
|
- removeMessages(Messages.ADD_CROUTON_TO_VIEW);
|
|
|
- removeMessages(Messages.DISPLAY_CROUTON);
|
|
|
- removeMessages(Messages.REMOVE_CROUTON);
|
|
|
- }
|
|
|
-
|
|
|
- private void removeAllMessagesForCrouton(Crouton crouton) {
|
|
|
- removeMessages(Messages.ADD_CROUTON_TO_VIEW, crouton);
|
|
|
- removeMessages(Messages.DISPLAY_CROUTON, crouton);
|
|
|
- removeMessages(Messages.REMOVE_CROUTON, crouton);
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Generates and dispatches an SDK-specific spoken announcement.
|
|
|
- * <p>
|
|
|
- * For backwards compatibility, we're constructing an event from scratch
|
|
|
- * using the appropriate event type. If your application only targets SDK
|
|
|
- * 16+, you can just call View.announceForAccessibility(CharSequence).
|
|
|
- * </p>
|
|
|
- * <p/>
|
|
|
- * note: AccessibilityManager is only available from API lvl 4.
|
|
|
- * <p/>
|
|
|
- * Adapted from https://http://eyes-free.googlecode.com/files/accessibility_codelab_demos_v2_src.zip
|
|
|
- * via https://github.com/coreform/android-formidable-validation
|
|
|
- *
|
|
|
- * @param context
|
|
|
- * Used to get {@link AccessibilityManager}
|
|
|
- * @param text
|
|
|
- * The text to announce.
|
|
|
- */
|
|
|
- public static void announceForAccessibilityCompat(Context context, CharSequence text) {
|
|
|
- if (Build.VERSION.SDK_INT >= 4) {
|
|
|
- AccessibilityManager accessibilityManager = null;
|
|
|
- if (null != context) {
|
|
|
- accessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
|
|
- }
|
|
|
- if (null == accessibilityManager || !accessibilityManager.isEnabled()) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Prior to SDK 16, announcements could only be made through FOCUSED
|
|
|
- // events. Jelly Bean (SDK 16) added support for speaking text verbatim
|
|
|
- // using the ANNOUNCEMENT event type.
|
|
|
- final int eventType;
|
|
|
- if (Build.VERSION.SDK_INT < 16) {
|
|
|
- eventType = AccessibilityEvent.TYPE_VIEW_FOCUSED;
|
|
|
- } else {
|
|
|
- eventType = AccessibilityEvent.TYPE_ANNOUNCEMENT;
|
|
|
- }
|
|
|
-
|
|
|
- // Construct an accessibility event with the minimum recommended
|
|
|
- // attributes. An event without a class name or package may be dropped.
|
|
|
- final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
|
|
|
- event.getText().add(text);
|
|
|
- event.setClassName(Manager.class.getName());
|
|
|
- event.setPackageName(context.getPackageName());
|
|
|
-
|
|
|
- // Sends the event directly through the accessibility manager. If your
|
|
|
- // application only targets SDK 14+, you should just call
|
|
|
- // getParent().requestSendAccessibilityEvent(this, event);
|
|
|
- accessibilityManager.sendAccessibilityEvent(event);
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() {
|
|
|
+ return "Manager{" +
|
|
|
+ "croutonQueue=" + croutonQueue +
|
|
|
+ '}';
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public String toString() {
|
|
|
- return "Manager{" +
|
|
|
- "croutonQueue=" + croutonQueue +
|
|
|
- '}';
|
|
|
- }
|
|
|
}
|