如何将TabLayout与Recyclerview同步?

时间:2018-08-06 06:12:10

标签: java android android-layout android-recyclerview android-tablayout

我有一个TabLayout和一个Recyclerview,因此当单击选项卡时,Recyclerview会滚动到特定位置。 我也希望执行相反的过程,以便将Recyclerview滚动到特定位置时,该特定选项卡会突出显示。

例如:如果TabLayout中有4个标签,并且当Recyclerview滚动到第5个位置(项目在TabLayout下方且可见)时,第3个标签应突出显示。{{ 3}}

此处TabLayout下方显示“工作原理”时,应突出显示“工作原理”标签。

5 个答案:

答案 0 :(得分:6)

尝试一下

遵循此步骤

  1. ScrollListener添加到您的RecyclerView
  2. 而不是找到您的RecyclerView的第一个可见项目
  3. 根据TabLayout的位置在RecyclerView中设置选择标签

示例代码

    myRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            int itemPosition=linearLayoutManager.findFirstCompletelyVisibleItemPosition();

            if(itemPosition==0){ //  item position of uses
                TabLayout.Tab tab = myTabLayout.getTabAt(Index);
                tab.select();
            }else if(itemPosition==1){//  item position of side effects 
                TabLayout.Tab tab = myTabLayout.getTabAt(Index);
                tab.select();
            }else if(itemPosition==2){//  item position of how it works
                TabLayout.Tab tab = myTabLayout.getTabAt(Index);
                tab.select();
            }else if(itemPosition==3){//  item position of precaution 
                TabLayout.Tab tab = myTabLayout.getTabAt(Index);
                tab.select();
            }
        }
    });

编辑

public class MyActivity extends AppCompatActivity {


    RecyclerView myRecyclerView;
    TabLayout myTabLayout;
    LinearLayoutManager linearLayoutManager;
    ArrayList<String> arrayList = new ArrayList<>();
    DataAdapter adapter;
    private boolean isUserScrolling = false;
    private boolean isListGoingUp = true;




    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

        myTabLayout = findViewById(R.id.myTabLayout);



        myRecyclerView = findViewById(R.id.myRecyclerView);
        linearLayoutManager = new LinearLayoutManager(this);
        myRecyclerView.setLayoutManager(linearLayoutManager);
        myRecyclerView.setHasFixedSize(true);

        for (int i = 0; i < 120; i++) {
            arrayList.add("Item " + i);
        }

        adapter= new DataAdapter(this,arrayList);
        myRecyclerView.setAdapter(adapter);

        myTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                isUserScrolling = false ;
                int position = tab.getPosition();
                if(position==0){
                    myRecyclerView.smoothScrollToPosition(0);
                }else if(position==1){
                    myRecyclerView.smoothScrollToPosition(30);
                }else if(position==2){
                    myRecyclerView.smoothScrollToPosition(60);
                }else if(position==3){
                    myRecyclerView.smoothScrollToPosition(90);
                }
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });
        myRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
                    isUserScrolling = true;
                    if (isListGoingUp) {
                        //my recycler view is actually inverted so I have to write this condition instead
                        if (linearLayoutManager.findLastCompletelyVisibleItemPosition() + 1 == arrayList.size()) {
                            Handler handler = new Handler();
                            handler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    if (isListGoingUp) {
                                        if (linearLayoutManager.findLastCompletelyVisibleItemPosition() + 1 == arrayList.size()) {
                                            Toast.makeText(MyActivity.this, "exeute something", Toast.LENGTH_SHORT).show();
                                        }
                                    }
                                }
                            }, 50);
                            //waiting for 50ms because when scrolling down from top, the variable isListGoingUp is still true until the onScrolled method is executed
                        }
                    }
                }

            }
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                int itemPosition = linearLayoutManager.findFirstVisibleItemPosition();


                if(isUserScrolling){
                    if (itemPosition == 0) { //  item position of uses
                        TabLayout.Tab tab = myTabLayout.getTabAt(0);
                        tab.select();
                    } else if (itemPosition == 30) {//  item position of side effects
                        TabLayout.Tab tab = myTabLayout.getTabAt(1);
                        tab.select();
                    } else if (itemPosition == 60) {//  item position of how it works
                        TabLayout.Tab tab = myTabLayout.getTabAt(2);
                        tab.select();
                    } else if (itemPosition == 90) {//  item position of precaution
                        TabLayout.Tab tab = myTabLayout.getTabAt(3);
                        tab.select();
                    }
                }



            }
        });


    }


}

答案 1 :(得分:0)

    private fun syncTabWithRecyclerView() {
            // Move recylerview to the position selected by user

            menutablayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
                override fun onTabSelected(tab: TabLayout.Tab) {
                    isUserScrolling = false // Global variable
                    val position = tab.position
                    linearLayoutManager.scrollToPositionWithOffset(position, 0);

                }

                override fun onTabUnselected(tab: TabLayout.Tab) {

                }

                override fun onTabReselected(tab: TabLayout.Tab) {

                }
            })

            // Detect recyclerview position and select tab respectively.
            menuRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {

                override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                    if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
                        isUserScrolling = true
                    }

                }

                override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                    super.onScrolled(recyclerView, dx, dy)
                    val itemPosition = linearLayoutManager.findLastCompletelyVisibleItemPosition()
                    val tab = menutablayout.getTabAt(itemPosition)
                    tab?.select()
                }
            })
        }

答案 2 :(得分:0)

我认为您甚至不需要这些标志,就足以覆盖RecyclerView的onScrolled并选择选项卡,并在选择选项卡且尚未选择该选项卡时滚动到位置:

override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
     val llm = recyclerView.layoutManager as LinearLayoutManager

     // depending on sections'heights one may want to add more logic 
     // on how to determine which section to scroll to
     val firstCompletePos = llm.findFirstCompletelyVisibleItemPosition()

     if (firstCompletePos != tabLayout.selectedTabPosition)
         tabLayout.getTabAt(firstCompletePos)?.select()
}

然后我有一个TextView,它设置为tabLayout的customView:

tabLayout.addTab(newTab().also { tab ->
         tab.customView = AppCompatTextView(context).apply {
             // set layout params match_parent, so the entire section is clickable
             // set style, gravity, text etc.
             setOnClickListener { 
                tabLayout.selectTab(tab)

                recyclerView.apply {
                    val scrollTo = tabLayout.selectedTabPosition
                    smoothScrollToPosition(scrollTo)
                }
             }
          }
})

通过此设置,您可以:

  1. 用户滚动和弹起时选择制表符
  2. 当用户单击选项卡时,RecyclerView会滚动。

答案 3 :(得分:0)

我使用了其他答案中的信息,但这里有一些代码遗漏,这使得它不完整且工作不佳。我的解决方案 100% 无延迟地工作。最后你可能会看到完整的屏幕照片。

这是Fragment里面的代码:

private val layoutManager get() = recyclerView?.layoutManager as? LinearLayoutManager

/**
 * [SmoothScroller] need for smooth scrolling inside [tabListener] of [recyclerView] 
 * to top border of [RecyclerView.ViewHolder].
 */
private val smoothScroller: SmoothScroller by lazy {
    object : LinearSmoothScroller(context) {
        override fun getVerticalSnapPreference(): Int = SNAP_TO_START
    }
}

/**
 * Variable for prevent calling of [RecyclerView.OnScrollListener.onScrolled]
 * inside [scrollListener], when user click on [TabLayout.Tab] and 
 * [tabListener] was called.
 *
 * Fake calls happens because of [tabListener] have smooth scrolling to position,
 * and when [scrollListener] catch scrolling and call [TabLayout.Tab.select].
 */
private var isTabClicked = false

/**
 * Variable for prevent calling of [TabLayout.OnTabSelectedListener.onTabSelected]
 * inside [tabListener], when user scroll list and function 
 * [RecyclerView.OnScrollListener.onScrolled] was called inside [scrollListener].
 *
 * Fake calls happens because [scrollListener] contains call of [TabLayout.Tab.select],
 * which in turn calling click handling inside [tabListener].
 */
private var isScrollSelect = false

private val scrollListener = object : RecyclerView.OnScrollListener() {

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        /**
         * Reset [isTabClicked] key when user start scroll list.
         */
        if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
            isTabClicked = false
        }
    }

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        /**
         * Prevent scroll handling after tab click (see inside [tabListener]).
         */
        if (isTabClicked) return

        val commonIndex = commonIndex ?: return
        val karaokeIndex = karaokeIndex ?: return
        val socialIndex = socialIndex ?: return
        val reviewIndex = reviewIndex ?: return
        val addIndex = addIndex ?: return

        when (layoutManager?.findFirstVisibleItemPosition() ?: return) {
            in commonIndex until karaokeIndex -> selectTab(TabIndex.COMMON)
            in karaokeIndex until socialIndex -> selectTab(TabIndex.KARAOKE)
            in socialIndex until reviewIndex -> {
                /**
                 * In case if [reviewIndex] can't reach top of the list,
                 * to become first visible item. Need check [addIndex] 
                 * (last element of list) completely visible or not.
                 */
                if (layoutManager?.findLastCompletelyVisibleItemPosition() != addIndex) {
                    selectTab(TabIndex.CONTACTS)
                } else {
                    selectTab(TabIndex.REVIEWS)
                }
            }
            in reviewIndex until addIndex -> selectTab(TabIndex.REVIEWS)
        }
    }

    /**
     * It's very important to skip cases when [TabLayout.Tab] is checked like current,
     * otherwise [tabLayout] will terribly lagging on [recyclerView] scroll.
     */
    private fun selectTab(@TabIndex index: Int) {
        val tab = tabLayout?.getTabAt(index) ?: return
        if (!tab.isSelected) {
            recyclerView?.post {
                isScrollSelect = true
                tab.select()
            }
        }
    }
}

private val tabListener = object : TabLayout.OnTabSelectedListener {
    override fun onTabSelected(tab: TabLayout.Tab?) = scrollToPosition(tab)

    override fun onTabUnselected(tab: TabLayout.Tab?) = Unit

    /*
     * If user click on tab again.
     */
    override fun onTabReselected(tab: TabLayout.Tab?) = scrollToPosition(tab)

    private fun scrollToPosition(tab: TabLayout.Tab?) {
        /**
         * Prevent scroll to position calling from [scrollListener].
         */
        if (isScrollSelect) {
            isScrollSelect = false
            return
        }

        val position = when (tab?.position) {
            TabIndex.COMMON -> commonIndex
            TabIndex.KARAOKE -> karaokeIndex
            TabIndex.CONTACTS -> socialIndex
            TabIndex.REVIEWS -> reviewIndex
            else -> null
        }

        if (position != null) {
            isTabClicked = true

            smoothScroller.targetPosition = position
            layoutManager?.startSmoothScroll(smoothScroller)
        }
    }
}

private val commonIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.Info }
private val karaokeIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.Karaoke }
private val socialIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.Social }
private val reviewIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.ReviewHeader }
private val addIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.AddReview }

扩展:

private const val ND_INDEX = -1

fun <T> List<T>.validIndexOfFirst(predicate: (T) -> Boolean): Int? {
    return indexOfFirst(predicate).takeIf { it != ND_INDEX }
}

TabIndex 按位置获取标签的类:

@IntDef(TabIndex.COMMON, TabIndex.KARAOKE, TabIndex.CONTACTS, TabIndex.REVIEWS)
private annotation class TabIndex {
    companion object {
        const val COMMON = 0
        const val KARAOKE = 1
        const val CONTACTS = 2
        const val REVIEWS = 3
    }
}

这就是我的ClubScreenItem

sealed class ClubScreenItem {
    class Info(val data: ClubItem): ClubScreenItem()
    ...
    class Karaoke(...): ClubScreenItem()
    class Social(...): ClubScreenItem()
    ...
    class ReviewHeader(...): ClubScreenItem()
    ...
    object AddReview : ClubScreenItem()
}

这是屏幕的样子:

TabLayout and RecyclerView screen

答案 4 :(得分:0)

试试这个,

简单步骤:

  1. 检测 RecyclerView 滚动状态
  2. 使用 findFirstVisibleItemPosition() 返回第一个可见视图的适配器位置
  3. 根据 RecyclerView 项目位置更改选项卡
  4. 完成
 private fun syncTabWithRecyclerView() {
        var isUserScrolling = false
        val layoutManager = binding.recyclerViewGroup.layoutManager as LinearLayoutManager

        val tabListener = object : TabLayout.OnTabSelectedListener {
                override fun onTabSelected(tab: TabLayout.Tab?) {
                    val tabPosition = tab?.position
                    if (tabPosition != null) {
                        viewModel.setTabPosition(tabPosition)
                        
                        // prevent RecyclerView to snap to its item start position while user scrolling,
                        // idk how to explain this XD
                        if (!isUserScrolling){
                            layoutManager.scrollToPositionWithOffset(tabPosition, 0)
                        }
                    }
                }
                override fun onTabUnselected(tab: TabLayout.Tab?) {}
                override fun onTabReselected(tab: TabLayout.Tab?) {}
            }

        binding.tabLayout.addOnTabSelectedListener(tabListener)

        // Detect recyclerview scroll state
        val onScrollListener = object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
                    isUserScrolling = true
                } else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    isUserScrolling = false
                }
            }

            // this just represent my tab name using enum class ,
            // and ordinal is just the index of its position in enum
            val hardcase3D = CaseType.HARDCASE_3D.ordinal
            val softcaseBlackmatte = CaseType.SOFTCASE_BLACKMATTE.ordinal
            val softcaseTransparent = CaseType.SOFTCASE_TRANSPARENT.ordinal


            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                if (isUserScrolling) {
                    when (layoutManager.findFirstVisibleItemPosition()) {
                        in hardcase3D until softcaseBlackmatte -> {
                            viewModel.setTabPosition(hardcase3D)
                        }
                        in softcaseBlackmatte until softcaseTransparent -> {
                            viewModel.setTabPosition(softcaseBlackmatte)
                        }
                        softcaseTransparent -> {
                            viewModel.setTabPosition(softcaseTransparent)
                        }
                    }
                }
            }
        }

        binding.recyclerViewGroup.addOnScrollListener(onScrollListener)
    } 

viewModel ,如果需要,您可以简单地使用 liveData

private var _tabPosition = MutableStateFlow(CaseType.HARDCASE_3D)
    val tabPostition : StateFlow<CaseType>
        get() = _tabPosition

 fun setTabPosition(position: Int){
        _tabPosition.value = CaseType.values()[position]
    }

观察者

lifecycleScope.launch(Dispatchers.Default) {
            viewModel.tabPostition.collect { caseType ->
                val positionIndex = CaseType.values().indexOf(caseType)
                handleSelectedTab(positionIndex)
            }
        }

和handleSelectedTab

private fun handleSelectedTab(index: Int) {
       val tab = binding.tabLayout.getTabAt(index)
       tab?.select()
    }

枚举

enum class CaseType(val caseTypeName:String) {
    HARDCASE_3D("Hardcase 3D"),
    SOFTCASE_BLACKMATTE("Softcase Blackmatte"),
    SOFTCASE_TRANSPARENT("Softcase Transparent")
}