Android 自制轮播插件:AutoPlayViewpager

相信大家对轮播插件并不陌生,它频繁地出现在网页端,移动端的广告推广banner中,宣传效果和视觉非常不错,于是我又稍微思考了一下它实现的原理,恩,又完成了一个自制的插件,效果还行,就是差一点:无限轮播!不管怎样,通过脑力劳动产生的硕果总是让人感到分外喜悦。


1.实际效果

网易云音乐APP广告轮播:
这里写图片描述

自制的轮播:
这里写图片描述

2.原理机制

这是一个自定义ViewPager(继承自RelativeLayout),子View为ViewPage和Indicator(继承自RelativeLayout)。通过Indicator, 动态添加其子View:Dot(自定义View,继承自ImageView),作为圆点指示器。通过子View :ViewPager的OnPageChangeListener监听其滑动状态, 进而动态绘制圆点指示器,达到指示效果;通过添加线程,隔一段时间跳转ViewPager到下一页面达到播放效果。

实际上实现起来并不难,可以媲美那些流行的轮播框架了。

3.代码解析

完成这个小插件重点在于圆点指示器的重绘工作,在自定义View:Dot中,有两种显示状态(选中与非选中),即当ViewPager播放到指定页时,圆点指示器的指定圆点处于选中状态(这时要对其进行重绘了),当离开此页时,圆点变为非选中状态(又要进行重绘)。下面来看源码:

package chen.capton.custom;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.widget.ImageView;

import static android.content.ContentValues.TAG;

/**
 * Created by CAPTON on 2017/1/15.
 */

public class Dot extends ImageView {
    private int dotColor= Color.WHITE; //默认颜色,白色
    private final int RADIUS_SMALL=6;
    private final int RADIUS_NORMAL=8;
    private final int RADIUS_LARGE=12;
    private int diameter =RADIUS_NORMAL;//默认尺寸,正常
    private Paint paint;
    private int width,height;

    public Dot(Context context) {
        super(context);

    }

    public Dot(Context context, AttributeSet attrs) {
        super(context, attrs);
 /*    测试的时候在xml文件中实例化时调用此方法   
       width= (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,this.diameter,getResources().getDisplayMetrics());
        height=(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,this.diameter,getResources().getDisplayMetrics());
        paint=new Paint();
        paint.setColor(dotColor);
        paint.setAntiAlias(true);
        */ 
    }
   /*
     * 被Indicator创建对象时调用,获取指定的直径大小和颜色
     * */
    public Dot(Context context, int dotColor, int diameter ) {
        super(context);
        this.dotColor=dotColor;
        this.diameter =diameter;
        /*
        * 将dp转为px,获取宽高
        * */
        width= (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,this.diameter,getResources().getDisplayMetrics());
        height=(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,this.diameter,getResources().getDisplayMetrics());
        /*
        * 设置画笔颜色,抗锯齿
        * */
        paint=new Paint();
        paint.setColor(this.dotColor);
        paint.setAntiAlias(true);
    }

    /*
    * 设置选择与非选中的关键,改变其透明度,=1:选中,<1:非选中
    * */
    public void setAlpha(float alpha){
        int tureAlpha= (int) (255*alpha);
        paint.setAlpha(tureAlpha);
        invalidate();  //使画面失效,系统调用onDraw方法
    }

    /*
    * 用户通过Java代码改变圆点颜色的方法
    * */
    public void setColor(int color){
        paint.setColor(color);
        invalidate();
    }
    /*
    * 用户通过Java代码改变圆点尺寸的方法
    * */
    public void setSize(int size){
        switch (size){
            case RADIUS_SMALL:
            case RADIUS_NORMAL:
            case RADIUS_LARGE:
                diameter=size;
                width= (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,diameter,getResources().getDisplayMetrics());
                height=(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,diameter,getResources().getDisplayMetrics());
                measure(width,height);
                invalidate();
                break;
        }
    }

    /*
    * 关键,重绘方法
    * */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        canvas.drawCircle(width/2,height/2,width/2,paint);
        canvas.restore();
    }

    /*
    * 测量自身宽高
    * */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize=MeasureSpec.getSize(widthMeasureSpec);
        int heightSize=MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(width,height);
    }

是不是很简单?呵呵,主要是我还没有贴出逻辑代码(在AutoPlayViewpager这个类中),这里讲轮播逻辑已经没有意义了,实际业务中不可能思维定式,这里贴出的实现思路是自我提升的最好选择。
接下来看看Indicator类,作为Dot(ImageView)的父布局,主要起到包裹Dot,确定Dot位置的作用

package chen.capton.custom;

import android.content.Context;
import android.graphics.Color; 
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue; 
import android.widget.RelativeLayout;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by CAPTON on 2017/1/15.
 */

public class Indicator extends RelativeLayout {
    private int dotCount=3;    //测试用的数据
    private int dotColor= Color.WHITE;  //默认颜色
    private final int GAP_SMALL=2;
    private final int GAP_NORMAL=3;
    private final int GAP_LARGE=4;
    private int dotGap= GAP_NORMAL;   //默认圆点间距
    private  final int DOT_SMALL=6;  
    private  final int DOT_NORMAL=8;
    private  final int DOT_LARGE=12;   
    private int diameter=DOT_NORMAL;  //默认尺寸(直径)
    private List<Dot> dotList=new ArrayList<>(); //保存圆点对象的集合
    /*
    * 通过java代码改变圆点颜色的方法
    * */
    public void setDotColor(int dotColor){
        this.dotColor=dotColor;
        for (int i = 0; i <dotList.size(); i++) {
            dotList.get(i).setColor(dotColor);
            dotList.get(i).setAlpha(0.4f);
        }
        dotList.get(0).setAlpha(1f);
    }
    /*
    * 通过java代码改变圆点尺寸的方法
    * */
    public void setDotSize(int dotSize){
        //这里我只提供三种尺寸,如果用户输入其他尺寸,则使用默认尺寸
        if(dotSize==DOT_SMALL||dotSize==DOT_NORMAL||dotSize==DOT_LARGE) {
            this.diameter =dotSize;
            for (int i = 0; i <dotList.size(); i++) {
               dotList.get(i).setSize(dotSize);
            }
            switch (dotSize){
                case DOT_SMALL:dotGap=GAP_SMALL;break;
                case DOT_NORMAL:dotGap=GAP_NORMAL;break;
                case DOT_LARGE:dotGap=GAP_LARGE;break;
            }
        }else {
            Log.e("setDotSize", " Wrong setting!Please input a correct dotSize:'Indicator." +
                    "DOT_SMALL','Indicator.DOT_NORMAL'or'Indicator.DOT_NORMAL'");
        }
    }

    /*
    * 通过AutoPlayViewpager逻辑判断后调用此方法,以重绘Dot,达到指示的显示效果
    * */
   public void setDotChecked(int position){
       for (int i = 0; i <dotList.size(); i++) {
           dotList.get(i).setAlpha(0.4f);
       }
       dotList.get(position).setAlpha(1f);
   } 

    public Indicator(Context context, AttributeSet attrs) {
        super(context, attrs);
       /*  测试时调用的方法(同下),弃用
        for (int i = 0; i < dotCount; i++) {
            Dot dot = new Dot(context, dotColor, diameter);
            RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(diameter, diameter);
            dot.setLayoutParams(lp);
            dotList.add(dot);
            addView(dot);
        }*/
    }

    /*
    * 构建对象时,初添加指定大小,颜色的圆点到自身布局内
    * */
    public Indicator(Context context, int dotCount,int dotColor) {
        super(context);
        this.dotColor=dotColor;
        this.dotCount=dotCount;
        for (int i = 0; i < this.dotCount; i++) {
            Dot dot = new Dot(context,this.dotColor, diameter);
            //圆点(ImageView)宽高设置为上一级传来的直径
            RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(diameter, diameter); 
            dot.setLayoutParams(lp);
            dotList.add(dot);
            addView(dot);
        }
        setDotColor(this.dotColor);
    }

    /*
    * 根据上一级传来的圆点的直径,圆点(页面)数量确定自身宽高
    * */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,diameter*dotCount+2*dotGap*dotCount,getResources().getDisplayMetrics());
        int height=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,diameter,getResources().getDisplayMetrics());
        setMeasuredDimension(width,height);
    }

    /*
    * 确定所有圆点的显示位置的方法
    * */
    private void setChildLayout(){
        for (int i = 0; i < dotList.size(); i++) {
            int left = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dotGap + (diameter + dotGap * 2) * i, getResources().getDisplayMetrics());
            int right = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dotGap + (diameter + dotGap * 2) * i + diameter, getResources().getDisplayMetrics());
            int top = 0;
            int bottom = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, diameter, getResources().getDisplayMetrics());
            dotList.get(i).layout(left, top, right, bottom);
        }
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        setChildLayout(); //调用上文确定圆点位置的方法
    }
}

是不是感觉没头绪?那是因为兄台还不太了解自定义ViewGroup和自定义View的用法,建议去看看鸿洋大神的关于自定义ViewGroup与自定义View的文章。

接下来提出AutoPlayViewpager的代码,关于这部分,我不想多说了,大家能看懂多少是多少,因为我的水平也是有限。大家主要能理解,核心思路就行了。

package chen.capton.custom;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Handler; 
import android.os.Message;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log; 
import android.widget.RelativeLayout; 

/**
 * Created by CAPTON on 2017/1/15.
 */

public class AutoPlayViewpager extends RelativeLayout implements ViewPager.OnPageChangeListener{
      private Context context;
      public static final String INDICATOR_LOCATION_LEFT="left";
      public static final String INDICATOR_LOCATION_CENTER="center";
      public static final String INDICATOR_LOCATION_RIGHT="right";
      public static final int DOT_SMALL=6;
      public static final int DOT_NORMAL=8;
      public static final int DOT_LARGE=12;
      private String indicatorLocation=INDICATOR_LOCATION_CENTER;
      private int dotSize=DOT_NORMAL;
      private int interval=3000;
      private ViewPager mViewPager;
      private Indicator mIndicator;
      private int dotColor= Color.WHITE;
      private  Handler handler;
    public AutoPlayViewpager(Context context) {this(context,null);}
    public AutoPlayViewpager(Context context, AttributeSet attrs) {this(context, attrs,0);}
    public AutoPlayViewpager(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context=context;
        handler=new Handler(){
            public void handleMessage(Message msg){
                if(msg.what==mViewPager.getAdapter().getCount()-1){
                    mViewPager.setCurrentItem(0,false);
                }else {
                    mViewPager.setCurrentItem(msg.what + 1);
                }
            }
        };
        obtainAttrs(attrs);
         mViewPager=new ViewPager(context);
    }

    /*
    *  获取xml中各种属性
    * */
    private void obtainAttrs(AttributeSet attrs){
        TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.AutoPlayViewpager);
        if(ta.getString(R.styleable.AutoPlayViewpager_incator_location)!=null) {
            if(ta.getString(R.styleable.AutoPlayViewpager_incator_location).equals(INDICATOR_LOCATION_LEFT)
            ||ta.getString(R.styleable.AutoPlayViewpager_incator_location).equals(INDICATOR_LOCATION_CENTER)
            ||ta.getString(R.styleable.AutoPlayViewpager_incator_location).equals(INDICATOR_LOCATION_RIGHT)) {
                indicatorLocation = ta.getString(R.styleable.AutoPlayViewpager_incator_location);
            }else {
                indicatorLocation=INDICATOR_LOCATION_CENTER;
                Log.w("setIndicatorLocation", " Wrong setting!Please input a correct indicatorLocation:'AutoPlayViewpager." +
                        "INDICATOR_LOCATION_LEFT','AutoPlayViewpager.INDICATOR_LOCATION_CENTER'or'AutoPlayViewpager.INDICATOR_LOCATION_RIGHT'");
            }
        }
        dotSize=ta.getInt(R.styleable.AutoPlayViewpager_dot_size,dotSize);
        dotColor=ta.getColor(R.styleable.AutoPlayViewpager_dot_color,dotColor);
        interval=ta.getInt(R.styleable.AutoPlayViewpager_interval,interval);
        ta.recycle();
    }

    boolean once;
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize=MeasureSpec.getSize(widthMeasureSpec);
        int heightSize=MeasureSpec.getSize(heightMeasureSpec);
        /*
        * 设置内部ViewPager的大小与自身一致,添加内部ViewPager与Indicator指示器到自身布局内,测量自身宽高
        * */
        if(!once) {
            RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(widthSize, heightSize);
            lp.width=widthSize;
            lp.height=heightSize;
            mViewPager.setLayoutParams(lp);
            addView(mViewPager);
            addView(mIndicator);
            setMeasuredDimension(widthSize, heightSize);
          once=true;
        }
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        setIndicatorLayout();
    }
    /*
    * 设置Indicatord位置,私有方法
    * */
    private void setIndicatorLayout(){
        int indicatorWidth=mIndicator.getWidth();
        int indicatorHeight=mIndicator.getHeight();
        int left,top,right,bottom;
        switch (indicatorLocation){
            case INDICATOR_LOCATION_CENTER:
                left=(getMeasuredWidth()-indicatorWidth)/2;
                top=getMeasuredHeight()-indicatorHeight*2;
                right= (getMeasuredWidth()+indicatorWidth)/2;
                bottom=getMeasuredHeight()-indicatorHeight;
                mIndicator.layout(left,top,right,bottom);
                break;
            case INDICATOR_LOCATION_LEFT:
                left=indicatorWidth/2;
                top=getMeasuredHeight()-indicatorHeight*2;
                right=indicatorWidth*3/2;
                bottom=getMeasuredHeight()-indicatorHeight;
                mIndicator.layout(left,top,right,bottom);
                break;
            case INDICATOR_LOCATION_RIGHT:
                left=getMeasuredWidth()-indicatorWidth*3/2;
                top=getMeasuredHeight()-indicatorHeight*2;
                right= getMeasuredWidth()-indicatorWidth/2;
                bottom=getMeasuredHeight()-indicatorHeight;
                mIndicator.layout(left,top,right,bottom);
                break;
        }
    }

    /*
    * 设置适配器,初始化指示器,开启轮播线程
    * */
    public void setAdapter(PagerAdapter adapter){
        mViewPager.setAdapter(adapter);
        mViewPager.setOnPageChangeListener(this);
        mIndicator=new Indicator(context,mViewPager.getAdapter().getCount(),dotColor);
        mIndicator.setDotSize(dotSize);
        setIndicatorLocation(indicatorLocation);
         new AutoPlayThread(interval).start();
    }
    /*
    * 用户通过Java代码改变指示器位置的方法(与私有方法setIndicatorLocation()要区分开来)
    * */
    public AutoPlayViewpager setIndicatorLocation(String location){
        if(location!=null) {
            if (location.equals(INDICATOR_LOCATION_LEFT)
                    || location.equals(INDICATOR_LOCATION_CENTER)
                    || location.equals(INDICATOR_LOCATION_RIGHT)) {
                indicatorLocation = location;
            } else {
                indicatorLocation = INDICATOR_LOCATION_CENTER;
                Log.e("setIndicatorLocation", " Wrong setting!Please input a correct indicatorLocation:'AutoPlayViewpager." +
                        "INDICATOR_LOCATION_LEFT','AutoPlayViewpager.INDICATOR_LOCATION_CENTER'or'AutoPlayViewpager.INDICATOR_LOCATION_RIGHT'");
            }
            setIndicatorLayout();
        }else {
            Log.e("setIndicatorLocation", " Wrong setting!Please input a correct indicatorLocation:'AutoPlayViewpager." +
                    "INDICATOR_LOCATION_LEFT','AutoPlayViewpager.INDICATOR_LOCATION_CENTER'or'AutoPlayViewpager.INDICATOR_LOCATION_RIGHT'");
        }
        return this;
    }
    //设置圆点尺寸(直径)
    public AutoPlayViewpager setDotSize(int dotSize) {
        mIndicator.setDotSize(dotSize);
        return this;
    }
    //设置圆点颜色
    public AutoPlayViewpager setDotColor(int color) {
        mIndicator.setDotColor(color);
        return this;
    }
    //设置播放时间间隔
    public AutoPlayViewpager setInterval(int interval){
        this.interval=interval;
        return this;
    }
    @Override
    public void onPageSelected(int position) {
        mIndicator.setDotChecked(position);
    }
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }
    @Override
    public void onPageScrollStateChanged(int state) {
        /*
        * 当页面滑动时,根据ViewPager的滑动状态控制轮播的播放状态。
        * 这个状态可以自己测试来得到结果,state 一共 1,2,0 三个状态
        * 分别对应:滑动中,滑动快要结束时(手指滑动屏幕后,离开屏幕时),滑动结束
        * */
        switch (state){
            //滑动结束
            case 0:
                if(!autoPlaying){
                    autoPlaying = true;
                }
                break;
            //滑动中(手指与屏幕接触中)
            case 1:
                if(autoPlaying) {
                    autoPlaying = false;
                }
                break;
        }
    }

    /*
    * 设置轮播线程,通过autoPlaying标志控制轮播的暂停与播放
    * */
    boolean autoPlaying=true;
    int current;
    class AutoPlayThread extends Thread{
        private long interval;
        public AutoPlayThread(long interval) {
            this.interval=interval;
        }
        public void run(){ 
                while (true) {
                    try {
                        Thread.sleep(interval);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(autoPlaying) {

                        current = mViewPager.getCurrentItem();
                        handler.sendEmptyMessage(current);
                    }
                } 
        }
    }
    /*
    * 下面是一大堆对ViewPager公共方法的引用。
    * */
    public int getPageMargin() {
        return mViewPager.getPageMargin();
    }
    public int getOffscreenPageLimit() {
        return   mViewPager.getOffscreenPageLimit();
    }
    public PagerAdapter getAdapter() {
        return mViewPager.getAdapter();
    }
    public int getCurrentItem() {
        return mViewPager.getCurrentItem();
    }
    public int getchildCount() {
        return mViewPager.getChildCount();
    }
    public void setPageMarginDrawable(Drawable drawable) {
        mViewPager.setPageMarginDrawable(drawable);
    }
    public void setPageMarginDrawable(int resId) {
        mViewPager.setPageMarginDrawable(resId);
    }
    public void setPageMargin(int margin) {
        mViewPager.setPageMargin(margin);
    }
    public void setCurrentItem(int item) {
        mViewPager.setCurrentItem(item);
    }
    public ViewPager getViewPager(){
        return mViewPager;
    }
}

哈哈,一脸懵逼?很简单的一个自定义插件,也花费了我不少时间。通过对自定义ViewGroup的深入,相信大家都可以做出自己想要的效果。


4.相关文件

关于怎么使用我的插件,请参考我的项目

Github地址:
https://github.com/Ccapton/AutoPlayViewpager

猜你喜欢

转载自blog.csdn.net/ccapton/article/details/54576356