打造萬能的BannerView(ViewPager)無限輪播圖
為什麼寫這篇文章,因為在網上看到的絕大多數BannerView實現了右無限輪播圖,甚至沒有實現無限輪播圖,說成是無限輪播圖,實現了左右無限輪播圖的,並沒有做效能上的最佳化。
先看張效果圖
device-2018-05-11-173850.gif
工程目錄圖
project.png
BannerAdapter:banner輪播圖的介面卡,因為伺服器返回的列表圖片的url,顯示的時候需要轉成IamgeViw; BannerScroller:設定切換頁面的持續時間; BannerView:繼承RelativeLayout,包含BannViewPager和底部DotIndicatorView指示器; BannerViewPager:繼承ViewPager,設定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); //預設是圓形 mShape = typedArray.getInteger(R.styleable.DotIndicatorView_shape, SHAPE_CIRCLE); typedArray.recycle(); }@Overrideprotected 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轉bitmap * * @param drawable * @return */private Bitmap drawableToBitmap(Drawable drawable) { if (drawable instanceof BitmapDrawable) { return (( BitmapDrawable ) drawable).getBitmap(); } //其他型別 ColorDrawable //建立一個什麼也沒有的Bitmap; Bitmap outBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(outBitmap); //把drawable畫到Bitmap上 --》將drawable繪製在canvas內部 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) { //建立一個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); //設定畫筆的圖層,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); //設定畫筆的圖層,PorterDuff.Mode.SRC_IN 取圖層交集的上層 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); //在把原來的bitmap繪製到圓上面 canvas.drawBitmap(bitmap, 0, 0, paint); //回收Bitmap bitmap.recycle(); return recBitmap; } }
一般底部會有兩種型別指示器,一是矩形的,二是圓形的,這個類實現瞭如何自定義矩形和圓形指示器,其實這個類也可以實現圓形的和帶圓角的矩形的圖片,用PorterDuffXfermode圖層的概念。
BannerAdapter類
public abstract class BannerAdapter { /** * 根據位置獲取ViewPager的子View * * @param position * @return */ public abstract View getView(int position, View convertView); /** * 返回數量 * * @return */ public abstract int getCount(); }
BannerAdapter這個類是輪播圖的介面卡,因為伺服器返回的列表圖片的url,顯示的時候需要轉成IamgeViw,用介面卡設計模式轉一下。
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; //記憶體最佳化介面複用 private ListmConvertView; @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); //設定強制改變 field.setAccessible(true); //設定引數 第一個引數object當前屬性的那個類 第二引數需要設定的值 field.set(this, mBannerScroller); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } mConvertView = new ArrayList(); }/** * 設定切換頁面的持續時間 * * @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 */@Overrideprotected void onDetachedFromWindow() { super.onDetachedFromWindow(); mHandler.removeMessages(SCROLL_MSG); mHandler = null; }private class BannerPagerAdapter extends PagerAdapter { /** * 給一個很大的值,為了實現無限輪播 * 這個方法是返回ViewPager有多少個View */ @Override public int getCount() { return Integer.MAX_VALUE; } @NonNull @Override public Object instantiateItem(@NonNull ViewGroup container, int position) { //Adapter設計模式為了完全讓使用者自定義 //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;@Overridepublic 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: //左滑動到第一張,跳轉到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); }/** * 處理頁面複用 * * @return */public View getConvertView() { for (int i = 0; i 繼承PagerAdapter實現getCount()這個方法,這個方法返回的是ViewPager有多少個View。為了實現無限輪播圖返回了Integer.MAX_VALUE,使用者不會手殘一直向右滑動吧,造成溢位吧,哈。DefaultActivityLifecycleCallbacks 去監聽Activity的生命週期,為什麼要監聽呢?因為當使用者點選home鍵的時候,此時應用會在後臺,但是ViewPager裡面的ImageView還會迴圈,所以在Activity執行onPaused()的時候,停止輪播。getConvertView()這個方法是處理介面複用的,意思是跟RecycleView或者ListView實現列表滑動一樣的,需要介面複用。最後,小編想實現一個左滑動到position=0,也就是第一張的時候,想跳轉到getCount-1張,具體的做法是想在onTouchEvent()方法監聽,手指按下記錄下mDownX,手指抬起的時候ev.getX(),用ev.getX() - mDownX > 0坐下判斷。在設定下 setCurrentItem(mBannerAdapter.getCount() - 1);
發現並沒有實現。也不知道這是為什麼,但是我認為這種思路沒錯,哪位大神看到了,請給出具體解決方案。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; //當前頁面的位置 private int mCurrentPosition; //指示器的位置 private int mDotGravity = -1; //指示器的大小 private int mDotSize = 6; //指示器的間距 private int mDotDistance = 2; //底部顏色預設透明 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()); } /** * 設定介面卡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) { //監聽下當前的位置 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() { //獲取廣告位的數量 int count = mAdapter.getCount(); //設定指示器的位置 mDotContainerView.setGravity(getDotGravity()); for (int i = 0; iSlidePageTransformer類
public class SlidePageTransformer implements ViewPager.PageTransformer {@Override public void transformPage(@NonNull View page, float position) { if (position > 0 && position = -1 && positionBannerView這個類主要是一些自定義屬性,底部指示器的大小、顏色、間距等等。主要說下這個 mBannerViewPager.setPageTransformer(false, new SlidePageTransformer());這個給ViewPager設定了一個平滑的縮放的動畫,但是看到了一個ViewPager設定動畫的一個坑,發現滑到第一張的時候,在向右滑動的時候,圖片會滑出一點邊緣。也不知道為什麼?我認為我的程式碼沒有問題,也聽說Android的原始碼ViewPager去設定動畫,會有坑的存在。哪位大神看到了,望賜教!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2331/viewspace-2805259/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- ViewPager最簡單的無限輪播Viewpager
- Android常用控制元件-BannerView(無限輪播圖控制元件)Android控制元件View
- ViewPager兩種方式實現無限輪播Viewpager
- 有間隙卡片縮放/無縫CollectionViewBanner無限輪播圖View
- 安卓之viewPager簡單用法圖片輪播安卓Viewpager
- 直播平臺製作,ViewPager自動輪播,手指按住停止輪播Viewpager
- 如何使用RecyclerView打造首頁輪播圖View
- 利用RecyclerView實現無限輪播廣告條View
- 原生js系列之無限迴圈輪播元件JS元件
- 在 Flutter 中實現一個無限輪播Flutter
- react無縫滾動輪播圖React
- 無縫輪播圖的一種方式原理
- android可以無限迴圈滑動的ViewPagerAndroidViewpager
- jQuery輪播圖之上下輪播jQuery
- 輪播圖
- 無縫輪播
- ViewPager系列之 仿魅族應用的廣告BannerViewViewpager
- 微信小程式------輪播圖------縱向輪播圖微信小程式
- vue輪播圖Vue
- Flutter輪播圖Flutter
- Flutter教程-自定義無限滾動輪播器infinity_slider-增加多輪播巢狀聯動功能(二)FlutterIDE巢狀
- JQuery實現圖片輪播無縫滾動jQuery
- js 輪播圖 (原生)JS
- 文字輪播與圖片輪播?CSS 不在話下CSS
- 淡入淡出的輪播圖元件元件
- ViewPage實現輪播圖View
- Banner實現輪播圖
- vue輪播圖外掛Vue
- 圖片輪播--純cssCSS
- js實現輪播圖JS
- 邏輯難理解版本的輪播圖(實現無縫滾動)
- 造輪子之圖片輪播元件(swiper)元件
- 原生JS實現輪播圖的效果JS
- vue元件之輪播圖的實現Vue元件
- 微信小程式swiper輪播圖卡死來回瘋狂輪播微信小程式
- Android 和 iOS 圖片輪播AndroidiOS
- 輪播圖(JavaScript定時器)JavaScript定時器
- 原生js實現輪播圖JS