本文同步發佈在CSDN,未經容許不得轉載。android
《打造一個絲滑般自動輪播無限循環Android庫--源碼解析》github
最近公司項目在升級AndroidX,因爲項目中用到的一些比較老的庫都已中止更新維護,所以須要將這些庫替換掉,其中就包括自動輪播的Banner庫。恰逢筆者在以前寫過一個輪播圖,所以就在此基礎上重構,打造出了一個全新的支持多種樣式的輪播庫---BannerViewPager。BannerViewPager有區別於市面上的其它Banner庫,它具備高度的可定製性,之因此這麼說是由於它不但能夠支持任意的頁面佈局,並且能夠支持任意的Indicator。是的,你沒看錯,BannerViewPager幾乎能夠實現市面上全部的Indicator樣式。無圖言叼,仍是先經過圖片一覽BannerViewPager的功能,下面開啓多圖預警模式。canvas
用在RecyclerView | 自定義頁面 | 嵌套PhotoView |
---|---|---|
![]() |
![]() |
![]() |
BannerViewPager開放了衆多設置Indicator樣式的接口,好比能夠設置Indicator的滑動模式、顏色、半徑大小、位置等。目前Indicator的滑動模式僅支持NORMAL和SMOOTH兩種,後續可能會支持更多滑動模式,敬請期待。兩種模式效果以下:bash
NORMAL | SMOOTH |
---|---|
![]() |
![]() |
若是以上樣式不能知足你的需求,BannerViewPager還提供了徹底自定義IndicatorView的功能。只要繼承BaseIndicatorView或者實現IIndicator接口,並重寫相應方法,就能夠隨心所欲的打造任意的Indicator了。下圖是一個自定義仿支付寶的Indicator:ide
關於Transform更好的方式應該是留給開發者本身去實現,所以BannerViewPager中僅僅內置了四種經常使用Transform樣式,若是不能知足需求,能夠經過getViewPager().setPageTransformer(ViewPager.PageTransformer transformer)設置自定義的Transform。四種內置Transform樣式以下:oop
STACK | ROTATE | DEPTH | ACCORDION |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
固然,BannerViewPager的功能並不只僅侷限於此,更多功能就再也不貼圖,能夠看下面全部開放的API接口。佈局
BannerViewPager開放了衆多API,以供知足不一樣的需求,具體以下表:post
方法名 | 方法描述 | 說明 |
---|---|---|
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> showIndicator(boolean showIndicator) | 是否顯示指示器 | 默認值true |
BannerViewPager<T, VH> setIndicatorStyle(IndicatorStyle 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(IndicatorSlideMode 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) | 設置自定義指示器 | 設置自定義指示器後以上關於IndicatorView的參數會部分失效 |
BannerViewPager<T, VH> setPageTransformerStyle(TransformerStyle style) | 設置頁面Transformer內置樣式 | |
void startLoop() | 開啓自動輪播 | 初始化BannerViewPager時沒必要調用該方法,設置setAutoPlay後會調用startLoop() |
void stopLoop() | 中止自動輪播 | 若是開啓自動輪播,爲避免內存泄漏須要在onStop()或onDestory中調用此方法 |
ViewPager getViewPager() | 獲取BannerViewPager內部封裝的ViewPager | |
List<T> getList() | 獲取Banner中的集合數據 | |
void create(List list) | 初始化並構造BannerViewPager | 必須調用,不然前面設置的參數無效 |
若是您已遷移到AndroidX請使用latestVersion(>=2.3.0)性能
implementation 'com.zhpan.library:bannerview:latestVersion'
複製代碼
若是未遷移到AndroidX請使用:
implementation 'com.zhpan.library:bannerview:2.2.7'
複製代碼
<com.zhpan.bannerview.BannerViewPager
android:id="@+id/banner_view"
android:layout_width="match_parent"
android:layout_margin="10dp"
android:layout_height="160dp" />
複製代碼
<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>
複製代碼
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());
}
}
複製代碼
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);
}
複製代碼
若是開啓了自動輪播功能,請務必在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();
}
複製代碼
(1)自定義View並繼承BaseIndicatorView
public class DashIndicatorView extends BaseIndicatorView{
private Paint mPaint;
private float sliderHeight;
...省略部分代碼
public DashIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setColor(normalColor);
mPaint.setAntiAlias(true);
sliderHeight = normalIndicatorWidth / 2;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension((int) ((mPageSize - 1) *mIndicatorGap + normalIndicatorWidth * mPageSize),
(int) (sliderHeight));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(normalColor);
for (int i = 0; i < mPageSize; i++) {
mPaint.setColor(normalColor);
canvas.drawRect(i * (normalIndicatorWidth) + i * +mIndicatorGap, 0, i * (normalIndicatorWidth) + i * +mIndicatorGap + normalIndicatorWidth, sliderHeight, mPaint);
}
drawSliderStyle(canvas);
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
private void drawSliderStyle(Canvas canvas) {
mPaint.setColor(checkedColor);
float left = currentPosition * (checkedIndicatorWidth) + currentPosition * +mIndicatorGap + (checkedIndicatorWidth + mIndicatorGap) * slideProgress;
canvas.drawRect(left, 0, left + checkedIndicatorWidth, sliderHeight, mPaint);
}
public DashIndicatorView setSliderHeight(int sliderHeight) {
this.sliderHeight = sliderHeight;
return this;
}
}
複製代碼
(2)BannerViewPager設置自定義Indicator
private void setUpViewPager() {
viewPager = findViewById(R.id.banner_view);
List<String> list = Arrays.asList(picUrls);
viewPager.setAutoPlay(false).setCanLoop(true)
.setRoundCorner(DpUtils.dp2px(5))
.setIndicatorView(setupIndicatorView(list.size()))
.setHolderCreator(SlideModeViewHolder::new).create(list);
}
private DashIndicatorView setupIndicatorView(int pageSize) {
DashIndicatorView indicatorView = new DashIndicatorView(this);
indicatorView.setPageSize(pageSize);
indicatorView.setIndicatorWidth(DpUtils.dp2px(8), DpUtils.dp2px(8));
indicatorView.setSliderHeight(DpUtils.dp2px(4));
indicatorView.setIndicatorGap(DpUtils.dp2px(5));
indicatorView.setCheckedColor(getResources().getColor(R.color.colorAccent));
indicatorView.setNormalColor(getResources().getColor(R.color.colorPrimary));
return indicatorView;
}
複製代碼
優化及重構IndicatorView~~(2.0.1)
修復2.1.0之前版本循環滑動時第一張切換卡頓問題~~ (2.1.0.1)
增長頁面滑動動畫~~(2.1.2)
遷移AndroidX~~(2.2.0)
增長IndicatorView的滑動樣式~~(2.2.2)
增添更多Indicator樣式(2.3.+)
ViewPager更換爲ViewPager2 (3.0.0)
目前Indicator部分代碼比較亂,還有很大很大的優化空間,後續版本將持續優化
關於實現原理將會再下一篇文章中講解。 都看到這裏了,肯定不留下你的star、fork?🙈連接再放一次,🙈🙈開啓瘋狂暗示🙈🙈若是有好的Idea也歡迎Pull Request。