在写寒假考核时,主界面是由一个viewpager2
的轮播图和RecyclerView
的文章列表,但这样有个问题,当我们向上滑动列表时,轮播图并不会跟着滑动,这样非常影响视觉体验,之前的效果参考WanAndroid的README
的主页界面展示,那么我们应该如何解决这个问题呢?就是将轮播图也添加进RecyclerView
里面。效果如下:

实现
先新建一个banner.xml
布局,用于展示我们的轮播图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <?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
一起传进去:
1 2 3 4 5 6 7 8 9 10 11 12
| 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
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) } } }
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
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
中加载不同的布局。