目录
2.kotlin自定义RecyclerView实现横竖滚动功能的功能分析和实现步骤
1.概述
在kotlin实现app的开发中,在使用RecyclerView来实现列表功能,但是原生的api不支持横竖屏通过滚动功能,所以需要自定义RecyclerView来实现这个功能,这就需要横竖屏一个RecyclerView来实现功能,同时解决滚动冲突的问题
如图:
2.kotlin自定义RecyclerView实现横竖滚动功能的功能分析和实现步骤
2.1 自定义RecyclerView实现横竖滚动功能分析
自定义RecyclerView实现横竖滚动功能实现,首选需要一个头部RecyclerView,然后在有一个
子条目RecyclerView,头部RecyclerView滚动的时候或者子条目横向滚动的时候实现联动功能
同时需要处理滚动的冲突,来实现既可以横屏滚动又可以竖屏滚动的功能
2.2 自定义RecyclerView实现横竖滚动功能实现
2.2.1 自定义数据实现类
自定义RecyclerView实现横竖滚动功能
package com.proj.linkage
import java.util.*
/**
* 数据类
*/
class DataEntity {
var leftTitle: String? = null
get() = if (field == null) "" else field
var rightDatas: List<String>? = null
get() = if (field == null) {
ArrayList()
} else field
}
数据类实现对数据的封装,然后在RecyclerView的横竖屏功能中调用数据实现功能
2.2.2 自定义子条目RecyclerView的适配器
package com.proj.linkage
import android.content.Context
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.proj.linkage.ContentAdapter.ItemViewHolder
import java.util.*
class ContentAdapter(private val context: Context, headRecycler: RecyclerView) : RecyclerView.Adapter<ItemViewHolder>() {
private var datas: List<DataEntity>? = null
private val observerList = HashSet<RecyclerView>()
private var firstPos = -1
private var firstOffset = -1
fun setDatas(datas: List<DataEntity>?) {
this.datas = datas
notifyDataSetChanged()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ItemViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.layout_item_content, viewGroup, false)
return ItemViewHolder(view)
}
override fun onBindViewHolder(itemViewHolder: ItemViewHolder, i: Int) {
//itemViewHolder.tvLeftTitle.setText(datas.get(i).getLeftTitle());
val linearLayoutManager = LinearLayoutManager(context)
linearLayoutManager.orientation = LinearLayoutManager.HORIZONTAL
itemViewHolder.rvItemRight.layoutManager = linearLayoutManager
itemViewHolder.rvItemRight.setHasFixedSize(true)
val rightScrollAdapter = RightScrollAdapter(context)
rightScrollAdapter.setDatas(datas!![i].rightDatas)
itemViewHolder.rvItemRight.adapter = rightScrollAdapter
initRecyclerView(itemViewHolder.rvItemRight)
}
override fun getItemCount(): Int {
return if (null == datas) 0 else datas!!.size
}
inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var rvItemRight: RecyclerView
init {
rvItemRight = itemView.findViewById(R.id.rv_item_right)
}
}
fun initRecyclerView(recyclerView: RecyclerView) {
recyclerView.setHasFixedSize(true)
//为每一个recycleview创建layoutManager
val layoutManager = recyclerView.layoutManager as LinearLayoutManager?
//todo
// 通过移动layoutManager来实现列表滑动 此行是让新加载的item条目保持跟已经滑动的recycleview位置保持一致
// 也就是上拉加载更多的时候 保证新加载出来的item 跟已经滑动的item位置保持一致
if (layoutManager != null && firstPos > 0 && firstOffset > 0) {
layoutManager.scrollToPositionWithOffset(firstPos + 1, firstOffset)
}
// 添加所有的 recyclerView
observerList.add(recyclerView)
//当触摸条目的时候 停止滑动
recyclerView.setOnTouchListener { view, motionEvent ->
when (motionEvent.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> for (rv in observerList) {
rv.stopScroll()
}
}
false
}
//添加当前滑动recycleview的滑动监听事件
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val linearLayoutManager = recyclerView.layoutManager as LinearLayoutManager?
//获取第一个item的位置
val firstPos1 = linearLayoutManager!!.findFirstVisibleItemPosition()
val firstVisibleItem = linearLayoutManager.getChildAt(0)
if (firstVisibleItem != null) {
//获取item的偏移量
val firstRight = linearLayoutManager.getDecoratedRight(firstVisibleItem)
for (rv in observerList) {
if (recyclerView !== rv) {
val layoutManager = rv.layoutManager as LinearLayoutManager?
if (layoutManager != null) {
firstPos = firstPos1
firstOffset = firstRight
//通过设置当前item的位置和偏移量的位置来更新recycleview 同步其它item的移动距离
layoutManager.scrollToPositionWithOffset(firstPos + 1, firstRight)
}
}
}
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
}
})
}
init {
initRecyclerView(headRecycler)
}
}
通过上述ContentAdapter的代码可以看出这个类很重要,在initRecyclerView中设置子条目recyclerview和头部recyclerview的联动监听头部recyclerView的滑动,然后也开始滑动
并且滑动距离也是需要一样的,保持头部数据和子条目数据的一致,在代码中做了详细的分析
2.2.3 自定义子条目右边部分滚动的适配器
package com.proj.linkage
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.proj.linkage.ItemScrollAdapter.ScrollViewHolder
class ItemScrollAdapter(private val context: Context) : RecyclerView.Adapter<ScrollViewHolder>() {
private var rightDatas: List<String>? = null
fun setDatas(rightDatas: List<String>?) {
this.rightDatas = rightDatas
notifyDataSetChanged()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ScrollViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.layout_item_scroll, viewGroup, false)
return ScrollViewHolder(view)
}
override fun onBindViewHolder(scrollViewHolder: ScrollViewHolder, i: Int) {
scrollViewHolder.mTvScrollItem.text = rightDatas!![i]
}
override fun getItemCount(): Int {
return if (null == rightDatas) 0 else rightDatas!!.size
}
inner class ScrollViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mTvScrollItem: TextView
init {
mTvScrollItem = itemView.findViewById(R.id.tv_right_scroll)
}
}
}
在上述的ItemScrollAdapter 中的相关代码,主要处理子条目右边滑动的时候的数据绑定和同步显示更新的布局绑定
通过在ContentAdapter的适配器中同步滚动数据
2.2.4 头部适配器数据的定义
package com.proj.linkage
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.proj.linkage.TopItemAdpater.TabViewHolder
/**
* 头部列表
*/
class TopItemAdpater(private val context: Context) : RecyclerView.Adapter<TabViewHolder>() {
private var datas: List<String>? = null
fun setDatas(datas: List<String>?) {
this.datas = datas
notifyDataSetChanged()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): TabViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.layout_item_scroll, viewGroup, false)
return TabViewHolder(view)
}
override fun onBindViewHolder(tabViewHolder: TabViewHolder, i: Int) {
tabViewHolder.mTabTv.text = datas!![i]
}
override fun getItemCount(): Int {
return if (null == datas) 0 else datas!!.size
}
inner class TabViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mTabTv: TextView
init {
mTabTv = itemView.findViewById(R.id.tv_right_scroll)
}
}
}
通过TopItemAdpater来实现头部RecyclerView的数据绑定功能,然后设置头部RecyclerView中,
在子条目数据横向滚动的时候同步更新数据
2.2.5 Acvitiy中实现数据的绑定和调用
package com.proj.linkage
import android.app.Activity
import android.os.Bundle
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import java.util.*
class SwipeRefreshActivity : Activity() {
private var rvTabRight: RecyclerView? = null
private var recyclerContent: RecyclerView? = null
private var swipeRefresh: SwipeRefreshLayout? = null
private val mEntities: MutableList<DataEntity> = ArrayList()
private val rightMoveDatas: MutableList<String> = ArrayList()
private val topTabs: MutableList<String> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_swipe)
rvTabRight = findViewById(R.id.rv_tab_right) as RecyclerView
recyclerContent = findViewById(R.id.recycler_content) as RecyclerView
swipeRefresh = findViewById(R.id.swipe_refresh) as SwipeRefreshLayout
//处理顶部标题部分
val linearLayoutManager = LinearLayoutManager(this)
linearLayoutManager.orientation = LinearLayoutManager.HORIZONTAL
rvTabRight!!.setLayoutManager(linearLayoutManager)
val topTabAdpater = TopItemAdpater(this)
rvTabRight!!.setAdapter(topTabAdpater)
for (i in 1..19) {
topTabs.add(i.toString() + "年级")
}
topTabAdpater.setDatas(topTabs)
//处理内容部分
recyclerContent!!.setLayoutManager(LinearLayoutManager(this))
recyclerContent!!.setHasFixedSize(true)
val contentAdapter = ContentAdapter(this, rvTabRight!!)
recyclerContent!!.setAdapter(contentAdapter)
recyclerContent!!.postDelayed(Runnable {
for (i in 1..19) {
val dataEntity = DataEntity()
rightMoveDatas.clear()
for (j in 1..19) {
rightMoveDatas.add(j.toString() + "班")
}
dataEntity.rightDatas = rightMoveDatas
mEntities.add(dataEntity)
}
contentAdapter.setDatas(mEntities)
Toast.makeText(this@SwipeRefreshActivity, "加载完毕,加载了30条数据", Toast.LENGTH_SHORT).show()
}, 1500)
//下拉刷新
swipeRefresh!!.setOnRefreshListener(OnRefreshListener {
recyclerContent!!.postDelayed(Runnable {
for (i in 1..29) {
val dataEntity = DataEntity()
rightMoveDatas.clear()
for (j in 1..29) {
rightMoveDatas.add(j.toString() + "班")
}
dataEntity.rightDatas = rightMoveDatas
mEntities.add(dataEntity)
}
contentAdapter.setDatas(mEntities)
swipeRefresh!!.setRefreshing(false)
Toast.makeText(this@SwipeRefreshActivity, "刷新完毕,刷新了50条数据", Toast.LENGTH_SHORT).show()
}, 1500)
})
}
}
在这个类里面实现对数据的绑定,下滑加载数据,头部数据和子条目数据的绑定,实现自定义RecyclerView横竖屏滚动
加载数据,
2.2.6 xml布局文件
activity_swipe.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".SwipeRefreshActivity">
<LinearLayout
android:id="@+id/ll_top_root"
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="horizontal"
android:visibility="visible">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_tab_right"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:scrollbars="none">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</androidx.recyclerview.widget.RecyclerView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
layout_item_content.xml
<?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="80dp"
android:orientation="horizontal">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_item_right"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:scrollbars="none">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
layout_item_scroll.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="120dp"
android:layout_height="80dp"
android:orientation="vertical">
<TextView
android:gravity="center"
android:id="@+id/tv_right_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="#000000" />
</LinearLayout>