当前位置: 首页 > news >正文

手机App上的轮播图是如何实现的—探究安卓轮播图

安卓开发轮播图

安卓原生开发实现轮播图一般有两种方法,一种是直接使用ViewFlipper组件,另一种是基于ViewPager2实现轮播图。本文将对这两种方法进行讲解。ViewFlipper相较于ViewPager2用法比较简单,但是性能和效果相较ViewPager2就相对差一些,下面先来看ViewFlipper

ViewFlipper

ViewFlipper继承自VIewAnimator,而ViewAnimator继承自FrameLayout,是一个ViewGroup。它是安卓系统提供的原生ui组件,主要用于在多个子视图中实现带有动画效果的切换,常用于制作轮播图。作为一个ui组件,ViewFlipper的使用是比较简单的。

如果只需要轮播图片或者其他单个ui控件的话可以直接将imageview或控件放进ViewFlipper中,下面我们展示对布局文件进行轮播的例子。首先在布局中写一个ViewFlipper

<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayoutxmlns: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"><ViewFlipperandroid:id="@+id/viewflipper"android:layout_width="match_parent"android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

写完ViewFlipper后再写对应的子项布局文件,下面以一个TextView和ImageView的简单组合为例

<?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"><TextViewandroid:layout_width="match_parent"android:layout_height="180dp"android:gravity="center"android:textSize="30sp"android:id="@+id/text"/><ImageViewandroid:layout_width="200dp"android:layout_height="200dp"android:layout_gravity="center_horizontal"android:id="@+id/image"/></LinearLayout>

这样轮播图ui方面基本就做好了,可以看到我们只是使用了一个ViewFlipper和对应要轮播的布局文件。下面是对应的活动的代码

class MainActivity : AppCompatActivity() {val bannerList = mutableListOf<BannerData>()lateinit var viewFlipper : ViewFlipperoverride 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 = 2000viewFlipper.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的子类,内部封装了一些简单的过渡动画,比如代码上用的就是简单的平移动画。最后的效果如下。

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 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.ConstraintLayoutandroid:layout_width="match_parent"android:background="@color/white"android:layout_height="wrap_content"><androidx.viewpager2.widget.ViewPager2android:background="@color/white"android:layout_width="match_parent"android:layout_height="55dp"android:id="@+id/mine_viewpager2"/></androidx.constraintlayout.widget.ConstraintLayout>
</layout>

下面定义ViewPager2所需的文件

<?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.ConstraintLayoutandroid:layout_width="match_parent"android:padding="2dp"android:layout_height="match_parent"><Viewandroid: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.CustomFontTextViewandroid: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" /><TextViewandroid: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" /><ImageViewandroid: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" /><ImageViewandroid: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”,“完善信息”以及后面的小图片都是要进行动态替换的。因此下面还要定义一个数据类

data class MyBanner (val name: String, val text: String, val path: Int)

既然是用ViewPager2,下面先写一个适配器。我们在Adapter的getItemCount()方法中返回一个极大的整数(如Integer.MAX_VALUE),并通过取模运算来决定每个位置显示哪张图片。

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.sizeholder.binding.tips.text = list[position].nameholder.binding.mineContent.text = list[position].textholder.binding.icTips.setImageResource(list[position].path)}override fun getItemCount(): Int {return Int.MAX_VALUE}
}

下面是活动中的代码,因为ViewPager2其实并没有内置轮播图,因此需要用代码逻辑实现定时播放,可以用Handler的延时机制或者Timer实现定时。Handler自动在主线程执行。下面就用Handler演示一下。

class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingprivate lateinit var bannerAdapter: MineBannerAdapterprivate lateinit var handler: Handlerprivate 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 = 3000Lprivate const val INITIAL_POSITION = 1000private 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 = bannerAdapterbinding.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.currentItembinding.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实现轮播图的缺点,我们重点看一下核心代码

private val autoScrollRunnable = object : Runnable {override fun run() {val currentItem = binding.viewPager2.currentItembinding.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无法被垃圾回收器回收,从而会造成内存泄漏。

override fun onResume() {super.onResume()startAutoScroll()
}override fun onPause() {super.onPause()stopAutoScroll()
}override fun onDestroy() {super.onDestroy()handler.removeCallbacks(autoScrollRunnable)
}

另外的代码则是对用户交互进行处理,当用户点击或者拖动时停止轮播,当用户点击或者拖动时继续轮播。

       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的形式实现轮播图

http://www.dtcms.com/a/541449.html

相关文章:

  • Day71 MQTT数据上传与ARM端交叉编译部署全链路实践
  • 2024年 5月 每日分享
  • 漯河英文网站建设创建私人网站
  • 网站开发学哪一个好电子商务网站建设模板
  • 5.4 大数据方法论与实践指南-存储-安全合规
  • 凡科建设网站图片怎么删除专门做自由行的网站
  • 车辆智能检索与缉查布控
  • Cesium中如何修改geojson数据的颜色
  • 51c大模型~合集42
  • C++语法—类的声明和定义
  • 企业网站建设方案资讯查询公司的网站
  • 高端企业网站建设公司免费网站代理访问
  • JavaEE初阶——多线程(4)线程安全
  • 杭州网站推广营销服务深圳做美颜相机的公司
  • 什么样 个人网站 备案适合做网站的软件有哪些
  • 做设计在哪个网站上找高清图青岛做物流网站
  • 数据源切换的陷阱:Spring Boot中@Transactional与@DS注解的冲突博弈与破局之道
  • Kubernetes节点资源优化:自托管代理配置实践
  • 1688网站怎样做推广东莞市路桥收费所
  • 做网站需要用到哪些开发软件潜江资讯网信息发布
  • Day2实战-元组的基本操作
  • 01 数学建模中M的取值影响及分析
  • 深入 Actix-web 源码:解密 Rust Web 框架的高性能内核
  • Linux远程控制Windows桌面的cpolar实战指南
  • 焦作网站建设哪家好自己怎么用h5做网站
  • 论坛程序做导航网站photoshop安卓版
  • FP16 vs INT8:Llama-2-7b 昇腾 NPU 精度性能基准报告
  • Steering Llama 2 via Contrastive Activation Addition
  • 座舱出行Agent实战(三):专能化架构如何实现效率与稳定性的双重飞跃
  • 淘宝联盟怎么新建网站网站设计教程