110

Is there any method to change the layout of a snackBar to custom View?

Now it comes black and we can change the background color. But I don't know the right way to inflate a new layout and making it as snackBars background?

Thanks...

Mike
  • 5,482
  • 2
  • 21
  • 25
noobEinstien
  • 3,147
  • 4
  • 24
  • 42

12 Answers12

163

The Snackbar does not allow you to set a custom layout. However, as Primoz990 suggested you can get the Snackbar's View. The getView function returns the Snackbar.SnackbarLayout, which is a horizontal LinearLayout object whose children are a TextView and a Button. To add your own View to the Snackbar, you just need to hide the TextView, and add your View to the Snackbar.SnackbarLayout.

// Create the Snackbar
Snackbar snackbar = Snackbar.make(containerLayout, "", Snackbar.LENGTH_LONG);
// Get the Snackbar's layout view
Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) snackbar.getView();
// Hide the text
TextView textView = (TextView) layout.findViewById(android.support.design.R.id.snackbar_text);
textView.setVisibility(View.INVISIBLE);

// Inflate our custom view
View snackView = mInflater.inflate(R.layout.my_snackbar, null);
// Configure the view
ImageView imageView = (ImageView) snackView.findViewById(R.id.image);
imageView.setImageBitmap(image);
TextView textViewTop = (TextView) snackView.findViewById(R.id.text);
textViewTop.setText(text);
textViewTop.setTextColor(Color.WHITE);

//If the view is not covering the whole snackbar layout, add this line
layout.setPadding(0,0,0,0);

// Add the view to the Snackbar's layout
layout.addView(snackView, 0);
// Show the Snackbar
snackbar.show();
Ayokunle Paul
  • 243
  • 1
  • 8
Mike
  • 5,482
  • 2
  • 21
  • 25
73

It is possible starting from 25.1.0 revision of Android Support Library

I. Declare custom layout in your values/layout folder.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="horizontal"
          android:layout_width="match_parent"
          android:layout_height="wrap_content">

<Button
    android:id="@+id/snackbar_action"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="@dimen/design_snackbar_extra_spacing_horizontal"              
    android:layout_marginStart="@dimen/design_snackbar_extra_spacing_horizontal"
    android:layout_gravity="center_vertical|right|end"
    android:paddingTop="@dimen/design_snackbar_padding_vertical"
    android:paddingBottom="@dimen/design_snackbar_padding_vertical"
    android:paddingLeft="@dimen/design_snackbar_padding_horizontal"
    android:paddingRight="@dimen/design_snackbar_padding_horizontal"
    android:visibility="gone"
    android:textColor="?attr/colorAccent"
    style="?attr/borderlessButtonStyle"/>

<TextView
    android:gravity="center_vertical|right"
    android:id="@+id/snackbar_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:paddingTop="@dimen/design_snackbar_padding_vertical"
    android:paddingBottom="@dimen/design_snackbar_padding_vertical"
    android:paddingLeft="@dimen/design_snackbar_padding_horizontal"
    android:paddingRight="@dimen/design_snackbar_padding_horizontal"
    android:textAppearance="@style/TextAppearance.Design.Snackbar.Message"
    android:maxLines="@integer/design_snackbar_text_max_lines"
    android:layout_gravity="center_vertical|left|start"
    android:ellipsize="end"/>

</LinearLayout>

Hints:

  • Use @dimen/design_snackbar values to match material design guidelines.
  • Use ?attr/colorAccent to apply your Application Theme changes to Snackbar.

II. Extend BaseTransientBottomBar class.

public class final CustomSnackbar extends BaseTransientBottomBar<CustomSnackbar> {

/**
 * Constructor for the transient bottom bar.
 *
 * @param parent The parent for this transient bottom bar.
 * @param content The content view for this transient bottom bar.
 * @param contentViewCallback The content view callback for this transient bottom bar.
 */
private CustomSnackbar(ViewGroup parent, View content,    
            ContentViewCallback contentViewCallback) {
    super(parent, content, contentViewCallback);
}
}

III. Add BaseTransientBottomBar.ContentViewCallback

public class final CustomSnackbar ...{

...

private static class ContentViewCallback implements        
                   BaseTransientBottomBar.ContentViewCallback {

  // view inflated from custom layout
  private View content;

  public ContentViewCallback(View content) {
      this.content = content;
  }

  @Override
  public void animateContentIn(int delay, int duration) {
      // add custom *in animations for your views
      // e.g. original snackbar uses alpha animation, from 0 to 1
      ViewCompat.setScaleY(content, 0f);
      ViewCompat.animate(content)
                .scaleY(1f).setDuration(duration)
                .setStartDelay(delay);
  }

  @Override
  public void animateContentOut(int delay, int duration) {
      // add custom *out animations for your views
      // e.g. original snackbar uses alpha animation, from 1 to 0
      ViewCompat.setScaleY(content, 1f);
      ViewCompat.animate(content)
                .scaleY(0f)
                .setDuration(duration)
                .setStartDelay(delay);
  }
}
}

IV. Add method to create Snackbar with custom layout and methods to fill it.

public class final CustomSnackbar ...{

...

public static CustomSnackbar make(ViewGroup parent, @Duration int duration) {
 // inflate custom layout
 LayoutInflater inflater = LayoutInflater.from(parent.getContext());
 View content = inflater.inflate(R.layout.snackbar_view, parent, false);

 // create snackbar with custom view
 ContentViewCallback callback= new ContentViewCallback(content);
 CustomSnackbar customSnackbar = new CustomSnackbar(parent, content, callback);
// Remove black background padding on left and right
customSnackbar.getView().setPadding(0, 0, 0, 0);


 // set snackbar duration
 customSnackbar.setDuration(duration);
 return customSnackbar;
 }

 // set text in custom layout
 public CustomSnackbar setText(CharSequence text) {
 TextView textView = (TextView) getView().findViewById(R.id.snackbar_text);
 textView.setText(text);
 return this;
 }

 // set action in custom layout
 public CustomSnackbar setAction(CharSequence text, final OnClickListener  listener) {
 Button actionView = (Button) getView().findViewById(R.id.snackbar_action);
 actionView.setText(text);
 actionView.setVisibility(View.VISIBLE);
 actionView.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View view) {
         listener.onClick(view);
         // Now dismiss the Snackbar
         dismiss();
     }
 });
 return this;
}
}

V. Create instance of CustomSnackbar and call show() method.

CustomSnackbar customSnackbar = CustomSnackbar.make(rooView,      CustomSnackbar.LENGTH_INDEFINITE);
customSnackbar.setText("No network connection!");
customSnackbar.setAction("Retry", new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // handle click here
    }
});
customSnackbar.show();

See more about Snackbar and its customization at materialdoc.com

Full CustomSnackbar.class code :

import android.support.annotation.NonNull;
import android.support.design.widget.BaseTransientBottomBar;
import android.support.v4.view.ViewCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;


public class CustomSnackbar extends BaseTransientBottomBar<CustomSnackbar> {

    /**
     * Constructor for the transient bottom bar.
     *
     * @param parent The parent for this transient bottom bar.
     * @param content The content view for this transient bottom bar.
     * @param callback The content view callback for this transient bottom bar.
     */
    private CustomSnackbar(ViewGroup parent, View content, ContentViewCallback callback) {
        super(parent, content, callback);
    }

    public static CustomSnackbar make(@NonNull ViewGroup parent, @Duration int duration) {
        final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        final View content = inflater.inflate(R.layout.snackbar_view, parent, false);
        final ContentViewCallback viewCallback = new ContentViewCallback(content);
        final CustomSnackbar customSnackbar = new CustomSnackbar(parent, content, viewCallback);

        customSnackbar.getView().setPadding(0, 0, 0, 0);
        customSnackbar.setDuration(duration);
        return customSnackbar;
    }

    public CustomSnackbar setText(CharSequence text) {
        TextView textView = (TextView) getView().findViewById(R.id.snackbar_text);
        textView.setText(text);
        return this;
    }

    public CustomSnackbar setAction(CharSequence text, final View.OnClickListener listener) {
        Button actionView = (Button) getView().findViewById(R.id.snackbar_action);
        actionView.setText(text);
        actionView.setVisibility(View.VISIBLE);
        actionView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                listener.onClick(view);
                // Now dismiss the Snackbar
                dismiss();
            }
        });
        return this;
    }

    private static class ContentViewCallback implements BaseTransientBottomBar.ContentViewCallback {

        private View content;

        public ContentViewCallback(View content) {
            this.content = content;
        }

        @Override
        public void animateContentIn(int delay, int duration) {
            ViewCompat.setScaleY(content, 0f);
            ViewCompat.animate(content).scaleY(1f).setDuration(duration).setStartDelay(delay);
        }

        @Override
        public void animateContentOut(int delay, int duration) {
            ViewCompat.setScaleY(content, 1f);
            ViewCompat.animate(content).scaleY(0f).setDuration(duration).setStartDelay(delay);
        }
    }
}
Nico
  • 2,570
  • 1
  • 9
  • 17
Yakiv Mospan
  • 8,174
  • 3
  • 31
  • 35
  • 1
    @AmirZiarati yes, and its components are swapped (by default action button is on the right side). – Yakiv Mospan May 13 '17 at 21:32
  • 2
    I am getting margins on the left and right so black background is visible. How to remove it? – Leo DroidCoder May 31 '17 at 21:17
  • 2
    @AmirZiarati to make it show from the bottom, you need to copy a private static method from the Snackbar class. I've highlighted the method in an answer to the question. – Tunji_D Aug 29 '17 at 12:39
  • 2
    how do I set transparent background? – DaniloDeQueiroz May 26 '18 at 04:13
  • 1
    @YakivMospan for me it's showing from bottom, can you please tell me how can I show it from top ? – Levon Petrosyan Oct 24 '18 at 16:05
  • To show snackbar show on bottom use snackbar.getView().setY() to set its position on parent.getHeight() - view.getHeight() - margin if you want. Remember to do it in OnGlobalLayoutListener to have actual values for heights. – Michał Powłoka Jun 11 '19 at 09:28
  • 1
    Note that custom Snackbar might not be visible on screen if it has incorrect parent, original Snackbar resolves parent via findSuitableParent() function, so it should also be copied for getting correct parent from the one provided into the make() function. – Kurovsky Aug 18 '20 at 10:25
24

The XML way:

The original layout xml file that is used for the Snackbar is this file:

design_layout_snackbar_include.xml:

<?xml version="1.0" encoding="utf-8"?>        
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <TextView
            android:id="@+id/snackbar_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
              ...
            android:ellipsize="end"/>

    <Button
            android:id="@+id/snackbar_action"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="gone"
              ...
            android:textColor="?attr/colorAccent"
            style="?attr/borderlessButtonStyle"/>

</merge>

So in order to override this layout you should write your own layout with the same android:ids as in this one and in your refs.xml file you should add this line:

<resources xmlns:tools="http://schemas.android.com/tools">
   ....   
    <item name="design_layout_snackbar_include" tools:override="true" type="layout">
        @layout/my_layout_snackbar
    </item>
   ....
</resources>
Morteza Jalambadani
  • 2,190
  • 6
  • 21
  • 35
Moh Mah
  • 2,005
  • 20
  • 29
  • 1
    This worked well for me previously but after updating to Design Support Library 25.1.0 I started to get a merge layout exception. I found [this code](https://github.com/dandar3/android-support-design/blob/a881a1f86c3aa15d1df489b521a6c73b67b3f397/res/layout/design_layout_snackbar_include.xml) that made me change the merge tag into a view and it works again now. – nilsi Jan 17 '17 at 05:57
  • 2
    this doesn't stretch the width. damn this android !!!! why changing a view must be a pain. damn it !! – Amir Ziarati May 14 '17 at 04:02
  • Material docs say to extend `BaseTransientBottomBar`, per https://stackoverflow.com/a/41154330/9636 – Heath Borders Mar 02 '20 at 16:23
24

Image for code written

private Snackbar showSnackbar(CoordinatorLayout coordinatorLayout, int duration) { // Create the Snackbar
    Snackbar snackbar = Snackbar.make(coordinatorLayout, "", duration);
    // 15 is margin from all the sides for snackbar
    int marginFromSides = 15;

    float height = 100;

    //inflate view
    View snackView = getLayoutInflater().inflate(R.layout.snackbar_layout, null);

    // White background
    snackbar.getView().setBackgroundColor(Color.WHITE);
    // for rounded edges
    snackbar.getView().setBackground(getResources().getDrawable(R.drawable.round_edges));

    Snackbar.SnackbarLayout snackBarView = (Snackbar.SnackbarLayout) snackbar.getView();
    FrameLayout.LayoutParams parentParams = (FrameLayout.LayoutParams) snackBarView.getLayoutParams();
    parentParams.setMargins(marginFromSides, 0, marginFromSides, marginFromSides);
    parentParams.height = (int) height;
    parentParams.width = FrameLayout.LayoutParams.MATCH_PARENT;
    snackBarView.setLayoutParams(parentParams);

    snackBarView.addView(snackView, 0);
    return snackbar;
}

In onCreate of the Activity:

CoordinatorLayout coordinatorLayout = findViewById(R.id.coordinator_layout);

final Snackbar snackbar = showSnackbar(coordinatorLayout, Snackbar.LENGTH_LONG);
            snackbar.show();
            View view = snackbar.getView();
            TextView tv = (TextView) view.findViewById(R.id.snackbar_action);
            tv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    snackbar.dismiss();
                }
            });
The Berga
  • 3,744
  • 2
  • 31
  • 34
aamitarya
  • 719
  • 6
  • 11
22

The answer is: Don't customize the Snackbar. It should not contain any more elements than a short text and one action. See Google Material design guidelines.

UPDATE: If you anyway want to customize the Snackbar here is what I have implemented in my app:

//generate the snackbar
Snackbar sb = Snackbar.make(rootView, snack.text, duration);
//set te action button text color
sb.setActionTextColor(mCurrentActivity.getResources().getColor(R.color.snack_text_action));
//Get the view of the snackbar
View sbView = sb.getView();
//set background color
sbView.setBackgroundColor(mCurrentActivity.getResources().getColor(backgroudResId));
//Get the textview of the snackbar text
TextView textView = (TextView) sbView.findViewById(android.support.design.R.id.snackbar_text);
//set text color
textView.setTextColor(mCurrentActivity.getResources().getColor(R.color.snack_text));
//increase max lines of text in snackbar. default is 2.
textView.setMaxLines(10);

I have never tried, but with getting the the root view of the Snackbar, you could programmatically add new views to the Snackbar.

Steve Blackwell
  • 5,904
  • 32
  • 49
Primoz990
  • 1,716
  • 2
  • 20
  • 34
18

I tried it and it works!

View custom = LayoutInflater.from(this).inflate(R.layout.custom_view, null);
snackbar.getView().setPadding(0,0,0,0);
((ViewGroup) snackbar.getView()).removeAllViews();
((ViewGroup) snackbar.getView()).addView(custom);
TextView textView = custom.findViewById(R.id.text);
View button = custom.findViewById(R.id.button);
textView.setText("Your text here");
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
       // do something
    }
});
Morteza Jalambadani
  • 2,190
  • 6
  • 21
  • 35
Nha Phạm Thị
  • 409
  • 4
  • 10
4

Here is my util classcode for kotlin: https://gist.github.com/Ryszardenko/db429bc7d177e646ffe27e0672a0958c#file-customsnackbar-kt

class CustomSnackbar(private val view: View) {

    fun showSnackBar(title: String, cancelFun: () -> Unit = {}) {
        val snackView = View.inflate(view.context, R.layout.snackbar, null)
        val binding = SnackbarBinding.bind(snackView)
        val snackbar = Snackbar.make(view, "", Snackbar.LENGTH_LONG)
        (snackbar.view as ViewGroup).removeAllViews()
        (snackbar.view as ViewGroup).addView(binding.root)
        snackbar.view.setPadding(0, 0, 0, 0)
        snackbar.view.elevation = 0f
        snackbar.setBackgroundTint(
            ContextCompat.getColor(
                view.context,
                android.R.color.transparent
            )
        )
        binding.tvTitle.text = title
        binding.btnCancel.setOnClickListener {
            cancelFun()
            snackbar.dismiss()
        }
        snackbar.show()
    }
}

cancelFun() is a lambda, by default empty - you can pass there for example "undo" function.

Drashyr
  • 2,355
  • 1
  • 4
  • 7
3

Try the following code.

Snackbar snackbar = Snackbar.make(container, "No Internet Connection", Snackbar.LENGTH_LONG);
View sbView = snackbar.getView();
sbView.setBackgroundColor(ContextCompat.getColor(this, R.color.colorPrimary));
snackbar.show();

Note:

container - parent view of layout.

Prerna Jain
  • 1,240
  • 2
  • 16
  • 34
Surendar D
  • 5,554
  • 4
  • 36
  • 38
3

To add to Yakiv Mospan's answer, to make your custom BaseTransientBottomBar show from the bottom like a Snackbar, copy this method from the Snackbar class to find a suitable parent in for the BaseTransientBottomBar constructor.

private static ViewGroup findSuitableParent(View view) {
    ViewGroup fallback = null;
    do {
        if (view instanceof CoordinatorLayout) {
            // We've found a CoordinatorLayout, use it
            return (ViewGroup) view;
        } else if (view instanceof FrameLayout) {
            if (view.getId() == android.R.id.content) {
                // If we've hit the decor content view, then we didn't find a CoL in the
                // hierarchy, so use it.
                return (ViewGroup) view;
            } else {
                // It's not the content view but we'll use it as our fallback
                fallback = (ViewGroup) view;
            }
        }

        if (view != null) {
            // Else, we will loop and crawl up the view hierarchy and try to find a parent
            final ViewParent parent = view.getParent();
            view = parent instanceof View ? (View) parent : null;
        }
    } while (view != null);

    // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback
    return fallback;
}
Morteza Jalambadani
  • 2,190
  • 6
  • 21
  • 35
Tunji_D
  • 3,677
  • 3
  • 27
  • 35
2

My personal utils + Time saving way to customize Snackbar and show it in your Activity or fragment

    fun showSnackbar(view: View, context: Context, text: String) {
    val snackbar = Snackbar.make(view, text, Snackbar.LENGTH_LONG)

    val snackbarLayout = snackbar.view as Snackbar.SnackbarLayout
    snackbarLayout.setBackgroundColor(Color.TRANSPARENT)
    snackbarLayout.elevation = 0F
    snackbarLayout.setPadding(0, 0, 0, 0)

    val snackContentLayout = snackbarLayout.getChildAt(0) as SnackbarContentLayout
    snackContentLayout.background = ContextCompat.getDrawable(context, R.drawable.bg_main_corner)
    snackContentLayout.elevation = 5F
    snackContentLayout.setPadding(30, 0, 30, 0)

    val tvSnackbarTextView = snackContentLayout.getChildAt(0) as AppCompatTextView
    tvSnackbarTextView.setTextColor(Color.parseColor("#FFFFFF"))
    tvSnackbarTextView.typeface = Typeface.SANS_SERIF
    tvSnackbarTextView.textSize = 14F

    val btnSnackbarActionButton = snackContentLayout.getChildAt(1) as AppCompatButton
    btnSnackbarActionButton.setTextColor(Color.parseColor("#8BC34A"))
    btnSnackbarActionButton.typeface = Typeface.SANS_SERIF
    btnSnackbarActionButton.textSize = 12F

    snackbar.setAction(R.string.rate_it) { rateApp(context) }

    snackbar.show()
}

Just call it in your Activity or fragment

 showSnackbar(findViewById(android.R.id.content),this@ScratchActivity, "Already Credit to your wallet!!!")

Thank You

Kumar Santanu
  • 603
  • 1
  • 7
  • 14
1

Kotlin Version for the accepted answer: https://stackoverflow.com/a/33441214/2437655

 private fun showSnackbar() {
        val snackbar = Snackbar.make(
                binding.root,
                "",
                Snackbar.LENGTH_INDEFINITE
        )
        (snackbar.view as Snackbar.SnackbarLayout).apply {
            findViewById<View>(R.id.snackbar_text).visibility = View.INVISIBLE
            findViewById<View>(R.id.snackbar_action).visibility = View.INVISIBLE
            val snackbarBinding = DataBindingUtil.inflate<SnackbarBinding>(
                    LayoutInflater.from(this@SnackbarActivity),
                    R.layout.snackbar,
                    binding.root as ViewGroup,
                    false
            )
            setPadding(0, 0, 0, 0)
            addView(snackbarBinding.root, 0)
        }
        snackbar.setDuration(8000).show()
    }
Shubham AgaRwal
  • 4,355
  • 8
  • 41
  • 62
0

You can try this library. This is a wrapper for android default snackbar. https://github.com/ChathuraHettiarachchi/CSnackBar

Snackbar.with(this,null)
    .type(Type.SUCCESS)
    .message("Profile updated successfully!")
    .duration(Duration.SHORT)
    .show();

or you can even use your own view,

View view = getLayoutInflater().inflate(R.layout.custom_view, null);

Snackbar.with(this,null)
        .type(Type.UPDATE)
        .contentView(view, 76)
        .duration(Duration.SHORT)
        .show();

Currently only issue with custom layout is, we need to pass view height in dp as an input

  • Is it possible to have custom full width snackbar on any device using your library? – yuralife Feb 03 '17 at 10:00
  • @yuralife Currently this is a wrapper for android snackbar, so cannot. only on mobile phone this will get whole width, on tablets this will not fill width – Chathura Jayanath Feb 03 '17 at 10:09