カテゴリー: デザイン

パララックスイメージのAppBarをListViewを使って実装しようとしてハマった話

この記事は最終更新から3ヶ月以上が経過しています。情報が古い可能性があります。

AppBar(Toolbar、ActionBar)の部分が大きめの画像になっていて、コンテンツをスクロールするとそれに合わせて画像が縮んでいき、最終的にToolbarだけが残る(もしくは全部隠れる)みたいなデザインがありますよね。あれを実装しようと思って試行錯誤してみました。

試行錯誤になってしまった原因は、スクロール可能なコンテンツ部分を横着してListViewで作ってしまったからでした。見かけるサンプルはだいたいRecyclerViewを使っていたのですが、使ったことがないため使い慣れているListViewでやろうとしたのが間違いでした。

ListViewで実装すると、ListViewをスクロールしてもAppBarは連動して動いてくれません。AppBarの部分をスクロールすると伸縮してはくれますが、巷にあふれるパララックスAppBarはこんな残念な動きはしていません。

コードで何か手を加えないといけないのだろうかと調べるうちに、なぜListViewではAppBarが連動して動かないのか原因が分かりました。今回はそのお話です。

Patterns– Scrolling techniques

layout.xmlの設定

基本的にパララックスなAppBarを実装するには、レイアウトXMLの記述のみで実装できます。

サンプルコード – GitHub

このMaterial Design(Android desgin support library)による階層構造を初めて見ると、なんだかややこしく感じてしまいますが、1つずつ紐解いていけばそう難しい構造ではありません。

正確にはandroid.support.design.widget.〜とFQCN(パッケージ名を含めたクラス指定)になりますが、ここでは長くなるので省略しています。

CoordinatorLayout
├AppBarLayout
│└CollapsingToolbarLayout
│ ├ImageView
│ └Toolbar
├ListView(などスクロール可能なコンテンツ)
└FABなどお好みで

基本的にXML上でちゃんと必要な指定さえ行えば動きます。コードは不要です。

CoordinatorLayout

今回の例ではListViewのスクロールにあわせてAppBarLayoutを伸縮させるために存在しています(FABをToolbarとListViewの中間に配置する役割も担っていますが)。このCoordinatorLayout自体は内包したView同士を連携させたりする単なる入れ物です。全然「単なる」ではないですけど。

AppBarLayout

AppBar部分のLayoutを管理するコンテナで、AppBarの部分に表示するViewをこの中に入れてやります。Blank Activityを作成すると、この中にはToolbarだけが入っていると思います。

ここではAppBarの高さを指定してやります。android:layout_height="192dp"

CollapsingToolbarLayout

折りたためるToolbarのための入れ物です。スクロールによるAppBarの動き方を指定することができます。ここではapp:layout_scrollFlags="scroll|exitUntilCollapsed"と指定しています。

ImageView

AppBarが全開のときに表示されるイメージ画像です。コンテンツのスクロールに合わせて縮み、最終的にToolbarだけが残ります。ここではapp:layout_collapseMode="parallax"を指定しています。

Toolbar

Toolbarです。ここではapp:layout_collapseMode="pin"を指定しています。この指定でToolbar自体は隠れずに残ります。

ListView

よく見かけるサンプルではRecyclerViewやNestedScrollViewが利用されています。しかし私はRecyclerViewの使い方がよくわからなかったのでListViewで代用しています。

ここでは必ずapp:layout_behavior="@string/appbar_scrolling_view_behavior"の指定が必要です。

この@string/〜はAndroid Support Libraryのstringリソースを参照していて、その中身はandroid.support.design.widget.AppBarLayout$ScrollingViewBehaviorとなっています。つまりこのListViewの振る舞いとして、AppBarLayoutのScrollingViewBehaviorを指定しているわけです。

ListViewを使うと、そのままではListViewがスクロールされるだけでAppBarが伸縮しません。ListViewのスクロールと連動させるためには、ListViewにandroid:nestedScrollingEnabled="true"を指定する必要があります。

なぜか。スクロール可能なコンテンツとAppBarの伸縮を連携させるためには、ListViewがスクロールされたということをCoordinatorLayoutに伝える必要があります。RecyclerViewやNestedScrollViewは標準でこれをやってくれるわけですが、ListViewは何もしません。そこでCoordinatorLayoutにスクロールイベントを通知するための設定を有効にしてやる必要があるのです。

android:nestedScrollingEnabled="true"(NestedScrollに関する処理)はAPI21以上のViewに実装されています。

余談:なぜAppBarが動くのか

仕組みを完全に理解したわけではないので、ざっくりとした説明です。

ListViewの上でスクロールを行うと、ListViewの中身がスクロールされます。これはListViewのonTouchEventで処理されています。これだけではListViewの中でスクロールイベントが処理されるだけで、AppBarの変形にはつながりません。

そこで登場するのがCoordinatorLayoutです。こいつが子Viewのスクロールと、別の子Viewを連携させるわけです。

連携させるためにはCoordinatorLayoutにスクロールイベントを通知する必要があり、その仕組がNestedScrollです。RecyclerViewやNestedScrollViewは初めからCoordinatorLayoutと連携する前提で作られていますし、ListViewなどでもSDK21からNestedScrollに関する処理が追加されています。ただし初期状態では無効化されているので、NestedScrollの処理を有効化してやる必要があり、それがandroid:nestedScrollingEnabled="true"になります。

NestedScrollの処理は子ViewのonTouchEvent(onTouchMove)でCoordinatorLayoutに伝わります。CoodinatorLayoutはonNestedPreScroll内でBehaviorが設定されている子Viewを探し、見つかったBehaviorに対してdispatchOnDependentViewChangedを呼び出します。今回の例ではScrollingViewBehaviorです。

最終的にAppBarのサイズを伸縮させる処理は、このBehaviorのonDependentViewChangedで行われているみたいです。

参考

Handling Scrolls with CoordinatorLayout

その画面がどんなViewを使って作られているか調べる方法

この記事は最終更新から3ヶ月以上が経過しています。情報が古い可能性があります。

「このアプリのデザインを参考にしたいんだけど、どうやって作ってるのか知りたい」というときに便利かもしれないコマンドです。

調べたい画面を表示させた状態で、ターミナルからadb shell dumpsys activity topと入力すると、現在表示中のView階層などが表示されます。

View階層だけを調べたいなら、hierarchyviewerを使った方がグラフィカルに見えて便利なのですが、hierarchyviewerはroot権限がないと起動しないので、実機で調べたい画面を表示して解析することができません。

その点、このadb shell dumpsys activity topはroot権限を必要としないので、実機でちょっと調べたいという時に便利だと思います。

どこからどこまでがActionBarの領域で、どこがコンテンツの領域なのかが非常に分かりづらいのですが、Viewに割り振られているIDも一緒に表示されるのである程度把握できると思います。

このIDが表示されるのを利用して、View階層の中でIDの衝突が起こっていないかなんてことを調べるのにも便利かもしれません。

ちなみにadb shell dumpsys activityと最後のtopを省略すると、Activity Managerの情報がズラズラと表示されます。

Broad castがどうなってるかとか、Content Providerがどんなのが動いているかとか、どんなServiceが動いているかとか、スタックがどうなってるかとかが出力されます。

実機でコマンドを打つだけで調べられるので、手軽で便利だと思います。

ペーパープロトタイピングをまずやろう

この記事は最終更新から3ヶ月以上が経過しています。情報が古い可能性があります。

アプリを開発する上で、コーディングを始める前にペーパープロトタイプを作るといろいろなメリットを享受できます。

  • どんな画面が必要になるか検討できる
  • 作り終わってから使い勝手の悪いところに気づいて、実装をやり直す事態を未然に防げる可能性がある
  • 必要な機能の絞り込みができる
  • 作ろうとしているアプリのイメージがはっきりしてくる
  • などなど

デザインに疎いとどうしてもコーディングを優先してしまいがちです。画面設計よりシステムを作っている方が性に合ってますし。ついついデザインを後回しにしてしまいます。

しかしその作り方をすると、アプリ完成したけれど残念な見た目だったり、使い勝手が悪くて使えないアプリになってしまったりしているかもしれません。それを後から直そうとすると、せっかく実装したシステムも作りなおしになる可能性もあります。せっかく作ったコードを、使い勝手が悪いからと泣く泣く切り捨てることになったら、目も当てられません。

POP

ありがたいことに、ペーパープロトタイピングを簡単に行うことのできるツールが世の中には存在しています。

POPというアプリを利用すると、紙に書いた画面デザインにいとも簡単に動きをつけることができます。ここを押したらこの画面に移動して・・・なんていうのが簡単に作れて非常に便利です。

Android版のPOP2.0だとジェスチャーを設定する項目があるものの、設定しても動かなかったり、スマホで撮影した画面を削除しようとしたらゾンビのごとく復活してきたりと、使い勝手が微妙なところがあります。私の使ってる端末固有の問題なのかもしれませんけれども・・・。

ただこれがあるだけでも画面の動きのイメージがつきます。この画面遷移は使いづらいとか、こういう画面も用意しないといけないなというのが簡単に分かります。このアプリ、便利なのは間違いありません。

Androidからだけでなく、パソコンのブラウザやiPhoneなどからも使うことができるので、ブラウザで編集してスマホで動作を確認するなんて使い方ができます。

POPブラウザからアクセス

テンプレート

紙にアプリのデザインを書くのに、さり気なくネックなのが画面を描画するところです。画面の外枠です。私はこれが面倒くさくて、画面を紙に書けないでいました。

枠を決めるのが難しいのであれば、最初から用意されているものを利用すればいい。そこで私は、iPhone5のワイヤーフレームに使えるアイデアシートをイラレで作りましたさんで公開されているアイデアシートを利用させていただきました。

現在はリンク切れで見れなくなってますね・・・。

iPhone5向けのテンプレートではありますが、画面の比率はAndroidとそう大差ありません。画面の外枠が決まっているだけでも、やりやすさが段違いです。

どう実装するかはとりあえず考えない

とりあえず考えるのは、ボタンをどこに置いて、どの画面に移動するのかだけに絞った方がいいと思います。できるだけシンプルに考えるのが大事です。どうやってコーディングしようかと考えだすと、何も書けなくなってしまいます。

実装方法を考えながらやってしまうと、実装しやすさを優先するあまりに使い勝手が犠牲になるのが悲しいです。とりあえずアイデアシートを印刷して、どしどし書くべしです。書いていればいいアイデアが浮かんだりします。紙に書くだけなのでやり直しも簡単ですしね。

せっかくアプリを作るのであれば、多くの人に使ってもらいたいです。そのためにも、せめて使い勝手がよくなるような努力はしておきたいですよね。

新旧1分間タイマーの変化

デザインを考える

この記事は最終更新から3ヶ月以上が経過しています。情報が古い可能性があります。

ださいデザインからの脱却

1分間タイマーは、最初のバージョンでは文字とボタンだけが表示されている、非常にダサいアプリでした。それに比べると現在の見た目はだいぶましになったように思います。あくまで最初の頃よりはましになったなというだけで、カッコイイ見た目にするにはどうすればいいのかよく分かりません。

ただ、見た目をかっこ良くするという観点からのアプローチは難しくとも、使いやすくするという観点からのアプローチであれば、少し突破口が見えるような気がします。私がSmashing Android UI レスポンシブUIとデザインパターンという本を読んで、1分間タイマーに加えた変更を例にしてみましょう。

開始ボタンを押しやすくする

1分間タイマーを自分で使っていてとても不便だったのが、タイマーの開始ボタンが押しづらいことでした

1分間タイマーの当初のバージョンでは、デフォルトのButtonを開始ボタンとして利用していました。私はタイマーを開始させたらすぐに紙に向かって文字を書き出していきたいため、アプリの開始ボタンは横目で見ながら押すような感じで使っていました。しかし以前のバージョンの四角い小さなボタンでは、開始させたつもりが押せていないということがよくあってテンポが悪かったのです。

ボタンを大きくすれば使い勝手はかなり向上します。

新旧1分間タイマーの変化

残り時間の表示方法

ボタンの巨大化にともなって、残り秒数の表現方法も変えることにしました。開始ボタンを円形にしたので、その周りにバーのような感じで残り秒数が分かればスマートかなと考えました。

以前は単に文字で残り秒数を表示していました。しかし自分で使っていて、あと何秒残っているかを文字で読み取ることはいままで一度もありませんでした。そもそも音声による通知もあるので、残り秒数を文字で把握する必要性はありません。

円形のバーであれば視覚的に横目であとどれくらい残っているかが分かるので、文字で表示されるよりもマシだと思います。

ただ、このバーの動きがカクカクしているのが残念なところです。スムーズに動くように見せることができれば、見た目もよくなるのですが、実装方法が分かりませんでした。いずれやり方を調べて実装できたらいいなぁと思っています。

アプリ終了時の確認ダイアログをなくした

以前のバージョンでは、バックボタンを押した時に「終了しますか?」という確認ダイアログを表示していました。この終了するかどうか確認をとるアプリは、世の中にまだまだ多く存在しています。

みなさんは、アプリを使っていて終了時に確認ダイアログが出ることについてどう考えていますか?

「そんな確認はいらないんでさっさと終了しろよ」派でしょうか、「わざわざ確認してくれて親切やね、ありがとう」派でしょうか。

Smashing Android UI レスポンシブUIとデザインパターンでは、この終了時に確認ダイアログを出すのは、多くの場合開発者の都合によってつけられているものであると断じていました。

確認ダイアログは悪である

終了時に限らず、何らかの操作を行う際に確認ダイアログを表示するのは、取り返しの付かないことを実行する場合にユーザーに責任を転嫁させるため、開発者の都合で使われている悪しきものだとバッサリでした。

確認ダイアログを出したところでユーザーがちゃんと確認するかどうかは分かりません。操作ミスを恐れて確認ダイアログを表示させるという考え方もあるかもしれませんが、そのダイアログのボタンを操作ミスしないとは言い切れないはずです。

そこで確認ダイアログを使うくらいなら、操作を実行した後にそれを取り消すための手段をユーザーに提供することこそが、真のユーザーフレンドリーであるとこの本には書いてありました。ごもっともだと思いました。

終了時の確認で言えば、一度終了してしまうと起動するのに膨大な時間がかかってしまうため、操作ミスによる終了を防止する意味で出すというのはありかもしれません。起動するのに1分かかるゲームアプリで、やっと起動したと思った時に手が滑ってバックボタンを押してしまった。そういう場合であれば、確認ダイアログを表示する方が親切かもしれません。

ですが、その確認ダイアログは必要なのかと自問することが大切です。終了前の操作状態を復元することで対処できないか、いつ終了されてもデータが保存されるように作ればすむ話ではないか。そう考えることで使い勝手が向上していくのです。

アプリのデザインは難しい

デザインといえば見た目の話だけだと思っていましたが、使い勝手に関わるすべてのものがデザインです。アプリの開発と一口に言っても、プログラムのことだけではなく、使い勝手のことについても考えなければなりません。実際にやってみると奥が深いなぁとつくづく思います。

今まで「デザイン」というと見た目をカッコよくすることだと思っていましたが、考えることはそれだけではないのだということが分かりました。見た目をカッコよくするのは難しくても、使いやすくすることであれば考えれば何とかなりそうです

そんな見た目がおしゃれなデザインの話より、使い勝手を重視したデザインの話がいっぱいなSmashing Android UI レスポンシブUIとデザインパターン。読んでいるといろんな発見があると思います。