簡介
最近東西寫的挺多的,這不又要弄一個類似於京東的地址選擇器,然後剛開始我是不願意自己去寫的,這東西真的是浪費時間。但是下班後回到家找了一圈沒找到一個合適的,好吧,那我就自己來封裝一個唄,反正生命在於coding,是吧~哈哈哈!先看看效果圖,不知道是不是大家想要的。區別就是京東是用在一個從下而上的彈窗裏面的。
主要功能
1.大致分爲三個模塊:頂部的Tab模塊,中間的移動指示器模塊,還有就是下面的list了。
2.支持點擊數據後自動跳到下一個Tab
3.支持點擊Tab後回到當前Tab的狀態
4.還有就是可以隨意設置你想要的。
還是來說說怎麼用吧。
項目地址:https://github.com/Blincheng/AddressSelector
集成導入(gradle)
1.Add the JitPack repository to your build file .Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
2.Add the dependency
dependencies {
compile 'com.github.Blincheng:AddressSelector:v1.0.4'
}
其餘繼承方式看:https://jitpack.io/#Blincheng/AddressSelector
使用
XML直接使用
<com.mic.adressselectorlib.AddressSelector
android:id="@+id/address"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.mic.adressselectorlib.AddressSelector>
Java中使用
AddressSelector addressSelector = (AddressSelector) findViewById(R.id.address);
設置Tab數量
addressSelector.setTabAmount(3);
也可以不設置,默認3級。
設置數據列表的Itme回調OnItemClickListener
addressSelector.setOnItemClickListener(new OnItemClickListener() {
@Override
public void itemClick(AddressSelector addressSelector, CityInterface city, int tabPosition) {
}
});
設置Tab的點擊事件回調OnTabSelectedListener
addressSelector.setOnTabSelectedListener(new AddressSelector.OnTabSelectedListener() {
@Override
public void onTabSelected(AddressSelector addressSelector, AddressSelector.Tab tab) {
}
@Override
public void onTabReselected(AddressSelector addressSelector, AddressSelector.Tab tab) {
}
});
注意,一般來說這兩個點擊事件都要設置,並且數據的處理一定要搞清楚。
其他的一些屬性的設置
此處表示很憂傷,剛纔不知道按了什麼快捷鍵,剛寫的東西全丟了!!!不開心
實現
現在我們開始說說實現方式吧,從需求上面來講,我們需要寫出來的東西具有以下幾點:
1.有指示器(Tab),
2.有一條會動的橫線
3.下方有個列表
4.上方的Tab和下方的列表都是可點擊的。
其實,從功能角度實現上來講,其實我們用google提供的現成的控件堆以下,其實也可以寫出來。比如舉個例子啊,上面的tab就用google自己的TabLayout,下方的列表就用RecyclerView,然後把數據什麼的綁定以下,點擊事件做一下,把異常處理掉,也能出來,就是說不方便二次使用。
然後實現思路:我這邊直接繼承LinearLayout,然後一個一個往裏面addView就好,簡單粗暴。
Tab的話可以繼承TextView,Line的話繼承View應該也行,下面的列表就直接用RecyclerView。好了,看看如何實現吧。
Tab的實現
先thinking,我們的Tab需要有文字,選中狀態,然後應該還要一個index。看代碼:
/**
* 標籤控件
* */
public class Tab extends TextView{
private int index = 0;
private int TextSelectedColor = Color.parseColor("#11B57C");
private int TextEmptyColor = Color.parseColor("#333333");
/**
* 是否選中狀態
* */
private boolean isSelected = false;
public Tab(Context context) {
super(context);
init();
}
public Tab(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Tab(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
setTextSize(15);
}
@Override
public void setText(CharSequence text, BufferType type) {
if(isSelected)
setTextColor(TextSelectedColor);
else
setTextColor(TextEmptyColor);
super.setText(text, type);
}
@Override
public void setSelected(boolean selected) {
isSelected = selected;
setText(getText());
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public void resetState(){
isSelected = false;
setText(getText());
}
public void setTextSelectedColor(int textSelectedColor) {
TextSelectedColor = textSelectedColor;
}
public void setTextEmptyColor(int textEmptyColor) {
TextEmptyColor = textEmptyColor;
}
}
很簡單,就是重寫一下setText,然後根據選中的狀態來設置對應的顏色即可。
實現Line
本來想了一下直接用View也能實現,但是後來想想既然要移動,有點小動畫,外層既然用了線性佈局,這邊的橫線還有長度的問題,所以也直接用橫向的線性佈局了。如下:
/**
* 橫線控件
* */
private class Line extends LinearLayout{
private int sum = 3;
private int oldIndex = 0;
private int nowIndex = 0;
private View indicator;
private int SelectedColor = Color.parseColor("#11B57C");
public Line(Context context) {
super(context);
init(context);
}
public Line(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public Line(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context){
setOrientation(HORIZONTAL);
setLayoutParams(new LayoutParams(
LayoutParams.MATCH_PARENT,6));
setWeightSum(tabAmount);
indicator= new View(context);
indicator.setLayoutParams(new LayoutParams(0,LayoutParams.MATCH_PARENT,1));
indicator.setBackgroundColor(SelectedColor);
addView(indicator);
}
public void setIndex(int index){
int onceWidth = getWidth()/sum;
this.nowIndex = index;
ObjectAnimator animator = ObjectAnimator.ofFloat(indicator, "translationX", indicator.getTranslationX(), (nowIndex-oldIndex)*onceWidth);
animator.setDuration(300);
animator.start();
}
public void setSum(int sum) {
this.sum = sum;
}
public void setSelectedColor(int selectedColor) {
SelectedColor = selectedColor;
}
}
其實和Tab差不多,唯一不同的是需要之前選中的oldIndex,因爲畢竟有個動畫嘛。
public void setIndex(int index){
int onceWidth = getWidth()/sum;
this.nowIndex = index;
ObjectAnimator animator = ObjectAnimator.ofFloat(indicator, "translationX", indicator.getTranslationX(), (nowIndex-oldIndex)*onceWidth);
animator.setDuration(300);
animator.start();
}
看看這個接口,在設置index的同時,把移動的動畫也一起做了。
AddressSelectord 實現
因爲之前就想好用繼承LinearLayout的形式,所以也簡單粗暴,直接一層一層去addView是吧,需要注意的是,這邊有個這樣的方法:
/**
* 得到一個新的tab對象
* */
private Tab newTab(CharSequence text,boolean isSelected){
Tab tab = new Tab(mContext);
tab.setLayoutParams(new LayoutParams(0,LayoutParams.WRAP_CONTENT,1));
tab.setGravity(Gravity.CENTER);
tab.setPadding(0,40,0,40);
tab.setSelected(isSelected);
tab.setText(text);
tab.setTextEmptyColor(TextEmptyColor);
tab.setTextSelectedColor(TextSelectedColor);
tab.setOnClickListener(this);
return tab;
}
看出來了吧,其實就是去創建需要的Tab,然後把點擊事件等其他參數都設置好了,主要用來AddressSelectord 內部來創建Tab時候用。
然後必要的屬性還是要提供接口設置的:
/**
* 設置tab的數量,默認3個,不小於2個
* @param tabAmount tab的數量
* */
public void setTabAmount(int tabAmount) {
if(tabAmount >= 2){
this.tabAmount = tabAmount;
init(mContext);
}
else
throw new RuntimeException("AddressSelector tabAmount can not less-than 2 !");
}
需要設置當前tab的數量,然後看這邊又調用了init()方法,也就是說其實這個時候AddressSelectord 又重置了。所以在init()方法中有一個removeAllViews();
需要調用。
下邊兒列表的實現RecyclerView
然後一開始我就在想要不要提供什麼Adapter可以讓大家自己來綁定數據,然後又想了想,爲了簡單方便大家使用,所以我覺得還是暫時不寫Adapter了。但是想想每個item的Entity不應該是死的,畢竟大家的項目還是不一樣的,所以我最終採取了一種方式去實現。
public interface CityInterface {
String getCityName();
}
就是這個接口了,大家在設置數據源的時候,儘管設置自己的,然後唯一需要注意的是大家的 Item需要去實現這個接口,返回我列表需要展示的文本,我用來展示列表的內容。只能要求大家做這麼一點點了。所以在設置數據的時候也要求大家這麼做了。
/**
* 設置列表的數據源,設置後立即生效
* */
public void setCities(ArrayList cities) {
if(cities == null||cities.size() <= 0)
return;
if(cities.get(0) instanceof CityInterface){
this.cities = cities;
if(addressAdapter == null){
addressAdapter = new AddressAdapter();
list.setAdapter(addressAdapter);
}
addressAdapter.notifyDataSetChanged();
}else{
throw new RuntimeException("AddressSelector cities must implements CityInterface");
}
}
不然就簡單粗暴拋出throw new RuntimeException("AddressSelector cities must implements CityInterface"
這樣就好說了,我在setOnItemClickListener可以直接返回CityInterface,就解決一切問題了。
最後就是把要開放的接口開放一下,測測調調~
總結
然後,其實也是很簡單的,就是練練手,做一些讓自己和大家都覺得方便可行的事情。如果有什麼地方有問題,或者有更好的建議真的很歡迎大家多多提出建議和意見,還有一句話就是說沒事不要閒着,要多動動。