Поддержка дизайна Android TabLayout: Gravity Center и Scrollable Mode
Я пытаюсь использовать новый дизайн TabLayout в моем проекте. Я хочу, чтобы макет адаптировался к любому размеру и ориентации экрана, но его можно правильно увидеть в одной ориентации.
Я имею дело с гравитацией и режимом установки моего tabLayout как:
tabLayout.setTabGravity(TabLayout.GRAVITY_CENTER);
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
Поэтому я ожидаю, что если места нет, tabLayout можно прокручивать, но если место есть, оно центрируется.
Из руководств:
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, но он ничего не указывает для 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" />
16 ответов
Поскольку я не нашел, почему это происходит, я использовал следующий код:
float myTabLayoutSize = 360;
if (DeviceInfo.getWidthDP(this) >= myTabLayoutSize ){
tabLayout.setTabMode(TabLayout.MODE_FIXED);
} else {
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
}
По сути, я должен вручную рассчитать ширину моего tabLayout, а затем установить режим вкладок в зависимости от того, подходит ли tabLayout на устройстве или нет.
Причина, по которой я получаю размер макета вручную, заключается в том, что не все вкладки имеют одинаковую ширину в режиме прокрутки, и это может привести к тому, что некоторые имена будут использовать 2 строки, как это произошло со мной в примере.
Вкладка только гравитация эффекты 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
будет соответствовать ширине экрана и активируется прокрутка.
Вот как я это сделал
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);
}
}
};
Я внес небольшие изменения в решение @Mario Velasco для работающей части
Чтобы все было просто, просто добавьте приложение: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" />
Посмотрите на Android-TablayoutHelper
Автоматическое переключение TabLayout.MODE_FIXED и TabLayout.MODE_SCROLLABLE зависит от общей ширины вкладки.
Я создал класс 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.stackru.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, содержащий другие представления....). Это может вызвать проблемы в дальнейших обновлениях Библиотеки поддержки проектирования. Лучший подход / предложения?
<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"/>
Это решение, которое я использовал для автоматического переключения между 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.
Я думаю, что лучше будет установить
app:tabMode="auto"
а также
app:tabGravity="fill"
поскольку установка для tabMode значения fixed может сделать заголовки перегруженными и привести к тому, что заголовки будут занимать несколько строк на другой стороне, установка для него прокручиваемого значения может заставить их оставлять пробелы в конце при некоторых размерах экрана. ручная установка tabMode вызовет проблемы при работе с несколькими размерами экрана
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
app:tabGravity="fill"
android:textAlignment="center"
app:tabMode="auto"
/>
Это единственный код, который работал для меня:
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;
}
И ваш TabLayout XML должен выглядеть так (не забудьте установить 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"/>
Очень простой пример, и он всегда работает.
/**
* 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);
});
}
Все, что вам нужно, это добавить следующее в ваш 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"
/>
Я решил это, используя следующие
if(tabLayout_chemistCategory.getTabCount()<4)
{
tabLayout_chemistCategory.setTabGravity(TabLayout.GRAVITY_FILL);
}else
{
tabLayout_chemistCategory.setTabMode(TabLayout.MODE_SCROLLABLE);
}
Мое окончательное решение
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
}
}
}
добавьте эту строку в свою деятельность, когда вы добавляете вкладки в tablayout
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
Решение Sotti отличное! Он работает именно так, как должен работать базовый компонент.
В моем случае вкладки могут динамически развиваться в соответствии с изменением фильтра, поэтому я сделал небольшую адаптацию, чтобы разрешить обновление режима вкладок с помощью метода redraw(). Это тоже в Котлине
class AdaptiveTabLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : TabLayout(context, attrs, defStyleAttr) {
private var gravityAndModeSeUpNeeded = true
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
if (gravityAndModeSeUpNeeded) {
setModeAndGravity()
}
}
fun redraw() {
post {
tabMode = MODE_SCROLLABLE
gravityAndModeSeUpNeeded = true
invalidate()
}
}
private fun setModeAndGravity() {
val tabCount = tabCount
val screenWidth = Utils.getScreenWidth()
val minWidthNeedForMixedMode = getMinSpaceNeededForFixedMode(tabCount)
if (minWidthNeedForMixedMode == 0) {
return
} else if (minWidthNeedForMixedMode < screenWidth) {
tabMode = MODE_FIXED
tabGravity = if (Utils.isBigTablet()) GRAVITY_CENTER else GRAVITY_FILL
} else {
tabMode = MODE_SCROLLABLE
}
gravityAndModeSeUpNeeded = false
}
private fun getMinSpaceNeededForFixedMode(tabCount: Int): Int {
val linearLayout = getChildAt(0) as LinearLayout
var widestTab = 0
var currentWidth: Int
for (i in 0 until tabCount) {
currentWidth = linearLayout.getChildAt(i).width
if (currentWidth == 0) return 0
if (currentWidth > widestTab) {
widestTab = currentWidth
}
}
return widestTab * tabCount
}
init {
tabMode = MODE_SCROLLABLE
}
}
Если вам понравился этот фрагмент, проголосуйте за решение Сотти, оно сэкономило мне много времени.