Is there any way make Snackbar persist among activity changes?

后端 未结 6 981
一整个雨季
一整个雨季 2020-12-13 04:25

Although Snackbar is beautiful, it doesn\'t persist when changing activities. This is a bummer in scenarios where I would like to confirm that a message was sen

6条回答
  •  失恋的感觉
    2020-12-13 04:57

    To create a Snackbar with the application context which is visible across multiple activities:

    1. Get the WindowManager as system service
    2. Create and add a FrameLayout (rootView) with type WindowManager.LayoutParams.TYPE_TOAST and WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL to the WindowManager
    3. Wait until on FrameLayout.onAttachedToWindow() is called in the FrameLayout (rootView)
    4. Get the window token of the FrameLayout (rootView) with View.getWindowToken()
    5. Create a ContextThemeWrapper with the application context and a derived @style/Theme.AppCompat
    6. Use the new context to create an additional FrameLayout (snackbarContainer)
    7. Add this FrameLayout (snackbarContainer) with type WindowManager.LayoutParams.TYPE_APPLICATION_PANEL and flag WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
    8. Wait until on View.onAttachedToWindow() is called in the FrameLayout (snackbarContainer)
    9. Create the Snackbar like normal with the FrameLayout (snackbarContainer)
    10. Set View.onDismissed() callback to the Snackbar and remove the FrameLayouts (rootView and snackbarContainer)
    11. Show the snackbar Snackbar.show()

    Here a working wrapper (NOTE: Swipe to dismiss is not working. Maybe some one else find the correct WindowManager.LayoutParams flags to receive touch events Fixed by CoordinatorLayout):

    public class SnackbarWrapper
    {
        private final CharSequence text;
        private final int duration;
        private final WindowManager windowManager;
        private final Context appplicationContext;
        @Nullable
        private Snackbar.Callback externalCallback;
        @Nullable
        private Action action;
    
        @NonNull
        public static SnackbarWrapper make(@NonNull Context applicationContext, @NonNull CharSequence text, @Snackbar.Duration int duration)
        {
            return new SnackbarWrapper(applicationContext, text, duration);
        }
    
        private SnackbarWrapper(@NonNull final Context appplicationContext, @NonNull CharSequence text, @Snackbar.Duration int duration)
        {
            this.appplicationContext = appplicationContext;
            this.windowManager = (WindowManager) appplicationContext.getSystemService(Context.WINDOW_SERVICE);
            this.text = text;
            this.duration = duration;
        }
    
        public void show()
        {
            WindowManager.LayoutParams layoutParams = createDefaultLayoutParams(WindowManager.LayoutParams.TYPE_TOAST, null);
            windowManager.addView(new FrameLayout(appplicationContext)
            {
                @Override
                protected void onAttachedToWindow()
                {
                    super.onAttachedToWindow();
                    onRootViewAvailable(this);
                }
    
            }, layoutParams);
        }
    
        private void onRootViewAvailable(final FrameLayout rootView)
        {
            final CoordinatorLayout snackbarContainer = new CoordinatorLayout(new ContextThemeWrapper(appplicationContext, R.style.FOL_Theme_SnackbarWrapper))
            {
                @Override
                public void onAttachedToWindow()
                {
                    super.onAttachedToWindow();
                    onSnackbarContainerAttached(rootView, this);
                }
            };
            windowManager.addView(snackbarContainer, createDefaultLayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, rootView.getWindowToken()));
        }
    
        private void onSnackbarContainerAttached(final View rootView, final CoordinatorLayout snackbarContainer)
        {
            Snackbar snackbar = Snackbar.make(snackbarContainer, text, duration);
            snackbar.setCallback(new Snackbar.Callback()
            {
                @Override
                public void onDismissed(Snackbar snackbar, int event)
                {
                    super.onDismissed(snackbar, event);
                    // Clean up (NOTE! This callback can be called multiple times)
                    if (snackbarContainer.getParent() != null && rootView.getParent() != null)
                    {
                        windowManager.removeView(snackbarContainer);
                        windowManager.removeView(rootView);
                    }
                    if (externalCallback != null)
                    {
                        externalCallback.onDismissed(snackbar, event);
                    }
                }
    
                @Override
                public void onShown(Snackbar snackbar)
                {
                    super.onShown(snackbar);
                    if (externalCallback != null)
                    {
                        externalCallback.onShown(snackbar);
                    }
                }
            });
            if (action != null)
            {
                snackbar.setAction(action.text, action.listener);
            }
            snackbar.show();
        }
    
        private WindowManager.LayoutParams createDefaultLayoutParams(int type, @Nullable IBinder windowToken)
        {
            WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
            layoutParams.format = PixelFormat.TRANSLUCENT;
            layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
            layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
            layoutParams.gravity = GravityCompat.getAbsoluteGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, ViewCompat.LAYOUT_DIRECTION_LTR);
            layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
            layoutParams.type = type;
            layoutParams.token = windowToken;
            return layoutParams;
        }
    
        @NonNull
        public SnackbarWrapper setCallback(@Nullable Snackbar.Callback callback)
        {
            this.externalCallback = callback;
            return this;
        }
    
        @NonNull
        public SnackbarWrapper setAction(CharSequence text, final View.OnClickListener listener)
        {
            action = new Action(text, listener);
            return this;
        }
    
        private static class Action
        {
            private final CharSequence text;
            private final View.OnClickListener listener;
    
            public Action(CharSequence text, View.OnClickListener listener)
            {
                this.text = text;
                this.listener = listener;
            }
        }
    }
    

    EDIT
    Once SnackbarWrapper is defined you can use it like this:

    final SnackbarWrapper snackbarWrapper = SnackbarWrapper.make(getApplicationContext(),
                "Test snackbarWrapper", Snackbar.LENGTH_LONG);
    
    snackbarWrapper.setAction(R.string.snackbar_text,
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(getApplicationContext(), "Action",
                                Toast.LENGTH_SHORT).show();
                    }
                });
    
    snackbarWrapper.show();
    

    If you don't have a theme, you can quickly define one in styles.xml:

    
    

    EDIT
    For those on Android Oreo getting Bad Token Exception, change TYPE_TOAST to TYPE_APPLICATION_OVERLAY. This is due to Android Oreo implementing special permissions to draw over applications. You can ask for this permissions using:

        if(!Settings.canDrawOverlays(Activity.this){
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, URI.parse("package:" + getPackageName()));
            startActivityForResult(intent, REQ_CODE);
        }
    

提交回复
热议问题