Search code examples
androidandroid-layoutandroid-activityandroid-snackbarsnackbar

How does Google Inbox show a snackbar covering the keyboard?


How does Google Inbox show the snackbar over (covering, layering) the keyboard? This is not the default behaviour, which I've tested with a basic coordinator layout, the keyboard open and a snackbar: the default behaviour is showing the snackbar behind the keyboard. There are many answers on SO to the question: 'How do I show the snackbar above the keyboard', but I haven't found how to reproduce Google's own Inbox app's snackbar behaviour.

Can you show me how to achieve this?


Solution

  • Inbox doesn't use the standard Snackbar nor uses an overlay window, it uses a custom view. I decompiled the APK to verify how they achieve this effect and I tried to reproduce a minimal example.

    First of all create a new layout custom_snackbar.xml:

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#323232"
        android:paddingLeft="10dp"
        android:paddingRight="10dp">
    
        <TextView
            android:id="@+id/text"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:text="Marked done"
            android:textColor="@android:color/white"/>
    
        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:background="@android:color/transparent"
            android:gravity="center_vertical"
            android:minWidth="48dp"
            android:text="undo"
            android:textAllCaps="true"
            android:textColor="#a1c2fa"/>
    
    </LinearLayout>
    

    Then create these methods to show and dismiss the custom Snackbar:

    private View customSnackbar;
    
    private void showCustomSnackbar(final Context context) {
        customSnackbar = LayoutInflater.from(context).inflate(R.layout.custom_snackbar, null, false);
        customSnackbar.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //action implementation
            }
        });
    
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.width = WindowManager.LayoutParams.MATCH_PARENT;
        lp.format = PixelFormat.TRANSLUCENT;
        lp.gravity = Gravity.BOTTOM;
        lp.flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        customSnackbar.setLayoutParams(lp);
    
        WindowManager windowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE);
    
        if (windowManager != null) {
            windowManager.addView(customSnackbar, customSnackbar.getLayoutParams());
    
            Point m = new Point();
            windowManager.getDefaultDisplay().getSize(m);
            int childMeasureSpecWidth = ViewGroup.getChildMeasureSpec(View.MeasureSpec.makeMeasureSpec(m.x, View.MeasureSpec.EXACTLY), 0, lp.width);
            int childMeasureSpecHeight = ViewGroup.getChildMeasureSpec(View.MeasureSpec.makeMeasureSpec(m.y, View.MeasureSpec.EXACTLY), 0, lp.height);
            customSnackbar.measure(childMeasureSpecWidth, childMeasureSpecHeight);
    
            customSnackbar.setTranslationY(customSnackbar.getMeasuredHeight());
            customSnackbar.animate()
                    .setDuration(300)
                    .translationX(0.0f)
                    .translationY(0.0f);
        }
    }
    
    private void dismissCustomSnackbar(final Context context) {
        customSnackbar.animate()
                .setDuration(300)
                .translationX(0.0f)
                .translationY(customSnackbar.getMeasuredHeight())
                .withEndAction(new Runnable() {
                    @Override
                    public void run() {
                        WindowManager windowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE);
                        if (windowManager != null) {
                            windowManager.removeView(customSnackbar);
                        }
                    }
                });
    }
    

    This is the result obtained:

    Screenshot