【Android】Viewpager2实现无限轮播图

【Android】Viewpager2实现无限轮播图

文章目录

🏇先上效果图

这就类似于常用软件首页的广告轮播图,这里只是简单显示了几张图片,当然你还可以自定义更精美的子项布局来实现想要的效果。

🎍使用步骤

🏀step1 添加依赖

首先在app/build.gradle文件中添加依赖:

复制代码
implementation 'androidx.viewpager2:viewpager2:1.0.0'
    implementation 'me.relex:circleindicator:2.1.6'

🍔step2 自定义RecyclerView.Adapter

java 复制代码
package com.example.viewpager2test;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

public class CarouselAdapter extends RecyclerView.Adapter<CarouselAdapter.ViewHolder> {

    private final int[] imageResources; // 图片资源数组

    public CarouselAdapter(int[] imageResources) {
        this.imageResources = imageResources;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_carousel, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.imageView.setImageResource(imageResources[position]);
    }

    @Override
    public int getItemCount() {
        return imageResources.length;
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView imageView;

        ViewHolder(View view) {
            super(view);
            imageView = view.findViewById(R.id.imageView);
        }
    }
}

其实Viewpager2的底层是基于RecyclerView实现的,所以用法基本上差不多,都是要自定义适配器和子项布局,在适配器中实现布局加载,控件的初始化和数据绑定等操作。

🚗step3 在页面中使用

java 复制代码
package com.example.viewpager2test;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;

import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ViewPager2 viewPager;
    private LinearLayout indicatorLayout;
    private BannerAdapter adapter;
    private Handler handler = new Handler(Looper.getMainLooper());
    private Runnable autoScrollRunnable;
    private int currentPage = 0;
    private static final long AUTO_SCROLL_DELAY = 2000; // 3秒轮播间隔
    private static final int INITIAL_POSITION = 1000; // 初始位置

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EdgeToEdge.enable(this);

        // 初始化视图
        viewPager = findViewById(R.id.viewPager);
        indicatorLayout = findViewById(R.id.indicatorLayout);

        // 创建图片资源列表
        List<Integer> images = new ArrayList<>();
        images.add(R.drawable.image1);
        images.add(R.drawable.image2);
        images.add(R.drawable.image3);
        images.add(R.drawable.image4);

        // 设置适配器
        adapter = new BannerAdapter(images);
        viewPager.setAdapter(adapter);

        // 设置初始位置(实现无限循环)
        viewPager.setCurrentItem(INITIAL_POSITION, false);
        currentPage = INITIAL_POSITION;

        // 添加指示器
        setupIndicators(images.size());

        // 设置页面变化监听器
        viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                currentPage = position;
                updateIndicators(position % images.size());
            }
        });

        // 设置自动轮播
        autoScrollRunnable = new Runnable() {
            @Override
            public void run() {
                if (currentPage == adapter.getItemCount() - 1) {
                    currentPage = INITIAL_POSITION;
                    viewPager.setCurrentItem(currentPage, false);
                } else {
                    viewPager.setCurrentItem(++currentPage, true);
                }
                handler.postDelayed(this, AUTO_SCROLL_DELAY);
            }
        };

        // 触摸暂停功能
        viewPager.setOnTouchListener((v, event) -> {
            stopAutoScroll();
            return false;
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        startAutoScroll();
    }

    @Override
    protected void onPause() {
        super.onPause();
        stopAutoScroll();
    }

    private void setupIndicators(int count) {
        indicatorLayout.removeAllViews();
        for (int i = 0; i < count; i++) {
            ImageView indicator = new ImageView(this);
            indicator.setImageResource(i == 0 ? R.drawable.indicator_active : R.drawable.indicator_inactive);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                    dpToPx(8), dpToPx(8)
            );
            params.setMargins(dpToPx(4), 0, dpToPx(4), 0);
            indicator.setLayoutParams(params);
            indicatorLayout.addView(indicator);
        }
    }

    private void updateIndicators(int position) {
        for (int i = 0; i < indicatorLayout.getChildCount(); i++) {
            ImageView indicator = (ImageView) indicatorLayout.getChildAt(i);
            indicator.setImageResource(i == position ?
                    R.drawable.indicator_active : R.drawable.indicator_inactive);
        }
    }

    private void startAutoScroll() {
        handler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY);
    }

    private void stopAutoScroll() {
        handler.removeCallbacks(autoScrollRunnable);
    }

    private int dpToPx(int dp) {
        float density = getResources().getDisplayMetrics().density;
        return Math.round(dp * density);
    }

    // 适配器类
    static class BannerAdapter extends RecyclerView.Adapter<BannerAdapter.ViewHolder> {

        private final List<Integer> images;

        public BannerAdapter(List<Integer> images) {
            this.images = images;
        }

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_banner, parent, false);
            return new ViewHolder(view);
        }

        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            int realPosition = position % images.size();
            holder.imageView.setImageResource(images.get(realPosition));
        }

        @Override
        public int getItemCount() {
            return Integer.MAX_VALUE; // 实现无限循环
        }

        static class ViewHolder extends RecyclerView.ViewHolder {
            ImageView imageView;

            public ViewHolder(@NonNull View itemView) {
                super(itemView);
                imageView = itemView.findViewById(R.id.imageView);
            }
        }
    }
}

关键点分析

无限循环原理
java 复制代码
// 自动轮播系统
autoScrollRunnable = new Runnable() {
    @Override
    public void run() {
        if (currentPage == adapter.getItemCount() - 1) {
            currentPage = INITIAL_POSITION;
            viewPager.setCurrentItem(currentPage, false); // 无动画跳转
        } else {
            viewPager.setCurrentItem(++currentPage, true); // 平滑滚动
        }
        handler.postDelayed(this, AUTO_SCROLL_DELAY);
    }
};
java 复制代码
@Override
        public int getItemCount() {
            return Integer.MAX_VALUE; // 实现无限循环
        }

通过设置Integer的最大值(2147483646)达到无限循环效果,同时通过setCurrentItem(INITIAL_POSITION)设定初始位置在索引等于1000的位置确保起始点在中间位置,每次轮播时通过position % images.size()计算实际索引获取真实图片位置。

🍟如何自定义Indicator

java 复制代码
private void setupIndicators(int count) {
        indicatorLayout.removeAllViews();
        for (int i = 0; i < count; i++) {
            ImageView indicator = new ImageView(this);
            indicator.setImageResource(i == 0 ? R.drawable.indicator_active : R.drawable.indicator_inactive);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                    dpToPx(8), dpToPx(8)
            );
            params.setMargins(dpToPx(4), 0, dpToPx(4), 0);
            indicator.setLayoutParams(params);
            indicatorLayout.addView(indicator);
        }
    }

    private void updateIndicators(int position) {
        for (int i = 0; i < indicatorLayout.getChildCount(); i++) {
            ImageView indicator = (ImageView) indicatorLayout.getChildAt(i);
            indicator.setImageResource(i == position ?
                    R.drawable.indicator_active : R.drawable.indicator_inactive);
        }
    }

🛌内置IndicatorView使用方法介绍,没有提供任何自定义属性

方法名 描述
setIndicatorRadius(float indicatorRadius) 设置圆点半径
setIndicatorSpacing(float indicatorSpacing) 设置圆点间距
setIndicatorStyle(@IndicatorStyle int indicatorStyle) 设置圆点切换动画,内置五种切换动画
setIndicatorColor(@ColorInt int indicatorColor) 设置默认的圆点颜色
setIndicatorSelectorColor(@ColorInt int indicatorSelectorColor) 设置选中的圆点颜色
setParams(RelativeLayout.LayoutParams params) 设置IndicatorView在banner中的位置,默认底部居中,距离底部10dp
setIndicatorRatio(float indicatorRatio) 设置indicator比例,拉伸圆为矩形,设置越大,拉伸越长,默认1.0
setIndicatorSelectedRadius(float indicatorSelectedRadius) 设置选中的圆角,默认和indicatorRadius值一致,可单独设置选中的点大小
setIndicatorSelectedRatio(float indicatorSelectedRatio) 设置选中圆比例,拉伸圆为矩形,控制该比例,默认比例和indicatorRatio一致,默认值1.0
atorRatio(float indicatorRatio) 设置indicator比例,拉伸圆为矩形,设置越大,拉伸越长,默认1.0
setIndicatorSelectedRadius(float indicatorSelectedRadius) 设置选中的圆角,默认和indicatorRadius值一致,可单独设置选中的点大小
setIndicatorSelectedRatio(float indicatorSelectedRatio) 设置选中圆比例,拉伸圆为矩形,控制该比例,默认比例和indicatorRatio一致,默认值1.0
相关推荐
百锦再5 分钟前
国产数据库替代MongoDB的技术实践:金仓数据库赋能浙江省人民医院信息化建设新展望
java·开发语言·数据库·mongodb·架构·eclipse·maven
武子康6 分钟前
Java-160 MongoDB副本集部署实战 单机三实例/多机同法 10 分钟起集群 + 选举/读写/回滚全流程
java·数据库·sql·mongodb·性能优化·系统架构·nosql
sinat_2869451935 分钟前
Java事故排查
java·开发语言
Pluchon37 分钟前
硅基计划2.0 学习总结 玖 图书管理系统 2.0复盘版(文字末尾源码可复制)
java·学习·项目·源码可复制
摇滚侠38 分钟前
Spring Boot3零基础教程,事件驱动开发,设计登录成功后增加积分记录信息功能,笔记61
java·spring boot·笔记·后端
凯子坚持 c1 小时前
Docker LXC深度解析:从基础概念到实战演练
java·开发语言
m0_748233641 小时前
【C++篇】C++11入门:踏入C++新世界的大门
java·c++·算法
SWUT胖虎1 小时前
ArkTS 中 @State 底层原理详解
java·list·harmonyos·鸿蒙
SimonKing1 小时前
【开发者必备】Spring Boot 2.7.x:WebMvcConfigurer配置手册来了(四)!
java·后端·程序员
BAGAE1 小时前
HTTPS 加密原理介绍
java·c++·websocket·http·均值算法·启发式算法·最小二乘法