Wieloliniowy pasek przekąsek na Androida


84

Próbuję wykorzystać nowość Snackbarz biblioteki wsparcia projektowania Androida, aby wyświetlić wielowierszowy pasek przekąskowy, jak pokazano na 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();

Wyświetla się tylko First line...na moim Nexusie 7. Jak sprawić, by wyświetlał wszystkie linie?

PS: Spróbowałem Toasti wyświetliło się wszystkie linie.


AFAIK, batony z przekąskami są przeznaczone do szybkiego ostrzegania użytkownika / informacji zwrotnej i zostały zaprojektowane tak, aby je wspierać, np. „Pojedyncza linia”. Jeśli chcesz wyświetlić alert / informację zwrotną, która zawiera wiele linii, sugerowałbym wyświetlenie okna dialogowego, w którym użytkownik może podjąć działanie po przeczytaniu wiadomości.
błoto

3
@mudit pomyśl o internacjonalizacji. Nawet jeśli angielski ciąg może zmieścić się w jednej linii, niemiecki może nie. Również Google dostarczyło specyfikację Material Design dla wielowierszowego snackbaru (podczyłem to w pytaniu) - dlaczego należy tego unikać?
Dima Kornilov

Odpowiedzi:


224

Wystarczy ustawić maxLinesatrybut Snackbars Textview

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

Jeśli używasz nowszej "com.google.android.material:material:1.0.0"zależności, użyjesz tego: com.google.android.material.R.id.snackbar_textaby uzyskać dostęp do TextView Snackbar.

Możesz użyć nawet R.id.snackbar_text. to dla mnie praca.


Czy istnieje sposób obliczenia rzeczywistej liczby potrzebnych linii, czy też musimy zgadywać?
Chris,

@Chris może mieć Androida, ale możesz określić maxLine, jeśli tekst ma małą długość, automatycznie się zmniejszy ...
Nilesh Senta

22
Najnowsze biblioteki androidx powinny mieć postać com.google.android.material.R.id.snackbar_text. Ale IMHO ID może się zmienić w przyszłości, więc należy unikać tej praktyki.
mtsahakis

1
Ponieważ zależy to od identyfikatora TextView, który może się zmieniać wraz z każdą wersją biblioteki wsparcia, ta odpowiedź nie jest tak naprawdę rozwiązaniem trwałym, ale hackowaniem. Może działać, ale może też losowo przerwać produkcję.
Chapz

1
Czy jest do tego zalecany interfejs API? Zgadzam się, że ta metoda to hack, ale jeśli nie ma do tego ujawnionego interfejsu API, nie ma alternatywy.
fobbymaster

31

Można zastąpić predefiniowaną wartość używaną do tego w values.xml aplikacji

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

Ta wartość jest domyślnie używana przez Snackbar.


18
Należy unikać tej praktyki, ponieważ jest to prywatna liczba całkowita zdefiniowana przez bibliotekę projektu, której nazwa może zostać zmieniona lub usunięta bez generowania jakiegokolwiek błędu kompilacji lub czasu wykonywania w aplikacji.
Oasis Feng

4
Tak, ale myślę, że należy wyjaśnić, że różni się to bardzo od dostępu do prywatnych zasobów systemu operacyjnego. Będzie to działać, o ile będziesz trzymać się tej samej wersji biblioteki projektów. Jeśli zaktualizujesz do nowszej wersji, i tak musisz dokładnie przetestować swoją aplikację, po prostu umieść to na swojej liście kontrolnej.
devconsole

Informuję tylko, że to nie działa na urządzeniach Kindle Fire, które wydają się wyświetlać tylko 1 linię. Odpowiedź @Nilesh zadziałała.
Naveen Dissanayake

1
Negocjowany, ponieważ nie podoba mi się pomysł wydłużania listy kontrolnej.
zakaz geoinżynierii

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

11

Oto moje odkrycie:

Android obsługuje wielowierszowe paski przekąsek, ale ma maksymalny limit 2 linii, co odpowiada wytycznej projektowej, w której mówi, że wysokość wielowierszowego batonika powinna wynosić 80 dp (prawie 2 linie)

Aby to sprawdzić, użyłem przykładowego projektu cheesesquare android. Jeśli użyję następującego ciągu:

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

W tym przypadku widzę wielowierszowy batonik z tekstem drugiego wiersza, tj. „Kiedy uruchamiany jest drugi pasek przekąskowy”, ale jeśli zmienię ten kod na następującą implementację:

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

Widzę tylko „Losowy tekst \ nKiedy ... ”. Oznacza to, że biblioteka projektów celowo wymusza na widoku tekstowym maksymalnie 2 wiersze.


2
Zaakceptowałem tę odpowiedź, jednak specyfikacje materiałów wspominają również o snackbar z 112 dp: i.imgur.com/1ACCoQb.png
Dima Kornilov

czy możesz nazwać wymiar wysokości wielowierszowego batonika?
alp


1
Nie kieruj się żadnymi wskazówkami, jeśli nie chcesz zaakceptować tej linii przewodniej. Jeśli Google ma aż tyle racji w swoich wytycznych i narzędziu, to dlaczego system budowania Gradle jest problemem dla programisty?
Jigar

@DimaKornilov zostanie ustawiony na 112dp wtedy i tylko wtedy, gdy masz tekst, który ma pełne 2 wiersze ORAZ podasz akcję. W takim przypadku akcja spowoduje rozszerzenie Snackbar do 112 dp.
Christopher Rucinski

5

W przypadku Material Design odniesienie to 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()

3

Alternatywą dla sugestii, które obejmują zakodowanie na stałe identyfikatora zasobu dla widoku tekstu zawartego w pasku przekąskowym, jest iteracja w celu znalezienia TextView. Jest to bezpieczniejsze w dłuższej perspektywie i pozwala aktualizować bibliotekę wsparcia przy minimalnym strachu przed zmianą identyfikatora.

Przykład:

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

tekst jakoś zawsze null
stannums

Dokładnie sprawdź swój kod. Oto kod źródłowy AOSP dla układu Snackbar. Zauważysz, że jest tam obiekt TextView, więc nie jest możliwe, aby tekst był pusty.
Chantell Osejo

Może być jeszcze lepszy w użyciu findViewsWithText, ponieważ znasz już tekst. To rozwiązanie może się zepsuć w kilku sytuacjach (np. Jeśli nasz widok tekstu stanie się dzieckiem dziecka układu snackbar).
natario

To nie zadziała dla mnie. SnackbarLayout ma inny widok wewnątrz. Więc snackbarLayout.getChildAt()nigdy nie zwróci TextView.
Vitor Hugo Schwaab

2

Zamiast używać setMaxLines, używam setSingleLine, aby zawijać tekst do zawartości.

String yourText = "First line\nSecond line\nThird line";
Snackbar snackbar = Snackbar.make(mView, yourText, Snackbar.LENGTH_SHORT);
    TextView textView =
        (TextView) snackbar.getView().findViewById(android.support.design.R.id.snackbar_text);
    textView.setSingleLine(false);
snackbar.show();

2

to działa dla mnie

Snackbar snackbar =  Snackbar.make(mView, "Your text string", Snackbar.LENGTH_INDEFINITE);
((TextView) snackbar.getView().findViewById(android.support.design.R.id.snackbar_text)).setSingleLine(false);
snackbar.show();

2

Spóźniony, ale może być pomocny dla kogoś:

public void showSnackBar(String txt, View view){
    final Snackbar snackbar = Snackbar.make(view,txt,Snackbar.LENGTH_INDEFINITE)
        .setAction("OK", new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //do something
            }
        });
    View view = snackbar.getView();
    TextView textView = (TextView) view.findViewById(android.support.design.R.id.snackbar_text);
    textView.setMaxLines(5);
    snackbar.show();
}

Wat? Właśnie przypisałeś wartość null do zmiennej końcowej i wywołałeś ją w odbiorniku? Chcesz 100% NPE?
osipxd

@OsipXD Naprawiono dla niego :)
programista Androida

2

Za pomocą biblioteki komponentów materiałów można ją zdefiniować za pomocą snackbarTextViewStyleatrybutu w motywie aplikacji:

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

wprowadź opis obrazu tutaj

Uwaga : wymaga wersji 1.2.0 biblioteki.


2
Słuszna uwaga, ale biblioteka nadal jest w stanie alfa (maj 2020) :)
Anton Malyshev

2

W kotlin możesz używać rozszerzeń.

// SnackbarExtensions.kt

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

Stosowanie:

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

2

W Kotlinie możesz po prostu to zrobić

Snackbar.make(root_view, "Yo\nYo\nYo!", Snackbar.LENGTH_SHORT).apply {
    view.snackbar_text.setSingleLine(false)
    show()
}

Można również wymienić setSingleLine(false)się maxLines = 3.

Android Studio powinno poprosić o dodanie

import kotlinx.android.synthetic.main.design_layout_snackbar_include.view.*

EDYTOWAĆ

Nie udało mi się ponownie uruchomić tego, więc po prostu podzielę się tym, co uważam za najczystszy sposób pisania w Kotlinie, co kilka innych już udostępniło:

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


Nie oferuje użycia / importu snackbar_text.
programista Androida

@androiddeveloper Też nie mogę go ponownie uruchomić, więc zaktualizowałem odpowiedź.
Gumby The Green

Awarie teraz: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setSingleLine(boolean)' on a null object reference
programista Androida

@androiddeveloper Czy masz tę zależność: implementation 'com.google.android.material:material:1.1.0'i czy Twój Snackbar jest tym com.google.android.material.snackbar.Snackbarjedynym? Jeśli nie, spróbuj android.support.design.R.id.snackbar_textjako identyfikator zasobu.
Gumby The Green

Oto, co otrzymujesz, kiedy tworzysz nowy projekt (a ja to zrobiłem), a on się zawiesił.
programista Androida

1

Sposób na zrobienie tego, który nie ulegnie awarii w przypadku zmiany rzeczy w nowszych wersjach biblioteki:

Snackbar.make(...).setAction(...) {
    ...
}.apply {
    (view.findViewById<View?>(R.id.snackbar_text) as? TextView?)?.setSingleLine(false)
}.show()

I sposób na zrobienie tego bez używania identyfikatorów, ustawiając wszystkie TextViews w Snackbar tak, aby miały nieograniczoną liczbę wielu linii:

@UiThread
fun setAllTextViewsToHaveInfiniteLinesCount(view: View) {
    when (view) {
        is TextView -> view.setSingleLine(false)
        is ViewGroup -> for (child in view.children)
            setAllTextViewsToHaveInfiniteLinesCount(child)
    }
}

Snackbar.make(...).setAction(...) {
    ...
}.apply {
    setAllTextViewsToHaveInfiniteLinesCount(view)
}.show()

Ta sama funkcja w Javie:

@UiThread
public static void setAllTextViewsToHaveInfiniteLines(@Nullable final View view) {
    if (view == null)
        return;
    if (view instanceof TextView)
        ((TextView) view).setSingleLine(false);
    else if (view instanceof ViewGroup)
        for (Iterator<View> iterator = ViewGroupKt.getChildren((ViewGroup) view).iterator(); iterator.hasNext(); )
            setAllTextViewsToHaveInfiniteLines(iterator.next());
}

0

Tylko krótki komentarz, jeśli używasz com.google.android.material:materialprefiksu lub pakietu dla R.idpowinno byćcom.google.android.material

val snackbarView = snackbar.view
val textView = snackbarView.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
textView.maxLines = 3

0

więc używam najnowszej biblioteki projektowania materiałów z Google com.google.android.material:material:1.1.0 i użyłem prostego poniższego fragmentu kodu, aby rozwiązać kwestię zezwalania na więcej linii w pasku przekąskowym. mam nadzieję, że pomoże to również nowym programistom.

TextView messageView = snackbar.getView().findViewById(R.id.snackbar_text);
messageView.setMaxLines(5);

0

Aby uniknąć niestabilności innych odpowiedzi updateMaxLine, to rozwiązanie jest mniej prawdopodobne, jeśli Google zdecyduje się zmienić identyfikator widoku tekstu)

val snackBar = Snackbar.make(view, message, duration)
 snackBar.view.allViews.updateMaxLine(5)
 snackBar.show()

uwaga, ta opcja zaktualizuje linię max dla wszystkich widoków tekstu w widoku Snakbar (co, nie sądzę, że ma to znaczenie)

dodaj to jako rozszerzenie

private fun <T> Sequence<T>.updateMaxLine(maxLine : Int) {
    for (view in this) {
        if (view is TextView) {
            view.maxLines = maxLine
        }
    }
}

wprowadź opis obrazu tutaj

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.