android android-5.0-lollipop material-design gravity

Diseño de soporte de Android TabLayout: Centro de gravedad y modo desplazable



android-5.0-lollipop material-design (13)

Estoy tratando de usar el nuevo diseño 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, el tabLayout es desplazable, pero si hay espacio, está centrado.

De los guías:

public static final int GRAVITY_CENTER Gravity solía diseñar las pestañas en el centro del TabLayout.

public static final int GRAVITY_FILL Gravity solía 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 simultáneamente y se utilizan mejor con contenido que se beneficia de pivotes 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 un momento dado y pueden contener etiquetas de pestañas más largas y una mayor cantidad de pestañas. Se utilizan mejor para examinar contextos 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, al no especificar nada para GRAVITY_CENTER, espero que sea compatible con MODE_SCROLLABLE, pero esto es lo que obtengo usando GRAVITY_CENTER y MODE_SCROLLABLE

Por lo tanto, usa SCROLLABLE en ambas orientaciones, pero no usa GRAVITY_CENTER.

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

¿Por qué GRAVITY_CENTER no funciona para SCROLLABLE si tabLayout se ajusta a la pantalla? ¿Hay alguna forma de establecer 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" />


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 tabulación dependiendo de si el tabLayout cabe en el dispositivo o no.

La razón por la que obtengo el tamaño del diseño manualmente 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.


Creé una clase AdaptiveTabLayout para lograr esto. Esta fue la única forma en 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_SCROLLABLE pero no hay suficiente espacio para MODE_FIXED . Si no maneja este caso, sucederá en algunos dispositivos, verá diferentes tamaños de texto u horneará dos líneas de texto en algunas pestañas, lo que se ve mal.
  • Obtiene medidas reales y no hace suposiciones (como la pantalla tiene 360dp de ancho o lo que sea ...). Esto funciona con tamaños de pantalla reales y tamaños de pestañas 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 los diferentes pases en la fase onLayout para evitar trabajo extra.
  • El ancho del diseño debe ser wrap_content en el 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; } }

Use ejemplo:

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

Gist

Problemas / Pedir 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 resolverlo. ¿Alguna sugerencia?

  • Para realizar las medidas secundarias TabLayout, estoy haciendo algunos cambios y suposiciones (como el elemento secundario es un LinearLayout que contiene otras vistas ...) Esto podría causar problemas con las actualizaciones posteriores de la Biblioteca de soporte de diseño. ¿Un mejor enfoque / sugerencias?

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


Esta es la solución que solía cambiar automáticamente entre SCROLLABLE y 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.


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

DisplayMetrics se puede recuperar con 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"/>


La gravedad de tabulación solo afecta a MODE_FIXED .

Una posible solución es establecer su layout_width en wrap_content y layout_gravity en 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, el TabLayout también será más pequeño y estará centrado debido a la gravedad. Si las pestañas son más grandes que el ancho de la pantalla, TabLayout coincidirá con el ancho de la pantalla y se activará el desplazamiento.


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


Mira android-tablayouthelper

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


Resolví esto usando el siguiente

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


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


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

Crear

@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 en la solución de @Mario Velasco en la parte ejecutable


simplifica las cosas simplemente agregue la aplicación: tabMode = "scrollable" y android: layout_gravity = "bottom"

solo asi

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


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