本文已授權微信公眾號:鴻洋(hongyangAndroid)原創首發。
商業級控件最重要的特性:高內聚、低耦合!
1、分析需求
我們想要實現什么功能、達到什么效果?
站在用戶在這里就是我們的程序員的角度上來看,我們在使用的時候是希望以最少的代碼實現更多的功能,且能有足夠地自由去自定義該功能。功能需求如下:
我們想要實現的效果 :
2、理清技術點
在開發之前,大致評估一下技術可行性,結合技術點的特性才能更好的去劃分層次和搭建框架,首先梳理幾個功能點 以及它們相對應的技術點;
- ViewPeger作為view的載體(為什么不用recyclerView,懶,使用recyclerVIew會多出很多代碼,比如自己寫滑動監聽,換頁換頁監聽,換頁動畫等等比較麻煩的代碼,以及各種計算)
- 如何無限循環,如圖所示; 首尾各增加一項item; 且當滑動至 第0項 時無縫切換至 倒數第二項(真實數據最后一項下標),當滑動到最后一項時無縫切換至第二項(真實數據第一項);
2.0.0 版本采用的思路是:
getCount() 6百倍實際長度;
將開始下標置為300倍; 做一個假的無限循環;
在下標不在 200~400倍時; 重新定位在300倍位置;
- 翻頁動畫,有一個
ViewPager
有個接口PageTransformer
;賊好用,在滑動過程中 會計算各個itemVIew的左坐標與滑動距離差 與 總寬度的比例;
protected void onPageScrolled(int position, float offset, int offsetPixels) {
...
if (mPageTransformer != null) {
final int scrollX = getScrollX();
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
....
final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
mPageTransformer.transformPage(child, transformPos);
}
...
- 默認指示器,一個自定義View, 動態添加指示點。根據狀態切換指示點圖標。
- 如何一頁顯示多屏(如下效果),其中用到了
clipChildren
屬性
,在ViewGroup
中該屬性是這樣注釋的
// When set, ViewGroup invalidates only the child's rectangle
// Set by default
static final int FLAG_CLIP_CHILDREN = 0x1;
/**
* By default, children are clipped to their bounds before drawing. This
* allows view groups to override this behavior for animations, etc.
*
* @param clipChildren true to clip children to their bounds,
* false otherwise
* @attr ref android.R.styleable#ViewGroup_clipChildren
*/
public void setClipChildren(boolean clipChildren) {
...
通過設置根布局 android:clipChildren="false"
這樣我們就能實現一屏顯示多view的效果了。
3、面向對象設計
我們需要根據業務的細分對象,每個對象要實現什么功能;各個對象之間如何關聯起來貫穿整個業務流程; 設計框架與高層抽象;
3.1、 細分對象
我們都知道面向對象設計思想呢 有一種原則叫 單一職責原則! 所以我們第一步就是劃分這個對象。每個對象該做什么事情。 且遵循單一職責原則的優點在于:我的任一單一的對象都可以拿出來單獨使用,這樣就能極大程度保證我們的代碼的可擴展性。
ok,那我們一個一個來分析:
-
wenldBanner :
也是我們最外層一個對象,它包含了viewPager
對象與我們的指示器對象;負責協調我們VIewPager與指示器之間的關聯關系與位置擺放,同時能夠對各個對象的屬性進行相對應的設置。 -
AutoTurnViewPager :
負責自動翻頁相關功能; -
LoopViewPager :
負責自動翻頁設置; 重寫滑動監聽事件(addOnPageChangeListener,removeOnPageChangeListener);重寫真實選中事件(setCurrentItem,getCurrentItem)等等; -
WenldPagerAdapter :
展示真實的View;提供適配器下標與真實數據下標相互轉換算法; 并實現對View的復用; -
Holder:
適配器View,Holder -
DefaultPageIndicator
默認指示器 -
PageIndicatorListener
指示器監聽接口。
3.2 簡單UML
大概事件有這么多,如下圖:
根據以上搭建類圖:
4 拆分代碼編程
根據uml流程用例圖以及類圖 搭建好框架后 定義出接口并寫出偽代碼;依次實現細節代碼。
具體可以進入源碼 WenldBanner查看。
5 總結
5.1 遇到的坑
5.1.1、 notifyDataSetChanged "不刷新問題"
這個坑還算是比較輕松就能填充的: 首先是數據源改變后 調用notifyDataSetChanged
界面不刷新
例如:數據從{"一","二","三" }
變為{"四","五","六" }
時的效果展現失敗,如下圖所示
slove:
1、進入notifyDataSetChanged
源碼查看 ,通過幾步找到了ViewPager的 dataSetChanged() 方法
;
void dataSetChanged() {
...
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
//調用adapter的 getItemPosition方法 ,然而 該方法返回值 一直為:POSITION_UNCHANGED
final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
//如果返回的是 PagerAdapter.POSITION_NONE 那么移除所有view 重新填充
if (newPos == PagerAdapter.POSITION_NONE) {
//移除
mItems.remove(i);
...
//移除
mAdapter.destroyItem(this, ii.position, ii.object);
...
//在該方法內 如果 mItems 為空 該方法內最終會調用 到 adapter.instantiateItem 方法 填充新的view
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
可以看到 如果 mAdapter.getItemPosition()方法 返回的是 PagerAdapter.POSITION_NONE
,那么我們就會將舊items清除掉;經過方法判斷items為空, 最終會重新填充新的view;
2、所以我們就可以在 adapter的 getItemPosition
方法內下手:
在調用notifyDataSetChanged
時保證 getItemPosition
返回的是POSITION_NONE
WenldPagerAdapter.class
@Override
public void notifyDataSetChanged() {
myNotify=true;
super.notifyDataSetChanged();
myNotify=false;
}
@Override
public int getItemPosition(Object object) {
if (!myNotify) {
return super.getItemPosition(object);
} else {
return POSITION_NONE;
}
}
5.1.2、 設置切換動畫后 notifyDataSetChanged 錯亂
1、這個坑算是比較大的:如果我們設置了切換動畫,調用notifyDataSetChanged 以后效果會錯亂, 如下圖所示
2、還是進入源碼來分析解決問題,不過分析比較多,請查看ViewPager源碼解析 和 PageAdapter刷新問題;
5.2 收獲
一款商業級Banner控件
既然敢說商業級,那么就支持足夠的自定義與擴展性! 實現思路請看這 開發一款商業級Banner控件
效果圖:
可以做到以下效果
- 設置自定義指示器效果
- 設置指示器位置
- 設置是否循環
- 是否可以跳轉
- 頁面切換間隔
- 頁面切換持續時間
- 是否支持手動滑動
- 是否反向切換頁面(切換方向)
use
1、引用:
版本號:[圖片上傳失敗...(image-e7ddb3-1511330475991)]
// root build.gradle
repositories {
jcenter()
maven { url "https://www.jitpack.io" }
}
// yout project build.gradle
dependencies {
compile 'com.github.LidongWen:WenldBanner:2.0.0'
}
具體查看代碼:Github地址
希望我的文章不會誤導在觀看的你,如果有異議的地方歡迎討論和指正。
如果能給觀看的你帶來收獲,那就是最好不過了。