本文已授權微信公眾號:鴻洋(hongyangAndroid)原創(chuàng)首發(fā)。
商業(yè)級控件最重要的特性:高內聚盈包、低耦合代乃!
1旬牲、分析需求
我們想要實現(xiàn)什么功能、達到什么效果搁吓?
站在用戶在這里就是我們的程序員的角度上來看原茅,我們在使用的時候是希望以最少的代碼實現(xiàn)更多的功能,且能有足夠地自由去自定義該功能堕仔。功能需求如下:
我們想要實現(xiàn)的效果 :
2擂橘、理清技術點
在開發(fā)之前,大致評估一下技術可行性贮预,結合技術點的特性才能更好的去劃分層次和搭建框架贝室,首先梳理幾個功能點 以及它們相對應的技術點契讲;
- ViewPeger作為view的載體(為什么不用recyclerView仿吞,懶,使用recyclerVIew會多出很多代碼捡偏,比如自己寫滑動監(jiān)聽唤冈,換頁換頁監(jiān)聽,換頁動畫等等比較麻煩的代碼银伟,以及各種計算)
- 如何無限循環(huán)你虹,如圖所示; 首尾各增加一項item彤避; 且當滑動至 第0項 時無縫切換至 倒數(shù)第二項(真實數(shù)據(jù)最后一項下標)傅物,當滑動到最后一項時無縫切換至第二項(真實數(shù)據(jù)第一項);
2.0.0 版本采用的思路是:
getCount() 6百倍實際長度琉预;
將開始下標置為300倍董饰; 做一個假的無限循環(huán);
在下標不在 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诈嘿, 動態(tài)添加指示點堪旧。根據(jù)狀態(tài)切換指示點圖標。
- 如何一頁顯示多屏(如下效果)奖亚,其中用到了
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"
這樣我們就能實現(xiàn)一屏顯示多view的效果了。
3遂蛀、面向對象設計
我們需要根據(jù)業(yè)務的細分對象谭跨,每個對象要實現(xiàn)什么功能;各個對象之間如何關聯(lián)起來貫穿整個業(yè)務流程李滴; 設計框架與高層抽象螃宙;
3.1、 細分對象
我們都知道面向對象設計思想呢 有一種原則叫 單一職責原則所坯! 所以我們第一步就是劃分這個對象谆扎。每個對象該做什么事情。 且遵循單一職責原則的優(yōu)點在于:我的任一單一的對象都可以拿出來單獨使用芹助,這樣就能極大程度保證我們的代碼的可擴展性堂湖。
ok,那我們一個一個來分析:
-
wenldBanner :
也是我們最外層一個對象状土,它包含了viewPager
對象與我們的指示器對象无蜂;負責協(xié)調我們VIewPager與指示器之間的關聯(lián)關系與位置擺放,同時能夠對各個對象的屬性進行相對應的設置蒙谓。 -
AutoTurnViewPager :
負責自動翻頁相關功能斥季; -
LoopViewPager :
負責自動翻頁設置; 重寫滑動監(jiān)聽事件(addOnPageChangeListener累驮,removeOnPageChangeListener)酣倾;重寫真實選中事件(setCurrentItem,getCurrentItem)等等谤专; -
WenldPagerAdapter :
展示真實的View躁锡;提供適配器下標與真實數(shù)據(jù)下標相互轉換算法; 并實現(xiàn)對View的復用置侍; -
Holder:
適配器View映之,Holder -
DefaultPageIndicator
默認指示器 -
PageIndicatorListener
指示器監(jiān)聽接口。
3.2 簡單UML
大概事件有這么多墅垮,如下圖:
根據(jù)以上搭建類圖:
4 拆分代碼編程
根據(jù)uml流程用例圖以及類圖 搭建好框架后 定義出接口并寫出偽代碼惕医;依次實現(xiàn)細節(jié)代碼。
具體可以進入源碼 WenldBanner查看算色。
5 總結
5.1 遇到的坑
5.1.1抬伺、 notifyDataSetChanged "不刷新問題"
這個坑還算是比較輕松就能填充的: 首先是數(shù)據(jù)源改變后 調用notifyDataSetChanged
界面不刷新
例如:數(shù)據(jù)從{"一","二","三" }
變?yōu)?code>{"四","五","六" } 時的效果展現(xiàn)失敗,如下圖所示
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清除掉寞宫;經(jīng)過方法判斷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 收獲
一款商業(yè)級Banner控件
既然敢說商業(yè)級竭业,那么就支持足夠的自定義與擴展性! 實現(xiàn)思路請看這 開發(fā)一款商業(yè)級Banner控件
效果圖:
可以做到以下效果
- 設置自定義指示器效果
- 設置指示器位置
- 設置是否循環(huán)
- 是否可以跳轉
- 頁面切換間隔
- 頁面切換持續(xù)時間
- 是否支持手動滑動
- 是否反向切換頁面(切換方向)
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地址
希望我的文章不會誤導在觀看的你及舍,如果有異議的地方歡迎討論和指正未辆。
如果能給觀看的你帶來收獲,那就是最好不過了击纬。