【Android】地址選擇器 類似於京東的地址選擇

簡介

最近東西寫的挺多的,這不又要弄一個類似於京東的地址選擇器,然後剛開始我是不願意自己去寫的,這東西真的是浪費時間。但是下班後回到家找了一圈沒找到一個合適的,好吧,那我就自己來封裝一個唄,反正生命在於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,就解決一切問題了。

最後就是把要開放的接口開放一下,測測調調~

總結

然後,其實也是很簡單的,就是練練手,做一些讓自己和大家都覺得方便可行的事情。如果有什麼地方有問題,或者有更好的建議真的很歡迎大家多多提出建議和意見,還有一句話就是說沒事不要閒着,要多動動。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章