前言
我們知道,地址選擇器是一個通用元件,網上的開源專案也有很多。那麼為什麼還會有這篇文章呢?因為我在調研過程中發現,雖然都是地址選擇器,但是實現的方式卻各不相同。以下是調研地址選擇器的一些總結和思考實踐。希望對大家有幫助,大家有什麼更好的想法,也要告知我哦~
地址選擇器實現方式介紹
-
本地存放area.db檔案
大多數App都是此種實現- 優點:啟動快,不受網路影響
- 缺點:
- 不能實時更新,資料更新依賴釋出新版本。(或則自己實現一套更新機制,檔案從後端下載,實現較為複雜)
- 各端(伺服器、前端、移動端)需要維護一份相同的地址資訊表,維護成本高。
- 地址資訊表本地儲存,增大安裝包體積
-
一次性從服務端拉取所有地址資訊
在App啟動時候,一次性拉取所有地址資訊。- 優點:資料可配置、請求少
- 缺點:
- 每次啟動都請求伺服器,大部分是無用請求,浪費了伺服器資源。
- 請求資料量大
- 優化方案:更換請求時機,點選選擇地址的時候才去一次性請求所有資料。但是還是不能避免請求資料量過大問題。
-
實時獲取省、市、區資訊,選中上一級才去獲取對應的下一級資料
如:京東- 優點:資料可配置,請求資料量小
- 缺點:請求較多,需要處理的異常case較多
- 思考優化方案:每次喚起地址選擇器時,快取獲取的地址資訊歷史資料。顯示地址資訊的時候,只有本地快取沒有當前資料,才向服務端傳送請求。(其實這種場景很少出現,但是再小的蒼蠅也是肉,能優化就優化了吧……)
因為使用者基本是在有網路的情況下才會使用,所以選擇聯網獲取地址資訊也是合理的。又考慮到網路請求的大小,服務端效能影響,安裝包大小以及地址資訊可配置性等因素,實時獲取地址資訊是一個不錯的方式。下面介紹地址選擇器實現一些關鍵點。
實時獲取資訊的地址選擇器設計
先來看效果,如下圖所示。喚起地址選擇器會有一次省份的網路請求,之後每一級資料都是實時去獲取。在當前地址選擇器喚起狀態,獲取之後就將歷史資料儲存在本地,下一次就不再傳送網路請求了。
整體的框架設計類圖如下所示:
資料結構設計
當前的地址資訊按照省、市、區/縣、街道四級劃分,後一級總是和前一級相關聯。
- Province
public class Province {
public long id;
public String name;
}
複製程式碼
- City
public class City {
public long id;
public long province_id;
public String name;
}
複製程式碼
- County
public class County {
public long id;
public long city_id;
public String name;
}
複製程式碼
- Street
public class Street {
public long id;
public long county_id;
public String name;
}
複製程式碼
回撥介面設計
主要提供了4個回撥方法:
public interface OnAddressSelectedListener {
// 獲取地址完成回撥
void onAddressSelected(Province province, City city, County county, Street street);
// 選取省份完成回撥
void onProvinceSelected(Province province);
// 選取城市完成回撥
void onCitySelected(City city);
// 選取區/縣完成回撥
void onCountySelected(County county);
}
複製程式碼
這裡著重說一下onAddressSelected
回撥方法,其是在地址選擇完成的時候呼叫。那麼是如何判斷地址選擇已經完成呢。這個在AddressSelector
中有如下一個機制。
每次一個級別選擇完成後,會獲取下一個級別的資料(網路請求或者快取獲取)進行顯示。顯示的時候有這麼一個邏輯,當前級別有資料,則正常顯示;若沒有,則說明地址選擇已經完成,此時呼叫onAddressSelected
方法。
快取設計
我們這裡預設服務端每個地址的id都是唯一的。一次點選選擇地址的快取操作,可以看下如下的流程圖。
- 建立三個快取map,分別快取
省-市
、市-區
和區-街道
。
/** 快取資料:省-市 */
private ArrayMap<Long, List<City>> province2city = new ArrayMap<>();
/** 快取資料:市-區 */
private ArrayMap<Long, List<County>> city2county = new ArrayMap<>();
/** 快取資料:區-街道 */
private ArrayMap<Long, List<Street>> county2street = new ArrayMap<>();
複製程式碼
- 選擇省、市、區某一級時,先檢視是否有快取資料,若有則使用快取資料;若沒有,則向服務端傳送網路請求。
// 有快取則直接使用快取,否則去重新請求
if(province2city.containsKey(province.id)){
setCities(province2city.get(province.id));
} else {
progressBar.setVisibility(View.VISIBLE);
listener.onProvinceSelected(province);
}
複製程式碼
3、每次獲取的歷史資料,儲存在相應的快取map中。
province2city.put(provinceId, cities);
複製程式碼
Tab切換動畫實現
不知道大家有沒有發現,當我們選擇地址時,頂部tab下有一個小橫條,它會跟著我們選擇進行平滑切換的。橫條的動畫主要包含兩個部分:
- 平滑移動到所選tab的文字下面
- 根據選擇tab內容的長度,在橫條移動過程中,動態變化橫條長度。當移動結束時,剛好變化為與tab內容長度一致。
具體思路是通過ObjectAnimator
來設定橫條移動的動畫效果,ValueAnimator
實現橫條長度的動態變化,最後通過AnimatorSet
來設定兩個動畫一起執行。控制元件中,封裝了一個方法實現此動畫效果。
private AnimatorSet buildIndicatorAnimatorTowards(TextView tab) {
ObjectAnimator xAnimator = ObjectAnimator.ofFloat(indicator, "X", indicator.getX(), tab.getX() + tab.getPaddingLeft() * 2);
final ViewGroup.LayoutParams params = indicator.getLayoutParams();
ValueAnimator widthAnimator = ValueAnimator.ofInt(params.width, tab.getMeasuredWidth() - tab.getPaddingLeft() * 2);
widthAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
params.width = (int) animation.getAnimatedValue();
indicator.setLayoutParams(params);
}
});
AnimatorSet set = new AnimatorSet();
set.setInterpolator(new FastOutSlowInInterpolator());
set.playTogether(xAnimator, widthAnimator);
return set;
}
複製程式碼
控制元件使用
- gradle匯入控制元件
compile `com.zr.addressselector:library:1.0.1`
複製程式碼
- Activity實現
OnAddressSelectedListener
介面
public class MainActivity extends AppCompatActivity implements OnAddressSelectedListener
複製程式碼
- 展現地址選擇器
dialog = new BottomSelectorDialog(MainActivity.this);
dialog.setOnAddressSelectedListener(MainActivity.this);
dialog.show();
複製程式碼
- 根據網路返回,設定資料
dialog.getSelector().setProvinces(Collections.singletonList(province));
複製程式碼
- 控制元件提供的幾個設定方法
/**
* 設定回撥介面
* @param listener
*/
public void setOnAddressSelectedListener(OnAddressSelectedListener listener) {
this.listener = listener;
}
/**
* 設定省列表
* @param provinces 省份列表
*/
public void setProvinces(List<Province> provinces){
handler.sendMessage(Message.obtain(handler, WHAT_PROVINCES_PROVIDED, provinces));
}
/**
* 設定市列表
* @param cities 城市列表
*/
public void setCities(List<City> cities){
handler.sendMessage(Message.obtain(handler, WHAT_CITIES_PROVIDED, cities));
}
/**
* 設定區列表
* @param countries 區/縣列表
*/
public void setCountries(List<County> countries){
handler.sendMessage(Message.obtain(handler, WHAT_COUNTIES_PROVIDED, countries));
}
/**
* 設定街道列表
* @param streets 街道列表
*/
public void setStreets(List<Street> streets){
handler.sendMessage(Message.obtain(handler, WHAT_STREETS_PROVIDED, streets));
}
複製程式碼
寫在最後
雖然提供了gradle compile匯入的方式,但是整個控制元件的原始碼其實是很簡單的。如果有定製需求,可以到這裡下載原始碼。
專案原始碼下載:ZRAddressSelector
特別感謝
JDAddressSelector,本元件設計也是受此開源專案啟發,非常感謝作者開源~