自動レイアウト

AutoLayout使ってる?

UIの配置を自動で行ってくれる。自動と言っても、もちろんAIが勝手に考えてくれるようなものではないが、少しのプロパティを設定するだけで各パーツのbounds(位置・サイズ)を自動で算出してくれる。これを使わない方法だと、各パーツの位置・サイズをいちいち全て指定しないといけない。UIパーツが増えるほど手間になってくるし、楽しい作業でもないのでめんどくさくなる⇒UIデザインに時間をかけなくなる⇒「ま、会社で使う分にはいいか」レベルでやめる⇒なんかどこか使いにくい。そんな悪循環から開放してくれて、UIデザインは楽しいんだぞ!と教えてくれるそんな素敵なAutoLayout。

スクリプトを勉強して1,2年経った頃、どこかから拾ってきた海外製のスクリプトの中身を見て「何だこれは!」と思ったのを覚えている。AutoLayoutという存在を知り調べてみるも、AutoLayoutの情報が少ない(もしかしたら書いてる書籍もあるのかもしれないが...)。なのでAutoLayoutについて書いてみる。

AEScriptを作る上で、自動レイアウトを使わない手はない。

メリット

      • パーツ1つ1つの位置・サイズを考えなくていい(サイズ指定したい時は個別にできる)

      • ウィンドウリサイズ時の関数が1行で済む

      • 間隔を詰める・広げる、向きを横並びにする縦並びにする等、レイアウトの変更が超簡単。

などなど。

AEだけでなくPhotoshopなどでももちろん使える。

とにかく見ろ!

とにかく長い記事になってしまったので、読むの飽きる前に、見たほうが早い!

AutoLayoutが楽しくなるソースコードです。コピペしてESTKなりで実行してみてください。ウィンドウが出たらウィンドウの端っこをドラッグしてウィンドウのサイズを変更してみてください。boundsプロパティの指定は何処にもありません。

var w = new Window('dialog{ text : "ウィンドウの端をドラッグしてサイズを変えるんだ!早く!" , preferredSize : [400,300] , orientation : "column", properties : {resizeable : true} ,\

gr : Group{ orientation : "row" , alignment : ["fill","fill"],\

gr2 : Group{ orientation : "stack", alignment : ["fill","fill"] , \

btn1 : Button{ text : "left" , alignment : "left" }\

btn2 : Button{ text : "center" , alignment : "center"}\

btn3 : Button{ text : "right" , alignment : "right"}\

btn4 : Button{ text : "top" , alignment : "top"}\

btn5 : Button{ text : "bottom" , alignment : "bottom"}\

btn6 : Button{ text : "left,top" , alignment : ["left","top"]}\

btn7 : Button{ text : "right,top" , alignment : ["right","top"]}\

btn8 : Button{ text : "left,bottom" , alignment : ["left","bottom"]}\

btn9 : Button{ text : "right,bottom" , alignment : ["right","bottom"]}\

btn10 : Button{ text : "left,center" , alignment : ["left","center"]}\

}\

btn11 : Button{ text : "right,fill" , alignment : ["right","fill"]}\

}\

btn12 : Button{ text : "fill,bottom" , alignment : ["fill","bottom"]}\

}');

w.onResize = function(){

w.layout.resize();

}


w.show();

最初の状態

ウィンドウサイズ変えたら...ボタンの座標やサイズが変わってる!あーはいはい...onResizeにめっちゃコマンド書いて...あれ?1行しかない!?それにソースコードのどこにもbounds、location、sizeのどれも触ってる形跡がないぞ?

実行してみましたか?どうでしょう。

「AutoLayoutな!知ってるぜ!」って人は、こんなところでフラフラしてないで、あなたのスクリプト制作を進めたほうがいいかもしれない。

「えっ!?どうなってんの!?Buttonにもbounds与えてないし...」て思った人は、この長いページを読む価値があるかもしれない。

「えっ、ちょっと何言ってるか全く理解できん」という人は、ここに来るのが早すぎたかもしれない。

AutoLayout使うときの考え方

Window、Panel、Groupオブジェクトのことをまとめて「コンテナ」と呼ぶ。ボタンなど、他のパーツをその中に格納できるから「入れ物」という意味で「コンテナ」。

1つのWindowをPanelとGroupを使って領域を分割していく。ウェブページのレイアウトなどでも使われる「グリッドレイアウト」のイメージ。

右の図で言うと、まず最初にウィンドウを上下2分割(緑の領域)。その後、上の領域を左右に分割(赤の領域)。そうやってできた領域の中にパーツを配置していくそんな感じ。

このイメージが大切。

AutoLayoutManagerとそのメソッド

Windowオブジェクト内に、layout = [object AutoLayoutManager] というのがある。これを使う。Window以外にもあるけど使ったことはない。

AutoLayoutManagerには2つのメソッドがある。

・layout()

実行した時点で各パーツのboundsが計算される。仮置きしてたUIの配置を完了させるイメージ。UIを追加し終わってからウィンドウの表示までの間に使う。

・resize()

AutoLayout対象の全てのパーツのboundsが修正される。ウィンドウリサイズ時に使う。

オブジェクトブラウザーで見たところ

layout()メソッドの使用方法

UIパーツを配置するときに、boundsを指定しないundefinedにしてやればいい。さすれば、boundsが設定されていないパーツはAutoLayoutの対象になる。

もし、

[WindowObject].add("button",[0,0,100,20],"OK");

のようにbounds(例でいうところの[0,0,100,20])を与えてしまうと対象にはならない。

一方で、bounds値がないままだと描画位置・サイズが不明ということなので、表示ができない。なので結局、最終的にはboundsを与える必要があるのだが、それを行ってくれるのがAutoLayoutManagerのlayout()メソッド。

//Windowで起動した時の例

var w = new Window('dialog');

/*

boundsを与えないでUIを作成して

*/

w.layout.layout(); //これによってboundsが算出される

w.show(); //各パーツにboundsが与えられたので無事に表示できる

layout()直後からbounds値が存在する。

Windowをshow()した時にも自動で実行される。なのでWindowの時は実は書かなくても表示できちゃう。でも事故の元だからやめたほうがいい。ちなみにドッキング可能なScriptUI Panelの時は書かないとUIが何も表示されず、パネルだけが出るので、やっぱりクセで書いといたほうがいい。この後、このページで出てくる例では省略しているが、真似してはいけない。

resize()メソッドの使用方法

WindowやPanelをリサイズした時、その大きさに合わせてUIパーツも位置やサイズ変更したいことがほとんどだと思う。

AutoLayoutを使わない時は、WindowのonResize関数に、結構な量のコードを書かないといけない。ウィンドウに対してこれは何%でその隣のパーツは何ピクセル空けるように移動、そしてサイズも云々........。全てのパーツに対してこれを設定していくのか........???ぐわあわわああーーーーーーーーー!!!!!!!

resize()はonResize関数内で次のように使う。

w.onResize = function(){

w.layout.resize();

}

これだけ。これだけで、AutoLayoutしたパーツたちが勝手にboundsを修正してくれる。涙出るわ。

AutoLayoutに関わるプロパティたち

AutoLayoutしてくれると言ってもAIが考えるわけではない(2回目)。なんとなくの指示はこちらから出さないといけない。その指示を出すためのプロパティを知っておかないと何もできない。

      • orientation:パーツを並べる方向

      • alignment/alignChildren:揃え

      • indent : 字下げ

      • spacing:間隔

      • margins:余白

      • preferredSize:希望のサイズ

      • characters:希望の文字数

orientation

各コンテナに対して、格納するパーツの並び順を指定する。指定方法は「行(横)」か「列(縦)」か「重ねる」か。

orientation = "row" //行(横→)方向にパーツを並べる。Groupの初期値。

orientation = "column" //列(縦↓)方向にパーツを並べる。Window、Panelの初期値。

orientation = "stack" //スタック、つまり重ねていくということ。並び方向の指示がない状態。

orientationを指定しない場合は初期値が適用される。個人的にはstackはほとんど使うことがない。rowとcolumnでだいたいの場合、事足りる。

○パターン1

初期値と同じ値を入れてみた。もしorientationを書かなかったとしても同じ表示になる。

var w = new Window('dialog{ text : "テストウィンドウ" , orientation : "column", \

pn : Panel{ text : "パネル" , orientation : "column",\

rb1 : RadioButton{ text : "ラジオボタン1" }\

rb2 : RadioButton{ text : "ラジオボタン2" }\

rb3 : RadioButton{ text : "ラジオボタン3" }\

}\

btns : Group{ text : "パネル2", orientation : "row",\

ok : Button{ text : "OK" }\

cancel : Button{ text : "cancel" }\

}\

}');

w.show();

初期値に従ってAutoLayoutで配置された。

○パターン2

全てのコンテナの orientation を、初期値と反対に設定した。他は一切変更していない。

var w = new Window('dialog{ text : "テストウィンドウ" , orientation : "row", \

pn : Panel{ text : "パネル" , orientation : "row",\

rb1 : RadioButton{ text : "ラジオボタン1" }\

rb2 : RadioButton{ text : "ラジオボタン2" }\

rb3 : RadioButton{ text : "ラジオボタン3" }\

}\

btns : Group{ text : "パネル2", orientation : "column",\

ok : Button{ text : "OK" }\

cancel : Button{ text : "cancel" }\

}\

}');

w.show();

それぞれのコンテナ内での並び方向が変わった。

○stackの場合

続けて、Windowはrowのまま、PanelとGroupを orientation = "stack" にした。

var w = new Window('dialog{ text : "テストウィンドウ" , orientation : "row", \

pn : Panel{ text : "パネル" , orientation : "stack",\

rb1 : RadioButton{ text : "ラジオボタン1" }\

rb2 : RadioButton{ text : "ラジオボタン2" }\

rb3 : RadioButton{ text : "ラジオボタン3" }\

}\

btns : Group{ text : "パネル2", orientation : "stack",\

ok : Button{ text : "OK" }\

cancel : Button{ text : "cancel" }\

}\

}');

w.show();

わかりにくいが、いろいろ重なっているだけで存在はしている

行列の入れ替えみたいなイメージ。これを気軽にやろうと思うと関数を組むのがなかなか面倒だと思う。UI作成中にやっぱ並び順変更したいってことはまぁまぁある(私、設計図書かずに脳内イメージだけで始める人なので...)。orientationの変更だけで変えられるのは嬉しいのでは。

alignment/alignChildren

「alignment」=「整列」であることからなんとなくは察せる。ウィンドウの端っこにいてほしいとか、中心にいてほしいとか、ウィンドウに合わせて目一杯表示とか、いろいろな事情があると思う。そういう指示を出すためのプロパティ。

alignmentは全てのパーツに対して設定できる。対してalignChildrenはChildrenの文字が示す通り、「子」に影響するプロパティ。コンテナのalignChildrenを指定することで、その子になっているパーツ全てのalignmentを一括で指定できる。alignChildrenした後で、子のパーツに個々に対して別のalignmentを指定することもできる。

alignmentとalignChildrenどちらも指定できる値は同じだが、親コンテナのorientationによって有効な値が変わる。

○orientation="row"のとき

        • top:上揃え

        • center:上下の中央

        • bottom:下揃え

        • fill:上下いっぱいに引き伸ばす

var w = new Window('dialog{ text : "テストウィンドウ" , preferredSize : [200,200] , orientation : "row", \

btn1 : Button{ text : "top" , alignment : "top" }\

btn2 : Button{ text : "center" , alignment : "center"}\

btn3 : Button{ text : "bottom" , alignment : "bottom"}\

btn4 : Button{ text : "fill" , alignment : "fill"}\

}');

w.show();

orientation = "row" なので左から順に並べられた後、上下の配置(赤い範囲内)をどうするかがalignmentで決まる。

○orientation="column"のとき

        • left:左揃え

        • center:左右の中央

        • right:右揃え

        • fill:左右いっぱいに引き伸ばす

var w = new Window('dialog{ text : "テストウィンドウ" , preferredSize : [200,200] , orientation : "column", \

btn1 : Button{ text : "top" , alignment : "left" }\

btn2 : Button{ text : "center" , alignment : "center"}\

btn3 : Button{ text : "bottom" , alignment : "right"}\

btn4 : Button{ text : "fill" , alignment : "fill"}\

}');

w.show();

orientation = "column" なので上から順に並べられた後、左右の配置(赤い範囲内)をどうするかがalignmentで決まる。

○orientation="stack"のとき

        • 上記のもの全部(left、right、top、bottom、center、fill)

var w = new Window('dialog{ text : "テストウィンドウ" , preferredSize : [300,300] , orientation : "stack", \

btn1 : Button{ text : "left" , alignment : "left" }\

btn2 : Button{ text : "center" , alignment : "center"}\

btn3 : Button{ text : "right" , alignment : "right"}\

btn4 : Button{ text : "top" , alignment : "top"}\

btn5 : Button{ text : "bottom" , alignment : "bottom"}\

btn6 : Button{ text : "fill" , alignment : "fill"}\

}');

w.show();

orientation = "stack" なので全て重なった状態に並べられた後、上下左右の配置(赤い範囲内)をどうするかがalignmentで決まる。

centerに隠れて文字が見えていないが、ウィンドウいっぱいに広がっている一番大きいボタンがfill。

alignmentについては見てもらったとおり。サイズ変更を伴うものはfillのみ。それ以外は位置のみの指示。

そしてlayout.resize()を使用すれば、Windowのリサイズ時も常に、上下左右、またはいっぱいいっぱい表示のままついてくる。ここまでが基本。

実はalignmentプロパティは配列でも与えることができる。

alignment : ["left","fill"]

といった具合。配列の最初の要素が「left、center、right、fill」、2番目が「top、center、bottom、fill」ということだけ注意したい。うっかり逆にしてしまうと思い通りに動かない。

個人的には配列で与えるほうが圧倒的に多い。「親コンテナのorientationによって有効な値が変わる。」と先に書いたが、配列で与えた場合、これを無視できる。

正確には無視するというよりかは、

「本来は配列で与えるのが正式で、長さ2の配列で初期値を持っている。配列じゃない場合は配列のどちらの要素に代入するのかを、親コンテナのorientationによって決めている。」

というのが真実かと思う。多分。

○alignmentを指定しない時

var w = new Window('dialog{ text : "テストウィンドウ" , preferredSize : [200,100] , orientation : "row", \

btn1 : Button{ text : "fill" }\

}');

w.show();

alignmentを指定していないのでこれがデフォルトの状態。

○直接文字列で与えた時(最初に説明したやり方)

var w = new Window('dialog{ text : "テストウィンドウ" , preferredSize : [200,100] , orientation : "row", \

btn1 : Button{ text : "fill" , alignment : "fill" }\

}');

w.show();

fillを指定したので縦いっぱいに伸びた

○配列で与えた時

var w = new Window('dialog{ text : "テストウィンドウ" , preferredSize : [200,100] , orientation : "row", \

btn1 : Button{ text : "[\\"right\\",\\"fill\\"]" , alignment : ["right","fill"] }\

}');

w.show();

fillで伸びて、さらに右に寄った

●ちなみに・・・

完全に余談だが、先程までのスクショはESTK上で実行したときのもの。AE上でスクリプトを実行すると、どうやら描画順が違うようで、見え方が変わる。もしコントロール同士が重なるようなUIを作る際は注意したい。

AE上で実行した時。描画順が逆になっていて、fillが一番上に。実際使うのはAE上なので、注意したい。後から乗せたものは上に上に...と思っておけばよい。

indent

文章で言うところの「字下げ」の量を指定する。alignment を left か top に指定している時のみ効果がある。

○orientation = "row" のとき

var w = new Window('dialog{ text : "テストウィンドウ" , preferredSize : [200,100] , orientation : "row", \

btn1 : Button{ text : "indent : 0" , alignment : "top" , indent : 0 }\

btn2 : Button{ text : "indent : 20" , alignment : "top" , indent : 20 }\

}');


w.show();

左 :indent 設定無し右:indent を20に

○orientation = "column" のとき

var w = new Window('dialog{ text : "テストウィンドウ" , preferredSize : [200,100] , orientation : "column", \

btn1 : Button{ text : "indent : 0" , alignment : "left" , indent : 0 }\

btn2 : Button{ text : "indent : 20" , alignment : "left" , indent : 20 }\

}');


w.show();

左 :indent 設定無し右:indent を20に

spacing

これは簡単。子パーツ間の間隔を指定する。別のパーツを含むことができる、コンテナに指定する。

○適当にspacingを設定した場合

画像の赤文字がWindow、緑文字がPanel、黄文字がGroup、それぞれのspacingで決定されたもの。

var w = new Window('dialog{ text : "テストウィンドウ" , orientation : "column", spacing : 30 , \

pn : Panel{ text : "パネル" , orientation : "column", spacing : 30 , \

rb1 : RadioButton{ text : "ラジオボタン1" }\

rb2 : RadioButton{ text : "ラジオボタン2" }\

rb3 : RadioButton{ text : "ラジオボタン3" }\

}\

btns : Group{ text : "パネル2", orientation : "row", spacing : 20 , \

ok : Button{ text : "OK" }\

cancel : Button{ text : "cancel" }\

}\

}');


w.show();

余裕を持ったレイアウトである

○spacingを0に設定した場合

var w = new Window('dialog{ text : "テストウィンドウ" , orientation : "column", spacing : 0 , \

pn : Panel{ text : "パネル" , orientation : "column", spacing : 0,\

rb1 : RadioButton{ text : "ラジオボタン1" }\

rb2 : RadioButton{ text : "ラジオボタン2" }\

rb3 : RadioButton{ text : "ラジオボタン3" }\

}\

btns : Group{ text : "パネル2", orientation : "row" , spacing : 0 , \

ok : Button{ text : "OK" }\

cancel : Button{ text : "cancel" }\

}\

}');


w.show();

先程に比べ、ぎっちぎちなのがわかる

参考までに各コンテナの初期値は次のよう。

      • Window:15

      • Panel:10

      • Group:10

とりあえず指定せずに組んでみて気に入らなかったらこれを目安に数値を変更してみては。

margins

これもコンテナに対しての指定。そのコンテナ内の四方のマージン(余白)を指定する。これは下の画像を見れば早い。

○marginsの値を設定

画像の赤文字がWindow、青文字がPanel、それぞれのmarginsで決定されたもの。

var w = new Window('dialog{ text : "テストウィンドウ" , orientation : "column", margins : 50 , \

pn : Panel{ text : "パネル" , orientation : "column", margins : 30 , \

rb1 : RadioButton{ text : "ラジオボタン1" }\

rb2 : RadioButton{ text : "ラジオボタン2" }\

rb3 : RadioButton{ text : "ラジオボタン3" }\

}\

btns : Group{ text : "パネル2", orientation : "stack",\

ok : Button{ text : "OK" }\

cancel : Button{ text : "cancel" }\

}\

}');

w.show();

marginsを設定して余白を付けた。実際こんなに大きな値にすることはない。

margin's' とsがついていることから複数形、つまり配列であろうことが見て取れる。[left,top,right,bottom]の順で辺ごとに余白を設定することができる。個人的には上の例のように数値をダイレクトに入れてしまう事が多い。配列ではなく数値を入れると[left,top,right,bottom]全てを同じ値に設定できる。

参考までに各コンテナの初期値は次のよう。

      • Window:[15,15,15,15]

      • Panel:[15,10,15,10]

      • Group:[0,0,0,0]

こちらもとりあえず指定せずに組んでみて気に入らなかったらこれを目安に数値を変更してみては。

preferredSize

「preferred」 = 「優先の」つまり「優先サイズ」。AutoLayoutで自動計算されるサイズよりもpreferredSizeで指定したサイズが「優先される」。AutoLayoutでは自動でboundsが計算されるので位置・サイズが自動的に決まる。しかし「このボタンだけはこのサイズにしたい」など希望サイズがあればそれを伝えておくことができる。

本来サイズの指定にはboundsやsizeを使うところだが、AutoLayoutする前はまだboundsが未確定であるので使用できない(使うとAutoLayoutの対象外になる)。なのでその代わりに使う。

WindowのpreferredSizeを指定して、ウィンドウ表示の初期サイズを指定するのにも使うことが多い。ただし、コンテナに使用する時は子のパーツが重なったり隠れないように、AutoLayoutによって自動的に指定サイズよりも広がることがあるので、必ずしもそのサイズになるとは限らないと覚えておく。

また、alignmentにfillを使用していると、そちらが優先になる。

どちらかに「-1」を入れた場合、その方向に関してはpreferredSizeで「指定しない」と認識されるので覚えておく。例えば、ボタンの幅はalignmentで計算してほしいけど高さだけは自分で決めときたい、とか。例を見ればわかる。

○PreferredSizeを指定しない場合

var w = new Window('dialog{ text : "preferredSize" , \

ok : Button{ text : "OK" }\

cancel : Button{ text : "cancel" }\

}');


w.show();

なんてことはない。普通のAutoLayout

○okのPreferredSizeに[200,100]を指定した

var w = new Window('dialog{ text : "preferredSize" , \

ok : Button{ text : "OK" , preferredSize : [200,100] }\

ok : Button{ text : "cancel" }\

}');


w.show();

OKボタンが指定したサイズになった

○片方に-1を指定した

var w = new Window('dialog{ text : "preferredSize" , \

ok : Button{ text : "OK" , preferredSize : [-1,100] }\

ok : Button{ text : "cancel" }\

}');


w.show();

-1にした方は幅方向は自動的に計算される

characters

preferredSizeと似て、大きさを指定するプロパティだが、幅のみ指定できる。何文字分の幅を持たせるか、というのを数値で指定する。厳密なものではないので、なんとなく。

StaticTextとEditTextで使う。文字数なので大体の数値の目安がつけやすい。preferredSizeでもよくね?と言われればそれまで。

入力文字数の制限ではないのでお間違いなきよう。

var w = new Window('dialog{ alignChildren : "left" , text : "characters" , \

et : EditText{ characters : 10 , text : "characters : 10"}\

et2 : EditText{ characters : 20 , text : "characters : 20" }\

}');

w.show();

実例で見せる

Hakoyomi3(Hon)ぽい見た目のもの。座標値など1つも書かずにいい感じのウィンドウが出てくる。ウィンドウのリサイズをしてもレイアウトを崩さずについてくるはず。

var w = new Window('dialog{ text : "ウィンドウ" , preferredSize : [250,250] , spacing : 5 , margins : 5 , properties : { resizeable : true},\

gr : Group{ alignment : ["fill","top"] , spacing : 2 ,\

dl1 : DropDownList{ alignment : ["fill","top"] , properties : { items : ["作品名"] }}\

dl2 : DropDownList{ alignment : ["right","top"] , properties : { items : ["話数"] }}\

btn : Button{ text : "設定" , alignment : ["right","top"] , preferredSize : [30,25] }\

}\

pn : Panel{ alignment : ["fill","fill"] , margins : 0 , orientation : "row" , spacing : 0 , \

gr : Group{ alignment : ["fill","fill"] , orientation : "column" , spacing : 3 , \

btn : Button{ text : "1" , alignment : ["fill","top"] }\

btn : Button{ text : "2" , alignment : ["fill","top"] }\

btn : Button{ text : "3" , alignment : ["fill","top"] }\

btn : Button{ text : "4" , alignment : ["fill","top"] }\

btn : Button{ text : "5" , alignment : ["fill","top"] }\

btn : Button{ text : "6" , alignment : ["fill","top"] }\

btn : Button{ text : "7" , alignment : ["fill","top"] }\

btn : Button{ text : "8" , alignment : ["fill","top"] }\

btn : Button{ text : "9" , alignment : ["fill","top"] }\

btn : Button{ text : "10" , alignment : ["fill","top"] }\

}\

sb : Scrollbar { alignment : ["right","fill"] , preferredSize : [20,-1] }\

}\

ok : Button{ text : "OK" , alignment : ["fill","bottom"] }\

}');

w.gr.dl1.selection = 0;

w.gr.dl2.selection = 0;


w.onResize = function(){

w.layout.resize();

w.pn.sb.maxvalue = w.pn.gr.size.height - w.pn.size.height;

w.pn.sb.enabled = (w.pn.sb.maxvalue > 0);

}


w.onShow = function(){

w.size.height = 200;

w.onResize();

}


w.pn.sb.onChanging = function(){

this.parent.gr.location.y = -this.value;

}


w.show();

初期表示状態

ウィンドウを伸ばしても問題なし!

終わり

長すぎた...。読んでくれた方ありがとうございまる○

これが何処かの誰かの役に立たんことを...。

ロケットビーバイ。