Android 多行 Snackbar

106

我正在尝试利用Android Design Support库中的新Snackbar来显示多行 Snackbar,如http://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-specs所示:

import android.support.design.widget.Snackbar;

final String snack = "First line\nSecond line\nThird line";
Snackbar.make(mView, snack, Snackbar.LENGTH_LONG).show();

在我的Nexus 7上,它只显示了第一行...。如何使其显示所有行?

PS:我尝试了Toast,它显示了所有行。


据我所知,Snackbar 旨在快速向用户提供警报/反馈,并已设计为支持“单行”模式。如果您想显示具有多行的警报/反馈,则建议显示对话框,因为用户可以在阅读消息后采取行动。 - mudit
4
@mudit 考虑国际化。即使英文字符串可以适合单行,德语可能不行。此外,谷歌提供了多行Snackbar的Material Design规范(我在问题中提供了链接)- 为什么要避免使用它? - Dima Kornilov
20个回答

259

只需设置 Snackbars 的 Textview 的 maxLines 属性即可。

View snackbarView = snackbar.getView();
TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
textView.setMaxLines(5);  // show multiple line

如果您正在使用较新的 "com.google.android.material:material:1.0.0" 依赖项,则将使用此代码:com.google.android.material.R.id.snackbar_text 来访问 Snackbar 的 TextView。

您也可以使用R.id.snackbar_text。这对我很有效。


@Chris,Android也许有这个功能,但是如果文本长度较小,你可以指定maxLine,它会自动缩小... - Nilesh Senta
25
最新的AndroidX库中,Snackbar文本的ID应该是com.google.android.material.R.id.snackbar_text。但我认为这个ID在未来可能会发生变化,所以最好避免使用这种做法。 - mtsahakis
2
由于这取决于每个支持库版本都可能更改的TextView ID,因此此答案并不是一个永久解决方案,而是一个hack。它可能有效,但也可能在生产中随机出现问题。 - Chapz
1
有没有推荐的API可以用于此?我同意这种方法是一种hack,但如果没有为此提供API,那就没有其他选择了。 - fobbymaster
1
这不是永久解决方案。请参考Gabriele Mariotti的解决方案。那是永久性的解决方案。 - Hitesh Sarsava
显示剩余2条评论

36

可以在应用程序的values.xml中覆盖预定义值

<integer name="design_snackbar_text_max_lines">5</integer>

此值默认由Snackbar使用。


22
应该避免这种做法,因为它是由设计库定义的私有整数,可能会在不生成任何编译或运行时错误的情况下被重命名或删除。 - Oasis Feng
6
是的,但我认为应该澄清这与访问操作系统的私有资源非常不同。只要你坚持使用相同版本的设计库,它就能运行。如果你升级到新版本,无论如何都必须彻底测试你的应用程序,只需将此添加到你的检查清单中即可。 - devconsole
只是想让您知道,在Kindle Fire设备上这个似乎只能显示一行,不起作用。 @Nilesh的答案确实可行。 - Naveen Dissanayake
1
因为我不喜欢清单变得更长的想法,所以我给它点了踩。 - ban-geoengineering

18

使用 Material Components 库,可以在应用主题中使用 snackbarTextViewStyle 属性来定义它:

<style name="AppTheme" parent="Theme.MaterialComponents.*">
  ...
  <item name="snackbarTextViewStyle">@style/snackbar_text</item>
</style>

<style name="snackbar_text" parent="@style/Widget.MaterialComponents.Snackbar.TextView">
    ...
    <item name="android:maxLines">5</item>
</style>

输入图像描述

注意: 它需要库的版本1.2.0


2
不错的观点,但是该库仍处于alpha状态(2020年5月) :) - Anton Malyshev

16
Snackbar snackbar =  Snackbar.make(view, "Text",Snackbar.LENGTH_LONG).setDuration(Snackbar.LENGTH_LONG);
View snackbarView = snackbar.getView();
TextView tv= (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
tv.setMaxLines(3); 
snackbar.show();

12

以下是我的发现:

Android支持多行Snackbar,但最多只能显示两行,这符合设计准则,其中提到多行Snackbar的高度应为80dp(几乎等于2行)。

为了验证这一点,我使用了Cheesesquare Android示例项目。如果我使用以下字符串:

Snackbar.make(view, "Random Text \n When a second snackbar is triggered while the first is displayed", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();

在这种情况下,我可以看到带有第二行文本的多行 Snackbar,即“当第二个 Snackbar 被触发时”,但是如果我将此代码更改为以下实现:

Snackbar.make(view, "Random Text \n When \n a second snackbar is triggered while the first is displayed", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();

我只能看到“随机文本\n当 ...”。 这意味着设计库有意强制将文本视图最多限制为2行。


2
我接受了这个答案,但是材料规格中提到Snackbar具有112dp的宽度:http://i.imgur.com/1ACCoQb.png。 - Dima Kornilov
你能否命名多行快糖栏的高度尺寸? - Alp
@alp 请查看 https://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-specs - Dan Dar3
1
如果您不想接受某个指南,请不要遵循任何指南。如果谷歌的指南和工具是如此正确,那么为什么Gradle构建系统会让开发人员头疼呢? - Jigar
@DimaKornilov 只有当您提供了一个操作,并且完整的文本正好有两行时,它才会被设置为112dp。在这种情况下,该操作将导致Snackbar展开到112dp。 - Christopher Rucinski
@Jigar 我看不出构建系统和设计准则之间的联系。 - The incredible Jan

12
在Kotlin中,您可以使用扩展。
// SnackbarExtensions.kt

fun Snackbar.allowInfiniteLines(): Snackbar {
    return apply { (view.findViewById<View?>(R.id.snackbar_text) as? TextView?)?.isSingleLine = false }
}

用法:

Snackbar.make(view, message, Snackbar.LENGTH_LONG)
                        .allowInfiniteLines()
                        .show()

很好,干净利落,我喜欢这个解决方案!顺便提一下,完整的ID是com.google.android.material.R.id.snackbar_text - 4gus71n
喜欢看到一个简短而精炼的 Kotlin 扩展,可以轻松地复制/粘贴到我的项目中。 - lasec0203
这实际上是最好的解决方案,而且非常有效 ✔️ - Dharamveer Mithilesh Gupta

7

对于Material Design,引用代码为com.google.android.material.R.id.snackbar_text

val snack = Snackbar.make(myView, R.string.myLongText, Snackbar.LENGTH_INDEFINITE).apply {
                view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text).maxLines = 10
            }
            snack.show()

5
在 Kotlin 中,你可以直接这样做:
Snackbar.make(root_view, "Yo\nYo\nYo!", Snackbar.LENGTH_SHORT).apply {
    view.snackbar_text.setSingleLine(false)
    show()
}

你可以将setSingleLine(false)替换为maxLines = 3
Android Studio应提示你添加。
import kotlinx.android.synthetic.main.design_layout_snackbar_include.view.*

编辑

我再次无法让它工作,所以我只分享我认为是最干净的用Kotlin编写的方式,其他人已经分享过了:

import com.google.android.material.R as MaterialR

Snackbar.make(root_view, "Yo\nYo\nYo!", Snackbar.LENGTH_SHORT).apply {
    val textView = view.findViewById<TextView>(MaterialR.id.snackbar_text)
    textView.setSingleLine(false)
    show()
}


现在崩溃了:java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setSingleLine(boolean)' on a null object reference - android developer
@androiddeveloper 你有这个依赖吗:implementation 'com.google.android.material:material:1.1.0',并且你的Snackbar是com.google.android.material.snackbar.Snackbar吗?如果不是,请尝试将资源ID设置为android.support.design.R.id.snackbar_text - Gumby The Green
@androiddeveloper 这可能是设备或操作系统版本的问题。我刚刚尝试了一个新的“基本活动”项目,它正常工作,而且似乎在此页面上对其他人也有效。 - Gumby The Green
我在安卓10的Pixel 4上进行了测试,也在安卓10的模拟器上进行了测试。并尝试更新到1.2.0-alpha06版本。仍然崩溃。起初我以为是因为“view”不正确,但即使修复后,在MainActivity本身上执行findViewById,它仍然找不到此视图。但是,使用snackbar.view.findViewById可以正常工作。 - android developer
顺便说一句,不需要 import com.google.android.material.R as MaterialR。你可以直接使用ID:com.google.android.material.R.id.snackbar_text,但是在导入中使用 "as" 的有用用法终于出现了,这很好。不过你是怎么找到正确的ID的呢?布局检查器似乎没有显示SnackBar的任何属性... - android developer
显示剩余6条评论

4

2021 年 Kotlin 版本的答案为 com.google.android.material:material:1.4.0

需要设置 isSingleLine = false,并且设置 maxLines = 5

Snackbar.make(view, "line 1\nline 2", BaseTransientBottomBar.LENGTH_INDEFINITE)
    .apply {
        this.view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)?.apply {
            maxLines = 5
            isSingleLine = false
        }
    }
    .show()

1
对于那些使用此解决方案的人,请确保在 view.find... 之前使用 this - yaugenka

3

有一种方法可以避免在snackbar中硬编码textview的资源ID,那就是通过迭代查找TextView。这样做可以更长期地保证安全,并且可以在最小程度上担心ID的更改而更新支持库。

示例:

 public static Snackbar getSnackbar(View rootView, String message, int duration) {
    Snackbar snackbar = Snackbar.make(rootView, message, duration);
    ViewGroup snackbarLayout = (ViewGroup) snackbar.getView();

    TextView text = null;

    for (int i = 0; i < snackbarLayout.getChildCount(); i++) {
        View child = snackbarLayout.getChildAt(i);

        // Since action is a button, and Button extends TextView,
        // Need to make sure this is the message TextView, not the 
        // action Button view.
        if(child instanceof TextView && !(child instanceof Button)) {
            text = (TextView) child;
        }
    }

    if (text != null) {
        text.setMaxLines(3);
    }
    return snackbar;
}

文本总是空的 - stannums
请仔细检查您的代码。这是Snackbar布局的AOSP源代码。您会注意到那里有一个TextView对象,因此文本不可能为空。 - Chantell Osejo
使用findViewsWithText可能会更好,因为您已经知道了文本内容。但是这种解决方案在某些情况下可能会出现问题(例如,如果我们的文本视图变成了Snackbar布局的子级的子级)。 - natario
这对我行不通。SnackbarLayout里面还有另一个视图。因此,snackbarLayout.getChildAt()永远不会返回一个TextView - Vitor Hugo Schwaab

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接