安卓开发轮播图
安卓原生开发实现轮播图一般有两种方法,一种是直接使用ViewFlipper组件,另一种是基于ViewPager2实现轮播图。本文将对这两种方法进行讲解。ViewFlipper相较于ViewPager2用法比较简单,但是性能和效果相较ViewPager2就相对差一些,下面先来看ViewFlipper
ViewFlipper
ViewFlipper继承自VIewAnimator,而ViewAnimator继承自FrameLayout,是一个ViewGroup。它是安卓系统提供的原生ui组件,主要用于在多个子视图中实现带有动画效果的切换,常用于制作轮播图。作为一个ui组件,ViewFlipper的使用是比较简单的。
如果只需要轮播图片或者其他单个ui控件的话可以直接将imageview或控件放进ViewFlipper中,下面我们展示对布局文件进行轮播的例子。首先在布局中写一个ViewFlipper
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ViewFlipper
android:id="@+id/viewflipper"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
写完ViewFlipper后再写对应的子项布局文件,下面以一个TextView和ImageView的简单组合为例
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="180dp"
android:gravity="center"
android:textSize="30sp"
android:id="@+id/text"/>
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
android:id="@+id/image"/>
</LinearLayout>
这样轮播图ui方面基本就做好了,可以看到我们只是使用了一个ViewFlipper和对应要轮播的布局文件。下面是对应的活动的代码
kotlin
class MainActivity : AppCompatActivity() {
val bannerList = mutableListOf<BannerData>()
lateinit var viewFlipper : ViewFlipper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
viewFlipper = findViewById<ViewFlipper>(R.id.viewflipper)
viewFlipper.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.slide_out_right))
viewFlipper.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.slide_in_left))
initBannerData()
}
fun initBannerData() {
bannerList.add(BannerData("最爱的closer", R.drawable.closer))
bannerList.add(BannerData("马孔多的炼金术士", R.drawable.bainiangudu))
for (item in bannerList) {
val itemView = createBanner(item)
viewFlipper.addView(itemView)
}
startAutoFlipper()
}
private fun startAutoFlipper() {
viewFlipper.flipInterval = 2000
viewFlipper.startFlipping()
}
fun createBanner(bannerData: BannerData): View {
val inflater = LayoutInflater.from(this)
val itemView = inflater.inflate(R.layout.flipper_banner, viewFlipper, false)
val imageView = itemView.findViewById<ImageView>(R.id.image)
val textView = itemView.findViewById<TextView>(R.id.text)
imageView.setImageResource(bannerData.imagePath)
textView.setText(bannerData.text)
return itemView
}
}
首先初始化了一段数据作为轮播图的子项,后面设置将对应的布局逐个添加到flipper中,最后设置轮播间隔再进行startFlipping()即可完成轮播图。vieFLipper作为ViewAnimator的子类,内部封装了一些简单的过渡动画,比如代码上用的就是简单的平移动画。最后的效果如下。
kotlin
viewFlipper.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.slide_out_right))
viewFlipper.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.slide_in_left))

缺陷
ViewFlipper的优点就在于简单易操作,但是它在性能和功能上相对于ViewPager2实现的轮播图都较基础,具体来说,它不支持手势滑动操作,如需手动切换需自行实现触摸事件监听,且滑动体验较为基础,更重要的是性能限制:所有子视图一次性加载,当图片数量多或资源大时易引发内存问题,因此除了简单的定时切换展示,一般更建议使用下面的ViewPager2实现轮播图
ViewPager2实现轮播图
ViewPager2基于RecyclerView构建,可以享受RecyclerView的性能优化和复用机制,因此极大地提高了轮播图的性能,具体来说,性能优化的核心优势在下面两点
- 轮播图的每个页面通过
ViewHolder管理,当页面滑出屏幕时,对应的ViewHolder会被缓存到「复用池」中,而非直接销毁。当新页面滑入屏幕时,优先从复用池获取缓存的ViewHolder,仅更新数据(如图片资源、文本内容),避免重复执行inflate布局(耗时操作)和创建 View 对象。 - 只有当页面即将进入屏幕时(通过
onBindViewHolder),才会触发数据加载(如网络图片请求、数据绑定),避免一次性加载所有轮播项的数据。
同时,RecyclerVIew内部触摸机制也更加高效,可以帮助我们更简洁地实现一些用户触摸滑动的交互(例如触摸或者手动滑动时停止轮播)。缺点就是ViewPager2的轮播图实现比较繁琐,我们以一个商城app中常见的轮播图为例,下面详细看一下。

首先定义对应的布局文件,里面放入VIewPager2
xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:background="@color/white"
android:layout_height="wrap_content">
<androidx.viewpager2.widget.ViewPager2
android:background="@color/white"
android:layout_width="match_parent"
android:layout_height="55dp"
android:id="@+id/mine_viewpager2"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
下面定义ViewPager2所需的文件
xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:padding="2dp"
android:layout_height="match_parent">
<View
android:id="@+id/decor"
android:layout_width="20dp"
android:layout_height="0dp"
android:background="@color/yellow"
app:layout_constraintTop_toTopOf="@id/tips"
app:layout_constraintBottom_toBottomOf="@id/tips"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="26dp" />
<com.example.mybusyfish.CustomFontTextView
android:id="@+id/tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TIPS"
android:textSize="18sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/decor"
android:layout_margin="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="mine_content"
android:id="@+id/mine_content"
android:textSize="10sp"
app:layout_constraintTop_toTopOf="@id/tips"
app:layout_constraintBottom_toBottomOf="@id/tips"
app:layout_constraintStart_toEndOf="@id/tips"
android:layout_marginStart="11dp" />
<ImageView
android:id="@+id/right_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/right_arrow_black"
app:layout_constraintTop_toTopOf="@id/tips"
app:layout_constraintBottom_toBottomOf="@id/tips"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp" />
<ImageView
android:id="@+id/ic_tips"
android:layout_width="27dp"
android:layout_height="27dp"
app:layout_constraintTop_toTopOf="@id/tips"
app:layout_constraintBottom_toBottomOf="@id/tips"
app:layout_constraintEnd_toStartOf="@id/right_arrow"
android:layout_marginEnd="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
这个item文件中"Tips","完善信息"以及后面的小图片都是要进行动态替换的。因此下面还要定义一个数据类
kotlin
data class MyBanner (val name: String, val text: String, val path: Int)
既然是用ViewPager2,下面先写一个适配器。我们在Adapter的getItemCount()方法中返回一个极大的整数(如Integer.MAX_VALUE),并通过取模运算来决定每个位置显示哪张图片。
kotlin
class MineBannerAdapter(private val list: List<MyBanner>) : RecyclerView.Adapter<MineBannerAdapter.ViewHolder>() {
class ViewHolder(val binding: MineLunboBinding) : RecyclerView.ViewHolder(binding.root) {
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): MineBannerAdapter.ViewHolder {
val binding = MineLunboBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: MineBannerAdapter.ViewHolder, position1: Int) {
var position = position1 % list.size
holder.binding.tips.text = list[position].name
holder.binding.mineContent.text = list[position].text
holder.binding.icTips.setImageResource(list[position].path)
}
override fun getItemCount(): Int {
return Int.MAX_VALUE
}
}
下面是活动中的代码,因为ViewPager2其实并没有内置轮播图,因此需要用代码逻辑实现定时播放,可以用Handler的延时机制或者Timer实现定时。Handler自动在主线程执行。下面就用Handler演示一下。
kotlin
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var bannerAdapter: MineBannerAdapter
private lateinit var handler: Handler
private val bannerList = listOf(
MyBanner("TIPS", "你的个人信息待完善", R.drawable.bianji),
MyBanner("TIPS", "淘宝买的宝贝看看还值多少钱", R.drawable.taobao),
MyBanner("上新", "你有宝贝落灰啦,快翻新一下卖的更快", R.drawable.gouwu)
)
companion object {
private const val AUTO_SCROLL_DELAY = 3000L
private const val INITIAL_POSITION = 1000
private const val RESUME_DELAY = 3000L // 用户交互后延迟3秒再恢复自动轮播
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
initDataBindingComponents()
setupViewPager()
startAutoScroll()
}
private fun initDataBindingComponents() {
handler = Handler(Looper.getMainLooper())
}
private fun setupViewPager() {
bannerAdapter = MineBannerAdapter(bannerList)
binding.viewPager2.adapter = bannerAdapter
binding.viewPager2.setCurrentItem(INITIAL_POSITION, false)
binding.viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrollStateChanged(state: Int) {
handleScrollStateChange(state)
}
override fun onPageSelected(position: Int) {
}
})
binding.viewPager2.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
stopAutoScroll()
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
// 触摸结束后延迟恢复自动轮播
handler.postDelayed({
startAutoScroll()
}, RESUME_DELAY)
}
}
false
}
}
private fun handleScrollStateChange(state: Int) {
when (state) {
ViewPager2.SCROLL_STATE_DRAGGING -> {
stopAutoScroll()
}
ViewPager2.SCROLL_STATE_SETTLING -> {
stopAutoScroll()
}
ViewPager2.SCROLL_STATE_IDLE -> {
// 滑动完全停止后,延迟恢复自动轮播
handler.postDelayed({
startAutoScroll()
}, RESUME_DELAY)
}
}
}
private val autoScrollRunnable = object : Runnable {
override fun run() {
val currentItem = binding.viewPager2.currentItem
binding.viewPager2.setCurrentItem(currentItem + 1, true)
handler.postDelayed(this, AUTO_SCROLL_DELAY)
}
}
private fun startAutoScroll() {
stopAutoScroll()
handler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY)
}
private fun stopAutoScroll() {
handler.removeCallbacks(autoScrollRunnable)
}
override fun onResume() {
super.onResume()
startAutoScroll()
}
override fun onPause() {
super.onPause()
stopAutoScroll()
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(autoScrollRunnable)
}
}
上面代码比较多,算是ViewPager2实现轮播图的缺点,我们重点看一下核心代码
kotlin
private val autoScrollRunnable = object : Runnable {
override fun run() {
val currentItem = binding.viewPager2.currentItem
binding.viewPager2.setCurrentItem(currentItem + 1, true)
handler.postDelayed(this, AUTO_SCROLL_DELAY)
}
}
private fun startAutoScroll() {
stopAutoScroll()
handler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY)
}
private fun stopAutoScroll() {
handler.removeCallbacks(autoScrollRunnable)
}
这里先通过Runnable定义了延迟执行的事件,即ViewPager2的翻页动作,该事件会进行递归调用一直轮询下去。然后调用postDelayed()方法进行延迟播放,该方法第一个参数是要执行的事件,第二个参数是延迟的时间,上面定义该常量为3秒。
另外一开始定义了private const val INITIAL_POSITION = 1000并调用binding.viewPager2.setCurrentItem(INITIAL_POSITION, false),将目前页数设置为1000,这是保证用户能向前滑动。如果一开始从0开始,轮播图可以正常轮播,但是用户如果想要向前滑动就做不到了。
再看三个生命周期的回调方法中,onResume中执行startAutoScroll()是为了确保用户离开该界面后重新返回界面可以继续开始轮播,
onPause中停止则是离开界面、该app进入后台时暂停轮播图以免耗电,onDestroy中调用removeCallbacks是因为,如果Handler中有未处理的延迟消息或Runnable,即使Activity已经被销毁,消息队列仍然持有Handler的引用,而Handler又持有Activity的引用,导致Activity无法被垃圾回收器回收,从而会造成内存泄漏。
kotlin
override fun onResume() {
super.onResume()
startAutoScroll()
}
override fun onPause() {
super.onPause()
stopAutoScroll()
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(autoScrollRunnable)
}
另外的代码则是对用户交互进行处理,当用户点击或者拖动时停止轮播,当用户点击或者拖动时继续轮播。
kotlin
binding.viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrollStateChanged(state: Int) {
handleScrollStateChange(state)
}
override fun onPageSelected(position: Int) {
}
})
binding.viewPager2.setOnTouchListener { _, event ->
when (event.action) {
//用户点下,或者移动时停止
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
stopAutoScroll()
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
// 触摸结束后延迟恢复自动轮播
handler.postDelayed({
startAutoScroll()
}, RESUME_DELAY)
}
}
false
}
private fun handleScrollStateChange(state: Int) {
when (state) {
//用户进行拖动时停止
ViewPager2.SCROLL_STATE_DRAGGING -> {
stopAutoScroll()
}
ViewPager2.SCROLL_STATE_SETTLING -> {
stopAutoScroll()
}
ViewPager2.SCROLL_STATE_IDLE -> {
// 滑动完全停止后,延迟恢复自动轮播
handler.postDelayed({
startAutoScroll()
}, RESUME_DELAY)
}
}
}
做好这些工作后,轮播图就能正常工作了。我们就得到了一个更高性能的轮播图了
总结
ViewPager2基于RecyclerVIew有着更好的性能,尽管在使用上对开发者来说较ViewFlipper更为繁琐,但是这对于app的性能是值得的。除了少数情况下轮播内容仅限于极简单的内容,更多时候仍然推荐以ViewPage2的形式实现轮播图