Android控件與佈局——基礎控件Spinner

        最近在用原生的控件和佈局繪製一些界面並使用,雖然這些都是Android基本知識,但是有的時候真的感覺力不從心,感覺有必要對Android常用的控件和佈局做一個系統的瞭解。後續一個月甚至更多的時間都會圍繞這個主題展開,畢竟這裏面還是有不少高級控件的,我也會盡量結合應用深入的進行了解。

項目GitHub地址入口

       上一篇:CheckedTextView      下一篇:ProgressBar

今天,我們的主題是Spinner,下面看一下官方文檔的介紹:同時只能展示一個子View,你可以選擇不同的子view顯示,這些數據來自於該控件的適配器。

* A view that displays one child at a time and lets the user pick among them.
* The items in the Spinner come from the {@link Adapter} associated with
* this view.

可見,這個View是一個ViewGroup,類似於ListView這種,我們可以通過爲其設置適配器來展示不同的數據,但是它同時只能展示一條數據,下面就來看看它的用法。

1,基本用法

基本用法中,我們簡單介紹一下這個控件的最基本使用,不通過自定義適配器來裝載數據,同時還會介紹一些該控件的部分API等

1.1 通過xml文件配合控件屬性填充數據

這種方式我們只需要提前編輯好Spinner中的數據(在strings.xml中的string-array標籤)配合Spinner控件的android:entries

       <Spinner
            android:id="@+id/spinner_year"
            android:entries="@array/year_list"
            android:tag="year"
            android:layout_width="240dp"
            android:layout_height="wrap_content" />

其中,year_list數據如下:

 <string-array name="year_list">
        <item>年</item>
        <item>1990</item>
        <item>1991</item>
        <item>1992</item>
        <item>1993</item>
        <item>1994</item>
        <item>1995</item>
    </string-array>

運行的效果如下

這種方式使用簡單,但是有一個問題,我們沒有辦法動態的修改數據,於是我們就有了另外一種加載數據的方式——動態加載:

1.2 動態加載數據

使用動態加載,我們需要執行以下幾步:

  • 獲取(初始化)顯示數據
private void initListData() {
        list = new ArrayList<>();
        list.add("年");
        list.add("1990");
        list.add("1991");
        list.add("1992");
        list.add("1993");
        list.add("1994");
        list.add("1995");
    }
  • 綁定數據到系統自定義的適配器
 adapter= new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, list);
  • 設置適配器的drop down view的顯示樣式
  adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  • 加載適配器到Spinner
yearSpinner.setAdapter(adapter);

 

執行之後的效果和上圖是一樣的效果,那我們的剛剛提到的動態的改變加載數據功能還沒有實現了,不急,其實非常之簡單,我們只需要修改綁定到適配器上的數據即可實現對Spinner顯示數據的修改,不需要調用任何接口:

我們增加兩個按鈕,佈局代碼和點擊邏輯實現如下:

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="addData"
            android:text="增加數據" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="deleteData"
            android:text="刪除數據" />
    public void addData(View view){
       list.add(String.valueOf(++maxYear));
    }

    public void deleteData(View view){
      list.remove(String.valueOf(maxYear--));
    }

運行的效果如下:

可見,通過動態的數據加載,我們可以很輕易的實現數據的更新。到這裏,我們也實現的數據的動態加載,但是,我們對動態加載數據中綁定數據到適配器以及設置適配器中的樣式實現有一點疑惑,現在我們把上面的第二和第三步重新拿來看看:

        //獲取適配器
        adapter= new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, list);
        //設置樣式
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

仔細看看,怎麼覺得ArrayAdapter那麼眼熟,不行,我們得到源碼中看看究竟是什麼導致我們會產生這種熟悉的感覺:

* You can use this adapter to provide views for an {@link AdapterView},
* Returns a view for each object in a collection of data objects you
* provide, and can be used with list-based user interface widgets such as
* {@link ListView} or {@link Spinner}.

上述是關於ArrayAdapter的部分介紹,哦哦,原來如此,我們在學習ListView的時候也用到了這個適配器,我說怎麼這麼眼熟,者,由此,我們心裏好像有了一種預感,那就是學習Spinner和ListView有很多相通點,其實就適配器這一塊,基本上差不多。其實我們還可以在追的深一些:ArrayAdapter繼承至BaseAdapter,BaseAdapter實現了ListAdapter與SpinnerAdapter;下面是BaseAdapter的介紹:

/**
 * Common base class of common implementation for an {@link Adapter} that can be
 * used in both {@link ListView} (by implementing the specialized
 * {@link ListAdapter} interface) and {@link Spinner} (by implementing the
 * specialized {@link SpinnerAdapter} interface).
 */

好了,到這裏基本差不多了,不能太偏離主題,我們接着看ArrayAdapter的構造函數中的參數

  • 上下文對象
  • 子view佈局
  • 數據

其中第一和第三個參數沒有什麼好說的,對於子View佈局,我們可以到android包中的R.layout中常看相關的佈局常量來使用,目前有:

static int activity_list_item
           
static int browser_link_context_header
           
static int expandable_list_content
           
static int preference_category
           
static int select_dialog_item
           
static int select_dialog_multichoice
           
static int select_dialog_singlechoice
           
static int simple_dropdown_item_1line
           
static int simple_expandable_list_item_1
           
static int simple_expandable_list_item_2
           
static int simple_gallery_item
           
static int simple_list_item_1
           
static int simple_list_item_2
           
static int simple_list_item_checked
           
static int simple_list_item_multiple_choice
           
static int simple_list_item_single_choice
           
static int simple_spinner_dropdown_item
           
static int simple_spinner_item
           
static int test_list_item
           
static int two_line_list_item

關於這個參數的介紹如下:

* @param resource The resource ID for a layout file containing a TextView to use when
*                 instantiating views.

這是一個包含TextView的佈局,比如:

  • simple_list_item_1.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceListItemSmall"
    android:gravity="center_vertical"
    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
    android:minHeight="?android:attr/listPreferredItemHeightSmall" />
<?xml version="1.0" encoding="utf-8"?>

<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="?android:attr/listPreferredItemHeightSmall"
    android:textAppearance="?android:attr/textAppearanceListItemSmall"
    android:gravity="center_vertical"
    android:checkMark="?android:attr/textCheckMark"
    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" />

上述中的設置下拉view樣式也是同樣的道理。這裏就不在多說了。因爲很多時候,我們都會使用自定義的佈局樣式。

 

2,自定義適配器

上面介紹了加載Spinner數據的兩種方式,也對部分源碼進行了分析,下面我們就來看看使用Spinner的重點——自定義適配器來顯示自定義的佈局。和ListView一樣,Spinner展示數據的樣式和數據都是通過通過適配器的getView()方法進行綁定,所以,我們只需要繼承BaseAdapter或者其子類,重寫getView()方法實現數據與佈局的綁定即可。下面就來看看具體的操作。首先,我們先自定義一個適配器繼承至我們上面說的BaseAdapter:

package aoto.com.commonwidgetandlayout.basic_widget.spinner;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;

import aoto.com.commonwidgetandlayout.R;

/**
 * author:why
 * created on: 2019/5/18 12:06
 * description:
 */
public class MySpinnerAdaptor extends BaseAdapter{

    Context mContext;
    private List<String> mList;

    public MySpinnerAdaptor(Context context, List<String> list) {
        this.mContext = context;
        this.mList = list;
    }

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public Object getItem(int position) {
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
    /**
     * 綁定數據與View
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        convertView=LayoutInflater.from(mContext).inflate(R.layout.spinner_item, null);
        if(convertView!=null)
        {
            ImageView icon=convertView.findViewById(R.id.item_icon);
            TextView info=convertView.findViewById(R.id.item_info);
            icon.setImageDrawable(mContext.getDrawable(R.drawable.year_icon));
            info.setText(mList.get(position));
        }
        return convertView;
    }
}

自定義的佈局很簡單,就是一個ImageView加一個TextView組成:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:id="@+id/item_icon"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginTop="10dp" />

        <TextView
            android:id="@+id/item_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30dp"
            android:layout_marginTop="10dp"
            android:textColor="@color/colorPrimary"
            android:textSize="20sp" />
    </LinearLayout>
</LinearLayout>

加載適配器的代碼還是一樣的,就是不用在設置適配器的佈局樣式了:

spinner.setAdapter(new MySpinnerAdaptor(this,list));

運行的結果如下:

這樣,我們自定義佈局的樣式就出來了,雖然比較難看,但是功能都是實現了的。好的,到這裏,我們總結一下實現步驟

  • 自定義佈局文件
  • 自定義Spinner加載適配器
  • 初始化數據,初始化適配器實現自定義佈局與數據的綁定
  • Spinner加載適配器在自定義佈局中顯示我們的數據
  • 其他:可以爲Spinner添加OnItemSelectedListener監聽等等

下面,我們就結合上面的步驟,優化一下上面的小例子:

步驟1:自定義佈局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <RelativeLayout
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:id="@+id/item_icon"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginTop="10dp" />
        <TextView
            android:id="@+id/result_info"
            android:textColor="#FF0000"
            android:visibility="invisible"
            android:textSize="20sp"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="10dp"
            android:layout_width="wrap_content"
            android:layout_height="30dp" />

        <TextView
            android:layout_toRightOf="@+id/item_icon"
            android:layout_marginLeft="20dp"
            android:id="@+id/item_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:textColor="@color/colorPrimary"
            android:textSize="20sp" />
    </RelativeLayout>

    <TextView
        android:layout_marginTop="5dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_width="match_parent"
        android:layout_height="1.5dp"
        android:background="@color/colorAccent" />
</LinearLayout>

步驟二:自定義Spinner適配器

package aoto.com.commonwidgetandlayout.basic_widget.spinner;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;

import aoto.com.commonwidgetandlayout.R;

/**
 * author:why
 * created on: 2019/5/18 12:06
 * description:
 */
public class MySpinnerAdaptor extends BaseAdapter{

    Context mContext;
    private List<String> mList;

    public MySpinnerAdaptor(Context context, List<String> list) {
        this.mContext = context;
        this.mList = list;
    }

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public Object getItem(int position) {
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
    /**
     * 綁定數據與View
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        convertView=LayoutInflater.from(mContext).inflate(R.layout.spinner_item, null);
        if(convertView!=null)
        {
            ImageView icon=convertView.findViewById(R.id.item_icon);
            TextView info=convertView.findViewById(R.id.item_info);
            TextView resultInfo=convertView.findViewById(R.id.result_info);
            icon.setImageDrawable(mContext.getDrawable(R.drawable.year_icon));
            info.setText(mList.get(position));
            resultInfo.setText(mList.get(position));
        }
        return convertView;
    }
}

步驟三:初始化數據,初始化適配器實現自定義佈局與數據的綁定

 private void initListData() {
        list = new ArrayList<>();
        list.add("1990");
        list.add("1991");
        list.add("1992");
        list.add("1993");
        list.add("1994");
        list.add("1995");
    }

new MySpinnerAdaptor(this,list)

步驟四:Spinner加載適配器在自定義佈局中顯示我們的數據

spinner.setAdapter(new MySpinnerAdaptor(this,list));

步驟五:爲Spinner添加OnItemSelectedListener監聽,執行具體邏輯

spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
           @Override
           public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
               view.findViewById(R.id.item_icon).setVisibility(View.INVISIBLE);
               view.findViewById(R.id.item_info).setVisibility(View.INVISIBLE);
               view.findViewById(R.id.result_info).setVisibility(View.VISIBLE);
               Log.e(TAG, "onItemSelected: " );
           }

           @Override
           public void onNothingSelected(AdapterView<?> parent) {

           }
       });

這樣設計之後,我們的代碼運行效果如下:

 

也可以通過設置android:spinnerMode屬性來設置子選項時下拉drop down還是彈出dialog方式:

    <Spinner
        android:id="@+id/spinner_year"
        android:spinnerMode="dialog"
        android:overlapAnchor="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:tag="year" />

這裏有幾點需要注意:

(1)我是通過RelativeLayout佈局中View的隱藏和顯示來實現選中項和非下拉項的顯示區別,有時會畫面抖動,需注意

(2)我這裏直接用TextView實現分割線的操作,建議使用自定義View

(3)通常展開子選項的時候會遮擋上次選中項,使用android:overlapAnchor屬性配置一下解決,我在源碼中找了一下注釋,沒有任何有用的解釋,其中文意思是“重疊錨”,這裏設置爲false,應該指的是子選項不重疊選中項的意思

好了,到這裏,關於Spinner的介紹就介紹了,如果喜歡的話,可以掃碼關注一波,謝謝啦。

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