Pestaña de diseño de soporte de Android Diseño: Centro de gravedad y modo desplazable

98

Estoy intentando utilizar el nuevo Design TabLayout en mi proyecto. Quiero que el diseño se adapte a cada tamaño y orientación de pantalla, pero se puede ver correctamente en una orientación.

Estoy tratando con Gravity and Mode configurando mi tabLayout como:

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

Así que espero que si no hay espacio, tabLayout sea desplazable, pero si hay espacio, estará centrado.

De los guías:

public static final int GRAVITY_CENTER Gravedad usada para diseñar las pestañas en el centro de TabLayout.

public static final int GRAVITY_FILL Gravedad usada para llenar el TabLayout tanto como sea posible. Esta opción solo tiene efecto cuando se usa con MODE_FIXED.

public static final int MODE_FIXED Las pestañas fijas muestran todas las pestañas al mismo tiempo y se utilizan mejor con contenido que se beneficia de cambios rápidos entre pestañas. El número máximo de pestañas está limitado por el ancho de la vista. Las pestañas fijas tienen el mismo ancho, según la etiqueta de pestaña más ancha.

public static final int MODE_SCROLLABLE Las pestañas desplazables muestran un subconjunto de pestañas en cualquier momento dado y pueden contener etiquetas de pestañas más largas y un mayor número de pestañas. Se utilizan mejor para contextos de navegación en interfaces táctiles cuando los usuarios no necesitan comparar directamente las etiquetas de las pestañas.

Entonces, GRAVITY_FILL es compatible solo con MODE_FIXED pero, no especifica nada para GRAVITY_CENTER, espero que sea compatible con MODE_SCROLLABLE, pero esto es lo que obtengo usando GRAVITY_CENTER y MODE_SCROLLABLE

ingrese la descripción de la imagen aquí

Entonces está usando SCROLLABLE en ambas orientaciones, pero no está usando GRAVITY_CENTER.

Esto es lo que esperaría del paisaje; pero para tener esto, necesito configurar MODE_FIXED, entonces lo que obtengo en retrato es:

ingrese la descripción de la imagen aquí

¿Por qué GRAVITY_CENTER no funciona para SCROLLABLE si tabLayout se ajusta a la pantalla? ¿Hay alguna forma de configurar la gravedad y el modo dinámicamente (y ver lo que estoy esperando)?

¡Muchas gracias!

EDITADO: Este es el diseño de mi 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
fuente
@ Fighter42, ¿encontraste alguna solución para esto? Estoy enfrentando el mismo problema.
Spynet
La única forma que encontré fue obtener el tamaño de sus pestañas (manualmente) y luego configurar los parámetros de diseño dinámicamente dependiendo de si encaja o no.
Javier Delgado
@ Fighter42, ¿puedes publicar algún código de muestra para la solución que estás usando?
Sammys
1
Sé que mi respuesta no es buena para lógica pero me ayuda. Pon algunos espacios antes y después del texto. Entonces será desplazable. en ambos modos.
AmmY

Respuestas:

127

Efectos de solo gravedad de tabulación MODE_FIXED.

Una posible solución es configurar su layout_widthto wrap_contenty layout_gravityto 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" />

Si las pestañas son más pequeñas que el ancho de la pantalla, la TabLayoutpropia también será más pequeña y estará centrada debido a la gravedad. Si las pestañas son más grandes que el ancho de la pantalla, TabLayoutcoincidirán con el ancho de la pantalla y se activará el desplazamiento.

tachyonflux
fuente
1
Estoy usando Material design para mi aplicación. Seguí la muestra de androidhive.info/2015/09/… La pestaña desplazable no funciona en dispositivos Kitkat y Jelly Bean. En Lollipop, puedo desplazar la pestaña, pero en otros dispositivos que no sean Lollipop, la pestaña desplazable no funciona, pero deslizar funciona bien. ¿Puedo saber cuál sería el problema?
user2681579
Esto no funcionó para mí en API 15 / support.design:25.1.0. Las pestañas se justificaron a la izquierda cuando eran más estrechas que el ancho de la ventana. Ajuste app:tabMaxWidth="0dp"y android:layout_centerHorizontal="true"trabajado para centrar las pestañas.
Baker
1
Funcionó para mí. Las pestañas siempre están centradas, pero cuando no son realmente desplazables, no llenan todo el ancho.
José Augustinho
14

así es como lo hice

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

Hice pequeños cambios de la solución de @Mario Velasco en la parte ejecutable

Flujo
fuente
Esta solución tiene los mismos problemas que mencioné en la solución de Tiago.
Sotti
Esta solución funciona en una pestaña sin icono, ¿qué pasa si tengo un icono con texto? : /
Emre Tekin
@EmreTekin sí, funciona con un ícono, mis pestañas tenían íconos con texto
Flux
tabLayout.getWidth()siempre devuelve cero para mí
ishandutta2007
9

mantiene las cosas simples solo agregue la aplicación: tabMode = "scrollable" y android: layout_gravity = "bottom"

así como esto

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

ingrese la descripción de la imagen aquí

Rajesh Kumar
fuente
7
Esto no es lo que pide el creador. El problema con este enfoque es que en escenarios donde tienes 2 pestañas o 3 pestañas de tres en teléfonos donde el texto es pequeño, tendrás un diseño como Google Play Store donde las pestañas están en el lado izquierdo. El creador quiere que las pestañas lleguen de un borde a otro cuando sea posible y, en caso contrario, se desplacen.
Sotti
El mismo problema. ¿Puedes arreglarlo?
Hoang Duc Tuan
8

Como no encontré por qué ocurre este comportamiento, he usado el siguiente código:

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

Básicamente, tengo que calcular manualmente el ancho de mi tabLayout y luego configuro el Modo de pestaña dependiendo de si el tabLayout encaja en el dispositivo o no.

La razón por la que obtengo el tamaño del diseño de forma manual es porque no todas las pestañas tienen el mismo ancho en el modo Desplazable, y esto podría provocar que algunos nombres usen 2 líneas como me sucedió en el ejemplo.

Javier Delgado
fuente
Con esta solución, el texto podría reducirse potencialmente y usar 2 líneas también. Vea otras respuestas y soluciones en este hilo al respecto. Está reduciendo el riesgo de que esto aparezca, pero aparecerá con frecuencia de todos modos cuando TabLayout sea un poco más pequeño que screenWidth.
Sotti
7

Mira android-tablayouthelper

Cambie automáticamente TabLayout.MODE_FIXED y TabLayout.MODE_SCROLLABLE depende del ancho total de la pestaña.

Derek Beattie
fuente
Pero cuando los títulos de las pestañas son dinámicos. El texto va en la siguiente línea. Y si usa un diseño personalizado para la pestaña. Texto elipsize al final como
AndroidGeek
4

Esta es la solución que usé para cambiar automáticamente entre SCROLLABLEy FIXED+ FILL. Es el código completo para la solución @ Fighter42:

(El siguiente código muestra dónde colocar la modificación si ha utilizado la plantilla de actividad con pestañas de 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);
            }
        }
    });
}

Diseño:

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

Si no necesita llenar el ancho, es mejor usar la solución @karaokyo.

Mario Velasco
fuente
esto funciona bien; cambiar entre paisaje y retrato hace exactamente lo que podría esperar. Google debería implementar esto en la plantilla predeterminada para la actividad con pestañas.
Someone Somewhere
Esta respuesta tiene los mismos problemas que mencioné en la solución de Tiago.
Sotti
4

Creé una clase AdaptiveTabLayout para lograr esto. Esta fue la única forma que encontré para resolver el problema, responder la pregunta y evitar / solucionar problemas que otras respuestas aquí no hacen.

Notas:

  • Maneja diseños de teléfono / tableta.
  • Maneja casos donde hay suficiente espacio para MODE_SCROLLABLEpero no hay suficiente espacio para MODE_FIXED. Si no maneja este caso, sucederá en algunos dispositivos, verá diferentes tamaños de texto o dos líneas de texto en algunas pestañas, lo que se ve mal.
  • Obtiene medidas reales y no hace ninguna suposición (como que la pantalla tiene 360 ​​dp de ancho o lo que sea ...). Esto funciona con tamaños de pantalla reales y tamaños de pestaña reales. Esto significa que funciona bien con las traducciones porque no asume ningún tamaño de pestaña, las pestañas se miden.
  • Se ocupa de las distintas pasadas de la fase onLayout para evitar trabajo extra.
  • El ancho del diseño debe estar wrap_contenten xml. No establezca ningún modo o gravedad en el 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;
    }

}

Y esta es la clase 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;
    }

}

Ejemplo de uso:

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

Esencia

Problemas / Solicite ayuda:

  • Verás esto:

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

No estoy seguro de cómo solucionarlo. ¿Alguna sugerencia?

  • Para hacer las medidas secundarias de TabLayout, estoy haciendo algunas fundiciones y suposiciones (como si el hijo fuera un LinearLayout que contiene otras vistas ...). Esto podría causar problemas en más actualizaciones de la Biblioteca de soporte de diseño. ¿Un mejor enfoque / sugerencias?
Sotti
fuente
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
fuente
1

Este es el único código que funcionó para mí:

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

Las DisplayMetrics se pueden recuperar usando esto:

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

Y su TabLayout XML debería verse así (no olvide establecer tabMaxWidth en 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
fuente
1
Ya casi está ahí, pero hay algunos problemas. El problema más obvio es que en algunos dispositivos (y la mayoría de los lentos) realmente verá cómo se modifica y cambia el diseño.
Sotti
1
Hay otro problema, este enfoque mide el ancho total de TabLayout para decidir si se puede desplazar o no. Eso no es muy exacto. Hay situaciones en las que TabLayout no alcanza ambos bordes en el modo desplazable, pero no hay espacio suficiente para el modo fijo sin reducir el texto o abusar del número de líneas de la pestaña (generalmente 2). Esto es una consecuencia directa de que las pestañas en modo fijo tienen un ancho fijo (screenWidth / numberOfTabs) mientras que en el modo desplazable están ajustando el texto + rellenos.
Sotti
1

Todo lo que necesita es agregar lo siguiente a su TabLayout

custom:tabGravity="fill"

Entonces tendrás:

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
fuente
Esto no es lo que pide el creador. El problema aquí es que las pestañas nunca serán desplazables y te quitarás el espacio en la pantalla. Las pestañas comenzarán a encoger su texto y eventualmente tendrán dos líneas.
Sotti
1

Resolví esto usando lo siguiente

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

    }
zohaib khaliq
fuente
1

Ejemplo muy simple y siempre 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
fuente
Gracias por la idea. Con un pequeño cambio para mi caso, funciona como un encanto
Eren Tüfekçi
1

Mi solucion 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
        }
    }
}
usuario4097210
fuente