コードから生成したViewにstyleを適用してもLayoutParamsについては無視される
コードから動的にViewを生成したい時がある。そしてそのとき見た目をカスタマイズしたいなんてときがある。もちろんsetBackground()
とかsetPadding()
を呼び出して設定することは可能であるが、どうせならXMLでやるときのようにstyleを適用したい、なんて場面があるだろう。・・・私にはあった。
さてそんなときに、どうやったらJavaのコードでnewしたTextViewにstyleを適用できるのだろうか、という話。2つ方法があって、どちらもコンストラクタでstyleを指定する。
1つはViewのコンストラクタに引数を4つとるものを使う方法。new TextView(context, null, 0, R.style.some_style);
という感じでTextViewを生成する。ただし引数4つのコンストラクタはAPI21からしか存在しないので注意1。
もう1つはContextThemeWrapperを使う方法。new TextView(new ContextThemeWrapper(context, R.style.some_style));
という感じで生成する。こちらも同じくJavaコードから生成したViewに、styleを適用することができる2。droidkaigiのアプリにコントリビュートしたら学べた方法3。
ただし、どっちの方法でstyleを適用しようとも、LayoutParamsに関する設定だけは無視されることに気をつけたい。私はstyleにandroid:layout_margin
を指定していたのだが、Javaのコードから生成した場合、marginが無視された。
レイアウトXMLでstyleを適用した場合は、marginも含めてstyleが適用される。しかしコードからだと適用されない。これはコードからstyleを適用した場合、コンストラクタでViewを生成した時点ではViewがLayoutParamsを持たないからだと思われる。
そもそもLayoutParamsはView自身が使う情報ではなく、そのViewを配置するViewGroupが利用する情報になる。コードから生成した場合、このLayoutParamsは親レイアウトにaddView()
を行った時点で設定される。もちろんsetLayoutParams()
を呼び出すことで事前に設定することも可能だが、コンストラクタを呼び出しただけでは生成されないことがポイント。つまり、View自身に関わるpaddingなどの情報はstyleの適用で設定されるけれども、LayoutParamsに関する情報は設定されないということである。
コンストラクタでLayoutParamsも持たせればいいのにと思うかもしれないが(私も思ったが)、どのViewGroupに配置されるのかがわからないので、コンストラクタの時点でLayoutParamsを設定することは無駄なのだと思う。プログラマが事前にどのViewGroupに配置するか決めているのであれば、setLayoutParams()
を使えということなのだろう。
そもそもstyleにLayoutParamsに関する情報をもたせることが間違いなのかもしれない。今まで特に気にせずにstyleでLayoutParamsに関する情報を持たせていたが、実は推奨されないやり方だったのだろうか。
ちなみに、今回の出来事ではじめて知ったのだが、XMLでandroid:layout_xxx
となるのがLayoutParamsらしい。どれがLayoutParamsなのかわからないじゃないかとか思ったけど、自明だった。
ちなみにViewクラスにはsetStyle()
みたいなメソッドは存在しない。よく考えてみると、XMLファイルでstyleを適用する際に、なぜかstyleだけはnamespaceがつかない。基本的にViewに要素を書くときは、android:xxx
だったりapp:xxx
といった感じで頭に必ずnamespaceをつけるのに、直接style="xxx"
と書く。つまりstyleの適用だけは特殊な扱いなのだろう。
setContentView()
からだとstyleに書いたLayoutParamsが有効になるが、addView()
だと無視されることも、このあたりが関係しているのかもしれない。