ウェブエンジニア、Androidに出会うもView要素をforeachで繰り返せないと知り途方に暮れる

エウレカのPairsグローバル事業部エンジニアの山内です。
この記事は eureka Native Advent Calendar 2017 — Qiita の20日目の記事です。


# 前置き

ずっとウェブの畑で働いてきましたが、ここ数ヶ月Androidを触る機会が増え
Pairsならなんとか機能を実装できる程度の知識がついてきました。


なにかと勝手の違うAndroidの世界。
ウェブのことしか知らない私はAndroidのViewについてずっと思うことがありました。

ああ、
 v-for=”” って書きたい
 ng-repeat=”” って書きたい
 <?php foreach() ?> って書きたい

ところがどっこい、Androidはそれを許さない。
現実は非情である。


この記事は、ウェブしか知らない人でも、Androidを嫌いにならずに
Viewを扱う上で必要な概念・感覚を理解するお手伝いができたらいいなぁと思って書きました。


android view 繰り返し とか検索したとき、
『この記事を読んでいたからスムーズに理解できた!コードがすんなり読めた!』
そんな内容を目指します。よろしければお付き合いください。



# Android前提知識

Androidの基本的な概要を列挙します。

  • プログラムコードとViewが対になった状態で構成されている
  • 1 Activity : 1 XML 1 Fragment : 1 XML
  • これらがいくつも組み合わさって1つのAndroidアプリになっているイメージ
  • ActivityやFragmentクラス(プログラムコード)は、Androidのライフサイクルにフックできる関数を持つ
  • onCreate onCreateView など
  • ライフサイクルごとのフック関数が実行される過程で画面が出来上がっていく
  • XMLにデータを充てたり
  • クリックイベントをハンドルしたり
  • etc…
// プログラムコードイメージ
class MyFragment extends Fragment {
override fun onCreate(...) {
// 初期化処理、イベントリスナをセットとか
}
override fun onCreateView(...): View {
// XMLレイアウトを決めたり
}
...
}
// XMLイメージ
<LinearLayout>
<TextView id="my_name" text="やまうち" />
</LinearLayout>
# プログラムからViewへアクセスする・データを連動する
MyFragmentクラスからXMLのTextViewへアクセスする手段はいくつかあります。
最も原始的なのが、例えば以下のようなアプローチです。
override fun onCreate(...) {
val txtView = findViewById<TextView>("my_name")
txtView.text = "やまうああ"
}
要素id my_name を指定して、TextViewを取得できます。
JavaScript getElementById() でHTMLへアクセスするのと同じ感覚です。


しかし、昨今のJavaScript事情としては
データフローとViewを繋いでくれる役割をReactやVueといったライブラリに任せるのが主流となっています。
Androidでは昔のウェブみたいなことしかできないんでしょうか。


いいえ、Androidは標準でその機能を備えています。DataBindingという仕組みです。


以下、DataBindingを利用した場合のソースイメージです。
// プログラムコードイメージ
class MyFragment extends Fragment {
var binding: MyFragmentBinding
override fun onCreate(...) {
binding.myName.text = "やまうああ"
}
}
// XMLイメージ
<layout>
<LinearLayout>
<TextView id="my_name" text="やまうち" />
</LinearLayout>
</layout>
ポイントを列挙します。
  • XMLを <layout> で囲むと、「DataBindingのViewである」とAndroidは認識する
  • MyFragmentBindingクラス をAndroidが自動で生成
  • プログラム-XML間でデータをブリッジするような存在
  • 以降、bindingを経由してデータのやり取りを行うことができるようになる
  • ロジック中でDOMを取得して〜〜といった、往年のjQueryみたいなことを回避できる
# XML上でforが書けないなんて...
表題の件です。
<layout>
<LinearLayout v-for="u as users">
<TextView text="{{ u.name }}" />
</LinearLayout>
</layout>
ウェブのお手軽な ↑ こんな感じのことはできません。代わりに ↓ こうなります
<layout>
<android.support.v7.widget.RecyclerView id="my_recycler_view"/>
</layout>
(個人的に、この考え方へシフトチェンジするのに少し戸惑いました)


Androidには Viewレイアウトデータ を繰り返して表示する用のViewライブラリが内蔵されている、と理解します。


上記 RecyclerView に対して、プログラム側で Viewレイアウト データ を指定することでAndroidが勝手に繰り返しのViewを表示する、というイメージです。


ウェブほどの手軽さはないですが、抽象的なAndroid基底ライブラリを利用することで
様々な場面に対応でき、プラットフォームとしての実装方針の共通化に一役買っています。


以下、使い方イメージ。
<layout>
<android.support.v7.widget.RecyclerView id="my_recycler_view"/>
</layout>
class MyFragment extends Fragment {
override fun onCreate(...) {
val usrlist = ["やまうああ", "やまうち", "Yamauaa"] // 繰り返しデータ3件
// 表示したいデータ+レイアウトを設定
binding.myRecyclerView.adapter = MyAdapter(userlist)
// レイアウトを設定
binding.myRecyclerView.layoutManager = GridLayoutManager(context, 2)
}
}
class MyAdapter(val userlist) {
override fun onCreateViewHolder(...): RecyclerView.ViewHolder {
// テンプレート(item_user.xml)を指定
val binding = MyItemUserBinding.inflate(...)
return RecyclerView.ViewHolder(binding.root)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {
// Viewへのデータ操作
holder.binding.userName = userlist[position]
}
}
// item_user.xml(結果、このクリエイティブが繰り返し表示される)
<layout>
<data>
<variable name="userName">
</data>
<LinearLayout>
<TextView text="@{userName}" />
</LinearLayout>
</layout>
# Viewスタイリング
HTMLの感覚で、AndroidXMLに違和感を覚えたポイントを3点挙げます。
1. 全部ブロック要素。インライン要素という概念はない
  • <TextView> もブロック要素扱い
2. CSSは無い
  • 正確には「CSSセレクタを駆使した複雑な要素の選択」はできない
  • 共通styleを定義して、特定の要素へ単純に適用することはできる
  • 基本的には、スタイルを全部要素のインラインに書き込んでいくイメージ
  • その代わり、Androidがスタイルプロパティをすべて熟知している
  • 使えないものは使えないと、エラーで教えてくれる
  • でもやっぱり、CSSは便利なんやなって思った(主観)
3. divは無い
  • RerativeLayout、LinearLayout、FrameLayout… といった、Android基底のレイアウトを組み合わせる
  • とても都合のいいタグだったんだと痛感(div)
  • 要素を気軽にまとめたい、意味は薄いけどまとめたい、みたいなことはできない
それ以外は、基本的には同じ感覚で組めると思います。
コラム:ConstraintLayout
上記に気をつけて、HTML感覚でViewを組んでいると、XMLのネストが深くなりがちです。
(複雑なクリエイティブを実現するために、 ○○Layout で囲うことが多くなるため)


それを解決できるのが ConstraintLayout です。
Constraint = 制約 の名の通り、要素一つ一つに位置情報の制約をもたせることで、レイアウトを決定します。


普通に組むのと何が違うのか。 ↓ 違いを表現しました。(HTMLだったとして)
// 普通の実装。配置しやすくするためdivでグルーピングしている
<div>
<div>
<img>
</div>
<div>
<div class="name"></div>
</div>
<div>
<div class="gender"></div>
<div class="age"></div>
<div class="job"></div>
</div>
<div>
<div class="note"></div>
</div>
</div>
// ConstraintLayout
<div>
<img>
<div class="name"></div>
<div class="gender"></div>
<div class="age"></div>
<div class="job"></div>
<div class="note"></div>
</div>
↑ 違いは一目瞭然で、レイアウト取りのための要素がすべて不要となり
構成要素だけXMLに記載するだけで済むようになります。


上記に位置情報のプロパティを追記していくことで、Androidが制約に従ってレイアウト取りをしてくれる、という考え方です。


触ってみた所感としては、慣れが必要で、呼吸するようにプロパティを使いこなせないと大変です。しかし、コンポーネントルートからの相対位置でレイアウトが決まるので、端末サイズに依らない「強い」クリエイティブを作れそうだと感じています。


ref. ConstraintLayoutプロパティ https://qiita.com/nakker1218/items/0faa8c1ab504cc4cedea


↓ ConstraintLayout 実装イメージ
# おわりに
「ウェブしか知らなくてもAndroidの雰囲気が分かる」を大切に進めてまいりました。
いかがだったでしょうか。


ここに書いたことは本当に基本的なことばかりで、実際に手を動かすと謎エラーに悩まされることはザラです。
それでもこの記事が、他のAndroidブログ記事の理解に少しでもお役に立てばと思います。
Like what you read? Give eureka_developers a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.