在Unity中用UGUI製作可輸入下拉框

夜影之空發表於2022-03-25

Unity中UGUI製作可輸入下拉框

前言

在搜尋引擎以及一些網頁中我們常常可以看見這樣一種UI控制元件,看上去是一個輸入框,在輸入之後會彈出一個列表(或者沒有輸入也會彈出,如搜尋引擎的歷史記錄),你可以選擇列表中的內容然後做出一些處理。
顯而易見,在UGUI中並沒有這種型別的控制元件。那麼只能自力更生自己製作一個了。

元件分析

首先,一個輸入框是必不可少的。
輸入後有一個列表,可以顯示多個內容,那麼還需要一個列表。
從頭製作一個UI控制元件是困難且麻煩的,我們就利用UGUI中現有的控制元件來製作一個組合控制元件。
於是我們自然而然的就聯想到了InputField以及Dropdown元件,一個輸入框和一個下拉選單。
嗯,看上去很符合我們的需求。
但是這裡會有一些坑,主要是Dropdown造成的。遂放棄使用Dropdown,下方是踩的坑,不想了解可以跳過。

Dropdown中改變在下拉選單中當前選中的值可以使用公共變數value。
但是有一個隱藏的坑:Dropdown預設的value是下拉選單的第一個
若用程式碼改變value且同樣為第一個的索引,即0,Dropdown的onValueChanged事件是不會被呼叫的。這也就意味的若點選的下拉選單選項的索引和上一次一樣,Dropdown的onValueChanged事件同樣也不會被呼叫。
這個問題解決辦法可以重寫Dropdown來搞定。

Dropdown中如果想要程式碼呼叫來顯示下拉選單可以使用Dropdown下的Show方法。
在顯示下拉選單的同時,Dropdown會在場景上創造一個Blocker物體(用於攔截事件),並且會將當前焦點控制元件改為Dropdown。
當然也可以用程式碼將當前焦點控制元件改回InputField,但這帶來了另一個問題,在成為焦點控制元件時,InputField會自動全選輸入框中的文字,且筆者在嘗試多種方法後並未發現解決方法。
例如:利用InputField的API:

  1. 變數caretPosition-當前 InputField 游標位置(以及選擇尾部)。
  2. 變數selectionAnchorPosition-選擇的起點。
  3. 變數selectionFocusPosition-選擇的結束點。
  4. 公共函式ForceLabelUpdate-強制使標籤立即更新。這將重新計算游標和可見文字的位置。
  5. 公共函式MoveTextEnd-將游標索引移動至文字末尾。
    以上API並未解決此問題,可能是由於程式碼執行順序的問題導致的,筆者並未去深究其內部實現,有興趣的可以自行研究。

那麼還有什麼辦法呢,我們回過來看看我們需要的,一個輸入框,一個列表。
輸入框我們有了,列表怎麼辦呢。於是筆者又想到了使用ScrollView來製作。
仿照Dropdown定製我們需要的列表。
設計一個條目的模版,通過程式碼動態建立和初始化來達到想要的效果。
那麼接下來就開始介紹下製作流程

製作流程

在最開始先放上效果展示以及其結構
在Unity中用UGUI製作可輸入下拉框在Unity中用UGUI製作可輸入下拉框

首先在場景上建立一個InputField,找到子物體Placeholder和Text上的Text元件將Alignment改為偏左居中,其他的引數沒有需要更改的,如果有其他需求自行更改。
在Unity中用UGUI製作可輸入下拉框

 

然後建立一個ScrollView作為InputField的子物體,由於筆者不需要滑動條,故將兩個Scrollbar都刪去了,ScrollView的引數修改如下,設定Anchor的位置以及Pivot的位置,取消勾選Horizontal,將Horizontal以及Vertical的Scrollbar置空。
其中ScrollSensitivity改為5是為了更好的響應滑鼠滾動事件,這個以及其他引數隨讀者喜好更改。
在Unity中用UGUI製作可輸入下拉框

 

然後在ScrollView-Viewport-Content下建立一個按鈕命名為Item作為我們的模板
將RectTransform引數作如下調整,其中重點是Anchor以及Pivot,目的是方便我們對動態建立的條目進行佈局
在Unity中用UGUI製作可輸入下拉框

 

接下來便是編寫邏輯來實現我們的效果
掛載在InputField上的是我們本次主要的指令碼InputTip
設定一個公共的GameObject來設定模板,然後在顯示輸入框下面的TipView時動態建立或更新條目內容,建立條目時給物體掛載InputTipItem指令碼,用來控制後續處理。
 
然後是處理輸入和顯示的邏輯,在InputField輸入框文字變化時我們需要對文字內容進行檢驗,來決定是否顯示TipView
在顯示之前首先通知新增了監聽的其他指令碼來讓其對需要顯示的文字進行設定,然後再顯示。
當點選一個條目後,條目掛載的指令碼InputTipItem通知InputTip對其的監聽來讓我們知道是點選了哪一個,然後改變InputField上的值以及隱藏TipView
 
文末給出了筆者所寫的成員及函式示例。

注:本文並未實現關於ScrollView的更多效果,如顯示位置及大小的自動設定,條目的排列等等
演示所採取的是在ScrollView下的Content上掛載Vertical Layout Group以及Content Size Fitter元件來達到自動佈局的,若想要更多更好的效果,自己來控制會好很多。

總結

雖然在UGUI中並沒有給出所有我們可能會用到的UI控制元件,但我們可以使用已經提供給我們的進行擴充和自定義,這樣更能滿足我們的需求。
 
最後,這是筆者第一篇教程,還望各位看官多多包涵,如有錯誤,盡情指出!

點選檢視程式碼
InputTipItem:

Text m_text;
Button m_button;
private class InputTipItemEvent : UnityEvent<InputTipItem>{}
//條目點選事件,將自身傳給InputTIp處理
private InputTipItemEvent m_onClick;
//該條目在列表中的Index
int m_index;
//該條目代表的值
string Value;
//初始化函式, 用於初始Index和顯示的text, 一定要在使用該指令碼前呼叫來初始化
public void Initialized(int index, string text);
//設定顯示的text, 主要用於外部重新設定顯示的text
public void SetValue(string text);
//內部設定Index和顯示的text
private void SetValue(int index, string text);
//新增點選的監聽
public void AddListener(UnityAction<InputTipItem> call);
//相應點選事件 新增到自身的Button元件
private void OnClick();
點選檢視程式碼
InputTip:

InputField m_inputField;
GameObject m_scrollView;
public class InputTipEvent : UnityEvent<string> { }
//當前所有的條目
List<InputTipItem> m_tipItems;
//需要顯示的文字條目
List<string> m_itemTexts;
//是否顯示TipView
bool m_isShowing;
//條目模板
public GameObject itemTemplate;
//當輸入文字變化時的回撥函式
public InputTipEvent onValueChanged;
//當前輸入框的文字
public string Value;
//根據模板建立一個條目物體, 並且作為模板 物體的父物體 的子物體
protected virtual GameObject CreateTipItem(GameObject template);
//建立條目列表檢視
protected virtual bool CreateTipView();
//新增一個條目
protected virtual InputTipItem AddTipItem(int index, string text);
//銷燬一個條目
protected virtual void DestroyItem(InputTipItem item);
//銷燬所有條目
public void DestroyItemView();
//當條目被點選時
protected virtual void OnItemClick(InputTipItem item);
//顯示條目檢視
public void ShowTipView();
//隱藏條目檢視
public void HideTipView();
//更新條目顯示
private void UpdateTipViewShow();
//當輸入文字變化時的回撥函式
protected virtual void OnInputValueChanged(string text);
//建立顯示的條目資訊
public void CreateTipViewTexts(params string[] texts);

相關文章