作ってみた✌
ありきたりやけどユーザー操作でViewの大きさを変更できるLinearLayoutを作ってみた(SplitLinearLayout)。GitHubのViewSplitterTestリポジトリ
まぁ探せば似たようなのは色々あるんだけど、子Viewの最小サイズ指定を無視するとか、子Viewの数が2個限定(2より多くても1つでもだめ)とかレイアウトファイルでのサイズ設定が無視されてしまうとか色々気に入らないのでオレオレ作ってみた。
任意に子Viewの移動・サイズ変更できるように作るのも可能だけどそれは趣旨がかなり変わってしまうので、今回はLinearLayoutをベースに縦または横方向のみのリサイズをできるように
ちゃんちゃん(^_^)/~
特徴
じゃなくて簡単な説明な。
- 画面に収まる限り子Viewの個数に制限なし。これ大事?
- 子Viewの最小幅/最小高さよりは小さくならないように処理。これも大事?
- レイアウトファイルで指定したとおりに子Viewを配置する。これも大事?
- 入れ子になっていても動作する。これ、p大事?
- 元々LinearLayoutにある子View間の区切り用Drawableを使ってスプリッター(サイズ変更用のハンドル)描画する。
android:divider 属性をセットするか
#setDividerDrawableを呼び出してセットしてな。
フォールバックのためにセットされていないときは内部で赤いPaintDrawableを生成して使うよ。 - スプリッターの太さをレイアウトxml/セッター/ゲッターで指定できる
- スプリッターのタッチ領域を描画太さより太くするようにレイアウトxml/セッター/ゲッターで指定できる
- ユーザー操作でのサイズ変更を有効/無効をレイアウトxml/セッター/ゲッターで切り替えることができる。
動作の概要
キーとなるのは次の4つのメソッドをoverrideすることです。
- #onMeasure
- #onLayout
- #dispatchDraw
- #onTouch /li>
AndroidのView/ViewGroupでは、レイアウト要求があると、自分自身および子Viewpのサイズ調整のために #onMeasure が1回以上呼ばれた後すべてのViewサイズと位置が確定すると #onLayout が1回呼び出されます。
ですので、
#onMeasure で子Viewのサイズ変更要求するようにoverrideします。ここで大事なのは、1回のレイアウト要求に対して
onMeasureが複数回呼ばれる可能性があるということです。
#oNMeasure が呼ばれるたびに処理をしてしまうとレイアウト完了までに非常に時間がかかってしまいますし、おかしな実装をするとレイアウト処理が永遠の終わらなくなってしまいますので注意しないといけません。
ではいつ子Viewのサイズ変更要求をすればいいかというと、自分自身の大きさが確定した時なのですが、そのような都合のいいフラグは存在しないので代わりに
View#getMeasuredWidth と
View#getMeasuredHeight の両方が0より大きい値を返したときに子Viewのサイズ変更要求をします。
なお、「レイアウトファイルで指定したとおりに子Viewを配置する」を実現するためにはSplitLinearLayoutの初回レイアウト要求に対しては子Viewのサイズ調整要求を行わないようにしたいといけません。SplitLinearLayoutでは mIsFirstTime フラグを設けて #onLayout が呼ばれたときにこのフラグをクリアする& mIsFirstTime フラグがtrueならば #onMeqasure での子Viewのサイズ変更要求をしないようにしています。
#onLayout が呼ばれるとスプリッター(ユーザーが子Viewサイズ変更につかうハンドル)の描画用に子Viewの位置とサイズにおおじてスプリッターの設定を更新します。SplitLinearLayoutでは #updateSplitter メソッドで行っています。
スプリッターの描画は
#dispatchDraw で行っています。
御存知の通りLinearLayoutには子View間の仕切り線を描画する機能が標準で実装されています。単純にスプリッターを描画するだけであればLinearLayoutの区切り線描画機能を有効にするだけでいいのですが、1つ問題があります。標準の区切り線描画機能はあくまでも子View間の区切りを表示するだけなので、スプリッターを動かしている最中には表示が更新されないのです。これだと操作しているときにどこまで移動したのかわかりにくく不便です。タッチ操作でスプリッターの位置を変更するたびに直接子Viewのサイズ変更をすればこの問題は一応は解消するのですが配下のViewの数や設定によっては非常に重たい処理になってしまう可能性があります。
そこで、タッチ操作でスプリッターの位置調整をしてい間(
MotionEvent.ACTION_DOWN イベントから
MotionEvent.ACTION_MOVE が呼ばれている間)はスプリッターの位置表示のみ更新して
MotionEvent.ACTION_UP イベントで子Viewのリサイズ要求を行うようにしています。このためLinearLayoutの区切り線描画機能では不十分なため
#dispatchDraw でオレオレと描画しています。
最後に
#onTouch メソッドです。
描画のところでも少し書きましたが、
MotionEvent.ACTION_DOWN でスプリッターの位置変更開始、
MotionEvent.ACTION_MOVE でスプリッターの位置変更、
MotionEvent.ACTION_UP でスプリッターの位置に合わせて子Viewのサイズ変更要求(==レイアウト要求)を行います。
大したことはしていないとは言えフルスクラッチでは作れない人も多いみたいだし参考にために記事にしてみた。
ちゃんちゃん
(^^)/~~~
2020/03/08 22:20追記
#onInterceptTouchEvent も実装してスプリッター上のタッチ&スプリッターのドラッグ中にtrueを返すようにした方がいいかも