android Banner控件的優(yōu)雅實現(xiàn)

標簽(空格分隔): android


最近在項目開發(fā)中需要用到廣告輪播控件余佃,由于項目時間比較緊張鞍历,看到開源社區(qū)類也有類似的實現(xiàn),于是就偷懶用了一下毛仪,但是悲劇就此來了订框,參考著給定的demo實現(xiàn)析苫,一切是那么完美,一開始的時候穿扳,沒有后臺數(shù)據(jù)衩侥,但是,當偽造的數(shù)據(jù)替換成網(wǎng)絡異步加載數(shù)據(jù)的時候矛物,發(fā)現(xiàn)控件直接crash茫死,于是查看了一下原因,瞬間蒙了履羞,這個控件在當初設計的時候峦萎,原來沒有考慮異步加載數(shù)據(jù)屡久,可能是作者只是想要展示一下實現(xiàn)原理,并沒有考慮這么多爱榔,如果除去這個缺點被环,這個控件整體實現(xiàn)還是很好的,但是详幽,不能異步網(wǎng)絡加載筛欢,就代表它再好也沒什么卵用,并且我們一般還要它有數(shù)據(jù)刷新的功能唇聘,感覺這么好的實現(xiàn)思路版姑,不能就這么廢了,很可惜了迟郎,于是畫了一天的時間剥险,在保持原作者理論的基礎上,重寫了控件宪肖,增加了異步數(shù)據(jù)的實現(xiàn)能力表制,同時,以一種更加優(yōu)雅的實現(xiàn)方式控乾,提供給調(diào)用者使用夫凸,這里特來跟大家分享一下。

準備知識

一阱持、ViewPager與pagerAdapter詳解

ViewPager有過一定android開發(fā)知識的人應該都很熟悉夭拌,用途我就不再這里詳述了,我們在開發(fā)的過程中常用的是這樣的使用方式: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上衷咽,從而加快我們的開發(fā)鸽扁,其實ViewPager并沒有什么,重點是Adapter的實現(xiàn)方式镶骗,ViewPager的繽紛多彩的實現(xiàn)效果桶现,其實都是得益于Adapter的實現(xiàn)方式,今天我們的重點也在這個Adapter上面鼎姊,來實現(xiàn)我們的無限滑動的ViewPager

pagerAdapter

pagerAdapterFragementPagerAdapter的父類骡和,相比FragmentPagerAdapter它通用性更強,可定制性更加靈活相寇。我們在定制自己的pagerAdapter首先是繼承PagerAdapter重寫里面的方法慰于,實現(xiàn)自己的功能:
pagerAdapter重寫方法分析:

public Object instantiateItem (ViewGroup container, int position)

這個函數(shù)的功能是創(chuàng)建指定位置的頁面視圖。適配器的責任就是將創(chuàng)建的view添加到指定的container中唤衫,返回值表示的是新增視圖頁面的key,一般的情況下我們將創(chuàng)建的視圖view返回就可以了婆赠。

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

這個方法的功能是是移除一個給定位置的頁面。適配器的責任就是從容器中刪除這個視圖佳励。

public abstract int getCount ()

返回當前有效視圖的個數(shù)休里。

public abstract boolean isViewFromObject (View view, Object object)

該函數(shù)用來判斷instantiateItem(ViewGroup,int)函數(shù)所返回來的Key與一個頁面視圖是否是代表的同一個視圖(即它倆是否是對應的蛆挫,對應的表示同一個View)

好了差不多就這些基礎知識,下面我們來說實現(xiàn)原理

實現(xiàn)原理

網(wǎng)上的實現(xiàn)原理大體上分為兩種:
一種是在適配器中將getcount的值設置為無限大妙黍,這種實現(xiàn)的效果可查看淘寶的客戶端悴侵,在第一次進去的時候向右滑動是無法滑動的最后一頁的,可見他并不是一個真正意義上的無限輪播方式拭嫁,我們今天不討論這個畜挨;
第二種:實現(xiàn)思路,首先看圖說話


Paste_Image.png

可以看出噩凹,它分別映射出兩個邊界的頁面,下面的個數(shù)是我們viewpager的條目毡咏,但是我們的viewPager只會在·
1-3(下標)之間切換驮宴,當viewpager1的位置時,我們向右滑動呕缭,會出現(xiàn)0位置的頁面堵泽,0位置上的頁面實際上和3頁面的內(nèi)容一樣,當我們松手恢总,它會瞬間切換到下標3頁面迎罗,同理,當我們滑到最后一個頁面的時候片仿,也是如此纹安,那么這樣就完成了無限滑動的viewPager的效果了,那么如何實現(xiàn)則個效果呢砂豌,請看下面的SLooperAdapterSLooperViewPager類厢岂,基本上每句代碼都有相關的說明。
以上就是無限滑動的ViewPager的原理阳距。
有了這個viewPager我們就可以構造出我們的banner塔粒,正常情況下我們的banner控件有兩大部分組成,一·展示的圖片筐摘,這里就是我們的ViewPager,二卒茬、下面的指示器。
指示器的組成通常也有兩部分咖熟,一個是文本 一個是一組圓點圃酵。
我們的實現(xiàn)思路就是,父容器用一個RelativeLayout將我們的ViewPager和指示器容器包裹住就行了馍管。具體實現(xiàn)思路請看banner類辜昵。

Paste_Image.png

有人說我不想看原理,只想怎么用好了咽斧,ok堪置。
為了增加使用的方便性躬存,在此我模仿listView的實現(xiàn)習慣,增加了一個適配器舀锨,調(diào)用者只需要這樣一下幾步就可以完成:

在項目的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>的時候將后面的<>中的泛型加上坎匿,然后在根據(jù)工具提示實現(xiàn)未完成的方法盾剩。這樣bindData(ImageView imageView, BannerModel bannerModel) 的第二個參數(shù)就是你加入的泛型類型。其中mDatas你的banner的數(shù)據(jù)集合替蔬,具體過程使用就會有所體會告私。
注意:不要忘了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數(shù)據(jù)不部署完成,為什么這樣做呢承桥,正常情況下驻粟,我們的數(shù)據(jù)都是從網(wǎng)絡上異步加載的,一般的情況下會以集合的形式傳遞過來凶异,當我們在完成網(wǎng)絡加載的時候蜀撑,改變了mDatas數(shù)據(jù),然后調(diào)用mBanner.notifiDataHasChanged();通知banner就行了剩彬,使用起來和listview的習慣是不是很相似呢酷麦,對就是這樣,我們已經(jīng)完成了喉恋。

 mBanner.notifiDataHasChanged();

本想附上apk但是不知道如何上傳上去沃饶,項目的github地址:Banner:github地址,歡迎forkandstart

以下是具體代碼的實現(xiàn)邏輯:
關鍵類: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轻黑,否則顯示與內(nèi)層個數(shù)一致
        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);
    }

    /**
     * 根據(jù)外層position的獲取內(nèi)層的position
     * @param position 外層ViewPager的position
     * @return 外層viewPager當前數(shù)據(jù)位置對應的內(nèi)層viewPager對應的位置绍坝。
     */
    public int getInnerAdapterPosition(int position) {
        //viewPager真正的可用的個數(shù)
        int realCount = getInnerCount();
        //內(nèi)層沒有可用的Item則換回為零
        if (realCount == 0)
            return 0;
        int realPosition = (position - 1) % realCount;
        if (realPosition < 0)
            realPosition += realCount;
        return realPosition;
    }

    /**
     * @return 內(nèi)層ViewPager中可用的item個數(shù)
     */
    public int getInnerCount() {
        return mAdapter.getCount();
    }

    /**
     * 根據(jù)內(nèi)層postion的位置,返回映射后外層position的位置
     * @param position 內(nèi)層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的被調(diào)用者傳遞過來的位置是沒有原始的位置苔悦,即切換位置是從0到DataSize-1之間切換
        //但是對于外層ViewPager而言轩褐,他需要的位置范圍應該是映射后的位置切換,即:出去兩邊映射的頁面
        //應該是從1到映射后的倒數(shù)第二個位置

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


    /**
     * 外層ViewPager中的item是通過內(nèi)層位置映射關系得到的
     *
     * @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) {
                            //如果內(nèi)層的位置沒有達到最后一個把介,內(nèi)層滾動監(jiān)聽器正常設置
                            if (innerPosition != mAdapter.getInnerCount() - 1) {
                                listener.onPageScrolled(innerPosition, positionOffset, positionOffsetPixels);
                            } else {
                                //如果到達最后一個位置,當偏移量達到0.5以上蟋座,這告訴監(jiān)聽器拗踢,這個頁面已經(jīng)到達內(nèi)層的第一個位置
                                //否則還是最后一個位置
                                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;

    /**
     * 布局參數(shù)
     */
    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;
    /**
     * 循環(huán)輪播的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;

    /**
     * 存放輪播信息的數(shù)據(jù)集合
     */
    protected List mData = new ArrayList<>();

    /**
     * 自動播放的間隔
     */
    private int mAutoPlayInterval = 3;

    /**
     * 頁面切換的時間(從下一頁開始出現(xiàn),到完全出現(xiàn)的時間)
     */
    private int mPageChangeDuration = 800;
    /**
     * 是否正在播放
     */
    private boolean mIsAutoPlaying = false;

    /**
     * 當前的頁面的位置
     */
    protected int currentPosition;

    private BannerAdapter mBannerAdapter;

    /**
     * 任務執(zhí)行器
     */
    protected ScheduledExecutorService mExecutor;


    /**
     * 播放下一個執(zhí)行器
     */
    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) {
            //頁面切換的持續(xù)時間
            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));

        //設置頁面切換的持續(xù)時間
        setPageChangeDuration(mPageChangeDuration);

        //創(chuàng)建指示器容器的相對布局
        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);
        //初始化指示器容器的布局參數(shù)
        LayoutParams indicatorContainerLp = new LayoutParams(RMP, RWC);

        // 設置指示器容器內(nèi)的子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);
        //設置點容器的布局參數(shù)
        LayoutParams pointContainerLp = new LayoutParams(RWC, RWC);
        //將點容器存放到指示器容器中
        indicatorContainerRl.addView(mPointContainerLl, pointContainerLp);
        //初始化tip的layout尺寸參數(shù)君纫,高度和點的高度一致
        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);
        }
    }


    /**
     * 初始化點
     * 這樣的做法,可以使在刷新獲數(shù)據(jù)的時候提升性能
     */
    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的內(nèi)容,
     * 同時你也可以設置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;
        }
    }

    /**
     * 創(chuàng)建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點擊監(jiān)聽器
     *
     * @param listener
     */
    public void setOnItemClickListener(OnVpItemClickListener listener) {
        this.onVpItemClickListener = listener;
    }

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


    /**
     * 方法使用狀態(tài) :viewpager處于暫停的狀態(tài)
     * 開始滾動
     */
    public void goScroll() {
        if (!isValid()) {
            return;
        }
        if (mIsAutoPlaying) {
            return;
        } else {
            pauseScroll();
            mExecutor = Executors.newSingleThreadScheduledExecutor();
            //command:執(zhí)行線程
            //initialDelay:初始化延時
            //period:兩次開始執(zhí)行最小間隔時間
            //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;
    }

    /**
     * 設置數(shù)據(jù)的集合
     */
    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();
    }
    /**
     * 通知數(shù)據(jù)已經(jīng)放生改變
     */
    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);

}

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肢执,隨后出現(xiàn)的幾起案子枉阵,更是在濱河造成了極大的恐慌,老刑警劉巖预茄,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兴溜,死亡現(xiàn)場離奇詭異,居然都是意外死亡反璃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門假夺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淮蜈,“玉大人,你說我怎么就攤上這事已卷∥嗵铮” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵侧蘸,是天一觀的道長裁眯。 經(jīng)常有香客問我,道長讳癌,這世上最難降的妖魔是什么穿稳? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮晌坤,結果婚禮上逢艘,老公的妹妹穿的比我還像新娘。我一直安慰自己骤菠,他們只是感情好它改,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著商乎,像睡著了一般央拖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天鲜戒,我揣著相機與錄音专控,去河邊找鬼。 笑死袍啡,一個胖子當著我的面吹牛踩官,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播境输,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蔗牡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嗅剖?” 一聲冷哼從身側響起辩越,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎信粮,沒想到半個月后黔攒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡强缘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年督惰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旅掂。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡赏胚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出商虐,到底是詐尸還是另有隱情觉阅,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布秘车,位于F島的核電站典勇,受9級特大地震影響,放射性物質發(fā)生泄漏叮趴。R本人自食惡果不足惜割笙,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望眯亦。 院中可真熱鬧咳蔚,春花似錦、人聲如沸搔驼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舌涨。三九已至糯耍,卻和暖如春扔字,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背温技。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工革为, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舵鳞。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓震檩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蜓堕。 傳聞我的和親對象是個殘疾皇子抛虏,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容