在写寒假考核时,主界面是由一个viewpager2
的轮播图和RecyclerView
的文章列表,但这样有个问题,当我们向上滑动列表时,轮播图并不会跟着滑动,这样非常影响视觉体验,之前的效果参考WanAndroid的README
的主页界面展示,那么我们应该如何解决这个问题呢?就是将轮播图也添加进RecyclerView
里面。效果如下:
实现
先新建一个banner.xml
布局,用于展示我们的轮播图:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/home_viewpager2"
android:layout_width="match_parent"
android:layout_height="140dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_carousel_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#80000000"
android:padding="12dp"
android:text="Banner Text"
android:textColor="#F5F5F5"
android:textSize="10dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/home_viewpager2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.generlas.winterexam.view.CarouselDot
android:id="@+id/home_carouselDot"
android:layout_width="80dp"
android:layout_height="38dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/tv_carousel_text"
app:layout_constraintTop_toTopOf="@+id/tv_carousel_text" />
</androidx.constraintlayout.widget.ConstraintLayout>
里面的一些其他具体布局内容(比如自定义的三个点)可以到我的WanAndroid项目里面去找。
然后在HomeFragment.kt
里面,之前是接收到请求的数据后在Fragment
里面直接展示,现在我们接收到后只需要存储进数组里然后一会儿随着Adapter
一起传进去:
//加载轮播图适配器
override fun createCarousel(carouselPassage: List<CarouselInfo>) {
if (activity != null) {
val mainActivity = activity as MainActivity
mainActivity.runOnUiThread {
//多添加两张,模拟最后一张到第一张
finalCarouselPassage.add(0, carouselPassage[carouselPassage.size - 1])
finalCarouselPassage.addAll(carouselPassage)
finalCarouselPassage.add(carouselPassage.size + 1, carouselPassage[0])
}
}
}
这里添加两张的原因是为了实现无限轮播,具体思路可在网上查询。
然后我们写一个HomePassageAdapter
:
/**
* description : TODO:home页的recyclerview
* date : 2025/3/2 13:20
*/
class HomePassageAdapter(private val context: Context, private val carouselInfo: List<CarouselInfo>) :
ListAdapter<PassageInfo, RecyclerView.ViewHolder>(ItemDiffCallback()) {
private val TYPE_BANNER = 0
private val TYPE_PASSAGE = 1
lateinit var handler: Handler
lateinit var runnable: Runnable
inner class PassageViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val passageAuthor: TextView = view.findViewById(R.id.tv_card_author)
val passageDate: TextView = view.findViewById(R.id.tv_card_date)
val passageTitle: TextView = view.findViewById(R.id.tv_card_title)
val passageChapterName: TextView = view.findViewById(R.id.tv_card_chapterName)
}
inner class BannerViewHolder(view: View): RecyclerView.ViewHolder(view) {
var dotView : CarouselDot = view.findViewById(R.id.home_carouselDot)
val carouselViewPager2: ViewPager2 = view.findViewById(R.id.home_viewpager2)
val mTvCarouselTitle: TextView = view.findViewById(R.id.tv_carousel_text)
init {
autoCarousel()
handCarousel()
}
//手动滑动时暂停自动滑动
private fun handCarousel() {
carouselViewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
if (state == ViewPager2.SCROLL_STATE_DRAGGING) {
handler.removeCallbacks(runnable)
} else {
if (state == ViewPager2.SCROLL_STATE_IDLE) {
handler.removeCallbacks(runnable) //需把之前的线程关闭掉,否则线程越来越多,自动切换速度越快
handler.postDelayed(runnable, 3000)
}
}
}
//当滑动到最后一张(实际上展示为第一张的内容)时,立刻跳到第一张,滑动到第0张(展示为最后一张)时,同理
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
mTvCarouselTitle.text = carouselInfo[position].title
if (position == carouselInfo.size - 1) {
dotView.changeDots(0)
carouselViewPager2.setCurrentItem(1, false)
} else if (position == 0) {
dotView.changeDots(carouselInfo.size - 1)
carouselViewPager2.setCurrentItem(carouselInfo.size - 2, false)
} else {
dotView.changeDots(position - 1)
}
}
})
}
//自动轮播
private fun autoCarousel() {
handler = Handler(Looper.getMainLooper())
runnable = object : Runnable {
override fun run() {
val nextPassage = carouselViewPager2.currentItem + 1
if(nextPassage == carouselInfo.size - 1) {
carouselViewPager2.setCurrentItem(1,false)
}
else {
carouselViewPager2.currentItem = nextPassage
}
handler.postDelayed(this, 3000)
}
}
handler.postDelayed(runnable, 3000)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if(viewType == TYPE_PASSAGE) {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.home_card_item, parent, false)
return PassageViewHolder(view)
} else {
val view = LayoutInflater.from(parent.context).inflate(R.layout.banner, parent, false)
return BannerViewHolder(view)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val passage = getItem(position)
when(holder) {
is PassageViewHolder -> {
if(passage.author != "") {
holder.passageAuthor.text = passage.author
holder.passageDate.text = passage.niceDate
} else {
holder.passageAuthor.text = passage.shareUser
holder.passageDate.text = passage.niceShareDate
}
val trueText = Html.fromHtml(passage.title, Html.FROM_HTML_MODE_COMPACT)
holder.passageTitle.text = trueText
holder.passageChapterName.text = passage.chapterName
//整个View的点击事件
holder.itemView.setOnClickListener {
val intent = Intent(context, WebViewActivity::class.java)
intent.putExtra("url", passage.link)
context.startActivity(intent)
}
}
is BannerViewHolder -> {
val carouselAdapter = CarouselViewPager2Adapter(context, carouselInfo)
holder.carouselViewPager2.adapter = carouselAdapter
holder.carouselViewPager2.currentItem = 1
holder.dotView.initDots(carouselInfo.size - 2, 0)
holder.mTvCarouselTitle.text = carouselInfo[1].title
}
}
}
override fun getItemViewType(position: Int): Int {
if(position == 0) {
return TYPE_BANNER
} else {
return TYPE_PASSAGE
}
}
}
这个与原来基本的RecyclerView
的区别,一是我们重写了一个getItemViewType()
方法,当当前position == 0
时,我们就要加载轮播图的布局,否则就加载文章数据的布局。然后在 onCreateViewHolder()
中,对不同的类型加载不同的布局并返回单独的ViewHolder()
,比如如果接受的viewType
是TYPE_BANNER
,就加载banner
的布局并进行banner
控件的绑定,反之亦然。然后在onBindViewHolder()
中,我们对当前holder
类型进行判断,如果是PassageViewHolder
就进行文章列表数据的加载,注意这里when()
方法可以自动将类型进行转换,比如这里可以转成PassageViewHolder
。
注意这里关于轮播图的自动播放和手动播放逻辑,一定要写在
BannerViewHolder()
里面,用init{}
代码块进行初始化加载。因为如果在onBindViewHolder()
中写的话会导致,往下滑后再往上滑,当轮播图重新进入页面时,会再次调用onBindViewHolder()
方法,就会添加很多线程,导致自动切换速度越来越快,而在BannerViewHolder
中就只会初始创建时调用一次。
这样就能实现了在RecyclerView
中加载不同的布局。