CycleRotationView:自定义控件之轮播图

原创 2017年01月03日 10:38:27

CycleRotationView:自定义控件,主要功能是实现类似与各种商城首页的广告轮播图。其实像这种比较常见的自定义控件早就满大街了,虽然说“不要重复发明轮子”,但是不代表不用关心轮子是怎么造的,本着“知其然知其所以然”的态度,完成了这个控件(总不能需要用到时才来学习吧)。老规矩,先来个效果图(没有效果图的文章都是耍流氓):

对于轮播图的图片切换,大家立马会想到 ViewPager 这个控件,因为它已经拥有左右滑动的功能,用来实现轮播图效果会简单一些,本篇也是基于 ViewPager 这个控件的实现,将其分成下面四个部分:
1、图片轮播
2、指示器
3、图片指示器联动
4、定时轮播
下面开始实现(有点啰嗦,勿喷…):

图片轮播

布局文件

<RelativeLayout
       android:layout_width="match_parent"
       android:layout_height="120dp">
       <android.support.v4.view.ViewPager
           android:id="@+id/viewPager"
           android:layout_width="match_parent"
           android:layout_height="120dp" />
       <LinearLayout
           android:layout_width="match_parent"
           android:layout_height="18dp"
           android:layout_alignParentBottom="true"
           android:background="#66000000"
           android:orientation="horizontal">
           <TextView
               android:id="@+id/title"
               android:layout_width="0dp"
               android:layout_height="wrap_content"
               android:layout_marginLeft="15dp"
               android:layout_weight="1"
               android:textColor="#FFFFFF" />
           <LinearLayout
               android:id="@+id/pointGroup"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:layout_gravity="center"
               android:layout_marginRight="15dp"
               android:orientation="horizontal" />
       </LinearLayout>
   </RelativeLayout>

先来看看布局文件,最外层的 RelativeLayout 代表整个控件,包含 ViewPager 和一个 LinearLayout;在 LinearLayout 中又包含 TextView 和 LinearLayout。这里说明一下:第一个 LinearLayout 包含标题(id=title)和指示器(id=pointGroup),第二个 LinearLayout 用来包裹指示器(在代码里完成),给张理解图(这里我并没有设置标题):

代码实现

    /**
     * 初始化布局
     * @param mContext
     */
    private void initView(Context mContext) {
        View view = LayoutInflater.from(mContext)
                .inflate(R.layout.cycle_rotation_layout, this, true);
        mViewPager = (ViewPager) view.findViewById(R.id.viewPager);
        mPointGroup = (LinearLayout)view.findViewById(R.id.pointGroup);
    }

首先是加载刚才的布局文件,把 ViewPager 和 pointGroup 找出来;

    for (int i = 0; i < images.length; i++) {
        // 创建 ImageView,并设置图片
        ImageView img = new ImageView(mContext);
        // 把图片扩大到 ImageView 的大小
        img.setScaleType(ImageView.ScaleType.FIT_XY);
        img.setImageResource(images[i]);
        mList.add(img);
        }

根据用户传递过来的图片的数量进行遍历创建对应数量的 ImageView ,然后给创建的 ImageView 设置图片,再用一个 List 集合把 ImageView 装起来待用。在 Fragement + ViewPager + TabLayout 的场景里 ViewPager 里填充的是 Fragement,而这里的 ViewPager 填充的是 ImageView;

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

        @Override
        public int getCount() {
            return Integer.MAX_VALUE;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            position = position % mList.size();
            // 判断该 item 是否已经有 parent
            final View child = mList.get(position);
            if (child.getParent() != null) {
                container.removeView(child);
            }
            container.addView(mList.get(position));
            return mList.get(position);
        }

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

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

适配器上场了,在 getCount() 方法里返回 Integer.MAX_VALUE 是因为 ViewPager 不会循环滑动,给这么一个数值(据说 Integer.MAX_VALUE 的值是 10亿,我没试过)可以让它一直滑下去。问题来了:这么大的数值,会不会出现性能问题?这个我也好奇,看谁更有兴趣咯。
还要说一下 instantiateItem() 方法,为什么要判断 parent 的状态?原因是如果不这样处理,在向左滑动 ViewPager 的时候会报错: The specified child already has a parent. You must call removeView
注意:instantiateItem() 进行了判断,destroyItem() 就不用再 removeView() 了,否则会出现空白页。

指示器

    /**
    * 创建指示器
    */
    for(int i = 0; i < images.length; i ++) {
        // 创建指示器,实质也是 ImageView
        ImageView point = new ImageView(mContext);
        point.setImageResource(R.drawable.shape_point_selector);
        // 指示器布局参数
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(pointSize, pointSize);
        // 第2个起才设置左边距
        if (i > 0) {
            params.leftMargin = pointMargin;
            point.setSelected(false); // 默认选中第1个
        } else {
            point.setSelected(true); // 默认选中第1个
        }
        point.setLayoutParams(params); // 给指示器设置参数
        mPointGroup.addView(point); // 把指示器添加到容器
    }

创建指示器的时候为了和 ImageView 的数量对应,所以同样要遍历用户设置的图片数量。下面是指示器的相关布局文件:
shape_point_selector:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true" android:drawable="@drawable/shape_point_selected" />
    <item android:state_selected="false" android:drawable="@drawable/shape_point_normal" />
</selector>

shape_point_selected:

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
    <solid android:color="#FFFFFF"/>
</shape>

shape_point_normal:

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
    <solid android:color="#66000000"/>
</shape>

ViewPager 和 pointGroup 都创建好了,接下来是他们俩的联动:

联动

 mViewPager.setAdapter(new CycleAdapter());

        // ViewPager 的监听事件
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}

            int lastPosition;
            @Override
            public void onPageSelected(int position) {
                position = position % mList.size();
                // 设置当前圆点选中
                mPointGroup.getChildAt(position).setSelected(true);
                // 设置前一个圆点不选中
                mPointGroup.getChildAt(lastPosition).setSelected(false);
                lastPosition = position;
            }

            @Override
            public void onPageScrollStateChanged(int state) {}
        });

给 ViewPager 添加监听事件,在选中某个页面的同时设置相应位置指示器也被选中。position % mList.size():Adapter 的 getCount() 中返回了很大的值,这里求余可以保证不会出现空指针异常。

定时轮播

 mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                // 当前选中的 item
                int currentItem = mViewPager.getCurrentItem();
                // 判断是否是最后一个 item
                if (currentItem == mViewPager.getAdapter().getCount() - 1) {
                    mViewPager.setCurrentItem(1);
                } else {
                    mViewPager.setCurrentItem(currentItem + 1);
                }
                // 不断给自己发消息
                mHandler.postDelayed(this, 3000);
            }
        }, 3000);

定时轮播也很简单,用 Handler 来延时给自己发消息。在 ViewPager 滑动到最后一个时给它设置 mViewPager.setCurrentItem(1) 来实现循环轮播。
基本的轮播功能到这里就完成了,但是还有很多的拓展功能,比如点击事件,用户设置图片链接,这些我都已经实现了,这里就不一一介绍了,有兴趣的可以看看源码:cycleRotation

版权声明:本文为小山原创文章,转载请注明出处,谢谢。

CycleRotationView:自定义控件之轮播图

CycleRotationView:自定义控件,主要功能是实现类似与各种商城首页的广告轮播图。其实像这种比较常见的自定义控件早就满大街了,虽然说“不要重复发明轮子”,但是不代表不用关心轮子是怎么造的,...
  • u013132744
  • u013132744
  • 2016年11月25日 10:05
  • 618

自定义控件之viewPager轮播图

1.activity中 public class MainActivity extends Activity { private static final int VIEWPAGER_...
  • tianzhu2725
  • tianzhu2725
  • 2016年10月06日 16:25
  • 441

自定义控件之轮播图RollViewPager

import java.util.List; import android.content.Context; import android.os.Handler; import android.su...
  • u012246348
  • u012246348
  • 2015年12月08日 15:05
  • 1949

自定义控件之轮播图

昨天写了关于RecyclerView添加头部布局的方法,一般我们的头部布局都会添加什么呢?自然是一个轮播形式的广告位。关于RecyclerView我们使用了比较简单的刷新提示,轮播图我们也要做一个简单...
  • a516972602
  • a516972602
  • 2016年03月31日 15:30
  • 543

自定义控件轮播图

public class CustomBanner extends FrameLayout {     private ViewPager viewPager;     private L...
  • cdb_680
  • cdb_680
  • 2017年12月03日 18:17
  • 18

Android自定义控件之轮播图控件

背景最近要做一个轮播图的效果,网上看了几篇文章,基本上都能找到实现,效果还挺不错,但是在写的时候感觉每次都要单独去重新在Activity里写一堆代码。于是自己封装了一下。本篇轮播图实现原理原文出处:循...
  • yissan
  • yissan
  • 2016年01月19日 02:12
  • 4157

Android面试准备:自定义控件(二)

LayoutInflater介绍 LayoutInflater的作用 Android中使用LayoutInflater类来加载布局(在Activity中常见的加载布局方法setContentView...
  • a362523
  • a362523
  • 2015年10月05日 23:26
  • 484

Android自定义控件之应用程序首页轮播图

现在基本上大多数的Android应用程序的首页都有轮播图,就是像下图这样的(此图为转载的一篇博文中的图,拿来直接用了): 像这样的组件我相信大多数的应用程序都会使用到,本文就是自定义一个这样的组件...
  • gc_gongchao
  • gc_gongchao
  • 2014年09月28日 10:50
  • 9405

WinForm自定义控件(一) EditBox

退休了,有的是时间,今天开始来这里晒晒多年来使用的自定义控件。 EditBox     山谷居士的EditBox,是应用在WinForm中的文本编辑控件。它由Control派生,内嵌一个用于文本的编辑...
  • hcc5176
  • hcc5176
  • 2015年12月09日 18:54
  • 326

Android自定义控件开发系列(一)——第一次动手做自定义控件

Android系统提供的控件多种多样,以至于很多初学者经常忘了还有这样那样的控件没用过甚至没听过。尽管如此,但是系统控件大多比较死板,而且不够美观,很多多样化的显示或是交互方式都没法完成。每每遇到这种...
  • a_running_wolf
  • a_running_wolf
  • 2015年10月10日 15:11
  • 4080
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:CycleRotationView:自定义控件之轮播图
举报原因:
原因补充:

(最多只允许输入30个字)