• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Android实现轮播控件Banner

武飞扬头像
wings专栏
帮助1

背景

最近做需求要实现一个轮播图,最后通过Handler ViewPager实现了需求,所以把实现的过程总结一下,方便以后学习参考,以下是轮播图的效果:

轮播图

实现思路

  • 定时轮播
    利用Handler ViewPager,Handler发送定时消息切换ViewPager的页面

  • 无限轮播效果
    学新通

如果我们只是在自动轮播到最后一页 然后进行判断让切换到第一页 这样是可以实现轮播的效果,但是 有两个问题
切换从最后一页切换到第一页的时候有一个很明显的回滚效果 不是我们想要的
当我们手动滑动的时候 在第一页和最后一页的时候 无法继续左右滑动 因为已经没有下一页了

在第一页前面插入一个最后一页,在最后一页后面插入一个第一页,这样用户在第一页继续向右滑动时,便可滑动看到最后一页,同理,用户在最后一页向左滑动时便可滑动到第一页。

用户虽然只看到三张图片,实际上却是五张,当在view4的时候自动切换到view5时,进行判断让到切换到view2,这样造成的感觉就是最后一张下来是第一张,当在view2的时候切换到view1时,进行判断让切换到view4。

我们利用viewpage自带的方法切换界面立即切换没有滚动效果 当图片一样的时候是看不出图片变化的

setCurrentItem(int item, boolean smoothScroll)
第二个参数设置false 界面切换的时候无滚动效果 默认true

下面是代码实现
layout_banner.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.viewpager.widget.ViewPager
        android:id="@ id/banner_view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:layout_gravity="bottom"
        android:orientation="horizontal"
        android:gravity="center_vertical"
        android:background="#33000000">
        <!-- 标题-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:layout_marginStart="10dp"
            android:layout_gravity="start"
            android:id="@ id/banner_title"
            tools:text="title"/>
        <!-- 小圆点-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@ id/banner_indicator"
            android:orientation="horizontal"
            android:gravity="end"
            android:layout_marginEnd="10dp"
            android:padding="10dp">
        </LinearLayout>
    </LinearLayout>

</FrameLayout>
学新通

BannerViewPager.java

package com.ryd.banner.banner;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;

import com.ryd.banner.R;
import com.ryd.banner.Utils;

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

/**
 * @author : ruanyandong
 * @e-mail : ruanyandong@didiglobal.com
 * @date : 12/29/22 4:23 PM
 * @desc : com.ryd.banner
 */
public class BannerViewPager extends FrameLayout {

    private ViewPager viewPager;
    private TextView tvTitle;
    private LinearLayout indicatorGroup;
    private BannerAdapter adapter;
    private List<String> titles;//标题集合
    private List imageUrls;//图片数据
    private List<View> views;//轮播图显示
    private ImageView[] tips;//保存显示的小圆点
    private int count;//保存imageUrls的总数
    private int bannerTime = 2500;//轮播图的间隔时间
    private int currentItem = 0;//轮播图的当前选中项
    private long releaseTime = 0;//保存触发时手动滑动的时间 进行判断防止滑动之后立即轮播
    private final int START = 10;
    private final int STOP = 20;
    private Context context;
    private Handler handler;

    private final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            long now = System.currentTimeMillis();
            if (now - releaseTime > bannerTime - 500) {
                handler.sendEmptyMessage(START);
            } else {
                handler.sendEmptyMessage(STOP);
            }
        }
    };


    public BannerViewPager(Context context) {
        super(context);
    }

    public BannerViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        titles = new ArrayList<>();
        titles.add("标题1");
        titles.add("标题2");
        titles.add("标题3");
        imageUrls = new ArrayList();
        views = new ArrayList<>();
        init(context, attrs);
    }


    private void init(final Context context, AttributeSet attrs) {
        View view = LayoutInflater.from(context).inflate(R.layout.layout_banner, this);
        viewPager = (ViewPager) view.findViewById(R.id.banner_view_pager);
        tvTitle = (TextView) view.findViewById(R.id.banner_title);
        indicatorGroup = (LinearLayout) view.findViewById(R.id.banner_indicator);
        handler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case START:
                        viewPager.setCurrentItem(currentItem   1);
                        handler.removeCallbacks(runnable);
                        handler.postDelayed(runnable, bannerTime);
                        break;
                    case STOP:
                        releaseTime = 0;
                        handler.removeCallbacks(runnable);
                        handler.postDelayed(runnable, bannerTime);
                        break;
                }
            }
        };
    }

    /**
     * 初始化数据 以及拿到数据后的各种设置
     * 可以是网络地址 也可是项目图片数据
     *
     * @param imageUrls
     */
    public void setData(List<?> imageUrls) {
        this.imageUrls.clear();
        this.count = imageUrls.size();

        //this.imageUrls.add(imageUrls.get(count-1));
        this.imageUrls.addAll(imageUrls);
        //this.imageUrls.add(imageUrls.get(0));

        initIndicator();
        getShowView();
        setUI();
    }

    /**
     * 设置标题
     *
     * @param titles
     */
    public void setTitles(List<String> titles) {
        this.titles.clear();
        this.titles.addAll(titles);
    }

    /**
     * 设置小圆点指示器
     */
    private void initIndicator() {
        tips = new ImageView[count-2];
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        layoutParams.height = Utils.dip2px(context,10);
        layoutParams.width = Utils.dip2px(context,10);
        layoutParams.leftMargin = Utils.dip2px(context,5);// 设置点点点view的左边距
        layoutParams.rightMargin = Utils.dip2px(context,5);// 设置点点点view的右边距
        for (int i = 0; i < count-2; i  ) {
            ImageView imageView = new ImageView(context);
            if (i == 0) {
                imageView.setBackgroundResource(R.drawable.shape_circle_red);
            } else {
                imageView.setBackgroundResource(R.drawable.shape_circle_white);
            }

            tips[i] = imageView;
            indicatorGroup.addView(imageView, layoutParams);
        }
    }

    /**
     * 获取显示图片view
     */
    private void getShowView() {
        for (int i = 0; i < imageUrls.size(); i  ) {
            ImageView imageView = new ImageView(context);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            if (imageUrls.get(i) instanceof String) {

            } else {
                imageView.setImageResource((Integer) imageUrls.get(i));
            }
            views.add(imageView);
        }
    }

    /**
     * 设置UI
     */
    private void setUI() {
        adapter = new BannerAdapter();
        viewPager.setAdapter(adapter);
        viewPager.addOnPageChangeListener(onPageChangeLis);
        viewPager.setCurrentItem(1);
        handler.postDelayed(runnable, bannerTime);
    }


    // 第一页到第二页
    //  onPageScrollStateChanged: state 1
    //  onPageScrolled: position 0 positionOffset 0.031481482 positionOffsetPixels 34
    //  onPageScrolled: position 0 positionOffset 0.107407406 positionOffsetPixels 116
    //  onPageScrolled: position 0 positionOffset 0.18888889 positionOffsetPixels 204
    //  onPageScrolled: position 0 positionOffset 0.20462963 positionOffsetPixels 221
    //  onPageScrollStateChanged: state 2
    //  onPageSelected: position 1
    //  onPageScrolled: position 0 positionOffset 0.28148147 positionOffsetPixels 304
    //  onPageScrolled: position 0 positionOffset 0.44351852 positionOffsetPixels 479
    //  onPageScrolled: position 0 positionOffset 0.5685185 positionOffsetPixels 614
    //  onPageScrolled: position 0 positionOffset 0.675 positionOffsetPixels 729
    //  onPageScrolled: position 0 positionOffset 0.7592593 positionOffsetPixels 820
    //  onPageScrolled: position 0 positionOffset 0.82222223 positionOffsetPixels 888
    //  onPageScrolled: position 0 positionOffset 0.87314814 positionOffsetPixels 943
    //  onPageScrolled: position 0 positionOffset 0.912037 positionOffsetPixels 985
    //  onPageScrolled: position 0 positionOffset 0.9388889 positionOffsetPixels 1014
    //  onPageScrolled: position 0 positionOffset 0.96018517 positionOffsetPixels 1037
    //  onPageScrolled: position 0 positionOffset 0.975 positionOffsetPixels 1053
    //  onPageScrolled: position 0 positionOffset 0.98425925 positionOffsetPixels 1063
    //  onPageScrolled: position 0 positionOffset 0.9916667 positionOffsetPixels 1071
    //  onPageScrolled: position 0 positionOffset 0.9953704 positionOffsetPixels 1075
    //  onPageScrolled: position 0 positionOffset 0.99814814 positionOffsetPixels 1078
    //  onPageScrolled: position 0 positionOffset 0.9990741 positionOffsetPixels 1079
    //  onPageScrolled: position 1 positionOffset 0.0 positionOffsetPixels 0
    //  onPageScrollStateChanged: state 0

    // 第二页到第三页
    // onPageScrollStateChanged: state 1
    // onPageScrolled: position 1 positionOffset 0.01111114 positionOffsetPixels 12
    // onPageScrolled: position 1 positionOffset 0.06666672 positionOffsetPixels 72
    // onPageScrolled: position 1 positionOffset 0.1240741 positionOffsetPixels 134
    // onPageScrolled: position 1 positionOffset 0.19259262 positionOffsetPixels 208
    // onPageScrolled: position 1 positionOffset 0.2527778 positionOffsetPixels 273
    // onPageScrolled: position 1 positionOffset 0.27592587 positionOffsetPixels 297
    // onPageScrollStateChanged: state 2
    // onPageSelected: position 2
    // onPageScrolled: position 1 positionOffset 0.30277777 positionOffsetPixels 327
    // onPageScrolled: position 1 positionOffset 0.4074074 positionOffsetPixels 440
    // onPageScrolled: position 1 positionOffset 0.4990741 positionOffsetPixels 539
    // onPageScrolled: position 1 positionOffset 0.57407403 positionOffsetPixels 619
    // onPageScrolled: position 1 positionOffset 0.64444447 positionOffsetPixels 696
    // onPageScrolled: position 1 positionOffset 0.70092595 positionOffsetPixels 757
    // onPageScrolled: position 1 positionOffset 0.7537037 positionOffsetPixels 814
    // onPageScrolled: position 1 positionOffset 0.79814816 positionOffsetPixels 862
    // onPageScrolled: position 1 positionOffset 0.8342593 positionOffsetPixels 901
    // onPageScrolled: position 1 positionOffset 0.8666667 positionOffsetPixels 936
    // onPageScrolled: position 1 positionOffset 0.89351857 positionOffsetPixels 965
    // onPageScrolled: position 1 positionOffset 0.91481483 positionOffsetPixels 988
    // onPageScrolled: position 1 positionOffset 0.9342593 positionOffsetPixels 1009
    // onPageScrolled: position 1 positionOffset 0.94907403 positionOffsetPixels 1025
    // onPageScrolled: position 1 positionOffset 0.96111107 positionOffsetPixels 1038
    // onPageScrolled: position 1 positionOffset 0.9703704 positionOffsetPixels 1048
    // onPageScrolled: position 1 positionOffset 0.97870374 positionOffsetPixels 1057
    // onPageScrolled: position 1 positionOffset 0.98425925 positionOffsetPixels 1063
    // onPageScrolled: position 1 positionOffset 0.98888886 positionOffsetPixels 1068
    // onPageScrolled: position 1 positionOffset 0.9925926 positionOffsetPixels 1072
    // onPageScrolled: position 1 positionOffset 0.9944445 positionOffsetPixels 1074
    // onPageScrolled: position 1 positionOffset 0.9962963 positionOffsetPixels 1076
    // onPageScrolled: position 1 positionOffset 0.9981482 positionOffsetPixels 1078
    // onPageScrolled: position 1 positionOffset 0.9990741 positionOffsetPixels 1079
    // onPageScrolled: position 2 positionOffset 0.0 positionOffsetPixels 0
    // onPageScrollStateChanged: state 0

    // 第三页到第二页
    // onPageScrollStateChanged: state 1
    // onPageScrolled: position 1 positionOffset 0.95277774 positionOffsetPixels 1029
    // onPageScrolled: position 1 positionOffset 0.88796294 positionOffsetPixels 959
    // onPageScrolled: position 1 positionOffset 0.83611107 positionOffsetPixels 902
    // onPageScrolled: position 1 positionOffset 0.82592595 positionOffsetPixels 892
    // onPageScrollStateChanged: state 2
    // onPageSelected: position 1
    // onPageScrolled: position 1 positionOffset 0.7851852 positionOffsetPixels 848
    // onPageScrolled: position 1 positionOffset 0.66388893 positionOffsetPixels 717
    // onPageScrolled: position 1 positionOffset 0.55277777 positionOffsetPixels 597
    // onPageScrolled: position 1 positionOffset 0.45648146 positionOffsetPixels 492
    // onPageScrolled: position 1 positionOffset 0.3787037 positionOffsetPixels 409
    // onPageScrolled: position 1 positionOffset 0.30833328 positionOffsetPixels 332
    // onPageScrolled: position 1 positionOffset 0.2490741 positionOffsetPixels 269
    // onPageScrolled: position 1 positionOffset 0.20092595 positionOffsetPixels 217
    // onPageScrolled: position 1 positionOffset 0.1592592 positionOffsetPixels 171
    // onPageScrolled: position 1 positionOffset 0.1240741 positionOffsetPixels 134
    // onPageScrolled: position 1 positionOffset 0.09722221 positionOffsetPixels 104
    // onPageScrolled: position 1 positionOffset 0.07407403 positionOffsetPixels 79
    // onPageScrolled: position 1 positionOffset 0.055555582 positionOffsetPixels 60
    // onPageScrolled: position 1 positionOffset 0.041666627 positionOffsetPixels 44
    // onPageScrolled: position 1 positionOffset 0.030555606 positionOffsetPixels 33
    // onPageScrolled: position 1 positionOffset 0.021296263 positionOffsetPixels 22
    // onPageScrolled: position 1 positionOffset 0.014814854 positionOffsetPixels 16
    // onPageScrolled: position 1 positionOffset 0.010185242 positionOffsetPixels 11
    // onPageScrolled: position 1 positionOffset 0.0064815283 positionOffsetPixels 7
    // onPageScrolled: position 1 positionOffset 0.004629612 positionOffsetPixels 4
    // onPageScrolled: position 1 positionOffset 0.0027778149 positionOffsetPixels 3
    // onPageScrolled: position 1 positionOffset 9.2589855E-4 positionOffsetPixels 0
    // onPageScrolled: position 1 positionOffset 0.0 positionOffsetPixels 0
    // onPageScrollStateChanged: state 0

    // 第二页到第一页
    // onPageScrollStateChanged: state 1
    // onPageScrolled: position 0 positionOffset 0.9592593 positionOffsetPixels 1036
    // onPageScrolled: position 0 positionOffset 0.8666667 positionOffsetPixels 936
    // onPageScrolled: position 0 positionOffset 0.75185186 positionOffsetPixels 812
    // onPageScrolled: position 0 positionOffset 0.7185185 positionOffsetPixels 776
    // onPageScrollStateChanged: state 2
    // onPageSelected: position 0
    // onPageScrolled: position 0 positionOffset 0.65185183 positionOffsetPixels 704
    // onPageScrolled: position 0 positionOffset 0.46203703 positionOffsetPixels 499
    // onPageScrolled: position 0 positionOffset 0.31851852 positionOffsetPixels 344
    // onPageScrolled: position 0 positionOffset 0.21851853 positionOffsetPixels 236
    // onPageScrolled: position 0 positionOffset 0.14166667 positionOffsetPixels 153
    // onPageScrolled: position 0 positionOffset 0.08796296 positionOffsetPixels 95
    // onPageScrolled: position 0 positionOffset 0.053703703 positionOffsetPixels 58
    // onPageScrolled: position 0 positionOffset 0.030555556 positionOffsetPixels 33
    // onPageScrolled: position 0 positionOffset 0.015740741 positionOffsetPixels 17
    // onPageScrolled: position 0 positionOffset 0.0074074073 positionOffsetPixels 8
    // onPageScrolled: position 0 positionOffset 0.0027777778 positionOffsetPixels 3
    // onPageScrolled: position 0 positionOffset 9.259259E-4 positionOffsetPixels 1
    // onPageScrolled: position 0 positionOffset 0.0 positionOffsetPixels 0
    // onPageScrollStateChanged: state 0
    /**
     * viewPage改变监听
     * 用于响应所选页面
     */
    private ViewPager.OnPageChangeListener onPageChangeLis = new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            Log.d("ruanyandong", "onPageScrolled: position " position " positionOffset " positionOffset " positionOffsetPixels " positionOffsetPixels);
            // 第一页到第二页  position 0->1  positionOffset 0->1->0  positionOffsetPixels 0->划过一页的距离->0
            // 第二页到第三页  position 1->2  positionOffset 0->1->0  positionOffsetPixels 0->划过一页的距离->0
            // 第三页到第二页  position 一直是1  positionOffset 1->0  positionOffsetPixels 一页的距离->0
            // 第二页到第一页  position 一直是0  positionOffset 1->0  positionOffsetPixels 一页的距离->0
        }

        @Override
        public void onPageSelected(int position) {
            Log.d("ruanyandong", "onPageSelected: position " position);
            // 每滑动一次都只打印一次方法,position打印最终选定页面的下标

            //计算当前页的下标,用于指示器改变
            int max = views.size() - 1;
            int temp = position;
            currentItem = position;
            if (position == 0) { // 当滑动到下标为0时,其实对于用户来说看到的是最后一个,所以需要将下标替换成倒数第二个
                currentItem = max - 1;
            } else if (position == max) {// 当滑动到最后一个时,其实对于用户来说看到的是第一个,所以需要将下标替换成正数第二个
                currentItem = 1;
            }
            temp = currentItem - 1;
            setIndicatorAndTitle(temp);
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            currentItem = viewPager.getCurrentItem();
            Log.d("ruanyandong", "onPageScrollStateChanged: state " state " currentItem " currentItem);
            // 0 静止  1 正在拖拽   2 松手后自动滑动
            // 每次拖动的状态变化都是 1->2->0  每次拖动一页,都会调用3次方法,依次打印1->2->0
            switch (state) {
                case 0: // 静止状态
                    //Log.e("aaa","=====静止状态======");
                    if (currentItem == 0) {
                        viewPager.setCurrentItem(count-2, false);
                    } else if (currentItem == count - 1) {
                        viewPager.setCurrentItem(1, false);
                    }
                    break;
                case 1: // 拖拽状态
//    Log.e("aaa","=======手动拖拽滑动时调用====");
                    releaseTime = System.currentTimeMillis();
                    if (currentItem == count - 1) {
                        viewPager.setCurrentItem(1, false);
                    } else if (currentItem == 0) {
                        viewPager.setCurrentItem(count-2, false);
                    }
                    break;
                case 2: // 松手自动滑动状态
//    Log.e("aaa","=======自动滑动时调用====");
                    break;
            }
        }
    };


    /**
     * 设置指示器和标题切换
     *
     * @param position
     */
    private void setIndicatorAndTitle(int position) {
        tvTitle.setText(titles.get(position));

        for (int i = 0; i < tips.length; i  ) {
            if (i == position) {
                tips[i].setBackgroundResource(R.drawable.shape_circle_red);
            } else {
                tips[i].setBackgroundResource(R.drawable.shape_circle_white);
            }
        }
    }

    /**
     * 适配器
     */
    class BannerAdapter extends PagerAdapter {

        /**
         * 返回可用的视图数量
         * @return
         */
        @Override
        public int getCount() {
            return views.size();
        }


        /**
         * 判断当前View是否是来自于object
         * @param view
         * @param object
         * @return
         */
        @Override
        public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
            return view == object;
        }

        /**
         * 创建初始化View,如果总共有三个View,刚开始会创建好第一二两个,当从第一个滑动到第二个时,会创建第三个view
         * @param container
         * @param position
         * @return
         */
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            container.addView(views.get(position));
            return views.get(position);
        }

        /**
         * 销毁view,当滑动到第三个时会销毁第一个,当滑动到第一个时会销毁第三个
         * @param container
         * @param position
         * @param object
         */
        @Override
        public void destroyItem(ViewGroup container, int position, @NonNull Object object) {
            container.removeView((View) object);
        }


    }

}

学新通

layout_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    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=".banner.MainActivity">

    <com.ryd.banner.banner.BannerViewPager
        android:id="@ id/banner"
        android:layout_width="match_parent"
        android:layout_height="200dp"/>

</FrameLayout>

MainActivity.java

package com.ryd.banner.banner;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;

import com.ryd.banner.R;
import com.ryd.banner.viewpager2.ViewPager2Activity;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

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

        BannerViewPager banner= (BannerViewPager) findViewById(R.id.banner);
        List<Integer> imageUrl=new ArrayList<>();
        imageUrl.add(R.drawable.water); // 当用户在第一页时,向右拖拽左滑时新增一个最后一页,让用户以为滑到了最后一页
        imageUrl.add(R.drawable.bubble);// 1 用户看到的第一页
        imageUrl.add(R.drawable.grass);// 2 用户看到的第二页
        imageUrl.add(R.drawable.water);// 3 用户看到的第三页
        imageUrl.add(R.drawable.bubble);// 当用户在第三页时,向左拖拽右滑时新增一个第一页,让用户以为滑到了第一页
        banner.setData(imageUrl);

    }


}
学新通

最后附上代码的github地址
参考
简单实现android轮播图

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgcihbj
系列文章
更多 icon
同类精品
更多 icon
继续加载