我有一个TabLayout
和一个Recyclerview
,因此当单击选项卡时,Recyclerview
会滚动到特定位置。
我也希望执行相反的过程,以便将Recyclerview
滚动到特定位置时,该特定选项卡会突出显示。
例如:如果TabLayout
中有4个标签,并且当Recyclerview
滚动到第5个位置(项目在TabLayout
下方且可见)时,第3个标签应突出显示。{{ 3}}
此处TabLayout
下方显示“工作原理”时,应突出显示“工作原理”标签。
答案 0 :(得分:6)
尝试一下
遵循此步骤
ScrollListener
添加到您的RecyclerView
RecyclerView
的第一个可见项目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)
}
}
}
})
通过此设置,您可以:
答案 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()
}
这是屏幕的样子:
答案 4 :(得分:0)
试试这个,
简单步骤:
findFirstVisibleItemPosition()
返回第一个可见视图的适配器位置 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")
}