Android Support Design TabLayout: Gravity Center e Mode Scrollable

98

Estou tentando usar o novo Design TabLayout em meu projeto. Quero que o layout se adapte a todos os tamanhos e orientações de tela, mas ele pode ser visto corretamente em uma orientação.

Estou lidando com Gravidade e Modo definindo meu tabLayout como:

    tabLayout.setTabGravity(TabLayout.GRAVITY_CENTER);
    tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);

Portanto, espero que, se não houver espaço, o tabLayout seja rolável, mas se houver espaço, ele será centralizado.

Dos guias:

public static final int GRAVITY_CENTER Gravidade usada para definir as guias no centro do TabLayout.

public static final int GRAVITY_FILL Gravidade usada para preencher o TabLayout o máximo possível. Esta opção só tem efeito quando usada com MODE_FIXED.

public static final int MODE_FIXED Guias fixas exibem todas as guias simultaneamente e são mais bem usadas com conteúdo que se beneficia de pivôs rápidos entre as guias. O número máximo de guias é limitado pela largura da visualização. As guias fixas têm largura igual, com base no rótulo da guia mais larga.

public static final int MODE_SCROLLABLE As guias roláveis ​​exibem um subconjunto de guias a qualquer momento e podem conter rótulos de guia mais longos e um número maior de guias. Eles são mais usados ​​para contextos de navegação em interfaces de toque quando os usuários não precisam comparar diretamente os rótulos das guias.

GRAVITY_FILL é compatível apenas com MODE_FIXED, mas não especifica nada para GRAVITY_CENTER, espero que seja compatível com MODE_SCROLLABLE, mas é isso que obtenho usando GRAVITY_CENTER e MODE_SCROLLABLE

insira a descrição da imagem aqui

Então está usando SCROLLABLE nas duas orientações, mas não está usando GRAVITY_CENTER.

Isso é o que eu esperaria da paisagem; mas para ter isso, eu preciso definir MODE_FIXED, então o que obtenho no retrato é:

insira a descrição da imagem aqui

Por que GRAVITY_CENTER não está funcionando para SCROLLABLE se tabLayout se ajusta à tela? Existe alguma maneira de definir a gravidade e o modo dinamicamente (e para ver o que estou esperando)?

Muito obrigado!

EDITADO: Este é o Layout do meu TabLayout:

<android.support.design.widget.TabLayout
    android:id="@+id/sliding_tabs"
    android:layout_width="match_parent"
    android:background="@color/orange_pager"
    android:layout_height="wrap_content" />
Javier Delgado
fonte
@ Fighter42 você encontrou alguma solução para isso? Estou enfrentando o mesmo problema.
Spynet
A única maneira que encontrei foi pegando o tamanho de suas abas (manualmente) e então configurar os parâmetros de layout dinamicamente dependendo se ele se encaixa ou não.
Javier Delgado
@ Fighter42, você pode postar algum código de amostra para a solução que está usando?
Sammys
1
Eu sei que minha resposta não é boa para lógica, mas me ajuda. Coloque alguns espaços antes e depois do texto. Em seguida, será rolável. em ambos os modos.
AmmY

Respostas:

127

Efeitos da gravidade da guia apenas MODE_FIXED.

Uma solução possível é definir seu layout_widthpara wrap_contente layout_gravitypara center_horizontal:

<android.support.design.widget.TabLayout
    android:id="@+id/sliding_tabs"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    app:tabMode="scrollable" />

Se as guias forem menores do que a largura da tela, o TabLayoutpróprio também será menor e centralizado devido à gravidade. Se as guias forem maiores que a largura da tela, o TabLayoutirá corresponder à largura da tela e a rolagem será ativada.

tachyonflux
fonte
1
Estou usando o material design no meu aplicativo. Segui o exemplo de androidhive.info/2015/09/… Tab scrollable não funciona em dispositivos Kitkat e Jelly bean. No lollipop, consigo rolar a tecla Tab, mas em dispositivos que não sejam lollipop A rolagem da guia não funciona, mas o deslizar está funcionando bem. Posso saber qual seria o problema.
user2681579,
Isso não funcionou para mim na API 15 / support.design:25.1.0. As guias foram justificadas à esquerda quando mais estreitas do que a largura da janela. Configurando app:tabMaxWidth="0dp"e android:layout_centerHorizontal="true"trabalhado para centralizar as guias.
Baker,
1
Funcionou para mim As guias estão sempre centralizadas, mas quando não são realmente roláveis, não preenchem toda a largura.
José Augustinho
14

foi assim que eu fiz

TabLayout.xml

<android.support.design.widget.TabLayout
            android:id="@+id/tab_layout"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:background="@android:color/transparent"
            app:tabGravity="fill"
            app:tabMode="scrollable"
            app:tabTextAppearance="@style/TextAppearance.Design.Tab"
            app:tabSelectedTextColor="@color/myPrimaryColor"
            app:tabIndicatorColor="@color/myPrimaryColor"
            android:overScrollMode="never"
            />

Oncreate

@Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mToolbar = (Toolbar) findViewById(R.id.toolbar_actionbar);
        mTabLayout = (TabLayout)findViewById(R.id.tab_layout);
        mTabLayout.setOnTabSelectedListener(this);
        setSupportActionBar(mToolbar);

        mTabLayout.addTab(mTabLayout.newTab().setText("Dashboard"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Signature"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Booking/Sampling"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Calendar"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Customer Detail"));

        mTabLayout.post(mTabLayout_config);
    }

    Runnable mTabLayout_config = new Runnable()
    {
        @Override
        public void run()
        {

           if(mTabLayout.getWidth() < MainActivity.this.getResources().getDisplayMetrics().widthPixels)
            {
                mTabLayout.setTabMode(TabLayout.MODE_FIXED);
                ViewGroup.LayoutParams mParams = mTabLayout.getLayoutParams();
                mParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
                mTabLayout.setLayoutParams(mParams);

            }
            else
            {
                mTabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
            }
        }
    };

Fiz pequenas alterações na solução de @Mario Velasco na parte executável

Fluxo
fonte
Esta solução tem os mesmos problemas que mencionei na solução do Tiago.
Sotti
Esta Solução funciona em aba sem ícone, e se eu tiver ícone com texto ?? : /
Emre Tekin
@EmreTekin sim, funciona com um ícone, minhas guias tinham ícones com texto
Flux
tabLayout.getWidth()sempre retorna zero para mim
ishandutta2007
9

mantém as coisas simples, basta adicionar app: tabMode = "scrollable" e android: layout_gravity = "bottom"

bem assim

<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
app:tabMode="scrollable"
app:tabIndicatorColor="@color/colorAccent" />

insira a descrição da imagem aqui

Rajesh Kumar
fonte
7
Não é isso que o criador está pedindo. O problema com essa abordagem é que em cenários onde você tem 2 guias ou 3 três guias em telefones onde o texto é pequeno, você terá um layout como o Google Play Store, onde as guias estão do lado esquerdo. O criador deseja que as guias alcancem de ponta a ponta quando isso for possível e rolar de outra forma.
Sotti
Mesmo problema. Você pode consertar isso?
Hoang Duc Tuan
8

Como não descobri por que esse comportamento acontece, usei o seguinte código:

float myTabLayoutSize = 360;
if (DeviceInfo.getWidthDP(this) >= myTabLayoutSize ){
    tabLayout.setTabMode(TabLayout.MODE_FIXED);
} else {
    tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
}

Basicamente, eu tenho que calcular manualmente a largura do meu tabLayout e então defino o Modo Tab dependendo se o tabLayout cabe no dispositivo ou não.

O motivo pelo qual obtenho o tamanho do layout manualmente é porque nem todas as abas têm a mesma largura no modo Scrollable, e isso pode fazer com que alguns nomes usem 2 linhas, como aconteceu comigo no exemplo.

Javier Delgado
fonte
Com esta solução, o texto pode ser potencialmente reduzido e usar 2 linhas também. Veja outras respostas e soluções neste tópico sobre isso. Você está reduzindo o risco de que isso apareça, mas aparecerá com frequência de qualquer maneira quando TabLayout for um pouco menor que screenWidth.
Sotti
7

Olhe para android-tablayouthelper

Alternar automaticamente TabLayout.MODE_FIXED e TabLayout.MODE_SCROLLABLE depende da largura total da guia.

Derek Beattie
fonte
Mas quando os títulos das guias são dinâmicos. O texto vai na próxima linha. E se usar layout personalizado para guia. O texto tem reticências no final, como
AndroidGeek
4

Esta é a solução que usei para mudar automaticamente entre SCROLLABLEe FIXED+ FILL. É o código completo para a solução @ Fighter42:

(O código abaixo mostra onde colocar a modificação se você usou o modelo de atividade com guias do Google)

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    // Create the adapter that will return a fragment for each of the three
    // primary sections of the activity.
    mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());

    // Set up the ViewPager with the sections adapter.
    mViewPager = (ViewPager) findViewById(R.id.container);
    mViewPager.setAdapter(mSectionsPagerAdapter);

    // Set up the tabs
    final TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
    tabLayout.setupWithViewPager(mViewPager);

    // Mario Velasco's code
    tabLayout.post(new Runnable()
    {
        @Override
        public void run()
        {
            int tabLayoutWidth = tabLayout.getWidth();

            DisplayMetrics metrics = new DisplayMetrics();
            ActivityMain.this.getWindowManager().getDefaultDisplay().getMetrics(metrics);
            int deviceWidth = metrics.widthPixels;

            if (tabLayoutWidth < deviceWidth)
            {
                tabLayout.setTabMode(TabLayout.MODE_FIXED);
                tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
            } else
            {
                tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
            }
        }
    });
}

Layout:

    <android.support.design.widget.TabLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

Se você não precisa preencher a largura, é melhor usar a solução @karaokyo.

Mario velasco
fonte
isso funciona bem; alternar entre paisagem e retrato faz exatamente o que você espera. O Google deve implementar isso no modelo padrão para atividades com guias.
Alguém em algum lugar
Essa resposta tem os mesmos problemas que mencionei na solução do Tiago.
Sotti
4

Criei uma classe AdaptiveTabLayout para fazer isso. Essa foi a única maneira que encontrei de realmente resolver o problema, responder à pergunta e evitar / contornar problemas que outras respostas aqui não conseguem.

Notas:

  • Lida com layouts de telefone / tablet.
  • Lida com casos em que há espaço suficiente, MODE_SCROLLABLEmas não há espaço suficiente para MODE_FIXED. Se você não cuidar desse caso, isso vai acontecer em alguns dispositivos, você verá tamanhos de texto diferentes ou duas linhas de texto em algumas guias, o que parece ruim.
  • Ele obtém medidas reais e não faz suposições (como a tela de 360 ​​dp de largura ou qualquer outra coisa ...). Isso funciona com tamanhos de tela reais e tamanhos de guia reais. Isso significa que funciona bem com traduções porque não assume nenhum tamanho de guia, as guias medem.
  • Lida com os diferentes passes na fase onLayout para evitar trabalho extra.
  • A largura do layout precisa estar wrap_contentno xml. Não defina nenhum modo ou gravidade no xml.

AdaptiveTabLayout.java

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import android.util.AttributeSet;
import android.widget.LinearLayout;

public class AdaptiveTabLayout extends TabLayout
{
    private boolean mGravityAndModeSeUpNeeded = true;

    public AdaptiveTabLayout(@NonNull final Context context)
    {
        this(context, null);
    }

    public AdaptiveTabLayout(@NonNull final Context context, @Nullable final AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public AdaptiveTabLayout
    (
            @NonNull final Context context,
            @Nullable final AttributeSet attrs,
            final int defStyleAttr
    )
    {
        super(context, attrs, defStyleAttr);
        setTabMode(MODE_SCROLLABLE);
    }

    @Override
    protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b)
    {
        super.onLayout(changed, l, t, r, b);

        if (mGravityAndModeSeUpNeeded)
        {
            setModeAndGravity();
        }
    }

    private void setModeAndGravity()
    {
        final int tabCount = getTabCount();
        final int screenWidth = UtilsDevice.getScreenWidth();
        final int minWidthNeedForMixedMode = getMinSpaceNeededForFixedMode(tabCount);

        if (minWidthNeedForMixedMode == 0)
        {
            return;
        }
        else if (minWidthNeedForMixedMode < screenWidth)
        {
            setTabMode(MODE_FIXED);
            setTabGravity(UtilsDevice.isBigTablet() ? GRAVITY_CENTER : GRAVITY_FILL) ;
        }
        else
        {
            setTabMode(TabLayout.MODE_SCROLLABLE);
        }

        setLayoutParams(new LinearLayout.LayoutParams
                (LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));

        mGravityAndModeSeUpNeeded = false;
    }

    private int getMinSpaceNeededForFixedMode(final int tabCount)
    {
        final LinearLayout linearLayout = (LinearLayout) getChildAt(0);
        int widestTab = 0;
        int currentWidth;

        for (int i = 0; i < tabCount; i++)
        {
            currentWidth = linearLayout.getChildAt(i).getWidth();

            if (currentWidth == 0) return 0;

            if (currentWidth > widestTab)
            {
                widestTab = currentWidth;
            }
        }

        return widestTab * tabCount;
    }

}

E esta é a classe DeviceUtils:

import android.content.res.Resources;

public class UtilsDevice extends Utils
{
    private static final int sWIDTH_FOR_BIG_TABLET_IN_DP = 720;

    private UtilsDevice() {}

    public static int pixelToDp(final int pixels)
    {
        return (int) (pixels / Resources.getSystem().getDisplayMetrics().density);
    }

    public static int getScreenWidth()
    {
        return Resources
                .getSystem()
                .getDisplayMetrics()
                .widthPixels;
    }

    public static boolean isBigTablet()
    {
        return pixelToDp(getScreenWidth()) >= sWIDTH_FOR_BIG_TABLET_IN_DP;
    }

}

Use o exemplo:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.com.stackoverflow.example.AdaptiveTabLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="?colorPrimary"
        app:tabIndicatorColor="@color/white"
        app:tabSelectedTextColor="@color/text_white_primary"
        app:tabTextColor="@color/text_white_secondary"
        tools:layout_width="match_parent"/>

</FrameLayout>

Essência

Problemas / Peça ajuda:

  • Você verá isto:

Logcat:

W/View: requestLayout() improperly called by android.support.design.widget.TabLayout$SlidingTabStrip{3e1ebcd6 V.ED.... ......ID 0,0-466,96} during layout: running second layout pass
W/View: requestLayout() improperly called by android.support.design.widget.TabLayout$TabView{3423cb57 VFE...C. ..S...ID 0,0-144,96} during layout: running second layout pass
W/View: requestLayout() improperly called by android.support.design.widget.TabLayout$TabView{377c4644 VFE...C. ......ID 144,0-322,96} during layout: running second layout pass
W/View: requestLayout() improperly called by android.support.design.widget.TabLayout$TabView{19ead32d VFE...C. ......ID 322,0-466,96} during layout: running second layout pass

Não tenho certeza de como resolver isso. Alguma sugestão?

  • Para fazer as medidas filho TabLayout, estou fazendo alguns castings e suposições (como o filho é um LinearLayout contendo outras visualizações ...) Isso pode causar problemas em novas atualizações da Design Support Library. Uma abordagem / sugestões melhores?
Sotti
fonte
4
 <android.support.design.widget.TabLayout
                    android:id="@+id/tabList"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    app:tabMode="scrollable"/>
user3205930
fonte
1

Este é o único código que funcionou para mim:

public static void adjustTabLayoutBounds(final TabLayout tabLayout,
                                         final DisplayMetrics displayMetrics){

    final ViewTreeObserver vto = tabLayout.getViewTreeObserver();
    vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {

            tabLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            int totalTabPaddingPlusWidth = 0;
            for(int i=0; i < tabLayout.getTabCount(); i++){

                final LinearLayout tabView = ((LinearLayout)((LinearLayout) tabLayout.getChildAt(0)).getChildAt(i));
                totalTabPaddingPlusWidth += (tabView.getMeasuredWidth() + tabView.getPaddingLeft() + tabView.getPaddingRight());
            }

            if (totalTabPaddingPlusWidth <= displayMetrics.widthPixels){

                tabLayout.setTabMode(TabLayout.MODE_FIXED);
                tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);

            }else{
                tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
            }

            tabLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
        }
    });
}

O DisplayMetrics pode ser recuperado usando este:

public DisplayMetrics getDisplayMetrics() {

    final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    final Display display = wm.getDefaultDisplay();
    final DisplayMetrics displayMetrics = new DisplayMetrics();

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
        display.getMetrics(displayMetrics);

    }else{
        display.getRealMetrics(displayMetrics);
    }

    return displayMetrics;
}

E seu TabLayout XML deve ser semelhante a este (não se esqueça de definir tabMaxWidth como 0):

<android.support.design.widget.TabLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/tab_layout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:tabMaxWidth="0dp"/>
Tiago
fonte
1
Isso está quase lá, mas há alguns problemas. O problema mais óbvio é que em alguns dispositivos (e na maioria dos lentos) você realmente verá como o layout é modificado e alterado.
Sotti
1
Há outro problema, essa abordagem mede a largura total do TabLayout para decidir se é rolável ou não. Isso não é muito preciso. Existem situações em que o TabLayout não atinge ambas as bordas no modo de rolagem, mas não há espaço suficiente para o modo fixo sem diminuir o texto de abusar do número de linhas na guia (geralmente 2). Esta é uma consequência direta das guias no modo fixo terem uma largura fixa (screenWidth / numberOfTabs) enquanto no modo rolável elas envolvem o texto + preenchimentos.
Sotti
1

Tudo que você precisa é adicionar o seguinte ao seu TabLayout

custom:tabGravity="fill"

Então você terá:

xmlns:custom="http://schemas.android.com/apk/res-auto"
<android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        custom:tabGravity="fill"
        />
RicNjesh
fonte
Não é isso que o criador está pedindo. O problema aqui é que as guias nunca serão roláveis ​​e você eliminará espaço na tela. As guias começarão a diminuir o texto e, eventualmente, terão duas linhas alinhadas.
Sotti
1

Eu resolvi isso usando o seguinte

if(tabLayout_chemistCategory.getTabCount()<4)
    {
        tabLayout_chemistCategory.setTabGravity(TabLayout.GRAVITY_FILL);
    }else
    {
        tabLayout_chemistCategory.setTabMode(TabLayout.MODE_SCROLLABLE);

    }
zohaib khaliq
fonte
1

Exemplo muito simples e sempre funciona.

/**
 * Setup stretch and scrollable TabLayout.
 * The TabLayout initial parameters in layout must be:
 * android:layout_width="wrap_content"
 * app:tabMaxWidth="0dp"
 * app:tabGravity="fill"
 * app:tabMode="fixed"
 *
 * @param context   your Context
 * @param tabLayout your TabLayout
 */
public static void setupStretchTabLayout(Context context, TabLayout tabLayout) {
    tabLayout.post(() -> {

        ViewGroup.LayoutParams params = tabLayout.getLayoutParams();
        if (params.width == ViewGroup.LayoutParams.MATCH_PARENT) { // is already set up for stretch
            return;
        }

        int deviceWidth = context.getResources()
                                 .getDisplayMetrics().widthPixels;

        if (tabLayout.getWidth() < deviceWidth) {
            tabLayout.setTabMode(TabLayout.MODE_FIXED);
            params.width = ViewGroup.LayoutParams.MATCH_PARENT;
        } else {
            tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
            params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
        }
        tabLayout.setLayoutParams(params);

    });

}
LordTAO
fonte
Obrigado pela ideia. Com uma pequena mudança para o meu caso, funciona como um charme
Eren Tüfekçi 30/01
1

Minha solução final

class DynamicModeTabLayout : TabLayout {

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun setupWithViewPager(viewPager: ViewPager?) {
        super.setupWithViewPager(viewPager)

        val view = getChildAt(0) ?: return
        view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
        val size = view.measuredWidth

        if (size > measuredWidth) {
            tabMode = MODE_SCROLLABLE
            tabGravity = GRAVITY_CENTER
        } else {
            tabMode = MODE_FIXED
            tabGravity = GRAVITY_FILL
        }
    }
}
user4097210
fonte