365連休

にわかのandroidとかの開発メモ。

Android Studio 3.5 クリッカブルマップの検討

オフラインで参照可能なクリッカブルマップの性質を持った画面を作りたかった。

 

実装方法検討

  1. AssetとしてHTMLファイルおよび画像を準備し、WebViewで表示する
    メリット
    • Webページなので汎用性が高い。
    • クリッカブルマップ自体の定義が簡単。
    デメリット
    • 実行時に画面に応じたスケーリングを行うと座標が狂う。
    • 座標を狂わせないためのCSSライブラリなどがあるが、そういったものを追加していくと、"簡単さ"というメリットはもはやない。
  2. クリッカブルマップ風に画像やボタンを配置したカスタムビューを作る
    メリット
    • 動作が軽量。
    • 画面に応じたスケーリングは割と得意。
    デメリット
    • めんどくさい。

 

それからどしたの?

最初はHTML書いて楽勝ムードだったが、いざ表示してみたらスケーリングどうすんの?という壁にぶち当たりカスタムビューを作ることにした。

だが、よく考えたらWebView自体のズームをコントロールすれば簡単だったのではと思う。

そんなこんなでカスタムビューを作り始めたら、ドラッグ移動できた方が便利とか、ピンチ操作で拡大縮小できた方が便利とか、大げさなものが出来上がった。

ドラッグとピンチ操作の検出には、親のdispatchTouchEventをオーバライドし、superをコールするとともに、以前作ったカスタムOnTouchListenerもコールして実現した。

ドラッグ時はView#setTranslationX、#setTranslationYで子を移動し、 ピンチ操作時はView#setScaleX、#setScaleYで子のスケールを変更した。

※Assetから読み込む場合であっても、高解像度の画像をImageViewに直接貼り付けるとメモリ不足でバグる。

 

ハマりポイント①

ImageViewに画像を設定しても勝手にスケーリングされて、原寸大で表示する方法がない。※レイアウトコンテナのonMeasure要求により変わる。

つまり画像に対するwrap_contentが効かない。

そのため、アスペクト比や子Viewの位置関係が変わらないように固定値dpで指定しなければならない。

ImageViewに重ねてTextViewやButtonを配置するが、上記固定値dp指定になっていないと、実行時に画面隅からの相対配置になるため、思った通りにレイアウトできない。

FrameLayout(サイズ固定dp)

・ImageView(サイズmatch_parent)

・TextView(marginで配置)

・Button(marginで配置)

 

ハマりポイント②

親のclipChildren=falseが設定されていないと、移動しても初期状態で隠れていた部分が描画されない。

 

ハマりポイント③

Scaleのアンカーが意味不明。 まずAnchorはpivotXとpivotYという値であることに気づくのに時間を要した。

あまり使われないプロパティは検索しても出てこなくて困る。

後で気づいたが、実はsetScaleメソッドのJavaDocに書いてあったw

pivotは手動で設定できるが、通常layoutGravityによって変わる。初期値は0, 0でない。実行時に親の中心点にあたる子の座標がpivotに設定される。子の中心点ではない。

 

ハマりポイント④

子のイベントと親のドラッグ&ピンチを同時に処理するのが、普通にやると無理。

onTouchListenerはtrueを返すと、他のViewへイベントを伝搬しなくなる。

そのため、親はsetOnTouchListenerは使わず、dispatchTouchEventでカスタムonTouchListenerの処理をする

親のdispatchTouchEventはイベント伝播において、子の状態にかかわらず必ずコールされる。

子のonTouchは普段通りに処理すればよいが、onClickについては、ドラッグ操作で子の座標が動的に変化するためか、ドラッグだったにもかかわらずClick検出されてしまった。

そこで、onTouchListenerでごく簡易的なクリックのみを検出するリスナーを作成した。

 

副産物

たぶんあらゆるView・ViewGroupを移動・拡大縮小可能なコンテナが出来上がった。 何かに使えそう。 実装コードについては、カスタムOnTouchListenerにバグがあるため、別な機会にする。