「ScrollView」でリストを作った時に、リストの先頭に説明を追加したり、
最後の要素の後ろに終了を示す文言等を付けたい事があります。
しかし、自動レイアウトが働いていると、それらの文言も整列されてしまい、
リスト内の1つの要素として認識されてしまいます。
今回は、動的に要素の増えるリストで、HeaderやFooterのような要素を
任意の位置に配置する方法をご紹介します。
目次
作業環境
- MacBook Air
- macOS Sierra 10.12.6
- Unity 5.5.4p5
ScrollViewの作成
ScrollViewを配置します。
ScrollViewのContentには、
- Vertical Layout Group
- Content Size Fitter
の、コンポーネントを追加しておきます。
VerticalLayoutGroupの設定
Paddingの中の、LeftとTopの値を以下の様に変更し、レイアウトを調節します。
- Left : 300
- Top : 50
- Bottom : 50
「Child Force Expand」の、Width及びHeightのチェックボックスをOFFにします。
今回は、各アイテムの大きさを自動で拡大する必要は無いためです。
ContentSizeFitterの設定
縦方向で合わせるので、Vertical Fitを「Preferred Size」に変更します。
リストに表示するアイテムを作成
今回リストに並べるのは文字なので、「Text」オブジェクトを作成します。
「Scroll View」の中の「Content」を右クリックして、UIからTextを選択します。
作成したTextのInspectorからAddComponentボタンで
「Layout Element」を追加します。
「Layout Element」の「Preferred Width」のチェックボックスをONにしておきます。
値は「30」に設定しました。
FontSizeも見やすい大きさに変更しておきましょう。
合わせて、TextのRectTransformのWidthも文字列が納まるように
適当な値に変更しておきます。
全て完了したら、今作成したTextオブジェクトをPrefab化しておきます。
Contentの子に、Header要素用のTextを配置します。
textの内容は「習得スキルリスト」とし、Textオブジェクトの名前を
「ArticleText」とします。
AddComponentより「Layout Element」コンポーネントを追加し、
「Ignore Layout」にチェックを入れます。
さらに、Contentの子に、Footer要素用のTextを配置します。
textの内容は「まだスキルを習得していません」とし、Textオブジェクトの名前を「InformationText」とします。
AddComponentより「Layout Element」コンポーネントを追加し、
「Ignore Layout」にチェックを入れます。
「Ignore Layout」をONにする事により、
このコンポーネントが追加されたゲームオブジェクトは、
自動レイアウトの対象から外されます。
要素追加用ボタンの作成
続いてボタンを配置し、ボタンを押すことでScrollViewの
Contentにアイテムを追加できるようにします。
スクリプトを作成し、内容は以下の様にします。
作成したスクリプトは、先ほど配置したボタンにアタッチします。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AddSkillButton : MonoBehaviour {
private GameObject content;
private GameObject textPrefab;
private Button getSkillButton;
private List<string> skillList = new List<string>();
private Text informationText;
private int learnedSkillCount = 0;
private int maxSkillCount = 0;
private Vector3 informationTextOffsetPosition = new Vector3(380,0,0);
void Awake(){
// skillListリストに要素を追加
skillList.Add ("料理");
skillList.Add ("栽培");
skillList.Add ("鑑定");
skillList.Add ("園芸");
skillList.Add ("裁縫");
skillList.Add ("調合");
skillList.Add ("整頓");
skillList.Add ("彫金");
skillList.Add ("木工");
skillList.Add ("鍛治");
skillList.Add ("採掘");
skillList.Add ("精製");
skillList.Add ("水泳");
skillList.Add ("錬金");
skillList.Add ("合成");
skillList.Add ("分解");
skillList.Add ("演奏");
skillList.Add ("作曲");
skillList.Add ("歌唱");
skillList.Add ("休憩");
Debug.Log (skillList.Count);
content = GameObject.Find ("Content");
textPrefab = (GameObject)Resources.Load("Prefabs/Text");
getSkillButton = transform.GetComponent<Button> ();
informationText = GameObject.Find ("InformationText").GetComponent<Text>();
maxSkillCount = skillList.Count;
}
public void OnClickAddSkillButton(){
// skillListリストに要素が残っている場合
if (skillList.Count > 0) {
GameObject _text = Instantiate (textPrefab, content.transform);
int index = Random.Range (0, skillList.Count);
_text.GetComponent<Text> ().text = skillList [index];
skillList.RemoveAt (index);
learnedSkillCount++;
informationText.text = learnedSkillCount+"/"+maxSkillCount;
informationText.transform.SetAsLastSibling ();
StartCoroutine (Wait ());
Debug.Log (_text.transform.localPosition);
informationText.transform.localPosition = _text.transform.localPosition + informationTextOffsetPosition;
Debug.Log (skillList.Count);
// 要素が無くなったらボタンを押せないようにし、文言を変更する
if (skillList.Count <= 0) {
getSkillButton.interactable = false;
getSkillButton.GetComponentInChildren<Text> ().text = "GetAllSkill!";
Debug.Log ("GetALLSkill!");
}
}
}
private IEnumerator Wait(){
yield return new WaitForEndOfFrame ();
}
}
リストにテキストを追加する毎に、「learnedSkillCount」をインクリメントし、
Footer要素である「informationText」の内容を、
learnedSkillCountとmaxSkillCountを使って
「スキル習得数/スキル総数」の表示に書き換えています。
learnedSkillCountが2の時
learnedSkillCountが10の時
その後「informationText」に対してSetAsLastSibling ()を実行し、
ヒエラルキーの一番下に配置します。
最後に、「informationText」の位置を、リストに追加したアイテムと、
オフセットを合わせた位置に移動させ、完了です。
自動レイアウトが行われるタイミングによる落とし穴
自動レイアウトは、マニュアルにもある通り、計算してから反映されます。
従って、自動レイアウトによって配置されたあとのオブジェクトの位置を使う場合には、
その計算が終わり、自動レイアウトの配置が終わるまで待つ必要があります。
処理が軽い場合には問題ないこともありますが、
処理の途中でオブジェクトの位置を拾わないために
Coroutineの呼び出しによって、処理が完了するフレームの終わりまで待っています。
サンプルプロジェクト
今回のサンプルプロジェクトはこちらです。
適宜ご活用ください。
入社3年目のエンジニアです。
主にiOS/Androidアプリの開発をしています。