We have this TabLayout in xml:
<android.support.design.widget.TabLayout
android:id="#+id/sliding_tabs"
app:tabGravity="fill"
app:tabContentStart="#dimen/tab_content_start"
app:tabIndicatorColor="#color/MaterialDividerColor"
app:tabSelectedTextColor="#color/DarkRed"
app:tabTextColor="#color/Black"
android:layout_width="match_parent"
android:layout_height="wrap_content"
The problem is, when displaying on a tablet it looks really weird, all the tabs have the same width and therefor have each wildly different padding:
Surprisingly, on a phone it works as expected, or at least the problem is not visible:
I am using the 23.2.0 support library, which frankly looks full of bugs.
When you have TabLaout which app:tabMode="scrollable", You need to give some value to tabMinWidth, such as app:tabMinWidth="40dp". Otherwise, tab paddings wouldn't work.
Related
In a project, we are using the Material Design Components' TabLayout with a ViewPager. There are 14 tabs, and we want 7 of those tabs to be visible at a time in the TabLayout. The tab content is narrow enough that we are sure that 7 will not be too many, and the design team wants a consistent number of tabs showing up regardless of screen width (tabs represent days of the week).
None of the pre-defined tab modes seem to match this:
MODE_FIXED and MODE_AUTO control the number of visible tabs... by showing all of them
MODE_SCROLLABLE allows the tabs to scroll... but then we do not have control over the number of visible tabs
Is there a way of accomplishing this that does not involve non-maintainable hacks, such as using reflection to tinker with tabPaddingStart at runtime, or iterating over the tab widgets and adjusting their LayoutParams?
I have seen this question, but the explanation is lacking — in particular, it is unclear how to use app:tabMaxWidth for what should be a dynamic value at runtime. Also, that question is about the older Design Support Library, which may differ somewhat with MDC's implementation.
There several ways to show a fixed number of tabs irrespective of screen width that could work, but the desired functionality is really locked down. Most notably, if getTabMinWidth() in TabLayout were not private, an easy solution would be to override that method in a custom TabLayout view.
The following is along that lines of, and maybe exactly, what Eugen Pechanec suggested in a comment above which involves a custom view for the tabs.
First the base layout.
activity_main.xml
tabMinWidth, tabPaddingEnd and tabPaddingStart are all set to 0dp. tabMinWidth has a default value that is probably too large for our needs. The padding could be set to other than zero, but I would rather deal with that in the custom views for the tabs.
Nothing really happens with the ViewPager.
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".MainActivity">
<androidx.viewpager.widget.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.tabs.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tabMinWidth="0dp"
app:tabMode="scrollable"
app:tabPaddingEnd="0dp"
app:tabPaddingStart="0dp" />
</androidx.viewpager.widget.ViewPager>
</androidx.appcompat.widget.LinearLayoutCompat>
custom_tab.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Tab x"
android:textAppearance="#style/TextAppearance.AppCompat.Body2" />
<!-- If an icon is needed. -->
<!-- <ImageView-->
<!-- android:id="#android:id/icon"-->
<!-- android:layout_width="48dp"-->
<!-- android:layout_height="48dp"-->
<!-- android:scaleType="centerCrop"-->
<!-- android:src="#drawable/ic_launcher_foreground" />-->
</LinearLayout>
MainActivity.kt
Tabs are loaded into the TabLayout one-by-one setting the custom views as we go. The minimum width of the custom views is set to 1/7 of the width of the TabLayout. Setting the minimum width suffices since it is given that the width needed will always be less than or equal to 1/7 of the total width.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val tabLayout = findViewById<TabLayout>(R.id.tabs)
tabLayout.doOnLayout {
val tabWidth = tabLayout.width / 7
for (i in 1..14) {
tabLayout.newTab().run {
setCustomView(R.layout.custom_tab)
customView?.minimumWidth = tabWidth
setText("Tab $i")
tabLayout.addTab(this)
}
}
}
}
}
If custom tabs are used anyway, I think that this is a reasonable solution. However, it is little better (IMO) than iterating over the TabLayout children and setting widths.
Finally, a couple of pictures:
I had to do the same and I investigated the problem with the special attention. Spoiler: I failed - currently what you want to achieve is not possible via stock TabLayout and clean Android way.
There are hacks though)
The most adequate one is to use library that allows that - for example this
Although you mentioned it and do not wan't to do it - iterating TabLayout children changing their LayoutParams is the next by adequacy way to do it. This method utilizes the public methods Android provides and does not require usage of some not very good techniques. Speaking of which...
*Reflection start
Change tabPaddingStart or requestedTabMinWidth along with requestedTabMaxWidth. It is bad. I won't even start why. It is the most inadequate one way in this list. It is still a way to fix it though.
This one is the combination of clean Android way and TabLayout parameters app:tabMaxWidth and app:tabMinWidth and reflection. The reflection is a bit different in this case - it is almost normal(as much as a reflection can be). I propose to create an integer android resource in integers.xml called tabWidth or something (I'm sure you already know where I am going) and on application start you replace it via reflection with the value of your screenWidth divided by 7(or less depending on your paddings).
*Reflection finish
There is one more way and it is Android clean but quite inadequate nevertheless. You can use a combination of ViewPagers and/or fragments. The easiest one is a ViewPager of two ViewPagers and two TabLayouts each with 7 pages and 7 tabs accordingly. You might need to adjust gestures handler though. And there can be many more combinations.
I will not include a way of you writing the TabLayout by yourself with help of RecyclerView and SnapHelper because it always is an option to write something by yourself in Android... and waste tremendous amount of time.
I know that it is not an answer. Moreover it is rather list of how NOT to do stuff in Android but sometimes we need to choose the dark side...
Hope it helps though.
I am using tabs for my Viewpager, and this is the XML right now:
<android.support.design.widget.TabLayout
android:id="#+id/viewpager_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="fill"
app:tabMode="fixed"/>
They look great but I want to be able to have more tabs and not have everything squish on the screen. However I don't like switching to scrollable tabMode because then everything flattens out and looks messy. How can I set the width of the individual tabs?
TabLayout does not provide the attributes for particular tab's fixed width.
But you can set min and max width.
tabMinWidth and tabMaxWidth
https://developer.android.com/reference/android/support/design/widget/TabLayout.html
In my Android app running on Android 5.1.1 I have a layout using a Toolbar with a TabLayout, and underneath is a ViewPager. All of these are put together in a CoordinatorLayout.
In the first page of the ViewPager is a RecyclerView serving CardView items.
My problem is that my ViewPager keeps getting resized in a way so that my CardView list items are cropped.
My main layout looks basically like this:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.design.widget.CoordinatorLayout>
And the first fragment served by my ViewPager looks like:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.v7.widget.RecyclerView
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"/>
</FrameLayout>
This renders something that looks like this:
When clicking a button in my layout, I use startActivityForResult to invoke another activity, and when returning to my activity sometimes suddenly my list is cropped so that only half of the first item is visible:
After swiping horizontally to another pager in the ViewPager and then back, the problem disappears, so it does seem a re-layout has not been properly triggered. Pressing HOME and then resuming my activity does NOT resolve the problem though. Note that this happens even if I am not modifying my layout in any way, I am simply returning from a startActivityForResult call. And yes, it only happens sometimes... And I have no background threads running that could explain the apparent random behavior.
At first I thought it was the RecyclerView that had shrunk, but using HierarchyViewer I was able to find that it was actually the entire ViewPager that had shrunk to about half its original height.
I tried various hacks to get around this, including calling invalidate() and requestLayout() on my entire view hiearchy, but nothing seemed to help (although swiping to another page and back again fixes it). Also, those are not the kind of solutions I want to resort to... Then I tried changing my ViewPager height to wrap_content, which did in fact solve this particular problem; after returning to my activity the first item in my RecyclerView is never cropped, and I can scroll down to the other items. However, now instead the very last item of my list is always cropped, as can be seen in this screenshot where the list is scrolled all the way to the bottom:
Since I am now at a point where I don't really understand what's going on, I need some help. What should I really use as the layout_height for my ViewPager, and - above all - why? To me, match_parent makes sense, but how should I be thinking here? Is there a rational reason my views got cropped when using match_parent, or did I in fact encounter a bug in ViewPager, RecyclerView and/or CoordinatorLayout? How do I make sure that my ViewPager consistently fills the entire screen area below the AppBar, and that my RecyclerView can be scrolled vertically to properly render all CardView list items?
It turns out this is almost certainly a bug in CoordinatorLayout or even more likely in AppBarLayout$ScrollingViewBehavior. In an effort to create a MCVE I realized it was the fact that my sub-activity had an IME on screen that caused the shrinking of the ViewPager - when my activity is resumed after onActivityResult, the ViewPager is shrunk as a result of reduced screen real-estate from the IME, but is never expanded again despite the fact that the IME is no longer being shown and the fact that the CoordinatorLayout is indeed expanded.
After debugging and stepping through onLayout and onMeasure of CoordinatorLayout and ViewPager I am now fairly sure that the CoordinatorLayout does not properly propagate the change in size to its children.
I found that I can "fix" the problem by calling requestLayout on my ViewPager, but only if the call is sufficiently delayed (10 ms never works, 100 ms works most of the time):
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
mViewPager.requestLayout();
}
}, 100);
}
This obviously isn't a robust solution, but after investigating some more it turns out I probably don't even need CoordinatorLayout since I don't really have any advanced scrolling behavior. So my solution will be to simply go with a LinearLayout or RelativeLayout as my root view group instead. Nice and simple, no need to complicate things.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
I will however try to condense this into a simple example and file a bug with Google.
As to what I should use as the height for my ViewPager, my original use of match_parent still seems reasonable. The fact that wrap_content solved the problem (but caused other problems) is probably just due to inconsistencies caused by the bug.
I've experienced a similar issue when using an older version of the support library.
See these related issues:
https://code.google.com/p/android/issues/detail?id=176406
https://code.google.com/p/android/issues/detail?id=176187
Make sure you're using the latest Support library, version 23.1 as of this writing.
In your fragment just remove the frameLayout and make recyclerView parent...I hope it will work:
<android.support.v7.widget.RecyclerView
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp">
<android.support.v7.widget.RecyclerView/>
I have added two tab view in the action bar which is looking fine in the portrait mode but is not good in Landscape mode and also in tablet devices. now i need to display two tab view that is to be split up with equal space in landscape mode and large devices like being in portrait mode (you can refer from youtube app which has two tabs that equally split up in both portrait and landscape mode). How can i achieve this? please let me know if know somebody.
output of Portrait Mode.
You should try this in onCreate slidingTabLayout.setDistributeEvenly(true);
I also linked you to a slidenerd video where he would explain more about it
You can use app:tabMode as FIXED as shown below.
<android.support.design.widget.TabLayout
android:id="#+id/pager_header"
android:layout_width="match_parent"
android:background="#color/white"
app:tabTextColor="#color/placeHolder"
android:elevation="1dp"
app:tabMode="fixed"
app:tabTextAppearance="#style/collection_tab"
app:tabSelectedTextColor="#color/blueBG"
android:layout_height="wrap_content"
app:tabGravity="fill" />
<android.support.v4.view.ViewPager
android:id="#+id/vpPager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v4.view.ViewPager>
I am creating an application which uses Tab Layout. The requirement of the app is that there should be many tabs. Making 4-5 tabs on the tab bar was ok. But when more tham 5 tabs are added, it gets very compressed. Is there a way in android where i can have many tabs and not make it look compressed? maybe some what like having a "More" button on the tab or having horizontal scrollable tabs?
I forgot to mention this earlier that it should work on android 2.2(api 8)
Thanks
For tab layout add the line
app:tabMode="scrollable"
to your Xml file eg
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:tabMode="scrollable"
app:tabTextColor="#color/colorWhite"
app:tabSelectedTextColor="#color/colorSecondary"
android:background="#color/colorPrimary"
app:tabIndicatorColor="#color/colorSecondary"
app:tabIndicatorHeight="5dp"/>
</android.support.design.widget.AppBarLayout>