自定义图片轮播(Banner)控件的实现解析

图片轮播控件,可以说是每个App基本上都会用到的。它可以用来动态的展示多个图片,之前写过两篇博客:实现ViewPager无限循环的方式一和实现ViewPager无限循环的方式二,在这两篇博客中,分析了两种实现ViewPager无限循环的原理,但是在使用的过程中,代码的解偶性很低,所以就使用自定义View的方式,实现无限循环的图片轮播的封装。

你还在为开发中频繁切换环境打包而烦恼吗?快来试试 Environment Switcher 吧!使用它可以在app运行时一键切换环境,而且还支持其他贴心小功能,有了它妈妈再也不用担心频繁环境切换了。https://github.com/CodeXiaoMai/EnvironmentSwitcher

先看看效果:


android banner方向 安卓banner轮播控件_android banner方向

功能特点

  1. 支持自定义宽高比例
  2. 支持自定义图片切换时间
  3. 支持自定义指示点的颜色
  4. 支持自定义指示点的背景色
  5. 支持自定义指示点的高度
  6. 支持是否显示指示点
  7. 支持每个图片设置不同的点击事件

使用简单

<com.xiaomai.bannerview.BannerView
        android:id="@+id/bannerView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:aspectRatio="1.5"
        app:defaultSrc="@mipmap/ic_launcher"
        app:indicatorBackground="@color/white"
        app:indicatorHeight="50dp"
        app:indicatorPositionSize="8dp"
        app:updateTime="3000" />

实现步骤

  • 声明自定义的属性
  • 创建一个类继承RelativeLayout
  • 解析属性

声明自定义的属性

在values/attrs文件中创建自定义的属性

<resources>

    <declare-styleable name="BannerView">
        <attr name="aspectRatio" format="float" />   <!-- 宽高比 -->
        <attr name="defaultSrc" format="integer|reference" />     <!-- 占位图 -->
        <attr name="updateTime" format="integer" />     <!-- 图片切换时间 -->
        <attr name="indicatorVisible" format="boolean" /> <!-- 是否显示指示器 -->
        <attr name="indicatorHeight" format="dimension" /> <!-- 指示器的高度 -->
        <attr name="indicatorBackground" format="color|reference" />    <!-- 指示器的背景颜色 -->
        <attr name="indicatorPositionSize" format="dimension" /> <!-- 指示点的大小 -->
    </declare-styleable>

</resources>

创建BannerView类

public class BannerView extends RelativeLayout {

    public BannerView(Context context) {
        this(context, null);
    }

    public BannerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);}

在BannerView中声明变量属性

private Context context;

    private Handler handler;

    private ImageLoader imageLoader;

    private DisplayImageOptions options;

    private boolean isHaveHandler = true;// 当用户点击轮播图时,取消handler队列,也就是取消滚动

    // 控件Start
    private ViewPager viewPager;

    private LinearLayout indicator;// 指示器

    private onItemClickListener listener;
    // 控件End

    // 自定义属性Start
    private float mAspectRatio; // 宽高比

    private int defaultImageResource; // 默认占位图

    private int updateTime; // 图片切换的时间间隔,默认3秒

    private boolean showIndicator; // 是否显示指示器,默认显示

    private int indicatorHeight;// 指示器的高度,默认35dp

    private int indicatorPositionSize; // 指示器的大小

    private int indicatorBackground; // 指示器的背景颜色

    // 自定义属性End

    // 数据Start
    private int imageCount;

    private int lastPosition;

    private List<Integer> imageResources;

    private List<Image> imageUrls;
    // 数据End

解析自定义属性的值

接下来为自定义的属性赋值,在3个参数的构造方法中初始化变量。

public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        parseCustomAttributes(context, attrs);
        this.context = context;
        handler = new Handler();
        imageLoader = ImageLoader.getInstance();
        options = HSApplication.getDisplayOptions().build();
        initViews();
    }
    /**
     * 解析自定义属性
     *
     * @param context
     * @param attrs
     */
    private void parseCustomAttributes(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BannerView);
        mAspectRatio = typedArray.getFloat(R.styleable.BannerView_aspectRatio, 0f);
        defaultImageResource = typedArray.getResourceId(R.styleable.BannerView_defaultSrc,
                R.drawable.about_us);
        updateTime = typedArray.getInt(R.styleable.BannerView_updateTime, 3000);
        showIndicator = typedArray.getBoolean(R.styleable.BannerView_indicatorVisible, true);
        indicatorHeight = (int) (typedArray.getDimension(R.styleable.BannerView_indicatorHeight,
                Utils.dip2px(context, 35)));
        indicatorBackground = typedArray.getResourceId(R.styleable.BannerView_indicatorBackground,
                R.color.white_alpha00);
        indicatorPositionSize = (int) typedArray.getDimension(
                R.styleable.BannerView_indicatorPositionSize, Utils.dip2px(context, 5));
        typedArray.recycle();
    }
    private void initViews() {
        viewPager = new ViewPager(context);
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset,
                    int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                if (showIndicator) {
                    for (int i = 0; i < indicator.getChildCount(); i++) {
                        indicator.getChildAt(i).setSelected(false);
                    }
                    indicator.getChildAt(position % imageCount).setSelected(true);
                }
                lastPosition = position;
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                switch (state) {
                    case ViewPager.SCROLL_STATE_IDLE:// 空闲状态
                        if (!isHaveHandler) {
                            isHaveHandler = true;
                            handler.postDelayed(updateRunnable, updateTime);
                        }
                        break;
                    case ViewPager.SCROLL_STATE_DRAGGING:// 用户滑动状态
                        handler.removeCallbacks(updateRunnable);
                        isHaveHandler = false;
                        break;
                }
            }
        });
        addView(viewPager, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));

        if (showIndicator) {
            indicator = new LinearLayout(context);
            indicator.setOrientation(LinearLayout.HORIZONTAL);
            indicator.setGravity(Gravity.CENTER);
            indicator.setBackgroundResource(indicatorBackground);
            RelativeLayout.LayoutParams layoutParams = new LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, indicatorHeight);
            layoutParams.addRule(ALIGN_PARENT_BOTTOM);
            addView(indicator, layoutParams);
        }
    }

控件和自定义的属性都经过赋值和初始化了,接下来,该为设置图片资源了。

public void setImageResources(List<Integer> imageResources) {
        if (imageResources == null || imageResources.size() == 0) {
            throw new RuntimeException("图片资源为空");
        }
        this.imageResources = imageResources;
        imageCount = imageResources.size();
    }

    public void setImageUrls(List<Image> imageUrls) {
        if (imageUrls == null || imageUrls.size() == 0) {
            throw new RuntimeException("图片地址资源为空");
        }
        this.imageUrls = imageUrls;
        imageCount = imageUrls.size();
        loadImages();
    }

    private void loadImages() {
        if (showIndicator) {
            addIndicationPoint();
        }
        viewPager.setAdapter(new MyViewPageAdapter());
        viewPager.setCurrentItem(200 - (200 % imageCount));
        handler.removeCallbacks(updateRunnable);
        handler.postDelayed(updateRunnable, updateTime);
    }

    /**
     * 添加指示点到指示器中
     */
    private void addIndicationPoint() {
        // 防止刷新重复添加
        if (indicator.getChildCount() > 0) {
            indicator.removeAllViews();
        }
        View pointView;
        int margin = Utils.dip2px(context, 5f);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
                indicatorPositionSize, indicatorPositionSize);
        layoutParams.setMargins(margin, margin, margin, margin);

        for (int i = 0; i < imageCount; i++) {
            pointView = new View(context);
            pointView.setBackgroundResource(R.drawable.indicator_selector);
            if (i == lastPosition) {
                pointView.setSelected(true);
            } else {
                pointView.setSelected(false);
            }
            indicator.addView(pointView, layoutParams);
        }
    }

    private class MyViewPageAdapter extends PagerAdapter {
        @Override
        public int getCount() {
            return Integer.MAX_VALUE;
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, final int position) {
            final ImageView imageView = new ImageView(container.getContext());
            imageView.setImageResource(defaultImageResource);
            imageView.setScaleType(ImageView.ScaleType.FIT_XY);
            final Image image = imageUrls.get(position % imageCount);
            imageLoader.displayImage(image.getImageUrl(), imageView, options);
            imageView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (listener != null) {
                        listener.onClick(v, position % imageCount, image.getAction(),
                                image.getUrl());
                    }
                }
            });
            container.addView(imageView);
            return imageView;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }
    }

    private Runnable updateRunnable = new Runnable() {
        @Override
        public void run() {
            viewPager.setCurrentItem(lastPosition + 1);
            handler.postDelayed(updateRunnable, updateTime);
        }

/**
     * 点击监听回调事件
     */
    public interface onItemClickListener {
        void onClick(View view, int position, String action, String url);
    }

    public void setOnItemClickListener(onItemClickListener listener) {
        this.listener = listener;
    }

时间太仓促,解释的不是太详细,可以移步我的GitHub查看完整代码。