【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
相关推荐
皮皮林5511 天前
利用闲置 Mac 从零部署 OpenClaw 教程 !
java
Sinclair1 天前
简单几步,安卓手机秒变服务器,安装 CMS 程序
android·服务器
雮尘1 天前
手把手带你玩转Android gRPC:一篇搞定原理、配置与客户端开发
android·前端·grpc
ktl1 天前
Android 编译加速/优化 80%:一个文件搞定,零侵入零配置
android
华仔啊1 天前
挖到了 1 个 Java 小特性:var,用完就回不去了
java·后端
SimonKing1 天前
SpringBoot整合秘笈:让Mybatis用上Calcite,实现统一SQL查询
java·后端·程序员
alexhilton2 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
冬奇Lab2 天前
InputManagerService:输入事件分发与ANR机制
android·源码阅读
日月云棠2 天前
各版本JDK对比:JDK 25 特性详解
java
张小潇2 天前
AOSP15 Input专题InputManager源码分析
android·操作系统