Вкладка «Дизайн поддержки Android» Макет: центр тяжести и режим прокрутки

99

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

Я имею дело с Gravity и Mode, устанавливая свой tabLayout как:

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

Поэтому я ожидаю, что если места нет, tabLayout можно прокручивать, но если есть место, он центрируется.

Из гидов:

public static final int GRAVITY_CENTER Gravity, используемый для размещения вкладок в центре TabLayout.

public static final int GRAVITY_FILL Gravity используется для максимального заполнения TabLayout. Эта опция действует только при использовании с MODE_FIXED.

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

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

Итак, GRAVITY_FILL совместим только с MODE_FIXED, но at 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 06
Единственный способ, который я нашел, - это получить размер ваших вкладок (вручную), а затем динамически настроить параметры макета в зависимости от того, подходит он или нет.
Хавьер Дельгадо
@ Fighter42, не могли бы вы опубликовать образец кода для решения, которое вы используете?
Sammys 08
1
Я знаю, что мой ответ не годится для логики, но мне он помогает. Поставьте пробелы до и после текста. Тогда его можно будет прокручивать. в обоих режимах.
AmmY

Ответы:

129

Вкладка только эффекты гравитации 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
Я использую для своего приложения Material Design. Я следовал примеру androidhive.info/2015/09/… Прокрутка вкладок не работает на устройствах Kitkat и Jelly bean. В lollipop я могу прокручивать вкладку, но в других устройствах, кроме леденцов, прокрутка вкладок не работает, но смахивание работает нормально. Могу я узнать, в чем будет проблема.
user2681579 03
У меня это не сработало с API 15 / support.design:25.1.0. Табуляторы были выровнены по левому краю, если они были меньше ширины окна. Установка app:tabMaxWidth="0dp"и android:layout_centerHorizontal="true"работа по центру вкладок.
Baker
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 для работающей части

Поток
источник
Это решение имеет те же проблемы, которые я упоминал в решении Tiago.
Сотти,
Это решение работает на вкладке без значка, что, если у меня есть значок с текстом? : /
Emre Tekin
@EmreTekin да, он работает со значком, на моих вкладках были значки с текстом
Flux
tabLayout.getWidth()всегда возвращает ноль для меня
ishandutta2007
9

упрощает работу, просто добавьте app: tabMode = "scrollable" и 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, где вкладки находятся с левой стороны. Создатель хочет, чтобы вкладки доходили от края до края, когда это возможно, и прокручивайте в противном случае.
Сотти
Та же проблема. Вы можете это исправить?
Хоанг Дык Туан
8

Поскольку я не нашел, почему происходит такое поведение, я использовал следующий код:

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

По сути, мне нужно вручную рассчитать ширину моего tabLayout, а затем я устанавливаю режим вкладок в зависимости от того, подходит ли tabLayout к устройству или нет.

Причина, по которой я получаю размер макета вручную, заключается в том, что не все вкладки имеют одинаковую ширину в режиме прокрутки, и это может спровоцировать использование в некоторых именах двух строк, как это случилось со мной в примере.

Хавьер Дельгадо
источник
С помощью этого решения текст потенциально может быть уменьшен до двух строк. См. Другие ответы и решения в этой теме. Вы уменьшаете риск появления этого, но все равно будете часто появляться, когда TabLayout немного меньше screenWidth.
Сотти
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 должен реализовать это в шаблоне по умолчанию для активности с вкладками.
Someone Somewhere
Этот ответ имеет те же проблемы, что я упоминал в решении Tiago.
Сотти
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"/>

user3205930
источник
2

Очень простой пример, и он всегда работает.

/**
 * 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
источник
Спасибо за идею. С небольшими изменениями для моего случая, работает как шарм
Эрен Тюфекчи
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"
        />
RicNjesh
источник
Создатель не об этом просит. Проблема здесь в том, что вкладки никогда не будут прокручиваться, и вы избавитесь от места на экране. Вкладки начнут сжимать текст, и в конечном итоге они станут двумя строками.
Сотти
1

Я решил это, используя следующие

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

    }
Зохайб Халик
источник
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
        }
    }
}
user4097210
источник