日期、列表、指示器【1列3行】3级联动
添加了LinearSnapHelper滚动自动对齐功能
RecyclerViewDemo3Fragment类:
package com.example.androidkotlindemo2.recyclerview.demo3 import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import androidx.fragment.app.Fragment import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearSnapHelper import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.OnScrollListener import com.example.androidkotlindemo2.MyApp import com.example.androidkotlindemo2.R import com.example.androidkotlindemo2.bean.ItemBean import com.example.androidkotlindemo2.databinding.RecyclerViewDemo3MainBinding import com.example.androidkotlindemo2.utils.LogUtils /** * Author : wangning * Email : maoning20080809@163.com * Date : 2023/9/24 9:58 */ class RecyclerViewDemo3Fragment : Fragment() { private lateinit var binding: RecyclerViewDemo3MainBinding /** * app列表适配器 */ private var mContentAdapter : RVDemo3Adapter? = null /** * app列表 */ private var mContentLayoutManager : LinearLayoutManager? = null /** * 显示app列表 */ private var mContentRecyclerView: RecyclerView? = null /** * 已选中的标题下标值 */ private var mSelectedTitlePosition = 0 override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { binding = RecyclerViewDemo3MainBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initTitle() initContent() initIndicator() } /** * 标题适配器 */ private var mTitleAdapter : RVDemo3TitleAdapter? = null /** * 标题列表 */ private var mTitleList = mutableListOf<ItemBean>() /** * 内容是否正在滚动,避免重复执行,快速跳动 */ private var mContentScrolling = false private fun initTitle(){ val recyclerViewTitle: RecyclerView = binding.recyclerTitle for(i in 11..17){ var itemBean = ItemBean(i, "${i}日") mTitleList.add(itemBean) } recyclerViewTitle.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false) mTitleAdapter = RVDemo3TitleAdapter(R.layout.recycler_view3_title_item, mTitleList) // //必须禁用adater的点击事件,这里才有效 。holder.getViewOrNull<TextView>(R.id.tv)?.isClickable = false mTitleAdapter?.setOnItemClickListener { adapter, view, position -> LogUtils.i(TAG, "position : ${position} , title : ${mTitleList[position].name}") isFirstVisible = true mDx = 0 mSelectedTitlePosition = position var list = getData(mTitleList[position].name) mContentAdapter?.setList(list) mTitleAdapter?.select(position) //滚动到顶部 mContentRecyclerView?.smoothScrollToPosition(0) // 立即定位到顶部,不显示滚动条 mContentLayoutManager?.scrollToPositionWithOffset(0, 0) //切换指示器 switchIndicator(0) //这个方法带滚动条显示 //mContentLayoutManager?.smoothScrollToPosition(mContentRecyclerView, null, 0) } recyclerViewTitle.adapter = mTitleAdapter } /** * 如果mDx大于0,从右向左滑动 <- , 可见下标从1到3 * 如果mDx小于0,从左向右滑动 -> ,可见下标从3到1 */ private var mDx = 0 /** * 当可见下标值 */ private var mCurrentPosition = 0 /** * 最后一个可见的下标值 */ private var mLastPosition = 0 /** * 选择指示器下标值 */ private var mSelectIndicatorPosition = 0 /** * SnapHelper辅助RecyclerView实现滚动对齐就是通过给RecyclerView设置OnScrollerListenerh和OnFlingListener这两个监听器实现的 */ private var mLinearSnapHelper: LinearSnapHelper = LinearSnapHelper() /** * 初始化显示内容列表 */ private fun initContent(){ mContentRecyclerView = binding.recycler mContentLayoutManager = object : GridLayoutManager(requireContext(), 3, HORIZONTAL, false){ override fun canScrollHorizontally(): Boolean { return true } override fun scrollHorizontallyBy( dx: Int, recycler: RecyclerView.Recycler?, state: RecyclerView.State? ): Int { mDx = dx mCurrentPosition = findFirstVisibleItemPosition() mLastPosition = findLastCompletelyVisibleItemPosition() //向右滑动,然后再向左边滑动到最左边,就要保留第一个显示,还不能切换标题tab if(dx > 0){ isFirstVisible = false } /*//最后一个可见 var lastPosition = findLastVisibleItemPosition() //取模 - 没列3行 spanCount var mode = lastPosition % spanCount mSelectIndicatorPosition = (lastPosition - mode) / spanCount LogUtils.e(TAG,"GridLayoutManager scrollHorizontallyBy ${findLastVisibleItemPosition()} , ${findLastCompletelyVisibleItemPosition()} , spanCount : ${spanCount} , mode : ${mode}, ${mSelectIndicatorPosition}") switchIndicator(mSelectIndicatorPosition)*/ return super.scrollHorizontallyBy(dx, recycler, state) } } var list = getData("11日") mContentRecyclerView?.layoutManager = mContentLayoutManager mContentAdapter = RVDemo3Adapter(R.layout.recycler_view3_item, list) mLinearSnapHelper.attachToRecyclerView(mContentRecyclerView) mContentRecyclerView?.adapter = mContentAdapter mContentRecyclerView?.addOnScrollListener(object : OnScrollListener(){ override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) LogUtils.e(TAG,"onScrollStateChanged newState:${newState}, ${mContentLayoutManager?.findFirstCompletelyVisibleItemPosition()}") if(newState == RecyclerView.SCROLL_STATE_SETTLING){ mContentScrolling = true } else if (newState == RecyclerView.SCROLL_STATE_DRAGGING){ mContentScrolling = true } else if (newState == RecyclerView.SCROLL_STATE_IDLE){ mContentScrolling = false LogUtils.i(TAG, "滚动到mSelectIndicatorPosition :${mSelectIndicatorPosition}") // 滚动已停止,此时可以检查哪个项目被对齐 var centerView = mLinearSnapHelper.findSnapView(mContentLayoutManager) centerView?.let { var selectContentPosition = mContentRecyclerView?.getChildAdapterPosition(centerView) selectContentPosition?.let { processContentSelectPosition() } //如果是1行1列可以直接切换指示器 //switchIndicator(selectContentPosition) } processContentScrolling() } } override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) } }) } /** * 处理显示内容列表的下标值 */ private fun processContentSelectPosition(){ var spanCount = 3 //最后一个可见 var lastPosition = mContentLayoutManager?.findLastVisibleItemPosition()?:0 //取模 - 没列3行 spanCount var mode = lastPosition % spanCount mSelectIndicatorPosition = (lastPosition - mode) / spanCount //切换指示器 switchIndicator(mSelectIndicatorPosition) } /** * 快速滑动到左边(当前第5个,从第5各滑动到第1个),不能切换标题tab */ private var isFirstVisible = false /** * 处理左右滑动 */ private fun processContentScrolling(){ LogUtils.d(TAG,"processA :dx : ${mDx} , mCurrentPosition : ${mCurrentPosition}, isFirstVisible : ${isFirstVisible} , ${mContentLayoutManager?.itemCount}}") if(mDx < 0){ if(!isFirstVisible){ if(mContentLayoutManager?.findFirstCompletelyVisibleItemPosition() == 0){ isFirstVisible = true } return } } var itemCount = (mContentLayoutManager?.itemCount?:0) - 1 LogUtils.d(TAG,"processContentScrolling :第a三: mCurrentPosition : ${mCurrentPosition} , mLastPosition : ${mLastPosition} , ${itemCount}, ${mContentLayoutManager?.itemCount}") if (mCurrentPosition == 0) { // 当滑动到第一个item的时候,继续滑动应该跳转到最后一个item if (mDx < 0 && mContentLayoutManager?.findFirstCompletelyVisibleItemPosition() == 0) { //从左向右滑动(->),不是第一个,要切换标题tab if(mSelectedTitlePosition != 0 && !mContentScrolling){ mContentScrolling = true goPrevious(mSelectedTitlePosition -1) } } } else if (mLastPosition === itemCount) { LogUtils.i(TAG,"processContentScrolling a最后一个:${mContentLayoutManager?.findLastCompletelyVisibleItemPosition()} , ${mContentLayoutManager?.itemCount} , ${mCurrentPosition}") // 当滑动到最后一个item的时候,继续滑动应该跳转到第一个item if (mDx > 0) { LogUtils.i(TAG,"processContentScrolling 最后一个:${mContentLayoutManager?.findLastCompletelyVisibleItemPosition()} , ${mContentLayoutManager?.itemCount} , ${mCurrentPosition}") goNext(mSelectedTitlePosition + 1) } } } /** * 切换到上一个tab * @param titlePosition 下标值 */ private fun goPrevious(titlePosition : Int){ mSelectedTitlePosition = titlePosition var list = getData(mTitleList[titlePosition].name) mContentAdapter?.setList(list) mTitleAdapter?.select(titlePosition) } /** * 切换到下一个tab * @param titlePosition 下标值 */ private fun goNext(titlePosition : Int){ mSelectedTitlePosition = titlePosition var list = getData(mTitleList[titlePosition].name) mContentAdapter?.setList(list) mTitleAdapter?.select(titlePosition) //滚动到顶部 mContentRecyclerView?.smoothScrollToPosition(0) // 立即定位到顶部,不显示滚动条 mContentLayoutManager?.scrollToPositionWithOffset(0, 0) mSelectIndicatorPosition = 0 switchIndicator(0) } private fun getData(title : String) : MutableList<ItemBean>{ var itemBeans = mutableListOf<ItemBean>() for(i in 0..14){ var itemBean = ItemBean(i, "【${title}】-我的测试数据$i ") itemBeans.add(itemBean) } return itemBeans } /** * 初始化指示器 */ private fun initIndicator(){ binding.indicatorLayout.removeAllViews() for (i in 0..4) { var view = View(requireContext()) var layoutParams = LinearLayout.LayoutParams(60, 60) layoutParams.leftMargin = 30 layoutParams.rightMargin = 30 view.layoutParams = layoutParams if(i == 0){ view.setBackgroundColor(MyApp.myApp.resources.getColor(R.color.red)) } else { view.setBackgroundColor(MyApp.myApp.resources.getColor(R.color.gray_e8e8e8)) } binding.indicatorLayout.addView(view) } } /** * 切换指示器 * @param selectPosition 选中下标值 */ private fun switchIndicator(selectPosition : Int){ var childCount = binding.indicatorLayout.childCount LogUtils.i(TAG, "选择switchIndicator:${selectPosition}") for (i in 0 until childCount) { var childView = binding.indicatorLayout.getChildAt(i) if(i == selectPosition){ LogUtils.i(TAG, "选择:${i}") childView.setBackgroundColor(MyApp.myApp.resources.getColor(R.color.red)) } else { LogUtils.i(TAG, "不选择:${i}") childView.setBackgroundColor(MyApp.myApp.resources.getColor(R.color.gray_e8e8e8)) } } } companion object { const val TAG = "RecyclerViewDemo2Fragment" } }
RVDemo3Adapter类:
package com.example.androidkotlindemo2.recyclerview.demo3 import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.module.LoadMoreModule import com.chad.library.adapter.base.viewholder.BaseViewHolder import com.example.androidkotlindemo2.R import com.example.androidkotlindemo2.bean.ItemBean /** * Author : wn * Email : maoning20080809@163.com * Date : 2023/9/24 10:07 * Description : */ class RVDemo3Adapter(layoutResId: Int, data : MutableList<ItemBean>) : BaseQuickAdapter<ItemBean, BaseViewHolder>(layoutResId = layoutResId, data = data) , LoadMoreModule{ override fun convert(holder: BaseViewHolder, item: ItemBean) { holder.setText(R.id.tv, item.name) } }
RVDemo3TitleAdapter类:
package com.example.androidkotlindemo2.recyclerview.demo3 import android.widget.TextView import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.module.LoadMoreModule import com.chad.library.adapter.base.viewholder.BaseViewHolder import com.example.androidkotlindemo2.MyApp import com.example.androidkotlindemo2.R import com.example.androidkotlindemo2.bean.ItemBean /** * Author : wn * Email : maoning20080809@163.com * Date : 2023/9/24 10:07 * Description : */ class RVDemo3TitleAdapter(layoutResId: Int, data : MutableList<ItemBean>) : BaseQuickAdapter<ItemBean, BaseViewHolder>(layoutResId = layoutResId, data = data) , LoadMoreModule{ private var mSelectPosition: Int = 0 override fun convert(holder: BaseViewHolder, item: ItemBean) { holder.setText(R.id.tv, item.name) holder.getViewOrNull<TextView>(R.id.tv)?.isClickable = false holder?.getViewOrNull<TextView>(R.id.tv)?.let { if(mSelectPosition == holder.layoutPosition){ it.setBackgroundColor(MyApp.myApp.resources.getColor(R.color.greenyellow)) } else { it.setBackgroundColor(MyApp.myApp.resources.getColor(R.color.gray_d9d9d9)) } } } fun select(selectPosition : Int){ mSelectPosition = selectPosition notifyDataSetChanged() } fun getPosition() : Int{ return mSelectPosition } }
recycler_view_demo3_main.xml布局
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginVertical="10dp" android:layout_gravity="center" android:gravity="center" android:layout_centerHorizontal="true" android:textColor="@color/black" android:textSize="28sp" android:text="日期、列表、指示器【1列3行】" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_title" android:layout_width="match_parent" android:layout_height="wrap_content"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="wrap_content"/> <LinearLayout android:id="@+id/indicator_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="20dp" android:orientation="horizontal"/> </LinearLayout> </layout>
recycler_view3_title_item.xml布局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_centerHorizontal="true" android:layout_height="wrap_content" android:layout_marginVertical="6dp" android:layout_marginHorizontal="6dp" android:paddingHorizontal="6dp" android:background="@color/greenyellow" android:textAllCaps="false" android:textColor="@color/blue" android:textSize="28sp" android:text="item" /> </RelativeLayout>
recycler_view3_item.xml布局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:background="@color/gray_c0c0c0" android:layout_marginHorizontal="20dp" android:layout_height="120dp"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_centerHorizontal="true" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginVertical="6dp" android:textColor="@color/red" android:textSize="28sp" android:text="item" /> </RelativeLayout>