如何在Android中编程滚动TabLayout

27

我使用TabLayout创建了30个可以滚动的选项卡。

因此,屏幕上只能看到前三个选项卡,其余的选项卡可以通过滑动手势来滚动。

问题是,当我以编程方式选择最后一个选项卡时,它并不会变得可见(tab布局没有滚动到最后一个选项卡)。

如何使TabLayout滚动到最后一个选项卡?


你能贴一些代码吗? - Kiril Aleksandrov
13个回答

66

我找到了解决方案。

首先,我找到了tablayout的宽度并将其位置滚动到宽度处,然后调用了最后一个标签的select()方法。

这很好地解决了问题。

以下是代码。

mTabLayout.setScrollX(mTabLayout.getWidth());
mTabLayout.getTabAt(lastTabIndex).select();

更新:

如果上述方法不起作用,您也可以使用下面的代码,它同样有效。

new Handler().postDelayed(
        new Runnable() {
            @Override public void run() {
                mTabLayout.getTabAt(TAB_NUMBER).select();
            }
        }, 100);

对我来说它不起作用,mTabLayout.getWidth() 是视图的宽度,而不是所有“选项卡”内部的宽度... - agirardello
谢谢!第二个解决方案对我有用!我在 Xamarin World 中将它放在这里,供那些感兴趣的人使用 new Handler().PostDelayed(() => { _tabLayout.GetTabAt(TAB_NUMBER).Select(); }, 100); - Eli
这个程序可以运行,但是我最初发现了一个小问题。 - GvSharma
2
第二个方案是最简单的方案,完美地运作着。 - Nikita Vishwakarma
新的解决方案完美无缺。new Handler().postDelayed( new Runnable() { @Override public void run() { mTabLayout.getTabAt(TAB_NUMBER).select(); } }, 100); - Muhamed El-Banna
https://dev59.com/4l0a5IYBdhLWcg3wCEsw#30944654 - emrcftci

8

在您自定义的TabLayout(扩展TabLayout的布局)中编写此方法。这样,将来您需要使用此方法而不是代码重复

public void selectTabAt(int tabIndex) {
        if (tabIndex >= 0 && tabIndex < getTabCount() && getSelectedTabPosition() != tabIndex) {
            final Tab currentTab = getTabAt(tabIndex);
            if (currentTab != null) {
                this.post(new Runnable() {
                    @Override
                    public void run() {
                        currentTab.select();
                    }
                });
            }
        }
    }

如果您不想使用CustomLayout,可以这样做。
final Tab currentTab = mTabLayout.getTabAt(tabIndex);
if(currentTab != null){
     mTabLayout.post(new Runnable() {
                    @Override
                    public void run() {
                        currentTab.select();
                    }
                });
}

很好,我已经找这个东西有一段时间了。我不知道为什么它没有工作。我只是缺少post runnable。谢谢你 - codeskraps

6
我找到了适合我的解决方案:
    TabLayout tabLayout = activity.getTabLayout();
    tabLayout.setSmoothScrollingEnabled(true);
    tabLayout.setScrollPosition(targetChannelPosition, 0f, true);

此外,如果您收到以下错误:"只有创建视图层次结构的原始线程才能触摸其视图。",您可以使用以下代码,在UI线程上运行:

    // find a way to get the activity containing the tab layout
    TabLayout tabLayout = activity.getTabLayout();
    activity.runOnUiThread(new Runnable()
    {
        @Override
        public void run()
        {
            TabLayout.Tab tab = tabLayout.getTabAt(targetChannelPosition);
            tab.select();
        }
    });

5

你是否在TabLayout及其子项实际测量和绘制之前调用了tab.select()?如果是这样,使用tab.select()(或Kayvan N的scrollTo()建议)将无法使TabLayout动画到所选内容。使用Handler可能会起作用,但这不是一个理想的解决方案。

只要布局还没有被布置,ViewTreeObserver就可以让你在布局过程完成后移动到所选的标签页。

private void scrollToTabAfterLayout(final int tabIndex) {
    if (getView() != null) {
        final ViewTreeObserver observer = mTabLayout.getViewTreeObserver();

        if (observer.isAlive()) {
            observer.dispatchOnGlobalLayout(); // In case a previous call is waiting when this call is made
            observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                        observer.removeOnGlobalLayoutListener(this);
                    } else {
                        //noinspection deprecation
                        observer.removeGlobalOnLayoutListener(this);
                    }

                    mTabLayout.getTabAt(tabIndex).select();

                }
            });
        }
    }
}

如果您有任何建议,请评论。


1
谢谢,伙计!这真是一个完美的解决方案,没有任何补丁。我过去几个小时一直在尝试实现它。你真的讲解得很好,我已经理解了概念。 - Dhara Vamja

4
new Handler().postDelayed(
    new Runnable() {
        @Override public void run() {
            mTabLayout.getTabAt(TAB_NUMBER).select();
        }
    }, 100);

4
以下代码片段对我有效。最初的回答。
class TriggerOnceListener(private val v: View, private val block: () -> Unit) : ViewTreeObserver.OnPreDrawListener {
    override fun onPreDraw(): Boolean {
        block()
        v.viewTreeObserver.removeOnPreDrawListener(this)
        return true
    }
}

fun onCreate() {
    val position = ***The tab position you want to scroll to, 29 for your case here***
    tabLayout.let { it.viewTreeObserver.addOnPreDrawListener(TriggerOnceListener(it)
    { it.setScrollPosition(position, 0f, true) } ) }
}

我深入研究了Tab.select(),发现Android使用Tablayout.setScrollPosition()来进行滚动。在onCreate()中,小部件还没有被测量,您需要推迟调用直到布局完成。"最初的回答"

太棒了,正在工作。 - Arda Kazancı

3
上面的答案不适用,因为首先,正如agirardello所提到的,您不应该使用mTabLayout.getWidth(),因为它不返回我们需要的内容(要滚动到的子元素的位置),而且更新后的解决方案并不总是有效,因为TabLayout中存在一个错误(在这里报告),但解决方法很简单。
选项卡在tabLayout上不是直接的子元素,所以我们需要深入一层使用。
((ViewGroup) mTabLayout.getChildAt(0)).getChildAt(YOUR_DESIRED_TAB_INDEX).getRight()

TabLayout的唯一子项是TabLayout.SlidingTabStrip,它也是一个ViewGroupgetRight()将给出我们所需选项卡视图的最右侧位置。因此,滚动到该位置将给我们想要的结果。以下是完整代码:

int right = ((ViewGroup) mTabLayout.getChildAt(0)).getChildAt(4).getRight();
mTabLayout.scrollTo(right,0);
mTabLayout.getTabAt(4).select();

注意:确保在布局绘制完成后调用这些方法(比如 onResume 而不是 onCreate)。
希望这能帮到你。

@Kayan N,每个子项都会得到 right = 0。 - Dhara Vamja

1
如果您的TabLayoutViewPager一起使用,这是很常见的情况,只需在Activity的onCreate()方法中添加以下内容即可:
tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout);

一些选项卡未显示,这表示tabMode属性设置为app:tabMode="scrollable"

1
要选择最后一个选项卡,请使用 tabLayout.getTabAt(X).select(); 其中 X 是最后一个选项卡的索引。

1
选项卡选择功能完美运作。问题在于所选的选项卡未能显示出来。 - Mohit Charadva
我正在使用tabLayout.getTabAt(X).select();来选择和显示标签X。当您调用它时,您的应用程序会发生什么? - jomartigcal
1
它选择了最后一个选项卡,但选项卡布局没有滚动到最后一个选项卡,因此所选选项卡仍然不可见。 - Mohit Charadva
我同意Mohit的观点 - 调用tabLayout.getTabAt(X).select();是不起作用的,即使从View.post(Runnable{})中调用也是如此。有效的方法是使用View.postDelayed(Runnable{},200)。这真是让人头疼。 - Someone Somewhere

0

这个解决方案对我很有用。不过我的情况略有不同;在我的情况下,我正在使用带有ViewPager的TabLayout,并添加更多视图并调用notifyDataSetChange()。

解决方案是在TabLayout的观察者上设置回调,并在实际添加子项到TabLayout时滚动。以下是我的示例:

/**
    Keep in mind this is how I set my TabLayout up...

    PagerAdapter pagerAdapter = new PagerAdapter(...);
    ViewPager pager = (ViewPager)findViewById(...);
    pager.setAdapter(pagerAdapter);

    TabLayout tabLayout = (TabLayout)findViewById(...);
    tabLayout.setupWithViewPager(pager);
*/
public void loadTabs(String[] topics) {
    animateTabsOpen(); // Irrelevant to solution

    // Removes fragments from ViewPager
    pagerAdapter.clear();

    // Adds new fragments to ViewPager
    for (String t : topics)
         pagerAdapter.append(t, new TestFragment());

    // Since we need observer callback to still animate tabs when we
    // scroll, it is essential to keep track of the state. Declare this
    // as a global variable
    scrollToFirst = true;

    // Alerts ViewPager data has been changed
    pagerAdapter.notifyOnDataSetChanged();

    // Scroll to the beginning (or any position you need) in TabLayout
    // using its observer callbacks
    tabs.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            /**
                 We use onGlobalLayout() callback because anytime a tab
                 is added or removed the TabLayout triggers this; therefore,
                 we use it to scroll to the desired position we want. In my
                 case I wanted to scroll to the beginning position, but this
                 can easily be modified to scroll to any position.
             */
            if (scrollToFirst) {
                tabs.getTabAt(0).select();
                tabs.scrollTo(0, 0);
                scrollToFirst = false;
            }
        }
    });
}

这是我的PagerAdapter代码,如果你需要的话lol:

public class PagerAdapter extends FragmentStatePagerAdapter {
    private List<Fragment> fragments;
    private List<String> titles;


    public PagerAdapter(FragmentManager fm) {
        super(fm);
        this.fragments = new ArrayList<>();
        this.titles = new ArrayList<>();
    }

    /**
     * Adds an adapter item (title and fragment) and
     * doesn't notify that data has changed.
     *
     * NOTE: Remember to call notifyDataSetChanged()!
     * @param title Fragment title
     * @param frag Fragment
     * @return This
     */
    public PagerAdapter append(String title, Fragment frag) {
        this.titles.add(title);
        this.fragments.add(frag);
        return this;
    }

    /**
     * Clears all adapter items and doesn't notify that data
     * has changed.
     *
     * NOTE: Rememeber to call notifyDataSetChanged()!
     * @return This
     */
    public PagerAdapter clear() {
        this.titles.clear();
        this.fragments.clear();
        return this;
    }

    @Override
    public Fragment getItem(int position) {
        return fragments.get(position);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return titles.get(position);
    }

    @Override
    public int getCount() {
        return fragments.size();
    }

    @Override
    public int getItemPosition(Object object) {
        int position = fragments.indexOf(object);
        return (position >= 0) ? position : POSITION_NONE;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接