如何定制 SnackBar 的布局?

是否有任何方法可以将 snackBar 的布局更改为自定义视图?

现在它是黑色的,我们可以改变背景颜色。 但我不知道正确的方法来充气一个新的布局,使它作为点心吧的背景?

谢谢..。

102979 次浏览

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.

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();

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>

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.

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);
}
}
}

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

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();
}
});

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;
}

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
}
});

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()
}

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.

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