1

I am using TabLayout with viewPager and viewPager used RecyclerView to for it's fragment which extends FragmentStatePageAdapter.

This all is working fine. But, I have a scenario where i want to set the number of items to be fixed to only (7) for the tabLayout in scrollable mode (when tabMode is set to scrollable).

Somehow, it's only setting 5 items per device when tabLayout set to scrollable, but in my scenario i want it to adjust 7 items irrespective of any device.

I tried giving the item width as totalDeviceWidht/7 , but no luck as it's still taking the same widht.

I also tried overriding the onMeasure() method by writing a custom tabLayout. But, no luck.

I am not sure if the issue is with pagerTabStrip or how how viewPager and tabLayout calculates the number of items to be fixed when tabMode is set to scrollable.

I am stuck with this issue from last 3 days.

This is my main xml layout..

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"

    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.movie.bms.views.activities.TimeActivity"
    tools:showIn="@layout/activity_time"
    >

    <android.support.design.widget.TabLayout
        android:id="@+id/time_sliding_tab"
        android:layout_width="match_parent"
        android:layout_height="61.8dp"
        android:background="#06acef"
        android:elevation="0dp"
        android:theme="@style/ThemeOverlay.AppCompat.Dark"
        app:elevation="@dimen/elevation.small"
        app:tabGravity="center"
        app:tabIndicatorColor="@color/white"
        app:tabMode="scrollable"
        app:tabPaddingEnd="-1dp"
        app:tabPaddingStart="-1dp"
        android:fillViewport="false"
        ></android.support.design.widget.TabLayout>

     <com.utils.customcomponents.CustomViewPager
            android:id="@+id/time_view_pager"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:transitionName="@string/event_body_transition">
    </com.utils.customcomponents.CustomViewPager>
</LinearLayout>

Any kind of help or suggestion would be appreciated.

Ritt
  • 3,181
  • 3
  • 22
  • 51
  • What if you encapsulate your TabLayout in a Horizontal Scrollable Layout and check within your code if you've seven and activate the scrollable layout only from seven onwards? – Bruno Bieri Feb 24 '16 at 15:41
  • This approach might not work, as after seven items i may have 20 more items. What i actually want is, i want the scrollView tabMode to fit 7 items, as per now, it's taking items as the device width. – Ritt Feb 24 '16 at 17:24
  • post your layout file – N J Feb 25 '16 at 04:54
  • @NJNileshJ updated, have a look please. – Ritt Feb 25 '16 at 08:29

1 Answers1

4

You can influence the actual width of the tabs by setting the app:tabMinWidth and app:tabMaxWidth element on the TabLayout in xml.

For a Nexus 5 (360dp) for instance:

<android.support.design.widget.TabLayout
        android:id="@+id/time_sliding_tab"
        android:layout_width="match_parent"
        android:layout_height="62dp"
        android:background="#06acef"
        app:tabGravity="center"
        app:tabMaxWidth="50dp"
        app:tabMinWidth="50dp"
        app:tabMode="scrollable" />

But truth being told, you need to take an estimated "guess" what your target width will be and optimise it for that. How it will look on an actual device will depend on its actual width.

In case you want to make it fit 100% you need to either:

  • Instantiate the TabLayout in code and pass in the correct app:tabMinWidth and tabMaxWidth values as AttributeSet. (which you calculated based on the screen width)
  • Use reflection on the existing TabLayout instance to set the mRequestedTabMinWidth and mRequestedTabMaxWidth private fields. (not recommended)

An example of how the reflection case would work:

import android.content.Context;
import android.support.design.widget.TabLayout;
import android.util.AttributeSet;

import java.lang.reflect.Field;

public class SevenTabLayout extends TabLayout {

    public class SetTabWidthException extends RuntimeException {

    }

    public SevenTabLayout(Context context) {
        super(context);
    }

    public SevenTabLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initializeTabWidths();
    }

    public SevenTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initializeTabWidths();
    }

    public void initializeTabWidths() {
        int tabwidth = 140; // Screensize divided by 7
        setPrivateField("mRequestedTabMinWidth", tabwidth);
        setPrivateField("mRequestedTabMaxWidth", tabwidth);
    }

    private void setPrivateField(String tabMinWidthFieldName, int value) {
        try {
            Field f = TabLayout.class.getDeclaredField(tabMinWidthFieldName);
            f.setAccessible(true);
            f.setInt(this, value);
        } catch (NoSuchFieldException e) {
            throw new SetTabWidthException();
        } catch (IllegalAccessException e2) {
            throw new SetTabWidthException();
        }
    }
}

Now you can easily add a unit test for the initializeTabWidths() method so you can detect when this stops working after upgrading the design support library.

Community
  • 1
  • 1
Jeroen Mols
  • 3,436
  • 17
  • 24
  • oh man... thankx a ton... that worked just fine, between i am trying to set the properties from program, but tabLayout instance is giving me these 2 properties. U have mentioned about AttributeSet, how should i use the instance of attribute in tabLayout. – Ritt Feb 26 '16 at 06:17
  • It seems to be harder than I thought, but what I would do is create a Subclass of TabLayout and override the constructor taking AttributeSet. Then create your own AttributeSet implementation, which wraps around the android generated AttributeSet, but returns a different value for the `tabMaxWidth` and `tabMinWidth` attribute. Complicated but that should work. – Jeroen Mols Feb 26 '16 at 06:42
  • The more I think about it I would actually simply use reflection to override the fields: create a subClass of TabLayout, override the constructors and after the `super` call a new method that uses reflection to set `mRequestedTabMinWidth` and `mRequestedTabMaxWidth`. This is a bit fragile though (if someone ever changes the parameter names), so you'll also want to add a unit test for this method. That way you'll know for sure whenever this stops working. – Jeroen Mols Feb 26 '16 at 06:45
  • okay, but if i use reflection, i need to write the unit test case and then on fail i again need to handle the failure case. Why not i just create a attributeSet and then pass in the constructor with the modified tabMaxWidth and tabMinWidth value. I think, this should work for all cases. – Ritt Feb 26 '16 at 06:56
  • Yeah sure, I just think that code will be slightly more complex to write. :) Regarding the unit test, that should only fail when you update the design support library, so in that case you would need to fix it. (Its not a silver bullet, but at least you can detect when something breaks) – Jeroen Mols Feb 26 '16 at 06:59
  • Okay, i am not sure though, how to use reflection there, can you please guide me with this or give me some link. – Ritt Feb 26 '16 at 07:06
  • I've updated my answer, you still have to write the code yourself to calculate 1/7th of the screensize. – Jeroen Mols Feb 26 '16 at 07:08
  • okay, thall will do, what are the paramaters mRequestedTabMinWidth and mRequestedTabMaxWidth, how they are set in the tabLayout xml, as in how the names are replaced from tabMaxWidth to mRequestedTabMaxWidth. I know, i am asking a naive question, but i am not sure i how this works. – Ritt Feb 26 '16 at 07:21
  • Those are the names of the actual fields in TabLayout.java. I've found them by looking at the source code here: https://android.googlesource.com/platform/frameworks/support/+/master/design/src/android/support/design/widget/TabLayout.java – Jeroen Mols Feb 26 '16 at 07:23
  • okay, so we are using reflection here, just to access and modify the private field at runtime. Why not i just copy the tabLayout.java source code and create a new class and make the mRequested fields public and then change those object properties. Is it a bad approach. – Ritt Feb 26 '16 at 07:33
  • If you copy the tablayout.java source code, then you won't get any bugfixes or new features any more for that file when the support library gets updated. (or you manually have to recopy the source code again) Therefore it is a bad approach, because you yourself become responsible for the maintenance. Better to subclass TabLayout and change the private fields using reflection. This is a bit of a risky approach (because member names can change), but chances that you get bugfixes/features are higher than that the names of the members would change. – Jeroen Mols Feb 26 '16 at 08:22
  • okay, i just tried using reflection, it's working fine like u said, but i think it's now a good approach as JVM take time to load at run time and also there is fear to that the variable name might change. I would like to go with the attribute set approach, which is passed in the constructor, can u please guide how to proceed with it. – Ritt Feb 26 '16 at 12:29
  • I've never done that myself to be honest... my answer there was just theoretical. So I cannot help you there I'm afraid. – Jeroen Mols Feb 26 '16 at 12:34
  • Okay, so i tried using custom attribute set, but i think,it can't be done by using attribute set as there are no public methods to set minTabWidth and maxTabWidth inside the TabLayout Class. Now, i am thinking of thinking of moving to SLidingTabLayout, as i will have control over the classes, using reflection is not a good idea i guess. – Ritt Feb 29 '16 at 10:51
  • Agree that reflection isn't great, but neither is the AttributeSet version: you are always forcing the TabLayout to do something the author didn't expose on its public interface. Personally I would use reflection, because: 1. it works 100% sure (based on source code), 2. risk of "breaking" can be managed by putting a unit test in place. But either way, its your call. – Jeroen Mols Mar 01 '16 at 11:36
  • Correct, But still i will try SlidingTabLayout though, if it works i will post the answer or else i have to go with reflection and will have to write unit Test Case to handle it. I also tried coying the tabLayout classes, but it internally uses many utils, which are private to the package and are not accessible outside, and if i copy all, it will unnecessarily will increase my app size. Thanks for the help mate.. I am accepting this answer, please post if you find a better solution or approach in future. – Ritt Mar 01 '16 at 15:08