【Android、IOS、Flutter、鸿蒙、ReactNative 】轮播图

388 阅读6分钟

Android Java 实现轮播

创建适配器 ImageAdapter

ImageAdapter 用于将数据(轮播图)绑定到 ViewPage2上。

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.viewpage.two.fragment.ImageFragment;
import java.util.List;

public class ImageAdapter extends FragmentStateAdapter {
    private List<String> imageUrls;

    public ImageAdapter(@NonNull FragmentActivity fragmentActivity, List<String> imageUrls) {
        super(fragmentActivity);
        this.imageUrls = imageUrls;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        // 图片集合大小
        int imageUrlSize = imageUrls.size();
        // 创建一个包含图片的Fragment实例
        return ImageFragment.newInstance(imageUrls.get(position % imageUrlSize));
    }

    @Override
    public int getItemCount() {
        //return imageUrls.size();
        return Integer.MAX_VALUE;// 无限循环滚动
    }
}

添加 glide 依赖

dependencies {
    ......
    implementation("com.github.bumptech.glide:glide:4.15.1")
}

创建 ImageFragment

用于显示每一张图片。

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.bumptech.glide.Glide;

public class ImageFragment extends Fragment {
    private String imageUrl;

    public static ImageFragment newInstance(String imageUrl) {
        ImageFragment fragment = new ImageFragment();
        Bundle args = new Bundle();
        args.putString("imageUrl", imageUrl);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // 初始化并返回包含ImageView的View
        ImageView imageView = new ImageView(getContext());
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
        imageView.setLayoutParams(layoutParams);

        // 加载图片
        Glide.with(this).load(imageUrl).into(imageView);

        return imageView;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            imageUrl = getArguments().getString("imageUrl");
        }
    }
}

创建布局文件

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="@android:color/holo_purple"
        app:layout_constraintRight_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:id="@+id/indicatorContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="20dp"
        android:gravity="center"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="@+id/viewPager"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"></LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

展示轮播图

import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;

import com.viewpage.two.R;
import com.viewpage.two.adapter.ImageAdapter;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ViewPager2 viewPager;//轮播图组件
    private LinearLayout indicatorContainer;//指示器容器
    private List<String> imageUrls;//要加载的网络图片集合
    private int preIndicatorIndex = 0;//当前指示器索引

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

        viewPager = findViewById(R.id.viewPager);
        indicatorContainer = findViewById(R.id.indicatorContainer);

        imageUrls = getImageUrls(); // 获取图片链接列表

        initViewPager();
        initIndicator();
    }

    private void initViewPager() {
        ImageAdapter adapter = new ImageAdapter(this, imageUrls);
        viewPager.setAdapter(adapter);
        viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                // 图片集合大小
                int imageUrlSize = imageUrls.size();
                // 根据上一次ViewPage的索引修改指示器状态
                indicatorContainer.getChildAt(preIndicatorIndex % imageUrlSize).setSelected(false);
                // 根据ViewPage当前索引修改指示器状态
                indicatorContainer.getChildAt(position % imageUrlSize).setSelected(true);
                // 保持当前ViewPage索引
                preIndicatorIndex = position;
            }
        });
    }

    // 绑定指示器
    private void initIndicator() {
        // 添加指示点
        for (int i = 0; i < imageUrls.size(); i++) {
            View indicator = new View(this);
            // 指示点选中与非选中状态的drawable
            indicator.setBackgroundResource(R.drawable.selector_indicator);
            indicator.setLayoutParams(new LinearLayout.LayoutParams(50, 50));
            indicatorContainer.addView(indicator);
        }
    }

    List<String> getImageUrls() {
        List<String> list = new ArrayList<>();
        list.add("https://photo.zastatic.com/images/photo/272596/1090382741/3908457282187447162.png");
        list.add("https://photo.zastatic.com/images/photo/272596/1090382741/142465111773882534.png");
        list.add("https://photo.zastatic.com/images/photo/272596/1090382741/144651611773882078.png");
        return list;
    }

}

预览 轮播图

Android Kotlin 实现轮播

创建适配器 ImageAdapter

ImageAdapter 用于将数据(轮播图)绑定到 ViewPage2上。

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.kotlin.viewpage.fragment.ImageFragment

class ImageAdapter(fragmentActivity: FragmentActivity, private val imageUrls: List<String>) :
    FragmentStateAdapter(fragmentActivity) {
    override fun createFragment(position: Int): Fragment {
        // 图片集合大小
        val imageUrlSize = imageUrls.size
        // 创建一个包含图片的Fragment实例
        return ImageFragment.newInstance(imageUrls[position % imageUrlSize])
    }

    override fun getItemCount(): Int {
        //return imageUrls.size();
        return Int.MAX_VALUE // 无限循环滚动
    }
}

添加 glide 依赖

image.png

创建 ImageFragment

用于显示每一张图片。

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide


class ImageFragment : Fragment() {
    private var imageUrl: String? = null
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // 初始化并返回包含ImageView的View
        val imageView = ImageView(context)
        imageView.scaleType = ImageView.ScaleType.CENTER_CROP
        val layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
        imageView.layoutParams = layoutParams

        // 加载图片
        Glide.with(this).load(imageUrl).into(imageView)
        return imageView
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (arguments != null) {
            imageUrl = requireArguments().getString("imageUrl")
        }
    }

    companion object {
        fun newInstance(imageUrl: String?): ImageFragment {
            val fragment = ImageFragment()
            val args = Bundle()
            args.putString("imageUrl", imageUrl)
            fragment.arguments = args
            return fragment
        }
    }
}

创建布局文件

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="@android:color/holo_purple"
        app:layout_constraintRight_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:id="@+id/indicatorContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="20dp"
        android:gravity="center"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="@+id/viewPager"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"></LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

展示轮播图

import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.kotlin.viewpage.R
import com.kotlin.viewpage.adapter.ImageAdapter


class MainActivity : AppCompatActivity() {

    private var viewPager: ViewPager2? = null //轮播图组件
    private var indicatorContainer: LinearLayout? = null //指示器容器
    private var imageUrls: List<String>? = null //要加载的网络图片集合
    private var preIndicatorIndex = 0 //当前指示器索引

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewPager = findViewById(R.id.viewPager)
        indicatorContainer = findViewById(R.id.indicatorContainer)

        imageUrls = getImageUrls() // 获取图片链接列表
        initViewPager()
        initIndicator()
    }

    private fun initViewPager() {
        val adapter = imageUrls?.let { ImageAdapter(this, it) }
        viewPager!!.adapter = adapter
        viewPager!!.registerOnPageChangeCallback(object : OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                // 图片集合大小
                val imageUrlSize = imageUrls!!.size
                // 根据上一次ViewPage的索引修改指示器状态
                indicatorContainer!!.getChildAt(preIndicatorIndex % imageUrlSize).isSelected = false
                // 根据ViewPage当前索引修改指示器状态
                indicatorContainer!!.getChildAt(position % imageUrlSize).isSelected = true
                // 保持当前ViewPage索引
                preIndicatorIndex = position
            }
        })
    }

    // 绑定指示器
    private fun initIndicator() {
        // 添加指示点
        for (i in imageUrls!!.indices) {
            val indicator = View(this)
            // 指示点选中与非选中状态的drawable
            indicator.setBackgroundResource(R.drawable.selector_indicator)
            indicator.layoutParams = LinearLayout.LayoutParams(50, 50)
            indicatorContainer!!.addView(indicator)
        }
    }

    private fun getImageUrls(): List<String> {
        val list: MutableList<String> = ArrayList()
        list.add("https://photo.zastatic.com/images/photo/272596/1090382741/3908457282187447162.png")
        list.add("https://photo.zastatic.com/images/photo/272596/1090382741/142465111773882534.png")
        list.add("https://photo.zastatic.com/images/photo/272596/1090382741/144651611773882078.png")
        return list
    }

}

预览 轮播图

Andoid Compose 实现轮播

导入依赖包

dependencies {
    ......

    implementation ("androidx.activity:activity-compose:1.3.1")
    implementation("androidx.compose.material:material:1.4.3")
    implementation("androidx.compose.ui:ui-tooling:1.4.3")
}

启用Compose

image.png

Compose自定义轮播图

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LineIndicatorExample()
        }
    }

    @OptIn(ExperimentalFoundationApi::class)
    @Preview
    @Composable
    fun LineIndicatorExample() {

        val list = mutableListOf<Int>()
        list.add(R.drawable.image1)
        list.add(R.drawable.image2)
        list.add(R.drawable.image3)

        Box(modifier = Modifier.fillMaxWidth()) {
            val pageCount = list.size
            // 初始化页面
            val pagerState = rememberPagerState(initialPage = 0)
            // 滚到当前页面(实现了无限循环滚动)
            val pageCountIndex = remember {
                derivedStateOf {
                    (pagerState.currentPage - Int.MAX_VALUE / 2).floorMod(list.size)
                }
            }

            HorizontalPager(
                pageCount = Int.MAX_VALUE,//当前页面数量(方便实现无限循环滚动)
                beyondBoundsPageCount = 2,//当前页面前后预加载的页面数量
                state = pagerState
            ) { page ->
                Image(
                    painter = painterResource(id = list[page % pageCount]),
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(220.dp),
                    contentDescription = "img",
                    contentScale = ContentScale.Crop
                )
            }
            Row(
                Modifier
                    .height(24.dp)
                    .fillMaxWidth()
                    .background(Color.Yellow)
                    .align(Alignment.BottomCenter),
                horizontalArrangement = Arrangement.Center,//水平居中
                verticalAlignment = Alignment.CenterVertically,//垂直居中
            ) {
                repeat(pageCount) { iteration ->
                    val color = if (pageCountIndex.value == iteration) {
                        Color.Red
                    } else {
                        Color.Red.copy(alpha = 0.3f)
                    }
                    Box(
                        modifier = Modifier
                            .padding(4.dp)//指示器之间的距离
                            .clip(RoundedCornerShape(4.dp))
                            .background(color)
                            .width(8.dp)
                            .height(8.dp)
                    )
                }
            }
        }
    }

    private fun Int.floorMod(other: Int): Int = when (other) {
        0 -> this
        else -> this - floorDiv(other) * other
    }
}

预览效果

IOS Object-c 轮播图 参考 博客园 参考 githunb

自定义 CarouseView

用于显示轮播图。

image.png

展示轮播图

image.png

预览轮播图

IOS Swift 轮播图 Object-c转Swift

自定义 CarouseView

用于显示轮播图。

image.png

展示轮播图

image.png

预览轮播图

Flutter 轮播图

引入card_swiper 插件

image.png

实现轮播图

// 轮播图
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // 定义轮播图列表
  List<Widget> imgList = [];

  // 记录一下当前图片的索引,激活指示器背景色
  var curIndex = 0;
  final PageController _pageController = PageController(initialPage: 1);

  var imgUrls = [    "https://photo.zastatic.com/images/photo/272596/1090382741/3908457282187447162.png",    "https://photo.zastatic.com/images/photo/272596/1090382741/142465111773882534.png",    "https://photo.zastatic.com/images/photo/272596/1090382741/144651611773882078.png"  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('轮播图'),
          backgroundColor: Colors.blue,
        ),
        body: SizedBox(
            height: 200,
            child: Swiper(
              itemBuilder: (BuildContext context, int index) {
                return Container(
                  color: Colors.blue.withOpacity(0.2),
                  width: double.infinity,
                  height: double.infinity,
                  child: Image.network(
                    imgUrls[index],
                    fit: BoxFit.cover,
                  ),
                );
              },
              onTap: (index) {
                if (kDebugMode) {
                  print(index);
                }
              },
              itemCount: imgUrls.length,
              autoplay: true,
              pagination: const SwiperPagination(
                  builder: DotSwiperPaginationBuilder(
                      color: Color(0xFFFFFFFF),
                      activeColor: Color(0xFFFF4646))),
              // control: const SwiperControl(),//< >
            )));
  }
}

预览轮播图

鸿蒙 轮播图 参考 OpenHarmonyOS Swiper

@Entry
@Component
struct Index {

  private swiperController: SwiperController = new SwiperController();
  private swiperData: string[] = [
    'https://photo.zastatic.com/images/photo/272596/1090382741/3908457282187447162.png',
    'https://photo.zastatic.com/images/photo/272596/1090382741/142465111773882534.png',
    'https://photo.zastatic.com/images/photo/272596/1090382741/144651611773882078.png',
  ];

  build() {
    Column() {
      Swiper(this.swiperController) {
        ForEach(this.swiperData, (item: string) => {
          //内容区
          Image(item)
            .width('100%')
            .height(150)
            .borderRadius(10)
            .onClick(() => {
                console.log("图片被点击了",item)
            })
        })
      }
      .margin({ top: 10 })
      .width('95%')
      .loop(true)
      .autoPlay(true)
      // 指示器样式
      .indicatorStyle({
        selectedColor: Color.Red,
        bottom: 10,
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F3F5')
  }
}

image.png

ReactNative 轮播图

安装Swiper

npm install react-native-swiper --save

自定义 SwiperComponent 组件

import React, {useEffect} from 'react';
import {View, Image, StyleSheet, TouchableOpacity} from 'react-native';
import Swiper from 'react-native-swiper';
import {pxToPd} from './device';

const SlideItem = ({item, onClick}) => {

  return (
    <View style={styles.slide}>
      <TouchableOpacity
        style={styles.touchImage}>
        <Image source={{uri: item.image}} style={styles.image} />
      </TouchableOpacity>
    </View>
  );
};

function SwiperComponent({items}) {

  useEffect(() => {
    return () => {};
  }, []);
  return (
      <View style={styles.container}>
        <Swiper
          style={styles.wrapper}
          autoplay={true}
          observer= {true}
          observeParents={true}
          dotStyle={styles.dot}
          horizontal={true}
          activeDotStyle={styles.activeDot}>
          {items.map((item, index) => (
            <SlideItem key={index} item={item}/>
          ))}
        </Swiper>
      </View>
  );
}

const styles = StyleSheet.create({
  container: {
    width: '100%',
    height: pxToPd(400),
  },
  wrapper: {},
  dot: {
    backgroundColor: '#999',
  },
  activeDot: {
    backgroundColor: '#eb5747',
  },
  slide: {
    flex: 1,
  },
  image: {
    borderRadius: pxToPd(24),
    width: '93.6%',
    height: '100%',
    resizeMode: 'cover',
  },
  touchImage: {
    width: '100%',
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default SwiperComponent;

创建轮播图

import React, { useState,} from 'react';
import {AppRegistry,View,} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
// 轮播图
import SwiperComponent from './SwiperComponent';


const Swiper = () => {

  //轮播图
   const [banner, setBanner] = useState([
      {
        image:"https://photo.zastatic.com/images/photo/272596/1090382741/3908457282187447162.png",
        isClick: true,
      },
      {
        image:"https://photo.zastatic.com/images/photo/272596/1090382741/142465111773882534.png",
        isClick: false,
      },
      {
         image:"https://photo.zastatic.com/images/photo/272596/1090382741/144651611773882078.png",
         isClick: false,
      },
   ]);

  return (
    <View>
      <SwiperComponent items={banner} />
    </View>
  );
};

AppRegistry.registerComponent(appName, () => Swiper);

预览轮播图

轮播图案例

切换到分支 picture_carousel_2024_06_02

image.png