如何实现双RecyclerView自定义View:点击标题加载指定XML布局
实现方案
核心思路
不用Fragment,直接将XML布局作为内容载体,把标题RecyclerView和内容容器(推荐用ViewPager2)封装到一个自定义View中,既满足点击标题切换内容的需求,又能保证整个组件可以在任意页面复用。
具体实现步骤
1. 自定义View的整体布局
先编写自定义View的根布局(比如custom_tab_content_view.xml),用垂直LinearLayout组合标题RecyclerView和内容ViewPager2:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- 标题头部RecyclerView --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/tab_recycler" android:layout_width="match_parent" android:layout_height="50dp" android:background="#f5f5f5"/> <!-- 内容详情ViewPager2 --> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/content_viewpager" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> </LinearLayout>
2. 实现标题RecyclerView的Adapter
处理标题展示和点击事件,点击时通知内容容器切换页面:
class TabAdapter(private val tabTitles: List<String>, private val onTabClick: (Int) -> Unit) : RecyclerView.Adapter<TabAdapter.TabViewHolder>() { private var selectedPosition = 0 inner class TabViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val tabTv: TextView = itemView.findViewById(R.id.tab_tv) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TabViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_tab, parent, false) return TabViewHolder(view) } override fun onBindViewHolder(holder: TabViewHolder, position: Int) { holder.tabTv.text = tabTitles[position] // 设置选中状态样式 holder.tabTv.setTextColor(if (position == selectedPosition) Color.BLUE else Color.GRAY) holder.tabTv.setOnClickListener { selectedPosition = position notifyDataSetChanged() onTabClick.invoke(position) } } override fun getItemCount() = tabTitles.size // 供内容容器滑动时同步更新标题选中状态 fun updateSelectedPosition(position: Int) { selectedPosition = position notifyDataSetChanged() } }
对应的标题item布局item_tab.xml:
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/tab_tv" android:layout_width="wrap_content" android:layout_height="match_parent" android:paddingHorizontal="20dp" android:gravity="center" android:textSize="16sp"/>
3. 实现ViewPager2的内容Adapter
直接加载指定XML布局作为页面,完全无需Fragment:
class ContentAdapter(private val layoutIds: List<Int>) : RecyclerView.Adapter<ContentAdapter.ContentViewHolder>() { inner class ContentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContentViewHolder { // 直接加载传入的XML布局作为页面 val view = LayoutInflater.from(parent.context).inflate(layoutIds[viewType], parent, false) return ContentViewHolder(view) } override fun onBindViewHolder(holder: ContentViewHolder, position: Int) { // 如果需要对加载后的布局做初始化(比如绑定数据),在这里处理 // 例:val textView = holder.itemView.findViewById<TextView>(R.id.content_tv) } override fun getItemCount() = layoutIds.size override fun getItemViewType(position: Int): Int { return position } }
4. 封装自定义View
把标题和内容的联动逻辑封装到自定义View中,对外暴露初始化方法:
class TabContentView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : LinearLayout(context, attrs, defStyleAttr) { private val tabRecycler: RecyclerView private val contentViewPager: ViewPager2 private lateinit var tabAdapter: TabAdapter init { // 加载根布局 LayoutInflater.from(context).inflate(R.layout.custom_tab_content_view, this, true) tabRecycler = findViewById(R.id.tab_recycler) contentViewPager = findViewById(R.id.content_viewpager) // 设置标题RecyclerView为横向布局 tabRecycler.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) } // 对外提供初始化入口:传入标题列表和对应的内容布局ID列表 fun setup(tabTitles: List<String>, contentLayoutIds: List<Int>) { // 初始化标题Adapter tabAdapter = TabAdapter(tabTitles) { position -> contentViewPager.currentItem = position } tabRecycler.adapter = tabAdapter // 初始化内容ViewPager2的Adapter val contentAdapter = ContentAdapter(contentLayoutIds) contentViewPager.adapter = contentAdapter // ViewPager滑动时同步更新标题选中状态 contentViewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) tabAdapter.updateSelectedPosition(position) } }) } }
5. 复用自定义View
在任意页面的布局中直接引用,然后在代码中初始化:
<com.yourpackage.TabContentView android:id="@+id/custom_tab_content" android:layout_width="match_parent" android:layout_height="match_parent"/>
代码初始化示例:
val tabContentView = findViewById<TabContentView>(R.id.custom_tab_content) // 标题列表 val tabTitles = listOf("标题1", "标题2", "标题3") // 对应的内容布局ID val contentLayouts = listOf(R.layout.content_layout1, R.layout.content_layout2, R.layout.content_layout3) tabContentView.setup(tabTitles, contentLayouts)
替代方案:轻量内容切换
如果内容布局数量少,也可以用单个ViewGroup作为内容容器,切换标题时直接移除旧布局、加载新XML布局。这种方式更轻量,但需要自己处理切换动画和状态保存,适合简单场景。
内容的提问来源于stack exchange,提问作者r3cc
相关产品推荐
相关产品推荐

