為什么寫這篇文章流纹,因為在網(wǎng)上看到的絕大多數(shù)BannerView實現(xiàn)了右無限輪播圖,甚至沒有實現(xiàn)無限輪播圖违诗,說成是無限輪播圖漱凝,實現(xiàn)了左右無限輪播圖的,并沒有做性能上的優(yōu)化诸迟。
先看張效果圖
工程目錄圖
BannerAdapter:banner輪播圖的適配器茸炒,因為服務(wù)器返回的列表圖片的url,顯示的時候需要轉(zhuǎn)成IamgeViw阵苇;
BannerScroller:設(shè)置切換頁面的持續(xù)時間壁公;
BannerView:繼承RelativeLayout,包含BannViewPager和底部DotIndicatorView指示器绅项;
BannerViewPager:繼承ViewPager紊册,設(shè)置ViewPager的適配器Adpter和動畫;
DotIndicatorView:底部指示器趁怔;
DotIndicatorView類
public class DotIndicatorView extends View {
//形狀
private int mShape;
// 矩形
public static final int SHAPE_REC = 1;
// 圓形
public static final int SHAPE_CIRCLE = 2;
private Drawable mDrawable;
public DotIndicatorView(Context context) {
this(context, null);
}
public DotIndicatorView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DotIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DotIndicatorView);
//默認(rèn)是圓形
mShape = typedArray.getInteger(R.styleable.DotIndicatorView_shape, SHAPE_CIRCLE);
typedArray.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawable != null) {
Bitmap bitmap = drawableToBitmap(mDrawable);
if (mShape == SHAPE_CIRCLE) {
Bitmap circleBitmap = getCircleBitmap(bitmap);
canvas.drawBitmap(circleBitmap, 0, 0, null);
} else if (mShape == SHAPE_REC) {
Bitmap recBitmap = getRecBitmap(bitmap);
canvas.drawBitmap(recBitmap, 0, 0, null);
}
}
}
public void setDrawable(Drawable drawable) {
mDrawable = drawable;
invalidate();
}
/**
* drawable轉(zhuǎn)bitmap
*
* @param drawable
* @return
*/
private Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return (( BitmapDrawable ) drawable).getBitmap();
}
//其他類型 ColorDrawable
//創(chuàng)建一個什么也沒有的Bitmap湿硝;
Bitmap outBitmap = Bitmap.createBitmap(getMeasuredWidth(),
getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(outBitmap);
//把drawable畫到Bitmap上 --》將drawable繪制在canvas內(nèi)部
drawable.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
drawable.draw(canvas);
return outBitmap;
}
public void setShape(int shape) {
mShape = shape;
}
public int getShape() {
return mShape;
}
/**
* 圓形
*
* @param bitmap
* @return
*/
private Bitmap getCircleBitmap(Bitmap bitmap) {
//創(chuàng)建一個Bitmap
Bitmap circleBitmap = Bitmap.createBitmap(getMeasuredWidth(),
getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(circleBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
//防止抖動
paint.setDither(true);
//在畫布上繪制一個圓
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, getMeasuredWidth() / 2, paint);
//設(shè)置畫筆的圖層薪前,PorterDuff.Mode.SRC_IN 取圖層交集的上層
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
//在把原來的bitmap繪制到圓上面
canvas.drawBitmap(bitmap, 0, 0, paint);
//回收Bitmap
bitmap.recycle();
return circleBitmap;
}
/**
* 帶圓角的矩形
*
* @param bitmap
* @return
*/
private Bitmap getRecBitmap(Bitmap bitmap) {
Bitmap recBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(recBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
//防止抖動
paint.setDither(true);
//在畫布上繪制一個圓角的矩形
canvas.drawRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
DensityUtil.dip2px(this.getContext(), 2), DensityUtil.dip2px(this.getContext(), 2), paint);
//設(shè)置畫筆的圖層润努,PorterDuff.Mode.SRC_IN 取圖層交集的上層
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
//在把原來的bitmap繪制到圓上面
canvas.drawBitmap(bitmap, 0, 0, paint);
//回收Bitmap
bitmap.recycle();
return recBitmap;
}
}
一般底部會有兩種類型指示器,一是矩形的示括,二是圓形的铺浇,這個類實現(xiàn)了如何自定義矩形和圓形指示器,其實這個類也可以實現(xiàn)圓形的和帶圓角的矩形的圖片垛膝,用PorterDuffXfermode圖層的概念鳍侣。
BannerAdapter類
public abstract class BannerAdapter {
/**
* 根據(jù)位置獲取ViewPager的子View
*
* @param position
* @return
*/
public abstract View getView(int position, View convertView);
/**
* 返回數(shù)量
*
* @return
*/
public abstract int getCount();
}
BannerAdapter這個類是輪播圖的適配器,因為服務(wù)器返回的列表圖片的url吼拥,顯示的時候需要轉(zhuǎn)成IamgeViw倚聚,用適配器設(shè)計模式轉(zhuǎn)一下。
BannerViewPager類
public class BannerViewPager extends ViewPager {
private static final String TAG = BannerViewPager.class.getSimpleName();
private static final int SCROLL_MSG = 0x011;
private BannerAdapter mBannerAdapter;
private int mCutDownTime = 3000;
private BannerScroller mBannerScroller;
//內(nèi)存優(yōu)化界面復(fù)用
private List<View> mConvertView;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case SCROLL_MSG:
setCurrentItem(getCurrentItem() + 1);
startLoop();
break;
}
}
};
public BannerViewPager(Context context) {
this(context, null);
}
public BannerViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
//改變ViewPager切換的速率
try {
//獲取ViewPager的私有的屬性mScroller
Field field = ViewPager.class.getDeclaredField("mScroller");
mBannerScroller = new BannerScroller(context);
//設(shè)置強制改變
field.setAccessible(true);
//設(shè)置參數(shù) 第一個參數(shù)object當(dāng)前屬性的那個類 第二參數(shù)需要設(shè)置的值
field.set(this, mBannerScroller);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
mConvertView = new ArrayList<>();
}
/**
* 設(shè)置切換頁面的持續(xù)時間
*
* @param scrollerDuration
*/
public void setScrollerDuration(int scrollerDuration) {
mBannerScroller.setScrollerDuration(scrollerDuration);
}
public void setAdapter(BannerAdapter adapter) {
this.mBannerAdapter = adapter;
setAdapter(new BannerPagerAdapter());
//管理Activity的生命周期
(( Activity ) (getContext())).getApplication().registerActivityLifecycleCallbacks(mDefaultActivityLifecycleCallbacks);
}
/**
* 開啟輪播
*/
public void startLoop() {
mHandler.removeMessages(SCROLL_MSG);
mHandler.sendEmptyMessageDelayed(SCROLL_MSG, mCutDownTime);
}
/**
* 銷毀Handler
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mHandler.removeMessages(SCROLL_MSG);
mHandler = null;
}
private class BannerPagerAdapter extends PagerAdapter {
/**
* 給一個很大的值凿可,為了實現(xiàn)無限輪播
* 這個方法是返回ViewPager有多少個View
*/
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
//Adapter設(shè)計模式為了完全讓用戶自定義
//position 0-2的31次方
Log.i(TAG, "instantiateItem:position=" + position + "mBannerAdapter.getCount()=" + mBannerAdapter.getCount());
//position % mBannerAdapter.getCount() 求模
View bannerItemView = mBannerAdapter.getView(position % mBannerAdapter.getCount(), getConvertView());
container.addView(bannerItemView);
return bannerItemView;
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(( View ) object);
mConvertView.add(( View ) object);
}
}
private float mDownX;
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
mHandler.removeMessages(SCROLL_MSG);
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
//左滑動到第一張惑折,跳轉(zhuǎn)到getCount() - 1
if (this.getCurrentItem() == 0) {
if (ev.getX() - mDownX > 0) {
this.setCurrentItem(mBannerAdapter.getCount() - 1);
Log.i(TAG, "onTouchEvent: " + this.getCurrentItem());
}
}
mHandler.sendEmptyMessageDelayed(SCROLL_MSG, mCutDownTime);
break;
}
return super.onTouchEvent(ev);
}
/**
* 處理頁面復(fù)用
*
* @return
*/
public View getConvertView() {
for (int i = 0; i < mConvertView.size(); i++) {
if (mConvertView.get(i).getParent() == null) {
return mConvertView.get(i);
}
}
return null;
}
/**
* 管理Activity的生命周期
*/
DefaultActivityLifecycleCallbacks mDefaultActivityLifecycleCallbacks = new DefaultActivityLifecycleCallbacks() {
@Override
public void onActivityResumed(Activity activity) {
super.onActivityResumed(activity);
if (activity == getContext()) {
//開啟輪播
mHandler.sendEmptyMessageDelayed(SCROLL_MSG, mCutDownTime);
}
}
@Override
public void onActivityPaused(Activity activity) {
super.onActivityPaused(activity);
if (activity == getContext()) {
//停止輪播
mHandler.removeMessages(SCROLL_MSG);
}
}
};
}
繼承PagerAdapter實現(xiàn)getCount()這個方法,這個方法返回的是ViewPager有多少個View枯跑。為了實現(xiàn)無限輪播圖返回了Integer.MAX_VALUE惨驶,用戶不會手殘一直向右滑動吧,造成溢出吧敛助,哈粗卜。DefaultActivityLifecycleCallbacks 去監(jiān)聽Activity的生命周期,為什么要監(jiān)聽呢纳击?因為當(dāng)用戶點擊home鍵的時候续扔,此時應(yīng)用會在后臺攻臀,但是ViewPager里面的ImageView還會循環(huán),所以在Activity執(zhí)行onPaused()的時候测砂,停止輪播茵烈。getConvertView()這個方法是處理界面復(fù)用的,意思是跟RecycleView或者ListView實現(xiàn)列表滑動一樣的砌些,需要界面復(fù)用呜投。最后,小編想實現(xiàn)一個左滑動到position=0存璃,也就是第一張的時候仑荐,想跳轉(zhuǎn)到getCount-1張,具體的做法是想在onTouchEvent()方法監(jiān)聽纵东,手指按下記錄下mDownX粘招,手指抬起的時候ev.getX(),用ev.getX() - mDownX > 0坐下判斷偎球。在設(shè)置下 setCurrentItem(mBannerAdapter.getCount() - 1);
發(fā)現(xiàn)并沒有實現(xiàn)洒扎。也不知道這是為什么,但是我認(rèn)為這種思路沒錯衰絮,哪位大神看到了袍冷,請給出具體解決方案。
BannerView類
public class BannerView extends RelativeLayout {
private BannerViewPager mBannerViewPager;
//底部的指示器的View
private LinearLayout mDotContainerView;
//適配器
private BannerAdapter mAdapter;
private Context mContext;
//選中的drawable
private Drawable mIndicatorFocusDrawable;
//未被選中的drawable
private Drawable mIndicatorNormalDrawable;
//當(dāng)前頁面的位置
private int mCurrentPosition;
//指示器的位置
private int mDotGravity = -1;
//指示器的大小
private int mDotSize = 6;
//指示器的間距
private int mDotDistance = 2;
//底部顏色默認(rèn)透明
private int mBottomColor = Color.TRANSPARENT;
private View mBannerBottomView;
public BannerView(Context context) {
this(context, null);
}
public BannerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflate(context, R.layout.banner_layout, this);
this.mContext = context;
initAttribute(attrs);
initView();
}
/**
* 初始化自定義屬性
*
* @param attrs
*/
private void initAttribute(AttributeSet attrs) {
TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.BannerView);
mDotGravity = typedArray.getInt(R.styleable.BannerView_dotGravity, -1);
mIndicatorFocusDrawable = typedArray.getDrawable(R.styleable.BannerView_dotIndicatorFocus);
if (mIndicatorFocusDrawable == null) {
mIndicatorFocusDrawable = new ColorDrawable(Color.RED);
}
mIndicatorNormalDrawable = typedArray.getDrawable(R.styleable.BannerView_dotIndicatorNormal);
if (mIndicatorNormalDrawable == null) {
mIndicatorNormalDrawable = new ColorDrawable(Color.WHITE);
}
mDotSize = ( int ) typedArray.getDimension(R.styleable.BannerView_dotSize, DensityUtil.dip2px(mContext, 6));
mDotDistance = ( int ) typedArray.getDimension(R.styleable.BannerView_dotDistance, DensityUtil.dip2px(mContext, 2));
mBottomColor = typedArray.getColor(R.styleable.BannerView_bottomColor, mBottomColor);
typedArray.recycle();
}
/**
* 初始化View
*/
private void initView() {
mBannerViewPager = findViewById(R.id.bannerViewPager);
mDotContainerView = findViewById(R.id.dot_container);
mBannerBottomView = findViewById(R.id.bannerBottomView);
mBannerBottomView.setBackgroundColor(mBottomColor);
mBannerViewPager.setPageTransformer(false, new SlidePageTransformer());
}
/**
* 設(shè)置適配器adapter
*
* @param adapter 適配器
*/
public void setAdapter(BannerAdapter adapter) {
this.mAdapter = adapter;
mBannerViewPager.setAdapter(adapter);
mBannerViewPager.setCurrentItem(mBannerViewPager.getChildCount() / 2);
initDotIndicator();
mBannerViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
//監(jiān)聽下當(dāng)前的位置
super.onPageSelected(position);
DotIndicatorView dotIndicatorView = ( DotIndicatorView ) mDotContainerView.
getChildAt(mCurrentPosition);
dotIndicatorView.setDrawable(mIndicatorNormalDrawable);
mCurrentPosition = position % mAdapter.getCount();
DotIndicatorView mCurrentIndicatorView = ( DotIndicatorView ) mDotContainerView.
getChildAt(mCurrentPosition);
mCurrentIndicatorView.setDrawable(mIndicatorFocusDrawable);
}
});
}
public void startLoop() {
mBannerViewPager.startLoop();
}
public void setScrollerDuration(int scrollerDuration) {
mBannerViewPager.setScrollerDuration(scrollerDuration);
}
/**
* 初始化指示器
*/
private void initDotIndicator() {
//獲取廣告位的數(shù)量
int count = mAdapter.getCount();
//設(shè)置指示器的位置
mDotContainerView.setGravity(getDotGravity());
for (int i = 0; i < count; i++) {
DotIndicatorView dot = new DotIndicatorView(mContext);
//設(shè)置指示器的形狀
dot.setShape(1);
LinearLayout.LayoutParams param = null;
//矩形
if (dot.getShape() == 1) {
//給指示器指定大小
param = new LinearLayout.LayoutParams(mDotSize * 3, DensityUtil.dip2px(this.getContext(), 2));
//圓形
} else if (dot.getShape() == 2) {
param = new LinearLayout.LayoutParams(mDotSize, mDotSize);
}
//設(shè)置間距
param.leftMargin = param.rightMargin = mDotDistance;
dot.setLayoutParams(param);
if (i == 0) {
dot.setDrawable(mIndicatorFocusDrawable);
} else {
dot.setDrawable(mIndicatorNormalDrawable);
}
mDotContainerView.addView(dot);
}
}
public int getDotGravity() {
switch (mDotGravity) {
case 0:
return Gravity.CENTER;
case 1:
return Gravity.RIGHT;
case -1:
return Gravity.LEFT;
}
return Gravity.RIGHT;
}
SlidePageTransformer類
public class SlidePageTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(@NonNull View page, float position) {
if (position > 0 && position <= 1) {
page.setPivotX(0);
page.setScaleX(1 - position);
} else if (position >= -1 && position < 0) {
page.setPivotX(page.getWidth());
page.setScaleX(1 + position);
}
}
}
BannerView這個類主要是一些自定義屬性猫牡,底部指示器的大小胡诗、顏色、間距等等淌友。主要說下這個 mBannerViewPager.setPageTransformer(false, new SlidePageTransformer());這個給ViewPager設(shè)置了一個平滑的縮放的動畫煌恢,但是看到了一個ViewPager設(shè)置動畫的一個坑,發(fā)現(xiàn)滑到第一張的時候震庭,在向右滑動的時候瑰抵,圖片會滑出一點邊緣。也不知道為什么器联?我認(rèn)為我的代碼沒有問題二汛,也聽說Android的源碼ViewPager去設(shè)置動畫,會有坑的存在主籍。哪位大神看到了,望賜教千元!