Writing Custom Views for Android
View のライフサイクルについて
- attach/detach
- traversals
- save state
■ Attach/Detach
onAttachedToWindow()
・super.onAttachedToWindow() を呼ぶこと
・関連する状態をリセットする
・状態の変化の監視を開始する(receiver を登録したりとか)
ListView はここでデータチェンジのリスナーをセットしている
onDetachedFromWindow()
・super.onDetachedFromWindow() を呼ぶこと
・ポストした Runnables を削除すること
・データや状態の監視を止めること
・リソース(Bitmap, Thread など)をクリーンアップすること
View は Activity や Fragment よりも low level で抽象化されている
View は Activity のライフサイクルの影響を受けないので、Activity の onResume() や onPause() でなにか処理をしたい場合は自分で View にリスナーを追加する
■ Traversals
Animate -> Measure -> Layout -> Draw
・Measurement and Layout
requestLayout() が最初のステップ
requestLayout() はビュー階層を降りていってサイズを計算するので、より階層の下の View で呼ぶべき
onMeasure() ですること
1. ビュー自身と子ビューに割り当てるサイズを決定する
2. MeasureSpec のパラメータを作る
int widthSpec = MeasureSpec.makeMeasureSpec(sizeInPx, MeasureSpec.EXACTLY);
int widthSize = MeasureSpec.getSize(widthSpec);
int widthMode = MeasureSpec.getMode(widthSpec);
3. 全ての子ビューに対して measure() を呼ぶ
4. メソッドから戻る前にかならず setMeasuredDimension() メソッドを呼ぶ
MeasureSpec にのモードについて
1.MeasureSpec.EXACTLY
・android:layout_width="150dp"
・android:layout_width="match_parent"
・android:layout_weight="1"
など、
例えば android:layout_width="150dp" を density = 2 のデバイスで動かした場合、次のように指定することになる
MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY);
2. MeasureSpec.AT_MOST
・android:layout_width="wrap_content"
など
3. MeasureSpec.UNSPECIFIED
・ScrollView
・ListView
など
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
ViewGroup.getChildMeasureSpec() を使うといいよ!
例えば
Parent : 300px EXACTLY で Child が wrap_content の場合
Child は 300px AT_MOST
onLayout() について
1. 子ビューの位置を決める
2. traversal 内で一度だけ呼ばれる(時間がかかる処理は layout() までに保持しておく)
3. onMeasure() ですでに計算した情報を使う(getMeasuredWidth(), getMeasuredHeight())
(getWidth(), getHeight() は layout の後に正しい値になる)
デフォルトの UI toolkit を組み合わせてすごく複雑なもになるなら、CustomView を使おう
LayoutParams について
・LayoutParams は ViewGroup.LayoutParams を継承する
・width, height は LayoutParams で与えられる
・ViewGroup に特定の引数を保存する(LinearLayout の weight とか)
・layout xml から inflate できる
・Validation/conversion
・checkLayoutParams(ViewGroup.LayoutParams)
・generateLayoutParams(ViewGroup.LayoutParams)
・generateLayoutParams(AttributeSet)
・generateDefaultLayoutParams()
generateLayoutParams(ViewGroup.LayoutParams) は例えば FrameLayout の子ビューを自分の CustomViewGroup に入れ替えるときに FrameLayout のときの LayoutParams から ViewGroup 共通の LayoutParams だけを取り出したものを返してくれる
・layout のための private データを保持できる
・Drawing
invalidate()
draw()
ビュー自身と子ビューの描画をする
onDraw()
・content を描画する(TextView での text など)
・背景は onDraw() より前に描画される
・setWillNotDraw(true) (この名前紛らわしくて好きじゃないんだよねとスピーカーが言っているw)がセットされていたらスキップされる
dispatchDraw()
・ViewGroup.drawChild() を介して各 Child() の draw() を呼ぶ
・drawChild() は変形やニアメーションなどを処理する
Software vs Hardware
・View.draw() の処理が異なる
・In software
・View.draw() が invalidate() ソースの各 Parent、および dirty region にかかる各 View で呼ばれる
・In hardware
・View.draw() は invalidate() ソースで呼ばれる
・Google I/O 2011 の "Accelerated Android Rendering" みてね
Instance State
画面回転などに対応するためデータを保持しておく
・onSaveInstanceState()
・onRestoreInstanceState(Parcelable)
・SavedState は View.BaseSavedState を継承している
・View は ID を持っていなければならない
何を保持するのか
・データモデルは View state から分離するべき
・ユーザーインタラクションの進捗を保持する
・スクロール位置
・選択中の項目
・選択中のモード
・コミットしていない入力(入力されたテキストなど)
Touch Event
適切なタッチイベントの流れは
・ACTION_DOWN
・ACTION_POINTER_DOWN/ACTION_POINTER_UP の一致したペア
・ゼロ以上の ACTION_MOVEs(有効なポインターだけを含むこと)
・ACTION_UP/ACTION_CANCEL
・onTouchEvent() では受けとりたい場合に true を返す
・onInterceptTouchEvent() でも受けとりたい場合に true を返すが、子に送られるイベントも観測できる
・requestDisallowInterceptTouchEvent()
・親の onInterceptTouchEvent() を呼ばれることを防ぐ
・ジェスチャーが終わるまで有効
・例えば、ViewPager の中に横にスクロールできる CustomView がある場合、CustomView がスクロールして ViewPager が動かないようにすることができる
・より適当な方法
・ViewConfiguration#getScaledTouchSlop() を使って、スクロールしたと判定できる距離をポインターが動くまでなにもしない
ViewConfiguration ではいろいろな定数を取得できる
Q. View のなかでアニメーションさせたいんだけど、どうするのがいい? Timer を使ったり、ちょっとずつ時間をあけて描画する?
A. 明日アニメーションのセッションがあるので、そっちで話すよ
Q. RelativeLayout についてのブログポストがあって。。。(よく聞き取れない)
A. LinearLayout のネストよりも RelativeLayout でできるならいいという意図のもので、コンテキストによる。FrameLayout でできる場合もあるし、、、
他みてないのでなんでRelativeLayout界隈の話がQAで出たかわからないんですが、そこだけ見たところこういう感じに聞き取れました。間違ってたら無視してください。
返信削除Q: RelativeLayout の件でのブログポストが誤解されてると言ってましたね (多分。misinterpreted)。私も誤解してた気がします。この点について、周囲ではハマってる人ばかりなので、もう少し詳しく (elaborate)
A:
(おそらく2009年時点のブログポストであるこれを参照している http://www.curious-creature.org/2009/02/22/android-layout-tricks-1/)
その記事は特にListView内で使うアイテムのレイアウトを考える際に、LinearLayoutをネストさせて実現させるよりRelativeLayoutを使うほうが良いケースがあるということを伝えたかった。
今でもその記事の内容は有効だが、必ずしも全てのケースでRelativeLayoutを使えば良い、ということではない。
例えば、RelativeLayoutはサイズ測定を必ず2度行う (always do the two measure paths) ので、RelativeLayoutをネストして使うと指数的に計算量が増えることがある。
StackViewやFrameLayoutでうまく動作するケースがあるのならそちらを使うべき。