TabLayout для дизайну підтримки Android: Gravity Center і Mode Scrolllable


98

Я намагаюся використати новий проект TabLayout у своєму проекті. Я хочу, щоб макет адаптувався до будь-якого розміру екрана та орієнтації, але його можна правильно бачити в одній орієнтації.

Я маю справу з гравітацією та режимом, встановлюючи вкладку Layout як:

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

Отже, я очікую, що якщо місця немає, вкладку Layout можна прокручувати, але якщо є місце, вона буде відцентрована.

З путівників:

public static final int GRAVITY_CENTER Гравітація використовувалась для розкладки вкладок у центрі TabLayout.

public static final int GRAVITY_FILL Гравітація, яка використовувалася для заповнення TabLayout якомога більше. Цей параметр набуває чинності лише при використанні з MODE_FIXED.

public static final int MODE_FIXED Фіксовані вкладки відображають всі вкладки одночасно і найкраще використовуються із вмістом, який виграє від швидких обертань між вкладками. Максимальна кількість вкладок обмежена шириною подання. Фіксовані вкладки мають однакову ширину на основі найширшої мітки вкладки.

public static final int MODE_SCROLLABLE Прокручувані вкладки відображають підмножину вкладок у будь-який момент і можуть містити довші мітки та більшу кількість вкладок. Їх найкраще використовувати для перегляду контекстів у сенсорних інтерфейсах, коли користувачам не потрібно безпосередньо порівнювати мітки вкладок.

Отже, GRAVITY_FILL сумісний лише з MODE_FIXED, але, в is нічого не вказує для GRAVITY_CENTER, я очікую, що він буде сумісний з MODE_SCROLLABLE, але це те, що я отримую за допомогою GRAVITY_CENTER і MODE_SCROLLABLE

введіть тут опис зображення

Отже, він використовує SCROLLABLE в обох орієнтаціях, але не використовує GRAVITY_CENTER.

Це те, чого я очікував би для ландшафту; але щоб це було, мені потрібно встановити MODE_FIXED, тож я отримую на портреті:

введіть тут опис зображення

Чому GRAVITY_CENTER не працює для SCROLLABLE, якщо tabLayout відповідає екрану? Чи є спосіб встановити гравітацію та режим динамічно (і побачити, чого я очікую)?

Велике спасибі!

ВИДАЛЕНО: Це макет мого 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" />

@ Fighter42 ти знайшов якесь рішення для цього? Я стикаюся з тією ж проблемою.
Spynet

Єдиний спосіб, який я знайшов, - це отримати розмір ваших вкладок (вручну), а потім налаштувати параметри макета динамічно, залежно від того, підходить він чи ні.
Хав'єр Дельгадо

@ Fighter42, ти можеш опублікувати зразок коду рішення, яке ти використовуєш?
Самміс

1
Я знаю, що моя відповідь не підходить для логічного, але мені допомагає. Додайте пробіли до і після тексту. Тоді його можна буде прокручувати. в обох режимах.
AmmY

Відповіді:


127

Вплив гравітації впливає лише MODE_FIXED.

Одне з можливих рішень для установки вашого , layout_widthщоб wrap_contentі layout_gravityв 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" />

Якщо вкладки менше ширини екрану, TabLayoutсам по собі також буде меншим, і він буде центрований через гравітацію. Якщо вкладки більші за ширину екрана, параметр TabLayoutбуде відповідати ширині екрана, і прокрутка активується.


1
Я використовую матеріальний дизайн для свого додатка. Я пішов за зразком з androidhive.info/2015/09/... Прокручувана вкладка не працює в пристроях Kitkat та Jelly bean. У льодянику я можу прокручувати вкладку, але в інших пристроях, ніж льодяник, прокручувана вкладка не працює, але проведіть пальцем. Чи можу я знати, в чому б проблема?
user2681579

Це не спрацювало для мене в API 15 / support.design:25.1.0. Вкладки залишали вирівняними, коли вони були меншими за ширину вікна. Встановлення app:tabMaxWidth="0dp"та робота android:layout_centerHorizontal="true"на центральних вкладках.
Бейкер

1
У мене це спрацювало. Вкладки завжди відцентровані, але коли їх фактично не можна прокрутити, вони не заповнюють всю ширину.
Хосе Августінью

14

ось як я це зробив

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

Створити

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

Я зробив невеликі зміни в рішенні @Mario Velasco щодо запущеної частини


Це рішення має ті самі проблеми, про які я вже згадував щодо рішення Тіаго.
Сотті

Це рішення працює на вкладці без піктограми, а якщо у мене є піктограма з текстом ?? : /
Емре Текін

@EmreTekin так, це працює з піктограмою, на моїх вкладках були піктограми з текстом
Flux

tabLayout.getWidth()завжди повертає для мене нуль
ishandutta2007

9

робить речі простими, просто додайте додаток: tabMode = "прокручуваний" та android: layout_gravity = "bottom"

просто так

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

введіть тут опис зображення


7
Це не те, про що просить творець. Проблема такого підходу полягає в тому, що у сценаріях, коли у вас є 2 вкладки або 3 три вкладки на телефонах, де текст невеликий, ви матимете макет, як-от Google Play Store, де вкладки розташовані зліва. Творець хоче, щоб вкладки переходили від краю до краю, коли це можливо, і прокручувати інакше.
Сотті

Та ж проблема. Ви можете це виправити?
Hoang Duc Tuan

8

Оскільки я не знайшов, чому трапляється така поведінка, я використав такий код:

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

В основному, мені доводиться обчислювати ширину мого tabLayout вручну, а потім я встановлюю режим Tab залежно від того, поміщається tabLayout у пристрої чи ні.

Причина, через яку я отримую розмір макета вручну, полягає в тому, що не всі вкладки мають однакову ширину в режимі прокрутки, і це може спровокувати, що деякі імена використовують 2 рядки, як це сталося зі мною у прикладі.


За допомогою цього рішення текст може потенційно стиснутись, використовуючи також 2 рядки. Дивіться інші відповіді та рішення в цій темі про це. Ви зменшуєте ризик цього, але все одно часто з’являтиметеся, коли вкладка TabLayout трохи менша за ширину екрана.
Сотті

7

Подивіться на android-tablayouthelper

Автоматично перемикати TabLayout.MODE_FIXED і TabLayout.MODE_SCROLLABLE залежить від загальної ширини вкладки.


Але коли заголовки вкладки динамічні. Текст йде в наступному рядку. І якщо використовувати спеціальний макет для вкладки. Текст еліпсисом в кінці, як
AndroidGeek

4

Це рішення я використовував для автоматичного переключення між SCROLLABLEі FIXED+ FILL. Це повний код рішення @ Fighter42:

(У наведеному нижче коді показано, куди слід внести модифікацію, якщо ви використовували шаблон активності 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);
            }
        }
    });
}

Макет:

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

Якщо вам не потрібно заповнювати ширину, краще скористайтеся розчином @karaokyo.


це добре працює; перемикання між пейзажем та портретом робить саме те, що ви могли очікувати. Google повинен застосувати це у шаблоні за замовчуванням для активності з вкладками.
Хтось десь

Ця відповідь має ті самі проблеми, про які я згадав у рішенні Тіаго.
Сотті

4

Для цього я створив клас AdaptiveTabLayout. Це був єдиний спосіб, який я знайшов насправді вирішити проблему, відповісти на запитання та уникнути / вирішити проблеми, яких інші відповіді тут не роблять.

Примітки:

  • Обробляє розкладки телефону / планшета.
  • Обробляє випадки, коли місця достатньо, MODE_SCROLLABLEале місця недостатньо MODE_FIXED. Якщо ви не впораєтеся з цією справою, це трапиться на деяких пристроях, ви побачите різний розмір тексту або складе два рядки тексту на деяких вкладках, що виглядає погано.
  • Він отримує реальні заходи і не робить жодних припущень (наприклад, екран має 360dp в ширину або що завгодно ...). Це працює з реальними розмірами екрану та реальними розмірами вкладок. Це означає, що добре працює з перекладами, оскільки не передбачає будь-якого розміру вкладки, вкладки отримують міру.
  • Угоди з різними пропусками на етапі onLayout, щоб уникнути зайвої роботи.
  • Ширина макета повинна бути wrap_contentна xml. Не встановлюйте будь-який режим чи силу тяжіння на 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;
    }

}

І це клас 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;
    }

}

Використовуйте приклад:

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

Суть

Проблеми / Попросити допомоги:

  • Ви побачите це:

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

Я не знаю, як це вирішити. Будь-які пропозиції?

  • Щоб зробити дочірні заходи TabLayout, я роблю деякі відливки та припущення (як дочірній це LinearLayout, що містить інші подання ....) Це може спричинити проблеми з подальшими оновленнями бібліотеки підтримки дизайну. Кращий підхід / пропозиції?

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

1

Це єдиний код, який працював у мене:

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 можна отримати за допомогою цього:

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

І ваш XML TabLayout повинен виглядати так (не забудьте встановити tabMaxWidth на 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"/>

1
Це майже є, але є деякі проблеми. Найбільш очевидна проблема полягає в тому, що на деяких пристроях (і більшості повільних) ви насправді бачите, як змінюється і змінюється макет.
Сотті

1
Існує ще одна проблема: цей підхід вимірює загальну ширину TabLayout, щоб вирішити, чи буде прокручуватися чи ні. Це не дуже точно. Бувають ситуації, коли TabLayout не досягає обох країв у прокручуваному режимі, але для фіксованого режиму не залишається місця, не скорочуючи текст зловживання кількістю рядків на вкладці (зазвичай 2). Це прямий наслідок того, що вкладки у фіксованому режимі мають фіксовану ширину (screenWidth / numberOfTabs), тоді як у прокручуваному режимі вони обертають текст + відступи.
Сотті

1

Все, що вам потрібно, - це додати наступне у свій TabLayout

custom:tabGravity="fill"

Тоді у вас буде:

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

Це не те, про що просить творець. Проблеми тут полягають у тому, що вкладки ніколи не можна прокручувати, і ви позбудетеся місця на екрані. Вкладки почнуть зменшувати текст, і зрештою вони отримають дві лінії.
Сотті

1

Я вирішив це, використовуючи наступне

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

    }

1

Дуже простий приклад, і він завжди працює.

/**
 * 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);

    });

}

Дякую за ідею. Трохи змін у моїй справі працює як шарм
Ерен Тюфекчі

1

Моє остаточне рішення

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
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.