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