1. 程式人生 > Android入門教學 >Android 下拉選擇框 Spinner

Android 下拉選擇框 Spinner

1. Spinner 的特性

Spinner 的功能是提供一個選擇框,預設情況下 Spinner 展示的是當前的選項,點選 Spinner 控制元件將會展示所有可選項供使用者點選選擇。Spinner 在很多情況下並不是獨立存在的,很有可能當前的 Spinner 的選項需要依賴於前一個 Spinner 的選擇結果。

比如我們常見的地址選擇頁面,首先一個 Spinner 展示所有的省份,在你選擇省份之後,第二個 Spinner 拿到你的選項生成相應的城市選項,所以 Spinner 是一個常用並且非常靈活的控制元件,它的實現依然需要 Adapter。

2. Spinner 的基本用法

2.1 Spinner 的相關屬性

  • android:gravity:
    設定 Spinner 內部 item 的對齊方式
  • android:dropDownHorizontalOffset:
    設定下拉選擇框的水平偏移距離
  • android:dropDownVerticalOffset:
    設定下拉選擇框的垂直偏移距離
  • android:dropDownWidth:
    設定下拉列表框的寬度
  • android:dropDownSelector:
    下拉選擇框被選中時的背景樣式
  • android:popupBackground:
    設定下拉選擇框的背景樣式
  • android:prompt:
    設定選擇框的提示資訊,此屬性不能直接設定 String,而必須設定一個 string 資源
  • android:spinnerMode:
    選擇框的模式,有兩個可選值:
    • dialog: 對話方塊風格
    • dropdown: 下拉列表風格
  • android:entries:
    通過 string 資源的方式設定下拉選擇項

2.2 Spinner 選擇事件監聽器

  • setOnItemSelectedListener:
    為 Spinner 設定選中事件回撥,該介面中包含兩個回撥方法:

    • onItemSelected:
      當 Spinner 中某個選項被選中時回撥該方法,在使用者選擇了 Spinner 中不同於當前已選中的選項或者當前沒有任何選項選中時,系統會回撥該方法。此時可以通過getItemAtPosition(position)
      來獲取當前被選中的 item 物件,比如文章開頭提到的選城市的功能就需要通過此介面實時獲取使用者的選擇。
    • onNothingSelected:
      這個回撥方法用的不較少,它是在選項消失的時候被系統回撥的,選項消失通常發生在資料清空的時候

    特別說明: 雖然 Spinner 和 ListView、GridView 一樣都是 AdapterView,但是在 Spinner 中不能使用setOnItemClickListener,如果使用系統會丟擲以下異常:

setOnItemClickListener cannot be used with a spinner

所以在 Spinner 中我們要用setOnItemSelectedListener來監聽選擇事件。

3. Spinner 使用示例

本節仍然採用“水果列表”的示例,之前是通過 ListView、GridView 將水果型別和圖片直接羅列在螢幕上。而本節只會在螢幕上暴露出一個選項,在點選水果的時候彈出所有的選擇項等待使用者選擇,由於 Adapter 完善的 MVC 模式,可以繼續在之前的程式碼上簡單修改即可。

3.1 定義 Spinner 佈局

佈局檔案很簡單,直接在根佈局中放置一個 Spinner 即可:

<?xml version="1.0" encoding="utf-8"?>
<Spinner xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/spinner"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center" />

Spinner 的屬性都比較好理解,大家可以在閱讀的同時自行新增嘗試。

3.2 編寫 Adapter

和上一節的 GridView 一樣,我們通過修改 MyAdapter 的getCountgetView兩個回撥方法來實現水果列表的擴充套件,程式碼如下:

package com.emercy.myapplication;

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;


public class MyAdapter extends BaseAdapter {

    private Context mContext;
    private String[] mName;
    private int[] mResId;

    public MyAdapter(Context context) {
        mContext = context;
    }

    public void setData(String[] name, int[] resId) {
        mName = name;
        mResId = resId;
    }


    @Override
    public int getCount() {
        return mName.length * 10;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // 針對convertView做一個簡單的優化
        if (convertView == null) {
            convertView = LayoutInflater.from(mContext).inflate(R.layout.list_view, null);
        }
        TextView name = convertView.findViewById(R.id.textView);
        ImageView image = convertView.findViewById(R.id.imageView);
        name.setText(mName[position % mName.length]);
        image.setImageResource(mResId[position % mResId.length]);
        return convertView;
    }
}

細心的讀者可能會注意到,相比上一節的例子,在getView當中有一個小小的改動:

 // 針對convertView做一個簡單的優化
        if (convertView == null) {
            convertView = LayoutInflater.from(mContext).inflate(R.layout.list_view, null);
        }

這個改動是一個簡單的優化,可以減少每次 inflate 造成的效能消耗,這樣 Adapter 只會在第一次去做 inflate,而後續的getView()回撥將直接複用之前的convertView。

3.3 定義資料來源

資料來源分兩部分:水果名稱和水果圖片,分別用一個 String 陣列和 int 陣列存放,如下:

String[] mDataName = {"蘋果", "梨", "香蕉", "桃子", "西瓜", "荔枝", "橘子"};
    int[] mDataImage = {R.drawable.apple, R.drawable.pear, R.drawable.banana, R.drawable.peach,
            R.drawable.watermelon, R.drawable.lychee, R.drawable.orange, R.drawable.orange};

然後通過 MyAdapter 提供的設定資料的介面設定給 Adapter:

        adapter.setData(mDataName, mDataImage);

3.4 完成 MainActivity

整體的 MainActivity 和之前的邏輯大體相同,但是在 2.2 中我們提到過,Spinner 不能使用setOnItemClickListener介面,所以我們將事件監聽器改成setOnItemSelectedListener,最終程式碼如下:

package com.emercy.myapplication;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Spinner;
import android.widget.Toast;

public class MainActivity extends Activity {

    Spinner mSpinner;
    String[] mDataName = {"蘋果", "梨", "香蕉", "桃子", "西瓜", "荔枝", "橘子"};
    int[] mDataImage = {R.drawable.apple, R.drawable.pear, R.drawable.banana, R.drawable.peach,
            R.drawable.watermelon, R.drawable.lychee, R.drawable.orange, R.drawable.orange};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSpinner = findViewById(R.id.spinner);

        MyAdapter adapter = new MyAdapter(this);
        adapter.setData(mDataName, mDataImage);
        mSpinner.setAdapter(adapter);
        mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(getApplicationContext(), mDataName[position / mDataImage.length], Toast.LENGTH_LONG).show();
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
                Toast.makeText(getApplicationContext(), "onNothingSelected", Toast.LENGTH_LONG).show();
            }
        });
    }
}

執行之後頁面中只會有一個預設選項,點選 Spinner 會彈出一個下拉框,任意選中一個會觸發onItemSelected回撥方法並通過Toast列印當前選擇項。選擇完成之後,Spinner 會展示新選擇的水果名稱和圖片,效果如下:

Spinner示例

4. 小結

本節繼 ListView、GridView 之後又講解了一個採用 Adapter 實現的 UI 樣式,它主要適用的是下拉選擇的場景,相比 ListView、GridView 它更省空間,只會在頁面上展示已選項,使用者需要通過點選才能調起所有的選項。

Spinner 的屬性也比較簡單,需要特別注意的是它不支援設定setOnItemClickListener介面,取而代之的是setOnItemSelectedListener介面,最後我們仍然採用“水果列表”的例子演示了一個 Spinner 的用法,當然對於 Spinner 還有很多花哨的樣式,大家可以在自己的例子程式碼中設定看看,會有驚喜哦!