fchiba memo

2010年05月

Androidで上位APIが使えるなら使い、使えないなら別な方法で対応する、というコードをどうやって書けばいいか検討してみた。

例えば、画像の拡大縮小を
・Ver2.0(API Level 5)以上でマルチタッチ対応なら、ピンチで行う。
・上記以外の場合は、ZoomControlのみで行う。
としたいとする。

Eclipseのプロジェクト作成時、API Levelは1つしか選べない(チェックボックスなのに)。
なので、上位のAPIを使いたい場合は、そのLevelに合わせる必要がある。
ただし、下位の端末でも該当するAPIを使っていなければそのアプリケーションは実行可能である。
(http://developer.android.com/intl/ja/guide/appendix/api-levels.html によると、"If the application were to be somehow installed on a platform with a lower API Level, then it would crash at run-time when it tried to access APIs that don't exist.")

"tried to access APIs"が何を意味するのか実験してみたところ、上位APIを呼び出す前直前ではなく「上位APIを呼び出すコードを含んだクラスをロードする時点」(※)でクラッシュ(java.lang.VerifyError)してしまった。

※…Class.forNameでの明示的ロード、インスタンス化、スタティックメソッド呼び出しなどのタイミング。

ただし、リフレクションでクラスの情報を読み取るのは問題ない。

ということで、
・上位APIにアクセスする部分を他のクラスに追い出し、
・リフレクションで上位APIの有無を確認し、
・存在すればそのクラスをインスタンス化orスタティックメソッドで呼び出し、実際の処理を行う
という手順を踏めばとりあえず実現できそう。

こんなトリッキーな方法よりましな手段がありそうだが、現時点ではみつけられず。
続きを読む

スクロール後の位置を計算してくれる Scroller というクラスがある。
スクロール中の軌道(ある時間にどの位置にあるか?)を計算する方法として、コンストラクタに Interpolator を指定できる。
これは、Scroller#startScrollでスクロールを開始した場合にしか有効でなく、Scroller#fling では単純な2次関数になる(等加速運動)。

よく考えれば当たり前か。終了位置がわからないとInterpolatorも設定できないし。
flingしたら、壁でbounceするアニメーションを実装したかったんだが、どうしよう??

またコピペか?!
コピペしないで済ませる方法を思いついた。初速から適当に終点を求めてそれでstartScrollすればよさそう。

AdapterViewを継承して、Galleryのような独自Viewを作ろうとしているのだが、困った事態に遭遇。

AdapterViewの中に、mFirstPositionという、最初の表示されているアイテムの位置を表す変数がある。
アイテムがどのように表示されるかはAdapterViewでは分からないので、継承したクラスでこの変数を更新する必要があるが、getterはある(getFirstVisiblePosition)のにsetterが存在しない!

変数のスコープはデフォルト(=同パッケージ内からのみ参照可能)なので、Galleryなど既存のViewは直接変数を書き換えている。うーん、お行儀が悪い…
続きを読む

ImageViewのビュー自体のサイズと、中身(Drawable)の表示のされ方はいろいろな要素に影響されて決まる。
ちょっとわかりにくいのでざっと整理してみたいと思う。

(この記事を読むには、Androidのレイアウト計算についてなどで、レイアウトの決まり方を知っておく必要あり)

サイズに影響を与える要素は以下の10個がある。

  1. 親ViewGroupからのmeasureSpec
  2. DrawableのIntrinsicWidth/Height(Bitmapなどの大きさ)
  3. minWidth/Height
  4. maxWidth/Height
  5. BackgroundDrawableのIntrinsicWidth/Height
  6. adjustViewBounds
  7. padding
  8. matrix
  9. scaleType
  10. cropToPadding

このうち、ビューのサイズに影響を与えるのは1~7のみで、8~10はDrawableの表示のみに影響を与える。

ImageViewのソースから、Drawableをセットした場合の処理の流れを追いかけてみる。
setImageURI/setImageResource/setImageDrawableなどDrawableを変更するメソッドを呼ぶと、最後にrequestLayoutが呼ばれる。
requestLayoutを呼び出すと、フレームワークからmeasureが呼ばれ、measureの中からonMeasureが呼ばれる。ここでViewのWidth/Heightが決まる。
Width/Heightが決まるとフレームワークからlayoutが呼ばれ、layoutの中からsetLayoutが呼ばれる。setLayoutはさらにconfigureBounds(private関数)を呼び出す。ここで、Drawableの表示方法が決められる。

onMeasure


onMeasureの中では、以下のような方法で幅・高さが決められている(ただし、説明を簡単にするためにpaddingは0と仮定)
・adjustViewBoundsがtrueで、measureSpecの指定が幅・高さのどちらかが可変の場合
 DrawableのIntrinsicWidth/Heightを、measureSpecとmaxで調整して幅・高さを仮ぎめする(ここで最大が決まる)
 幅・高さをDrawableのアスペクト比と同じになるように調整する。
 順序は、幅→高さの順。仮ぎめしたものより小さくなる。
 調整が効かなかった場合は、仮ぎめしたものがそのまま使われる。
 
・上記以外の場合
 DrawableのIntrinsicWidth/Heightを、
 ・measureSpec/EXACTLY
 ・min以上 && BackgroundDrawableのIntrinsicWidth/Height以上
 ・max以下 && measureSpec/AT_MOST 以下
 の制約条件を満たすように調整する(優先度は上から順)
 
(注)adjustViewBounds=trueの場合、minは無視されることになる

configureBounds


configureBoundsでは、DrawableのIntrinsicWidth/HeightとViewのWidth/Heightが所与のものとして、ScaleTypeを元にDrawableを描画する時のMatrixを計算する。
(レイアウトが完了していない場合にも呼ばれることもあるが、その場合は何もしない)

・fitXY
 Viewのwidth,heightになるようにMatrixを設定
・matrix
 setImageMatrixで指定したMatrixをそのまま使う
・center
 Drawableを中央に配置するようにMatrixを設定
・center_crop
 Drawableを、アスペクト比を維持しつつ隙間がなくなるように拡大・縮小して中央に配置するようにMatrixを設定
・center_inside
 Drawableを、アスペクト比を維持しつつ全てが収まるように拡大・縮小して配置するようにMatrixを設定
・fitStart, fitEnd, fitCenter
 Matrix#ScaleToFitにおまかせ

onDraw


onDrawで計算したMatrixを使って描画を行う。
ただし、ViewよりDrawableのほうが大きい場合、CropToPaddingでpaddingの処理が変わる。
具体的には、CropToPaddingがfalse(デフォルト)の場合は、はみ出した箇所にはpaddingが表示されない(paddingの領域にもDrawableが描画される)が、trueだと常にpaddingが表示される。

Drawable, Canvas, Bitmapの関係が最初わからなかったので整理。

Bitmapは、ピクセル(点)のデータの集まり。

Drawableは、http://developer.android.com/intl/ja/reference/android/graphics/drawable/Drawable.html
A Drawable is a general abstraction for "something that can be drawn."
とある通り、「何を」描くかを表す抽象クラス。

単純なBitmap以外にも、線・塗り、NinePatchなどがDrawableのサブクラスとして実装されている。Resourceと組み合わせて使うことが多い。

また、Viewで使用するための機能として、ステータスやレベルなどがあり、1つのインスタンスに複数の状態を保持できる。StateListDrawableなどといった専用のクラスがあり、他のDrawableを保持することで実現している。
ステータスは、"フォーカスが当たった"/"押された"/"無効"などがある。レベルは任意のint型の数値が指定可能(ボリュームコントロールやバッテリーメーターなどで使う)。


Canvas は「描く」部分を集約したクラス。「線を描く」「Bitmapを流し込む」「テキストを描く」などのメソッドを持つ。どこに描くかはコンストラクタで指定する。

Drawable#draw(canvas) で、Drawableの内容をCanvasに描き込むことができる。

このページのトップヘ