iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0

在 Android 開發中,原生 Spinner 雖然方便,但外觀與行為常常不容易客製化。這篇將以 QuerySpinner 為例,實作一個自訂 Spinner 元件,說明如何在 Java 中設計自訂元件,並在 XML 佈局中使用,讓你能完全掌控 UI 細節、互動行為和外觀。


一、什麼是自訂元件?為什麼要自訂 Spinner?

自訂元件(Custom View)是指開發者根據需求,繼承 Android 的基礎元件(如 View、Button、Spinner 等),並加以擴充或改寫,設計出獨特外觀、行為或功能的 UI 元件。
常見原因包含:

  • 客製化外觀:原生元件外型受限,無法符合品牌設計或特殊需求。
  • 加強互動:希望加入特殊手勢、動畫、資料過濾等功能。
  • 提升重用性:將複雜的 UI 元件封裝成自訂元件,提高維護與重用效率。
  • 易於維護:自訂元件可集中管理,統一更新外觀和邏輯。
  • 專案風格一致:確保所有畫面元件擁有一致的設計語言。

Spinner 是 Android 常用的下拉選單元件,但預設外觀與行為有限,無法直接修改下拉箭頭、背景、展開位置等細節。因此,開發自訂 Spinner 能讓我們更靈活地設計專案所需的選單效果。


二、QuerySpinner Java 實作解析

下面是完整的 QuerySpinner 範例,這個自訂 Spinner 支援動態切換背景、初始化選擇行為、選項被重複選擇時仍能觸發事件等功能。

// 自定義的 Spinner 元件
public class QuerySpinner extends AppCompatSpinner {
    private boolean isInitialization = false;
    private boolean isSettingInitialValue = false;

    // 建構函數
    public QuerySpinner(Context context) {
        super(context);
        init();
    }

    public QuerySpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    @SuppressLint("ClickableViewAccessibility")
    private void init() {
        // 初始化觸摸事件監聽器:點選時設細框背景
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    setBackgroundResource(R.drawable.process_button);
                }
                return false;
            }
        });

        // 選擇事件處理
        setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                // 初始化設置選擇項時不觸發粗框
                if (!isInitialization) {
                    isSettingInitialValue = true;
                    QuerySpinner.this.setSelection(0); //初始化選第一個
                    isSettingInitialValue = false;
                    setBackgroundResource(R.drawable.process_button); //細框
                    isInitialization = true;
                } else {
                    setBackgroundResource(R.drawable.select_button); //一般選擇為粗框
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
                setBackgroundResource(R.drawable.process_button); //沒選則細框
            }
        });
    }

    // 展開下拉選單時設細框
    @Override
    public boolean performClick() {
        setBackgroundResource(R.drawable.process_button);
        return super.performClick();
    }

    // 處理重複選擇同一選項的事件觸發
    @Override
    public void setSelection(int position) {
        if (isSettingInitialValue) {
            super.setSelection(position);
            return;
        }
        boolean sameSelected = position == getSelectedItemPosition();
        super.setSelection(position);
        if (sameSelected && isInitialization && getOnItemSelectedListener() != null) {
            getOnItemSelectedListener().onItemSelected(this, getSelectedView(), position, getSelectedItemId());
        }
    }
}

三、XML 佈局檔引用自訂 Spinner

在 layout 檔中直接使用自訂元件,並設定自訂屬性與外觀:

<com.example.aps_true.ui.QuerySpinner
    android:id="@+id/query_process_spinner"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginEnd="85dp"
    android:layout_marginTop="5dp"
    android:layout_marginBottom="5dp"
    android:spinnerMode="dropdown"
    android:background="@drawable/process_button" />
  • android:background="@drawable/process_button" 設定自訂背景(shape 或 vector)
  • 其他 layout 屬性可根據需求調整

四、自訂背景設計(VectorDrawable 範例)

自訂元件可以搭配自訂背景,讓外觀更有辨識度。例如用 VectorDrawable 設計格線效果:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
    <path android:fillColor="#3DDC84" android:pathData="M0,0h108v108h-108z" />
    <!-- 多條格線 ... 詳見原始檔案 -->
</vector>

將此 vector 檔案放在 res/drawable/process_button.xml,並在 Spinner 的 android:background 屬性引用。
你也可以用 shape 或圖片當作自訂背景。


五、進階設計:自訂選項、互動與重用

  1. 自訂選項 Layout
    Spinner 項目預設只能用簡單文字,可自訂 item layout(如 icon+文字):

    ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.custom_spinner_item, dataList);
    spinner.setAdapter(adapter);
    

    custom_spinner_item.xml 可設計自己想要的樣式。

  2. 支援多種資料型態
    可根據需求讓 Spinner 支援不同資料(如物件、圖片),並在 Adapter 轉換顯示。

  3. 事件回調
    可設定 setOnItemSelectedListener(),處理選項變動:

    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            // 自訂互動邏輯
        }
        @Override
        public void onNothingSelected(AdapterView<?> parent) {}
    });
    
  4. 封裝重用
    若常用同一種 Spinner 樣式/行為,可將元件包裝與初始化邏輯都放進 QuerySpinner,方便專案多處引用,維護更省力。

  5. 進階互動
    可加入動畫、過濾功能、非同步資料載入等,讓 Spinner 更智慧。


六、總結

自訂元件讓你能完全掌控 UI 細節,無論是外觀、互動或邏輯,皆可依需求擴充。
以 Spinner 為例,藉由繼承、屬性擴充、背景美化與自訂選項,你可以打造出專屬於自己專案的選單元件,並在多個畫面重複使用,大幅提升開發效率與設計一致性。
未來如果有更進階需求,也可以持續擴充元件功能,讓 APP 更貼近你的理想!


上一篇
Day 28.Git版本控管
下一篇
## Day 30 完賽總結
系列文
Android 新手的 30 天進化論:從初學者到小專案開發者30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言