【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
相关推荐
杉氧1 小时前
副作用 (Side Effects) 全攻略:如何像大师一样掌控 Composable 的生命周期?
android·架构·android jetpack
人活一口气4 小时前
从JVM调优到MCP协议:Java全栈技术体系深度总结与企业级架构实践
java·spring boot
NE_STOP5 小时前
Vibe Coding -- 完整项目案例实操
java
荣码5 小时前
GraphRAG:普通RAG只能回答"点"的问题,我踩了4个坑才搞懂
java·python
SimonKing5 小时前
Google第三方授权登录
java·后端·程序员
明月光8185 小时前
从一行 @Builder 说起:重新拾起 Java 的 Lombok、注解与 Builder 模式
java
Kapaseker6 小时前
Kotlin Toolchain 0.11 发布:主要是把 Amper 干没了
android·kotlin
三少爷的鞋7 小时前
Android 现代架构不需要事件总线进阶篇
android
考虑考虑15 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯15 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式