本文同步發佈在掘金,如需轉載請註明出處。
BannerViewPager系列文章共三篇,此文爲第一篇,另外兩篇參看下面鏈接:
《剖析BannerViewPager中Indicator的設計思想》
最近公司項目在升級AndroidX,由於項目中用到的一些比較老的庫都已停止更新維護,因此需要將這些庫替換掉,其中就包括自動輪播的Banner庫。恰逢筆者在之前寫過一個輪播圖,因此就在此基礎上重構,打造出了一個全新的支持多種樣式的輪播庫—BannerViewPager。個人覺得BannerViewPager要優於其它開源的Banner庫,不僅僅是因爲它擁有簡潔高效的代碼,更是因爲它高度的可定製性。BannerViewPager不僅支持任意的頁面佈局,而且可以支持任意的Indicator樣式。甚至連Indicator的位置都可以做到任意擺放。是的,就是這麼隨心所欲。無圖言叼,還是先通過圖片和代碼一覽BannerViewPager的功能吧(多圖預警)。
一、BannerViewPager效果預覽及API介紹
由於GIF圖片質量問題,下面的預覽圖並不清晰,大家可以點擊下面鏈接或者掃描二維碼下載Apk體驗。Apk存放在github上,下載速度可能會比較慢。
1.setIndicatorStyle(開局就放王炸?)
BannerViewPager目前內置了CIRCLE和DASH兩種樣式的指示器,通過setIndicatorStyle(int)一行代碼就可以切換指示器的樣式。當然,如果內置樣式不滿足你的需求。BannerViewPager還提供了自定義指示器的功能。只要繼承BaseIndicatorView或者實現IIndicator接口,並重寫相應方法,就可以通過自定義View爲所欲爲的打造任意的Indicator了。如下圖【自定義】就是自己實現的指示器樣式。
CIRCLE | DASH | 自定義 |
---|---|---|
下面通過代碼演示如何切換指示器:
mViewPager.setIndicatorStyle(IndicatorStyle.DASH)
.setIndicatorHeight(BannerUtils.dp2px(3f))
.setIndicatorWidth(BannerUtils.dp2px(3), BannerUtils.dp2px(10))
.setHolderCreator(() -> new ImageResourceViewHolder(0))
.create(mDrawableList)
通過5行代碼就輕鬆的實現了上圖【Dash】仿支付寶的Indicator樣式(大家可以留意一下支付寶的輪播Indicator,挺有意思)。
關於自定義IndicatorView將會放在後邊章節詳細講解。
2.setPageStyle
通過setPageStyle(int)一行代碼開啓一屏三頁模式,一屏三頁模式下目前有三種樣式,分別如下圖所示:
MULTI_PAGE | MULTI_PAGE_SCALE | MULTI_PAGE_OVERLAP |
---|---|---|
代碼演示:
mViewPager.setPageStyle(PageStyle.MULTI_PAGE)
.setPageMargin(BannerUtils.dp2px(10))
.setRevealWidth(BannerUtils.dp2px(10))
.setHolderCreator(() -> new ImageResourceViewHolder(BannerUtils.dp2px(5)))
.create(mDrawableList);
同樣通過短短5行代碼就實現了上圖【MULTI_PAGE】的效果,簡單好用!
3.如何實現指示器位置任意擺放?
我們看到上面圖表中MULTI_PAGE_OVERLAP模式下指示器顯示到了Banner的下邊。這種效果該怎麼實現呢?其實BannerViewPager是支持把Indicator擺放在任意位置的。之所以能如此強大是因爲我們通過自定義指示器替換了內置的IndicatorView,也就是說此時的IndicatorView已經脫離了BannerViewPager,也就理所當然的可以放在任意位置了。接下來通過代碼來看下如何實現:
(1)Xml佈局文件如下
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.zhpan.bannerview.BannerViewPager
android:id="@+id/banner_view"
android:layout_width="match_parent"
android:layout_height="180dp"
android:layout_marginTop="20dp"
app:bvp_page_style="multi_page" />
<com.zhpan.bannerview.indicator.CircleIndicatorView
android:id="@+id/indicator_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/banner_view"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp" />
</RelativeLayout>
(2)通過setIndicatorView(IIndicator)替換內部指示器
CircleIndicatorView indicatorView = findViewById(R.id.indicator_view);
mViewPager.setIndicatorView(indicatorView)
.setIndicatorColor(Color.parseColor("#888888"),
Color.parseColor("#118EEA"))
.setHolderCreator(() -> new ImageResourceViewHolder(BannerUtils.dp2px(5)))
.create(mDrawableList);
CircleIndicatorView是什麼?其實他就是內置在BannerViewPager中的指示器,現在你只需要把它同BannerViewPager放在同一個佈局文件中就可以了。又是僅僅通過一行代碼就完成了對內部指示器的替換,不知道你看完之後是否會拍案叫絕,竟然如此簡單!
4.setIndicatorSlideMode
我們應該見過很多App輪播圖的指示器都會跟隨頁面一起滑動。BannerViewPager自然也不會少了這個功能。通過setIndicatorSlideMode(int)一行代碼就可以輕鬆切換到下圖(SMOOTH)的效果。
NORMAL | SMOOTH |
---|---|
代碼實現仍然非常簡單,使用BannerViewPager你只需要記住一個核心–Only One Line!所以演示代碼不再貼出你應該不會揍我吧?
5.setPageTransformerStyle
關於Transform更好的方式應該是留給開發者自己去實現,因此BannerViewPager中目前僅內置了四種常用Transform樣式,如果不能滿足需求,可以通過BannerViewPager的setPageTransformer(ViewPager.PageTransformer transformer)設置自定義的Transform。四種內置Transform樣式如下:
STACK | ACCORDION | DEPTH | ROTATE |
---|---|---|---|
當然,BannerViewPager的功能並不僅僅侷限於此,更多功能就不再演示,可以看下面所有開放的API接口。
6.BannerViewPager開放的API
BannerViewPager開放了衆多API,以供滿足不同的需求,具體如下表:
方法名 | 方法描述 | 說明 |
---|---|---|
BannerViewPager<T, VH> setCanLoop(boolean canLoop) | 是否開啓循環 | 默認值true |
BannerViewPager<T, VH> setAutoPlay(boolean autoPlay) | 是否開啓自動輪播 | 默認值true |
BannerViewPager<T, VH> setInterval(int interval) | 自動輪播時間間隔 | 單位毫秒,默認值3000 |
BannerViewPager<T, VH> setScrollDuration(int scrollDuration) | 設置頁面滾動時間 | 設置頁面滾動時間 |
BannerViewPager<T, VH> setRoundCorner(int radius) | 設置圓角 | 默認無圓角 需要SDK_INT>=LOLLIPOP(API 21) |
BannerViewPager<T, VH> setOnPageClickListener(OnPageClickListener onPageClickListener) | 設置頁面點擊事件 | |
BannerViewPager<T, VH> setHolderCreator(HolderCreator<VH> holderCreator) | 設置HolderCreator | 必須設置HolderCreator,否則會拋出NullPointerException |
BannerViewPager<T, VH> setIndicatorVisibility(@Visibility int visibility) | indicator vibility | 默認值VISIBLE 2.4.2 新增 |
BannerViewPager<T, VH> setIndicatorStyle(int indicatorStyle) | 設置指示器樣式 | 可選枚舉(CIRCLE, DASH) 默認CIRCLE |
BannerViewPager<T, VH> setIndicatorGravity(int gravity) | 指示器位置 | 可選值(CENTER、START、END)默認值CENTER |
BannerViewPager<T, VH> setIndicatorColor(int normalColor,int checkedColor) | 指示器圓點顏色 | normalColor:未選中時顏色默認"#8C6C6D72", checkedColor:選中時顏色 默認"#8C18171C" |
BannerViewPager<T, VH> setIndicatorSlideMode(int slideMode) | 設置Indicator滑動模式 | 可選(NORMAL、SMOOTH),默認值SMOOTH |
BannerViewPager<T, VH> setIndicatorRadius(int radius) | 設置指示器圓點半徑 | 默認值4dp |
BannerViewPager<T, VH> setIndicatorRadius(int normalRadius,int checkRadius) | 設置指示器圓點半徑 | normalRadius:未選中時半徑 checkedRadius:選中時的半徑,默認值4dp |
BannerViewPager<T, VH> setIndicatorWidth(int indicatorWidth) | 設置指示器寬度,如果是圓形指示器,則爲直徑 | 默認值8dp |
BannerViewPager<T, VH> setIndicatorWidth(int normalWidth, int checkWidth) | 設置指示器寬度,如果是圓形指示器,則爲直徑 | 默認值8dp |
BannerViewPager<T, VH> setIndicatorHeight(int indicatorHeight) | 設置指示器高度,僅在Indicator樣式爲DASH時有效 | 默認值normalIndicatorWidth/2 |
BannerViewPager<T, VH> setIndicatorGap(int indicatorMargin) | 指示器圓點間距 | 默認值爲指示器寬度(或者是圓的直徑) |
BannerViewPager<T, VH> setIndicatorView(IIndicator indicatorView) | 設置自定義指示器 | |
BannerViewPager<T, VH> setPageTransformerStyle(int style) | 設置頁面Transformer內置樣式 | |
BannerViewPager<T, VH> setCurrentItem(int item) | Set the currently selected page. | 2.3.5新增 |
void getCurrentItem() | 獲取當前position | 2.3.5新增 |
BannerViewPager<T, VH> setPageStyle(PageStyle pageStyle) | 設置頁面樣式 | 2.4.0新增 可選(MULTI_PAGE、NORMAL)MULTI_PAGE:一屏多頁樣式 |
BannerViewPager<T, VH> setPageMargin(int pageMargin) | 設置頁面間隔 | 2.4.0新增 |
BannerViewPager<T, VH> setIndicatorMargin(int left, int top, int right, int bottom) | 設置Indicator邊距 | 2.4.1新增 |
BannerViewPager<T, VH> setOnPageChangeListener(OnPageChangeListener l) | 頁面改變的監聽事件 | 2.4.3新增 |
void startLoop() | 開啓自動輪播 | 初始化BannerViewPager時不必調用該方法,設置setAutoPlay後會調用startLoop() |
void stopLoop() | 停止自動輪播 | 如果開啓自動輪播,爲避免內存泄漏需要在onStop()或onDestroy中調用此方法 |
List<T> getList() | 獲取Banner中的集合數據 | |
void create(List list) | 初始化並構造BannerViewPager | 必須調用,否則前面設置的參數無效 |
7.BannerViewPager支持的attrs
你也可以通過xml來設置BannerViewPager,xml支持的attrs如下:
Attributes | format | description |
---|---|---|
bvp_interval | integer | 自動輪播時間間隔 |
bvp_scroll_duration | integer | 頁面切換時滑動時間 |
bvp_can_loop | boolean | 是否循環 |
bvp_auto_play | boolean | 是否自動播放 |
bvp_indicator_checked_color | color | indicator選中時顏色 |
bvp_indicator_normal_color | color | indicator未選中時顏色 |
bvp_indicator_radius | dimension | indicator圓點半徑或者Dash模式的1/2寬度 |
bvp_round_corner | dimension | Banner圓角大小 |
bvp_page_margin | dimension | 頁面item間距 |
bvp_reveal_width | dimension | 一屏多頁模式下兩邊item漏出的寬度 |
bvp_indicator_style | enum | indicator樣式(circle/dash) |
bvp_indicator_slide_mode | enum | indicator滑動模式(normal/smooth) |
bvp_indicator_gravity | enum | indicator位置(center/start/end) |
bvp_page_style | enum | page樣式(normal/multi_page/multi_page_overlap/multi_page_scale) |
bvp_transformer_style | enum | transform樣式(normal/depth/stack/accordion) |
bvp_indicator_visibility | enum | indicator visibility(visible/gone/invisible) |
二、BannerViewPager詳細使用說明
1.gradle中添加依賴
如果您已遷移到AndroidX請使用latestVersion(>=2.4.3.1)
implementation 'com.zhpan.library:bannerview:latestVersion'
如果未遷移到AndroidX請使用(非Androidx的包託管在JCenter上):
implementation 'com.zhpan.library:bannerview:2.4.3.1'
2. 在xml文件中添加如下代碼:
<com.zhpan.bannerview.BannerViewPager
android:id="@+id/banner_view"
android:layout_width="match_parent"
android:layout_margin="10dp"
android:layout_height="160dp" />
3.Banner的Item頁面佈局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/banner_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#66000000"
android:gravity="center_vertical">
<TextView
android:id="@+id/tv_describe"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_marginStart="15dp"
android:gravity="center_vertical"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:textColor="#FFFFFF"
android:textSize="16sp" />
</LinearLayout>
</RelativeLayout>
4.自定義ViewHolder
public class NetViewHolder implements ViewHolder<BannerData> {
private ImageView mImageView;
private TextView mTextView;
@Override
public View createView(ViewGroup viewGroup, Context context, int position) {
View view = LayoutInflater.from(context).inflate(R.layout.item_net, viewGroup, false);
mImageView = view.findViewById(R.id.banner_image);
mTextView = view.findViewById(R.id.tv_describe);
return view;
}
@Override
public void onBind(Context context, BannerData data, int position, int size) {
ImageLoaderOptions options = new ImageLoaderOptions.Builder().into(mImageView).load(data.getImagePath()).placeHolder(R.drawable.placeholder).build();
ImageLoaderManager.getInstance().loadImage(options);
mTextView.setText(data.getTitle());
}
}
5.BannerViewPager參數配置
private BannerViewPager<BannerData, NetViewHolder> mBannerViewPager;
private void initViewPager() {
mBannerViewPager = findViewById(R.id.banner_view);
mBannerViewPager.showIndicator(true)
.setInterval(3000)
.setCanLoop(false)
.setAutoPlay(true)
.setRoundCorner(DpUtils.dp2px(7))
.setIndicatorColor(Color.parseColor("#935656"), Color.parseColor("#FF4C39"))
.setIndicatorGravity(BannerViewPager.END)
.setScrollDuration(1000).setHolderCreator(NetViewHolder::new)
.setOnPageClickListener(position -> {
BannerData bannerData = mBannerViewPager.getList().get(position);
Toast.makeText(NetworkBannerActivity.this,
"點擊了圖片" + position + " " + bannerData.getDesc(), Toast.LENGTH_SHORT).show();
}).create(mList);
}
6.開啓與停止輪播
2.5.0之後版本無需自行在Activity或Fragment中管理stopLoop和startLoop方法,但這兩個方法依舊保留對外開發
如果開啓了自動輪播功能,請務必在onDestroy中停止輪播,以免出現內存泄漏。
@Override
protected void onDestroy() {
super.onDestroy();
if (mBannerViewPager != null)
mViewpager.stopLoop();
}
爲了節省性能也可以在onStop中停止輪播,在onResume中開啓輪播:
@Override
protected void onStop() {
super.onStop();
if (mBannerViewPager != null)
mBannerViewPager.stopLoop();
}
@Override
protected void onResume() {
super.onResume();
if (mBannerViewPager != null)
mBannerViewPager.startLoop();
}
三、高級功能—自定義IndicatorView
因爲指示器的樣式千變萬化,BannerViewPager中不可能內置所有的樣式,因此我將定義權限交給了開發者自己來實現,這樣就可以滿足所有開發者的需求了。但是自定義IndicatorView需要有一定的自定義View基礎,儘管我已經在BaseIndicatorView中處理了許多邏輯,但是還是要開發者根據自身需求進行Indicator的繪製。好了,下面就讓我們來看看如何實現自定義IndicatorView吧。
關於自定義IndicatorView其實我們在第一節中講解Indicator擺放位置時已經提到了,就是通過setIndicator(IIndicator)來替換內部的指示器。當然,這個方法接收的參數不僅僅是內置的兩個IndicatorView,它還可以是我們自己實現的Indicator。前提只需要繼承BaseIndicatorView或者繼承View並實現IIndicator,然後根據需求繪製即可。
(1)認識BaseIndicatorView
BaseIndicatorView是BannerViewPager庫中的一個類,它繼承自View並實現了IIndicator接口。在這個類中存儲了BannerViewPager的許多參數信息,比如頁面個數(pageSize)、頁面滑動進度(slideProgress)以及當前頁面位置(currentPosition)等,這些都是在繪製IndicatorView時會用到的信息。有了這些參數之後我們就可以比較輕鬆的去繪製指示器了。如果你覺得我這些數據計算的不夠精確或者計算存在錯誤,那麼你大可以自己實現IIndicator接口自行計算。本文我會通過繼承BaseIndicatorView的方式來實現一個自定義指示器的例子。
你可以點擊鏈接查看BaseIndicatorView的完整代碼。
(2)開啓自定義IndicatorView之路
好了,接下來我們就來完成一個如下圖所示的自定義IndicatorView吧!
新建一個FigureIndicatorView類並繼承BaseIndicatorView
public class FigureIndicatorView extends BaseIndicatorView {
private int radius = DpUtils.dp2px(20);
private int backgroundColor = Color.parseColor("#88FF5252");
private int textColor = Color.WHITE;
private int textSize=DpUtils.dp2px(13);
// ...省略無關代碼
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(2 * radius, 2 * radius);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(backgroundColor);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaint);
mPaint.setColor(textColor);
mPaint.setTextSize(textSize);
String text = currentPosition + 1 + "/" + pageSize;
int textWidth = (int) mPaint.measureText(text);
Paint.FontMetricsInt fontMetricsInt = mPaint.getFontMetricsInt();
int baseline = (getMeasuredHeight() - fontMetricsInt.bottom + fontMetricsInt.top) / 2 - fontMetricsInt.top;
canvas.drawText(text, (getWidth() - textWidth) / 2, baseline, mPaint);
}
public void setRadius(int radius) {
this.radius = radius;
}
@Override
public void setBackgroundColor(@ColorInt int backgroundColor) {
this.backgroundColor = backgroundColor;
}
public void setTextSize(int textSize) {
this.textSize = textSize;
}
// ...省略無關代碼
}
有自定義View基礎的同學應該能很輕鬆的看懂上邊的代碼。首先通過onMeasure()方法測量了View的大小,接下來就是在onDraw方法中繪製圓和文字了。很容易就實現了一個自定義的IndicatorView。當然,這個例子本身就比較簡單。如果你需要繪製比較複雜且帶有動畫的Indicator,可以參考源碼中的CircleIndicatorView和DashIndicatorView,或許它能給你一些靈感。
(3)設置自定義指示器
接下來就將我們自己繪製的指示器設置到BannerViewPager中吧!
FigureIndicatorView indicatorView = new FigureIndicatorView(mContext);
indicatorView.setRadius(BannerUtils.dp2px(18));
indicatorView.setTextSize(BannerUtils.dp2px(13));
indicatorView.setBackgroundColor(Color.parseColor("#aa118EEA"));
mViewPager.setIndicatorGravity(IndicatorGravity.END)
.setIndicatorView(indicatorView)
.setHolderCreator(() -> new ImageResourceViewHolder(0))
.create(mDrawableList);
依然如此瀟灑自然!好了,關於BannerViewPager的介紹今天就講解到這裏了。接下來的一篇文章將會對BannerViewPager的源碼進行剖析,瞭解下它是如何通過簡單的Api實現來實現複雜的功能的。
都看到這裏了,確定不到GitHub點個星再走?源碼已放到文章末尾。如果有好的Idea也歡迎Pull Request。
文章上次更新2019.11.16