【Android】使用ViewPager2实现简单的轮播图

一、轮播图的简单介绍

轮播图(Carousel)是一种在有限空间内循环展示多个内容项的UI组件,用户可以通过滑动或自动播放的方式浏览不同的内容,在有限的空间中展示更多的内容。

实现轮播图主要有两种方式,首尾添加法和取余法,这里主要讲解首尾添加法的实现。

二、轮播图的实现步骤

1.绘制轮播图页面

添加一个ViewPager2作为轮播图的容器,并添加LinearLayout放置指示图标

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">
​
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager2"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </androidx.viewpager2.widget.ViewPager2>
<LinearLayout
    android:id="@+id/botttom_viewpager2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:layout_marginBottom="100dp"
    app:layout_constraintBottom_toBottomOf="@+id/viewpager2"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="@+id/viewpager2"
   >
</LinearLayout>
​
</androidx.constraintlayout.widget.ConstraintLayout>

2.设置ViewPager2的子布局

新建xml文件,绘制ViewPager2的子布局

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="match_parent">
​
    <ImageView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"/>
</androidx.constraintlayout.widget.ConstraintLayout>
​

3.编写ViewPager2的适配器

因为ViewPager2实现基于RecyclerView,因此这里直接继承RecyclerView.Adapter

这里我们使用Glide来加载图片。

Glide 是一个专注于平滑滚动的 Android 图片加载库。

优点:

1.使用简单,并且支持多种图片格式,既可以获取网络资源又可以获取本地资源,不用处理网络请求、缓存、内存管理、图片解码等复杂逻辑;

2.性能优秀:自动管理 Bitmap 内存,防止内存溢出,可以自动根据ImageView调整图片大小,节省内存。

JAVA 复制代码
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ImageViewHolder> {
    private List<Integer> images;
    private Context context;
    public ImageAdapter(List<Integer> images){
        this.images=images;
    }
    public int getItemCount(){
        if(images!=null){
            return images.size();
        }
        return 0;
    }
    public ImageViewHolder onCreateViewHolder(ViewGroup parent,int viewType){
        context=parent.getContext();
        View view= LayoutInflater.from(context).inflate(R.layout.layout,parent,false);
        return new ImageViewHolder(view);
    }
    public void onBindViewHolder(ImageViewHolder holder,int position){
        Glide.with(context).load(images.get(position)).into(holder.imageView);
    }
    public static class ImageViewHolder extends RecyclerView.ViewHolder{
        ImageView imageView;
        public ImageViewHolder(View view){
            super(view);
            imageView=view.findViewById(R.id.image);
        }
    }
​

4.编写Carousel类具体实现轮播图

java 复制代码
public class Carousel {
    private Context mContext;
    private LinearLayout dotLinearLayout;
    private List<ImageView> mImageList=new ArrayList<>();//标志点集合
    private List<Integer> originalImages=new ArrayList<>();//轮播图图片资源id集合
    private ViewPager2 viewPager2;
    public Carousel(Context mContext, LinearLayout dotLinearLayout, ViewPager2 viewPager2){
        this.mContext=mContext;
        this.dotLinearLayout=dotLinearLayout;
        this.viewPager2=viewPager2;
    }
    public void initViews(int[] resourse){
        //初始化轮播图图片资源id集合
        for(int id:resourse){
            originalImages.add(id);
          ImageView dotImageView=new ImageView(mContext);//制作标记点的ImageView
          if(originalImages.size()==1){
              dotImageView.setImageResource(R.drawable.blue_dot);//加载第一张图片的标志
          }else {
              dotImageView.setImageResource(R.drawable.grey_dot);
          }
           LinearLayout.LayoutParams dotImageLayoutParams=new LinearLayout.LayoutParams(60,60);
          dotImageLayoutParams.setMargins(5,0,5,0);
           //设置 dotImageView的布局参数
          dotImageView.setLayoutParams(dotImageLayoutParams);
          mImageList.add(dotImageView);
            //将视图动态添加到布局容器
          dotLinearLayout.addView(dotImageView);
        }
        //将最后一张图片添加到开头,将第一张图片添加到末尾,可以更好实现无限轮播的视觉效果
        originalImages.add(0,originalImages.get(originalImages.size()-1));
        originalImages.add(originalImages.get(1));
        //设置适配器
        ImageAdapter adapter=new ImageAdapter(originalImages);
        viewPager2.setAdapter(adapter);
        viewPager2.setCurrentItem(1,false);
        //设置ViewPager2的页面更改监听器,处理标志点和边界情况
        viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position){
                for(int i=0;i<mImageList.size();i++){
                    if(i==position-1){
                        mImageList.get(i).setImageResource(R.drawable.blue_dot);
                    }else{
                        mImageList.get(i).setImageResource(R.drawable.grey_dot);
                    }
                }
                //滑动到最后一个元素时跳转到第一个元素,滑动到第一个元素时跳转到最后一个元素
                if(position==originalImages.size()-1){
                    viewPager2.setCurrentItem(1,false);
                } else if (position==0) {
                    viewPager2.setCurrentItem(originalImages.size()-2,false);
                }
            }
        });
    }
}

在这里简单解释一下取余法的实现原理

取余法实现

设置一个很大的数,通过取余运算获取真实位置,以实现无限循环的效果

less 复制代码
        真实数据: [A, B, C] (3个元素)
        虚拟位置: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ... (无限)
        通过取余: position % 3 = 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, ...
        对应数据:  A, B, C, A, B, C, A, B, C, A, ...

公式: 真实位置 = 虚拟位置 % 图片数量

开始时从中间位置开始,保证有足够的空间向左滑动,并且避免在边界处出现问题

优点:实现简单,不需要修改数据源

缺点:虽然虚拟位置很大,但RecyclerView会回收视图

5.实现自动轮播

使用Handler实现自动轮播效果,在初始化Carousel时同时注册Handler

Java 复制代码
    private android.os.Handler handler;
    //设置是否自动播放
    private boolean AUTO_SCROLL=false;
    public Carousel(Context mContext, LinearLayout dotLinearLayout, ViewPager2 viewPager2){
        this.mContext=mContext;
        this.dotLinearLayout=dotLinearLayout;
        this.viewPager2=viewPager2;
        handler=new Handler(Looper.getMainLooper());
    }

在Carousel中编写控制Handler的方法

Java 复制代码
 //开启自动轮播
public void startAutoScrool(){
        handler.removeCallbacks(autoScrollRunnable);//移除之前的回调,防止多次启用
        handler.postDelayed(autoScrollRunnable,Banner_Time);
        AUTO_SCROLL=true;
    }
//在Runnable中处理自动滚动
    private final Runnable autoScrollRunnable=new Runnable() {
        @Override
        public void run() {
            int current=viewPager2.getCurrentItem();
            if(current==originalImages.size()-2){
                viewPager2.setCurrentItem(1,false);
            } else{
                viewPager2.setCurrentItem(current+1);
            }
           handler.postDelayed(this,Banner_Time);
        }
    };
    public void stopAutoScrool(){
        handler.removeCallbacks(autoScrollRunnable);
        AUTO_SCROLL=false;
    }

6.触摸时停止自动轮播

开启自动轮播后,用户就无法通过滑动参看想要的页面,因此我们还应实现触摸时停止自动轮播功能。

可以通过监听Viewpager2的滑动行为来实现触摸时停止自动轮播。

ViewPager2中定义了三种滚动状态:

Java 复制代码
三种滚动状态常量
// 在 ViewPager2 类中定义的常量
public static final int SCROLL_STATE_IDLE = 0;      // 空闲状态,当前没有滚动操作,页面完全静止
public static final int SCROLL_STATE_DRAGGING = 1;  // 拖动状态, 用户正在用手指拖动页面
public static final int SCROLL_STATE_SETTLING = 2;  // 自动滑动状态,页面正在自动滑动到目标位置

我们通过对三种状态进行分别处理来实现想要的效果。

Java 复制代码
 viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position){
                for(int i=0;i<mImageList.size();i++){
                    if(i==position-1){
                        mImageList.get(i).setImageResource(R.drawable.blue_dot);
                    }else{
                        mImageList.get(i).setImageResource(R.drawable.grey_dot);
                    }
                }
                if(position==originalImages.size()-1){
                    viewPager2.setCurrentItem(1,false);
                } else if (position==0) {
                    viewPager2.setCurrentItem(originalImages.size()-2,false);
                }
            }
​
            @Override
            public void onPageScrollStateChanged(int state) {
                super.onPageScrollStateChanged(state);
                if(state==ViewPager2.SCROLL_STATE_DRAGGING){
                    handler.removeCallbacks(autoScrollRunnable);//在拖动时停止自动滚动
                } 
                //在页面静止时
                else if (state==ViewPager2.SCROLL_STATE_IDLE&&AUTO_SCROLL) {
                    handler.removeCallbacks(autoScrollRunnable);
                    handler.postDelayed(autoScrollRunnable,Banner_Time);
                }
            }
        });

到这里我们就实现了简单轮播图的全部功能。

三、完整代码

下面是实现轮播图的完整代码,以作参考。

Java 复制代码
public class Carousel {
    private Context mContext;
    private LinearLayout dotLinearLayout;
    private List<ImageView> mImageList=new ArrayList<>();
    private List<Integer> originalImages=new ArrayList<>();
    private ViewPager2 viewPager2;
    private long Banner_Time=1000;
    private android.os.Handler handler;
    private boolean AUTO_SCROLL=false;
    public Carousel(Context mContext, LinearLayout dotLinearLayout, ViewPager2 viewPager2){
        this.mContext=mContext;
        this.dotLinearLayout=dotLinearLayout;
        this.viewPager2=viewPager2;
        handler=new Handler(Looper.getMainLooper());
    }
    public void initViews(int[] resourse){
        for(int id:resourse){
            originalImages.add(id);
          ImageView dotImageView=new ImageView(mContext);
          if(originalImages.size()==1){
              dotImageView.setImageResource(R.drawable.blue_dot);
          }else {
              dotImageView.setImageResource(R.drawable.grey_dot);
          }
          LinearLayout.LayoutParams dotImageLayoutParams=new LinearLayout.LayoutParams(60,60);
          dotImageLayoutParams.setMargins(5,0,5,0);
          dotImageView.setLayoutParams(dotImageLayoutParams);
          mImageList.add(dotImageView);
          dotLinearLayout.addView(dotImageView);
        }
        originalImages.add(0,originalImages.get(originalImages.size()-1));
        originalImages.add(originalImages.get(1));
        ImageAdapter adapter=new ImageAdapter(originalImages);
        viewPager2.setAdapter(adapter);
        viewPager2.setCurrentItem(1,false);
        viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position){
                for(int i=0;i<mImageList.size();i++){
                    if(i==position-1){
                        mImageList.get(i).setImageResource(R.drawable.blue_dot);
                    }else{
                        mImageList.get(i).setImageResource(R.drawable.grey_dot);
                    }
                }
                if(position==originalImages.size()-1){
                    viewPager2.setCurrentItem(1,false);
                } else if (position==0) {
                    viewPager2.setCurrentItem(originalImages.size()-2,false);
                }
            }
​
            @Override
            public void onPageScrollStateChanged(int state) {
                super.onPageScrollStateChanged(state);
                if(state==ViewPager2.SCROLL_STATE_DRAGGING){
                    handler.removeCallbacks(autoScrollRunnable);
                }
                else if (state==ViewPager2.SCROLL_STATE_IDLE&&AUTO_SCROLL) {
                    handler.removeCallbacks(autoScrollRunnable);
                    handler.postDelayed(autoScrollRunnable,Banner_Time);
                }
            }
        });
    }
    public void startAutoScrool(){
        handler.removeCallbacks(autoScrollRunnable);
        handler.postDelayed(autoScrollRunnable,Banner_Time);
        AUTO_SCROLL=true;
    }
    private final Runnable autoScrollRunnable=new Runnable() {
        @Override
        public void run() {
            int current=viewPager2.getCurrentItem();
            if(current==originalImages.size()-2){
                viewPager2.setCurrentItem(1,false);
            } else{
                viewPager2.setCurrentItem(current+1);
            }
           handler.postDelayed(this,Banner_Time);
        }
    };
    public void stopAutoScrool(){
        handler.removeCallbacks(autoScrollRunnable);
        AUTO_SCROLL=false;
    }
}
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ImageViewHolder> {
    private List<Integer> images;
    private Context context;
    public ImageAdapter(List<Integer> images){
        this.images=images;
    }
    public int getItemCount(){
        if(images!=null){
            return images.size();
        }
        return 0;
    }
    public ImageViewHolder onCreateViewHolder(ViewGroup parent,int viewType){
        context=parent.getContext();
        View view= LayoutInflater.from(context).inflate(R.layout.layout,parent,false);
        return new ImageViewHolder(view);
    }
    public void onBindViewHolder(ImageViewHolder holder,int position){
        Glide.with(context).load(images.get(position)).into(holder.imageView);
    }
    public static class ImageViewHolder extends RecyclerView.ViewHolder{
        ImageView imageView;
        public ImageViewHolder(View view){
            super(view);
            imageView=view.findViewById(R.id.image);
        }
    }
}

最终效果展示如下

相关推荐
武子康3 小时前
Java-145 深入浅出 MongoDB 基本操作详解:数据库查看、切换、创建集合与删除完整教程
java·数据库·sql·mysql·mongodb·性能优化·系统架构
练习时长一年3 小时前
Spring内置功能
java·前端·spring
铉铉这波能秀3 小时前
如何在Android Studio中使用Gemini进行AI Coding
android·java·人工智能·ai·kotlin·app·android studio
_Yoke3 小时前
Java 枚举多态在系统中的实战演进:从枚举策略到自动注册
java·springboot·策略模式
人生导师yxc3 小时前
Java中Mock的写法
java·开发语言
青岛少儿编程-王老师4 小时前
CCF编程能力等级认证GESP—C++5级—20250927
java·数据结构·c++
毕设源码-郭学长4 小时前
【开题答辩全过程】以 办公管理系统为例,包含答辩的问题和答案
java·eclipse
脑子慢且灵4 小时前
C语言与Java语言编译过程及文件类型
java·c语言·开发语言·汇编·编辑器
是2的10次方啊4 小时前
看完JDK、Spring官方架构图,我发现:大厂的图也就这样!
java