android Banner控件的优雅实现

标签(空格分隔): android


最近在项目开发中需要用到广告轮播控件,由于项目时间比较紧张,看到开源社区类也有类似的实现,于是就偷懒用了一下,但是悲剧就此来了,参考着给定的demo实现,一切是那么完美,一开始的时候,没有后台数据,但是,当伪造的数据替换成网络异步加载数据的时候,发现控件直接crash,于是查看了一下原因,瞬间蒙了,这个控件在当初设计的时候,原来没有考虑异步加载数据,可能是作者只是想要展示一下实现原理,并没有考虑这么多,如果除去这个缺点,这个控件整体实现还是很好的,但是,不能异步网络加载,就代表它再好也没什么卵用,并且我们一般还要它有数据刷新的功能,感觉这么好的实现思路,不能就这么废了,很可惜了,于是画了一天的时间,在保持原作者理论的基础上,重写了控件,增加了异步数据的实现能力,同时,以一种更加优雅的实现方式,提供给调用者使用,这里特来跟大家分享一下。

准备知识

一、ViewPager与pagerAdapter详解

ViewPager有过一定android开发知识的人应该都很熟悉,用途我就不再这里详述了,我们在开发的过程中常用的是这样的使用方式:viewPager+fragment的方式

private ViewPager mViewPager;
private Fragment[] mFragments;
...
...
mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
        @Override
        public Fragment getItem(int position) {
             return mFragments[position];
        }
        @Override
        public int getCount() {
           return mFragments.length;
         }
    });

这样的使用方式是我们关注的重点放到到Fragment上,从而加快我们的开发,其实ViewPager并没有什么,重点是Adapter的实现方式,ViewPager的缤纷多彩的实现效果,其实都是得益于Adapter的实现方式,今天我们的重点也在这个Adapter上面,来实现我们的无限滑动的ViewPager

pagerAdapter

pagerAdapterFragementPagerAdapter的父类,相比FragmentPagerAdapter它通用性更强,可定制性更加灵活。我们在定制自己的pagerAdapter首先是继承PagerAdapter重写里面的方法,实现自己的功能:
pagerAdapter重写方法分析:

public Object instantiateItem (ViewGroup container, int position)

这个函数的功能是创建指定位置的页面视图。适配器的责任就是将创建的view添加到指定的container中,返回值表示的是新增视图页面的key,一般的情况下我们将创建的视图view返回就可以了。

public void destroyItem (ViewGroup container, int position, Object object)

这个方法的功能是是移除一个给定位置的页面。适配器的责任就是从容器中删除这个视图。

public abstract int getCount ()

返回当前有效视图的个数。

public abstract boolean isViewFromObject (View view, Object object)

该函数用来判断instantiateItem(ViewGroup,int)函数所返回来的Key与一个页面视图是否是代表的同一个视图(即它俩是否是对应的,对应的表示同一个View)

好了差不多就这些基础知识,下面我们来说实现原理

实现原理

网上的实现原理大体上分为两种:
一种是在适配器中将getcount的值设置为无限大,这种实现的效果可查看淘宝的客户端,在第一次进去的时候向右滑动是无法滑动的最后一页的,可见他并不是一个真正意义上的无限轮播方式,我们今天不讨论这个;
第二种:实现思路,首先看图说话


Paste_Image.png

可以看出,它分别映射出两个边界的页面,下面的个数是我们viewpager的条目,但是我们的viewPager只会在·
1-3(下标)之间切换,当viewpager1的位置时,我们向右滑动,会出现0位置的页面,0位置上的页面实际上和3页面的内容一样,当我们松手,它会瞬间切换到下标3页面,同理,当我们滑到最后一个页面的时候,也是如此,那么这样就完成了无限滑动的viewPager的效果了,那么如何实现则个效果呢,请看下面的SLooperAdapterSLooperViewPager类,基本上每句代码都有相关的说明。
以上就是无限滑动的ViewPager的原理。
有了这个viewPager我们就可以构造出我们的banner,正常情况下我们的banner控件有两大部分组成,一·展示的图片,这里就是我们的ViewPager,二、下面的指示器。
指示器的组成通常也有两部分,一个是文本 一个是一组圆点。
我们的实现思路就是,父容器用一个RelativeLayout将我们的ViewPager和指示器容器包裹住就行了。具体实现思路请看banner类。

Paste_Image.png

有人说我不想看原理,只想怎么用好了,ok。
为了增加使用的方便性,在此我模仿listView的实现习惯,增加了一个适配器,调用者只需要这样一下几步就可以完成:

在项目的app的gradle文件中加如下代码

compile 'com.xiwenhec:banner:1.0.2'  

第一步:在xml代码写入控件

<com.sivin.Banner
        android:id="@+id/id_banner"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        app:banner_pointGravity="right"
        />

第二步:java代码中绑定控件

 mBanner = (Banner) findViewById(R.id.id_banner);

第三步:实例化适配器,并设置适配器,建议您在new BannerAdapter<BannerModel>的时候将后面的<>中的泛型加上,然后在根据工具提示实现未完成的方法。这样bindData(ImageView imageView, BannerModel bannerModel) 的第二个参数就是你加入的泛型类型。其中mDatas你的banner的数据集合,具体过程使用就会有所体会。
注意:不要忘了mDatas的初始化

 BannerAdapter adapter = new BannerAdapter<BannerModel>(mDatas) {
    @Override
   protected void bindTips(TextView tv, BannerModel bannerModel) {
      tv.setText(bannerModel.getTips());
   }
   @Override
    public void bindImage(ImageView imageView, BannerModel bannerModel) {
        Glide.with(mContext)
        .load(bannerModel
        .getImageUrl())
        .placeholder(R.mipmap.empty)
        .error(R.mipmap.error)
        .into(imageView);
    }
 };
 mBanner.setBannerAdapter(adapter);

最后一步:告诉banner数据不部署完成,为什么这样做呢,正常情况下,我们的数据都是从网络上异步加载的,一般的情况下会以集合的形式传递过来,当我们在完成网络加载的时候,改变了mDatas数据,然后调用mBanner.notifiDataHasChanged();通知banner就行了,使用起来和listview的习惯是不是很相似呢,对就是这样,我们已经完成了。

 mBanner.notifiDataHasChanged();

本想附上apk但是不知道如何上传上去,项目的github地址:Banner:github地址,欢迎forkandstart

以下是具体代码的实现逻辑:
关键类:SLooperAdapter

package com.pactera.banner.SivinBanner;
import android.support.v4.view.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;

/**
 * 无限轮播的viewPager适配器
 * Created by xiwen on 2016/4/13.
 */
public class SLooperAdapter extends PagerAdapter {
    private PagerAdapter mAdapter;

    private int mItemCount=0;

    public SLooperAdapter(PagerAdapter adapter) {
        mAdapter = adapter;
    }

    @Override
    public int getCount() {
        //如果层ViewPager中有两个或两个以上的Item的时候,则映射出边界Item,否则显示与内层个数一致
        return mAdapter.getCount() < 1 ? mAdapter.getCount() : mAdapter.getCount() + 2;
    }

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


    @Override
    public void startUpdate(ViewGroup container) {
        mAdapter.startUpdate(container);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {

        return mAdapter.instantiateItem(container, getInnerAdapterPosition(position));
    }

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

        mAdapter.destroyItem(container, getInnerAdapterPosition(position), object);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        mAdapter.setPrimaryItem(container, position, object);
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        mAdapter.finishUpdate(container);
    }

    @Override
    public void notifyDataSetChanged() {
        mItemCount = getCount();
        super.notifyDataSetChanged();
    }

    @Override
    public int getItemPosition(Object object) {
        if (mItemCount>0){
            mItemCount--;
            return POSITION_NONE;
        }
        return super.getItemPosition(object);
    }

    /**
     * 根据外层position的获取内层的position
     * @param position 外层ViewPager的position
     * @return 外层viewPager当前数据位置对应的内层viewPager对应的位置。
     */
    public int getInnerAdapterPosition(int position) {
        //viewPager真正的可用的个数
        int realCount = getInnerCount();
        //内层没有可用的Item则换回为零
        if (realCount == 0)
            return 0;
        int realPosition = (position - 1) % realCount;
        if (realPosition < 0)
            realPosition += realCount;
        return realPosition;
    }

    /**
     * @return 内层ViewPager中可用的item个数
     */
    public int getInnerCount() {
        return mAdapter.getCount();
    }

    /**
     * 根据内层postion的位置,返回映射后外层position的位置
     * @param position 内层position的位置
     * @return 无限轮播ViewPager的切换位置
     */
    public int toLooperPosition(int position) {
        if (getInnerCount() > 1) {
            return position + 1;
        } else return position;
    }
}

关键类:SLooperViewPager

package com.pactera.banner.SivinBanner;

import android.content.Context;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;

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

/**
 * 无限轮播的ViewPager
 * Created by xiwen on 2016/4/13.
 */
public class SLooperViewPager extends ViewPager {
    private SLooperAdapter mAdapter;
    private List<OnPageChangeListener> mOnPageChangeListeners;
    public SLooperViewPager(Context context) {
        this(context, null);
    }


    public SLooperViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }


    @Override
    public void setAdapter(PagerAdapter adapter) {
        mAdapter = new SLooperAdapter(adapter);
        super.setAdapter(mAdapter);
        setCurrentItem(0, false);
    }

    @Override
    public PagerAdapter getAdapter() {
        return mAdapter;
    }

    @Override
    public void setCurrentItem(int item) {
        setCurrentItem(item, true);
    }

    @Override
    public void setCurrentItem(int position, boolean smoothScroll) {
        //item的被调用者传递过来的位置是没有原始的位置,即切换位置是从0到DataSize-1之间切换
        //但是对于外层ViewPager而言,他需要的位置范围应该是映射后的位置切换,即:出去两边映射的页面
        //应该是从1到映射后的倒数第二个位置

        super.setCurrentItem(mAdapter.toLooperPosition(position), smoothScroll);
    }


    /**
     * 外层ViewPager中的item是通过内层位置映射关系得到的
     *
     * @return 返回映射后的
     */
    @Override
    public int getCurrentItem() {
        return mAdapter.getInnerAdapterPosition(super.getCurrentItem());
    }
    
    @Override
    public void clearOnPageChangeListeners() {
        if (mOnPageChangeListeners != null) {
            mOnPageChangeListeners.clear();
        }
    }

    @Override
    public void removeOnPageChangeListener(OnPageChangeListener listener) {
        if (mOnPageChangeListeners != null) {
            mOnPageChangeListeners.remove(listener);
        }
    }

    @Override
    public void addOnPageChangeListener(OnPageChangeListener listener) {
        if (mOnPageChangeListeners == null) {
            mOnPageChangeListeners = new ArrayList<>();
        }
        mOnPageChangeListeners.add(listener);
    }

    private void init(Context context) {
        if (mOnPageChangeListener != null) {
            super.removeOnPageChangeListener(mOnPageChangeListener);
        }
        super.addOnPageChangeListener(mOnPageChangeListener);
    }

    private OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() {
        //上一次的偏移量
        private float mPreviousOffset = -1;
        //上一次的位置
        private float mPreviousPosition = -1;

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            if (mAdapter != null) {
                int innerPosition = mAdapter.getInnerAdapterPosition(position);
                /*
                    positionOffset =0:滚动完成,
                    position =0 :开始的边界
                    position =mAdapter.getCount()-1:结束的边界
                 */
                if (positionOffset == 0 && mPreviousOffset == 0 && (position == 0 || position == mAdapter.getCount() - 1)) {
                    //强制回到映射位置
                    setCurrentItem(innerPosition, false);
                }
                mPreviousOffset = positionOffset;

                if (mOnPageChangeListeners != null) {
                    for (int i = 0; i < mOnPageChangeListeners.size(); i++) {
                        OnPageChangeListener listener = mOnPageChangeListeners.get(i);
                        if (listener != null) {
                            //如果内层的位置没有达到最后一个,内层滚动监听器正常设置
                            if (innerPosition != mAdapter.getInnerCount() - 1) {
                                listener.onPageScrolled(innerPosition, positionOffset, positionOffsetPixels);
                            } else {
                                //如果到达最后一个位置,当偏移量达到0.5以上,这告诉监听器,这个页面已经到达内层的第一个位置
                                //否则还是最后一个位置
                                if (positionOffset > 0.5) {
                                    listener.onPageScrolled(0, 0, 0);
                                } else {
                                    listener.onPageScrolled(innerPosition, 0, 0);
                                }
                            }
                        }
                    }
                }
            }

        }

        @Override
        public void onPageSelected(int position) {
            int realPosition = mAdapter.getInnerAdapterPosition(position);
            if (mPreviousPosition != realPosition) {
                mPreviousPosition = realPosition;
                if (mOnPageChangeListeners != null) {
                    for (int i = 0; i < mOnPageChangeListeners.size(); i++) {
                        OnPageChangeListener listener = mOnPageChangeListeners.get(i);
                        if (listener != null) {
                            listener.onPageSelected(realPosition);
                        }
                    }
                }
            }
        }
        @Override
        public void onPageScrollStateChanged(int state) {
            if (mAdapter != null) {
                int position = SLooperViewPager.super.getCurrentItem();
                int realPosition = mAdapter.getInnerAdapterPosition(position);
                if (state == ViewPager.SCROLL_STATE_IDLE && (position == 0 || position == mAdapter.getCount() - 1)) {
                    setCurrentItem(realPosition, false);
                }
            }
            if (mOnPageChangeListeners != null) {
                for (int i = 0; i < mOnPageChangeListeners.size(); i++) {
                    OnPageChangeListener listener = mOnPageChangeListeners.get(i);
                    if (listener != null) {
                        listener.onPageScrollStateChanged(state);
                    }
                }
            }
        }
    };
}

关键类:Banner

package com.pactera.banner.SivinBanner;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.support.v4.view.PagerAdapter;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.pactera.banner.R;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Created by xiwen on 2016/4/12.
 */
public class Banner extends RelativeLayout {
    private static final String TAG = Banner.class.getSimpleName();

    private Context mContext;

    private SparseArray<ImageView> mItemArrays;

    /**
     * 布局参数
     */
    private static final int RMP = LayoutParams.MATCH_PARENT;
    private static final int RWC = LayoutParams.WRAP_CONTENT;
    private static final int LWC = LinearLayout.LayoutParams.WRAP_CONTENT;
    /**
     * 循环轮播的Viewpager
     */
    private SLooperViewPager mViewPager;


    //下面这两个控件,存放到一个相对布局中,由于不需要设成成员变量,故此没写

    /**
     * 轮播控件的提示文字
     */
    private TextView mTipTextView;
    /**
     * 提示文字的大小
     */
    private int mTipTextSize;

    /**
     * 提示文字的颜色
     */
    private int mTipTextColor = Color.WHITE;

    /**
     * 存放点的容器
     */
    private LinearLayout mPointContainerLl;
    /**
     * 点的drawable资源id
     */
    private int mPointDrawableResId = R.drawable.selector_basebanner_point;

    /**
     * 点的layout的属性
     */
    private int mPointGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
    private int mPointLeftRightMargin;
    private int mPointTopBottomMargin;
    private int mPointContainerLeftRightPadding;

    /**
     * 存放TipTextView和mPointContainerLl的相对布局的背景资源Id;
     */
    private Drawable mPointContainerBackgroundDrawable;

    /**
     * 存放轮播信息的数据集合
     */
    protected List mData = new ArrayList<>();

    /**
     * 自动播放的间隔
     */
    private int mAutoPlayInterval = 3;

    /**
     * 页面切换的时间(从下一页开始出现,到完全出现的时间)
     */
    private int mPageChangeDuration = 800;
    /**
     * 是否正在播放
     */
    private boolean mIsAutoPlaying = false;

    /**
     * 当前的页面的位置
     */
    protected int currentPosition;

    private BannerAdapter mBannerAdapter;

    /**
     * 任务执行器
     */
    protected ScheduledExecutorService mExecutor;


    /**
     * 播放下一个执行器
     */
    private Handler mPlayHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            scrollToNextItem(currentPosition);
        }
    };


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

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

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

        //初始化默认属性
        initDefaultAttrs(context);

        //初始化自定义属性
        initCustomAttrs(context, attrs);

        //控件初始化
        initView(context);
    }

    private void initDefaultAttrs(Context context) {

        //默认点指示器的左右Margin3dp
        mPointLeftRightMargin = dp2px(context, 3);
        //默认点指示器的上下margin为6dp
        mPointTopBottomMargin = dp2px(context, 6);
        //默认点容器的左右padding为10dp
        mPointContainerLeftRightPadding = dp2px(context, 10);
        //默认指示器提示文字大小8sp
        mTipTextSize = sp2px(context, 8);
        //默认指示器容器的背景图片
        mPointContainerBackgroundDrawable = new ColorDrawable(Color.parseColor("#33aaaaaa"));
    }

    public static int dp2px(Context context, float dpValue) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, context.getResources().getDisplayMetrics());
    }

    public static int sp2px(Context context, float spValue) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, context.getResources().getDisplayMetrics());
    }

    /**
     * 初始化自定义属性
     *
     * @param context context
     * @param attrs   attrs
     */
    private void initCustomAttrs(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BaseBanner);
        final int N = typedArray.getIndexCount();
        for (int i = 0; i < N; i++) {
            initCustomAttr(typedArray.getIndex(i), typedArray);
        }
        typedArray.recycle();
    }

    private void initCustomAttr(int attr, TypedArray typedArray) {
        if (attr == R.styleable.BaseBanner_banner_pointDrawable) {
            //指示器点的样式资源id
            mPointDrawableResId = typedArray.getResourceId(attr, R.drawable.selector_basebanner_point);
        } else if (attr == R.styleable.BaseBanner_banner_pointContainerBackground) {
            //指示器容器背景样式
            mPointContainerBackgroundDrawable = typedArray.getDrawable(attr);

        } else if (attr == R.styleable.BaseBanner_banner_pointLeftRightMargin) {
            //指示器左右边距
            mPointLeftRightMargin = typedArray.getDimensionPixelSize(attr, mPointLeftRightMargin);
        } else if (attr == R.styleable.BaseBanner_banner_pointContainerLeftRightPadding) {
            //指示器容器的左右padding
            mPointContainerLeftRightPadding = typedArray.getDimensionPixelSize(attr, mPointContainerLeftRightPadding);
        } else if (attr == R.styleable.BaseBanner_banner_pointTopBottomMargin) {

            //指示器的上下margin
            mPointTopBottomMargin = typedArray.getDimensionPixelSize(attr, mPointTopBottomMargin);
        } else if (attr == R.styleable.BaseBanner_banner_pointGravity) {
            //指示器在容器中的位置属性
            mPointGravity = typedArray.getInt(attr, mPointGravity);
        } else if (attr == R.styleable.BaseBanner_banner_pointAutoPlayInterval) {
            //轮播的间隔
            mAutoPlayInterval = typedArray.getInteger(attr, mAutoPlayInterval);
        } else if (attr == R.styleable.BaseBanner_banner_pageChangeDuration) {
            //页面切换的持续时间
            mPageChangeDuration = typedArray.getInteger(attr, mPageChangeDuration);
        } else if (attr == R.styleable.BaseBanner_banner_tipTextColor) {
            //提示文字颜色
            mTipTextColor = typedArray.getColor(attr, mTipTextColor);
        } else if (attr == R.styleable.BaseBanner_banner_tipTextSize) {
            //提示文字大小
            mTipTextSize = typedArray.getDimensionPixelSize(attr, mTipTextSize);
        }

    }

    /**
     * 控件初始化
     *
     * @param context context
     */
    private void initView(Context context) {
        mContext = context;

        mItemArrays = new SparseArray();

        //初始化ViewPager
        mViewPager = new SLooperViewPager(context);

        //以matchParent的方式将viewPager填充到控件容器中
        addView(mViewPager, new LayoutParams(RMP, RMP));

        //设置页面切换的持续时间
        setPageChangeDuration(mPageChangeDuration);

        //创建指示器容器的相对布局
        RelativeLayout indicatorContainerRl = new RelativeLayout(context);
        //设置指示器容器的背景
        if (Build.VERSION.SDK_INT >= 16) {
            indicatorContainerRl.setBackground(mPointContainerBackgroundDrawable);
        } else {
            indicatorContainerRl.setBackgroundDrawable(mPointContainerBackgroundDrawable);
        }
        //设置指示器容器Padding
        indicatorContainerRl.setPadding(mPointContainerLeftRightPadding, 0, mPointContainerLeftRightPadding, 0);
        //初始化指示器容器的布局参数
        LayoutParams indicatorContainerLp = new LayoutParams(RMP, RWC);

        // 设置指示器容器内的子view的布局方式
        if ((mPointGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.TOP) {
            indicatorContainerLp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
        } else {
            indicatorContainerLp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        }
        //将指示器容器添加到父View中
        addView(indicatorContainerRl, indicatorContainerLp);

        //初始化存放点的线性布局
        mPointContainerLl = new LinearLayout(context);
        //设置线性布局的id
        mPointContainerLl.setId(R.id.banner_pointContainerId);
        //设置线性布局的方向
        mPointContainerLl.setOrientation(LinearLayout.HORIZONTAL);
        //设置点容器的布局参数
        LayoutParams pointContainerLp = new LayoutParams(RWC, RWC);
        //将点容器存放到指示器容器中
        indicatorContainerRl.addView(mPointContainerLl, pointContainerLp);
        //初始化tip的layout尺寸参数,高度和点的高度一致
        LayoutParams tipLp = new LayoutParams(RMP, getResources().getDrawable(mPointDrawableResId).getIntrinsicHeight() + 2 * mPointTopBottomMargin);
        mTipTextView = new TextView(context);
        mTipTextView.setGravity(Gravity.CENTER_VERTICAL);
        mTipTextView.setSingleLine(true);
        mTipTextView.setEllipsize(TextUtils.TruncateAt.END);
        mTipTextView.setTextColor(mTipTextColor);
        mTipTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTipTextSize);
        //将TieTextView存放于指示器容器中
        indicatorContainerRl.addView(mTipTextView, tipLp);
        int horizontalGravity = mPointGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
        // 处理圆点容器位于指示器容器的左边、右边还是水平居中
        if (horizontalGravity == Gravity.LEFT) {
            pointContainerLp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
            //提示文字设置在点容器的右边
            tipLp.addRule(RelativeLayout.RIGHT_OF, R.id.banner_pointContainerId);
            mTipTextView.setGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
        } else if (horizontalGravity == Gravity.RIGHT) {
            pointContainerLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            tipLp.addRule(RelativeLayout.LEFT_OF, R.id.banner_pointContainerId);
        } else {
            pointContainerLp.addRule(RelativeLayout.CENTER_HORIZONTAL);
            tipLp.addRule(RelativeLayout.LEFT_OF, R.id.banner_pointContainerId);
        }
    }


    /**
     * 初始化点
     * 这样的做法,可以使在刷新获数据的时候提升性能
     */
    private void initPoints() {

        int childCount = mPointContainerLl.getChildCount();
        int dataSize = mData.size();
        int offset = dataSize - childCount;
        if (offset == 0)
            return;
        if (offset > 0) {
            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LWC, LWC);
            lp.setMargins(mPointLeftRightMargin, mPointTopBottomMargin, mPointLeftRightMargin, mPointTopBottomMargin);
            ImageView imageView;
            for (int i = 0; i < offset; i++) {
                imageView = new ImageView(getContext());
                imageView.setLayoutParams(lp);
                imageView.setImageResource(mPointDrawableResId);
                imageView.setEnabled(false);
                mPointContainerLl.addView(imageView);
            }
            return;
        }
        if (offset < 0) {
            mPointContainerLl.removeViews(dataSize, -offset);
        }
    }


    private final class ChangePointListener extends SLooperViewPager.SimpleOnPageChangeListener {
        @Override
        public void onPageSelected(int position) {
            currentPosition = position % mData.size();
            switchToPoint(currentPosition);
        }

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            if (mTipTextView != null) {
                if (positionOffset > 0.5) {
                    onTitleSlect(mTipTextView, currentPosition);
                    mTipTextView.setAlpha(positionOffset);
                } else {
                    mTipTextView.setAlpha(1 - positionOffset);
                    onTitleSlect(mTipTextView, currentPosition);
                }
            }
        }
    }

    /**
     * 将点切换到指定的位置
     * 就是将指定位置的点设置成Enable
     *
     * @param newCurrentPoint 新位置
     */
    private void switchToPoint(int newCurrentPoint) {
        for (int i = 0; i < mPointContainerLl.getChildCount(); i++) {
            mPointContainerLl.getChildAt(i).setEnabled(false);
        }
        mPointContainerLl.getChildAt(newCurrentPoint).setEnabled(true);

        if (mTipTextView != null) {
            onTitleSlect(mTipTextView, currentPosition);
        }
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                pauseScroll();
                break;
            case MotionEvent.ACTION_UP:
                goScroll();
                break;
            case MotionEvent.ACTION_CANCEL:
                goScroll();
                break;
        }
        return super.dispatchTouchEvent(ev);
    }


    /**
     * 重写方法,当Viewpager滚动到下一个位置的时候,设置title的内容,
     * 同时你也可以设置title的属性,例如textColor
     * 如果指示器的setIndicatorGravity设置的是center属性,则不做任何事情
     */
    public void onTitleSlect(TextView tv, int position) {
    }


    /**
     * 设置页码切换过程的时间长度
     *
     * @param duration 页码切换过程的时间长度
     */
    public void setPageChangeDuration(int duration) {

    }

    /**
     * 滚动到下一个条目
     *
     * @param position
     */
    private void scrollToNextItem(int position) {
        position++;
        mViewPager.setCurrentItem(position, true);
    }


    /**
     * viewPager的适配器
     */
    private final class InnerPagerAdapter extends PagerAdapter {
        int mCount = 0;

        @Override
        public int getCount() {
            return mData.size();
        }

        @Override
        public Object instantiateItem(ViewGroup container, final int position) {
            ImageView  view = createItemView(position);
            mBannerAdapter.setImageViewSource(view, mTipTextView, position);
            view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (onVpItemClickListener != null) {
                        onVpItemClickListener.onItemClick(position);
                    }
                }
            });

            container.addView(view);
            return view;
        }

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

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


        @Override
        public int getItemPosition(Object object) {
            return POSITION_NONE;
        }
    }

    /**
     * 创建itemView
     *
     * @param position
     * @return
     */
    private ImageView createItemView(int position) {
        ImageView iv = new ImageView(mContext);
        iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
        mItemArrays.put(position, iv);
        return iv;
    }

    ;


    private OnVpItemClickListener onVpItemClickListener;

    /**
     * 设置viewPage的Item点击监听器
     *
     * @param listener
     */
    public void setOnItemClickListener(OnVpItemClickListener listener) {
        this.onVpItemClickListener = listener;
    }

    public interface OnVpItemClickListener {
        void onItemClick(int position);
    }


    /**
     * 方法使用状态 :viewpager处于暂停的状态
     * 开始滚动
     */
    public void goScroll() {
        if (!isValid()) {
            return;
        }
        if (mIsAutoPlaying) {
            return;
        } else {
            pauseScroll();
            mExecutor = Executors.newSingleThreadScheduledExecutor();
            //command:执行线程
            //initialDelay:初始化延时
            //period:两次开始执行最小间隔时间
            //unit:计时单位
            mExecutor.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    mPlayHandler.obtainMessage().sendToTarget();
                }
            }, mAutoPlayInterval, mAutoPlayInterval, TimeUnit.SECONDS);
            mIsAutoPlaying = true;
        }
    }

    /**
     * 暂停滚动
     */
    public void pauseScroll() {
        if (mExecutor != null) {
            mExecutor.shutdown();
            mExecutor = null;
        }
        mIsAutoPlaying = false;
    }

    @Override
    protected void onVisibilityChanged(View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        if (visibility == VISIBLE) {
            goScroll();
        } else if (visibility == INVISIBLE) {
            pauseScroll();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        pauseScroll();
    }

    /**
     * 判断控件是否可用
     *
     * @return
     */
    protected boolean isValid() {
        if (mViewPager == null) {
            Log.e(TAG, "ViewPager is not exist!");
            return false;
        }
        if (mData == null || mData.size() == 0) {
            Log.e(TAG, "DataList must be not empty!");
            return false;
        }
        return true;
    }

    /**
     * 设置数据的集合
     */
    public void setSource() {
        List list = mBannerAdapter.getDatas();
        if (list == null) {
            Log.d(TAG, "setSource: list==null");
            return;
        }
        this.mData = list;
        setAdapter();
    }

    /**
     * 给viewpager设置适配器
     */
    private void setAdapter() {
        mViewPager.setAdapter(new InnerPagerAdapter());
        mViewPager.addOnPageChangeListener(new ChangePointListener());
    }

    public void setBannerAdapter(BannerAdapter adapter) {
        mBannerAdapter = adapter;
        setSource();
    }
    /**
     * 通知数据已经放生改变
     */
    public void notifiDataHasChanged() {
        initPoints();
        mViewPager.getAdapter().notifyDataSetChanged();
        mViewPager.setCurrentItem(0, false);
        goScroll();
    }
}

关键类:BannerAdapter

package com.pactera.banner.SivinBanner;

import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

/**
 * Created by sivin on 2016/5/1.
 */
public abstract class BannerAdapter<T> {
    private static final String TAG = "BannerAdapter";
    private List<T> mDatas;

    public List<T> getDatas() {
        return mDatas;
    }

    public BannerAdapter(List<T> datas) {
        mDatas = datas;
    }

    public void setImageViewSource(ImageView imageView, TextView textView, int position) {
        bindImage(imageView, mDatas.get(position));
    }

    public void selectTips(TextView tv, int position) {
        if (mDatas != null && mDatas.size() > 0)
            bindTips(tv, mDatas.get(position));
    }

    protected abstract void bindTips(TextView tv, T t);

    public abstract void bindImage(ImageView imageView, T t);

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,924评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,902评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,716评论 0 239
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,783评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,166评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,510评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,784评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,476评论 0 196
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,196评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,459评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,978评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,321评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,964评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,046评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,803评论 0 193
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,530评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,420评论 2 265

推荐阅读更多精彩内容