Upgrade to Pro — share decks privately, control downloads, hide ads and more …

詳解 ViewGroupのレイアウト内部実装

Ad449f3f4e595ade283a30c4f66010be?s=47 HiroYUKI Seto
February 09, 2018

詳解 ViewGroupのレイアウト内部実装

2018/2/9
DroidKaigi 2018 DAY02 room1 10:30~

付録1: RelativeLayoutの内部実装
https://speakerdeck.com/seto_hi/xiang-jie-relativelayoutfalsenei-bu-shi-zhuang
付録2: ConstraintLayoutの機能実現
https://speakerdeck.com/seto_hi/constraintlayoutfalseji-neng-falseshi-xian
付録3: ノハナ社のレイアウト戦略
https://speakerdeck.com/seto_hi/falsehanashe-falsereiautozhan-lue

Ad449f3f4e595ade283a30c4f66010be?s=128

HiroYUKI Seto

February 09, 2018
Tweet

More Decks by HiroYUKI Seto

Other Decks in Technology

Transcript

  1. 詳解 ViewGroupのレイアウト 内部実装 DroidKaigi 2018 株式会社ノハナ 瀬戸優之 @seto_hi

  2. 自己紹介 • 瀬戸優之 Seto HiroYUKI @seto_hi • Androidエンジニア & アプリデザイン

    • 株式会社ノハナ ◦ 2年連続DroidKaigiスポンサー! ◦ 一組でも多くの家族に笑顔を届ける ◦ 絶賛採用中 • Material Design大好き • 好きなAPIはCanvas#saveとViewGroup#layout
  3. おしながき • Viewってどうやってレイアウトされているの? ◦ measure, layout • 詳解FrameLayoutの内部実装 • 詳解LinearLayoutの内部実装

    • 詳解ConstraintLayoutの内部実装 ◦ ConstraintLayoutを支える技術 ◦ Optimizer ◦ 各機能の実現→付録にあります • 付録
  4. 参考環境 コードを読んだ環境 • FrameLayout, LinearLayout(, RelativeLayout) ◦ Android 8.0、一部Android8.1 •

    CosntraintLayout ◦ constraint-layout:1.1.0-beta3, beta4, beta5 ◦ 一部beta2
  5. Viewってどうやって レイアウトされているの?

  6. おしながき • レイアウトの流れ • measure ◦ measure, onMeasure ◦ MeasureSpec

    ◦ measuredWidth, measuredHeight • layout ◦ layout, onLayout
  7. レイアウトの流れ 1. レイアウトが要求される ◦ View#requestLayout 2. Viewのサイズを計算する ◦ measure, onMeasure

    3. Viewのレイアウト ◦ measureしたサイズを参考にする ◦ layout, onLayout
  8. View View Group View Gruop View Group View View request

     Layout Handler Traversal Runnable View Root Impl request  Layout request  Layout
  9. View View Group View Gruop View Group View View measure

    View Root Impl measure measure measure measure measure Handler Traversal Runnable
  10. View View Group View Gruop View Group View View layout

    View Root Impl layout layout layout layout layout
  11. measureとlayoutの分割 • Viewが自身だけでレイアウト位置は決められない ◦ 他のViewのサイズでレイアウト位置が変わる • 全部のViewをmeasureして 結果によってlayout位置を変える必要がある ◦ 高機能なViewGroupは

    measureでレイアウト位置を確定させている
  12. Viewのmeasure

  13. measureとonMeasure • measureがonMeasureを呼ぶ • measureはViewクラスのfinalメソッド • measureは呼び出す用 • onMeasureは各Viewがoverrideする用

  14. onMeasure • Viewの幅と高さを計測 ◦ ViewGroupは子Viewをmeasureする • 自身のサイズも設定 ◦ setMeasuredDimensionsで measuredWidth/Heightを設定

    ▪ 呼ばないと例外
  15. measuredWidth, measuredHeight • onLayoutで使う • setMeasureDimensionsで設定した値※ • View#getWidth/getHeightはlayout後の値 ◦ View.mLeft-View.mRight

    • layout以降は使ってはならない ◦ measuredWidth/Height通りにlayoutされるとは限らない ◦ layout以降はgetWidth/Heightを使う ※ 厳密にはMeasureSpec
  16. MeasureSpec • onMesureの引数 • measureの条件(Mode)とSizeをintで表現したもの ◦ Modeが上位2byte、Sizeが下位30bit Mode 意味 UNSPECIFIED

    Viewの好きなサイズにして良い EXACTLY 指定されたサイズにすべき AT_MOST Viewの好きなサイズにして良い でも指定されたサイズは超さないように
  17. Viewのlayout

  18. layoutとonLayout • layoutがonLayoutを呼ぶ • ViewGroup#layoutはfinalメソッド • layoutが呼び出す用 • onLayoutがoverrideする用 •

    各ViewGroupがonLayoutをoverrideする ◦ ViewはonLayoutをoverrideする必要はない
  19. ViewGroup#onLayout • 子Viewのレイアウトを行う ◦ ViewGroupに対する相対座標 • measureした結果を使う • measuredWidth/Height通りにlayoutすべき ◦

    例:TextViewはmeasuredWidth/Height通りの 文字改行がされている
  20. 詳解 FrameLayoutの 内部実装

  21. FrameLayout#onMeasure

  22. FrameLayout#onMeasure 1. すべての子Viewをmeasure ◦ FrameLayoutの引数と同じMeasureSpec 2. MeasureSpec#modeによって自身のサイズを変える ◦ AT_MOST→子Viewの最大値とspecのsizeのmin ◦

    EXACTLY→specのsize ◦ UNSPECIFIED →子Viewの最大値 3. match_parentな子Viewをmeasure ◦ mode:EXACTLY、size:FrameLayoutの幅
  23. FrameLayout#onMeasure FrameLayout View(w:match_parent,   h:warp_conten) View#measuredWidth MeasureSpecのsize

  24. FrameLayout#onLayout

  25. FrameLayout#onLayout • measureWidth/Heightのサイズでレイアウト • Gravityによってレイアウト位置を変える

  26. FrameLayout#onLayout • leftの図 FrameLayout View Gravity:LEFT|BOTTOM 0 View#measuredWidth

  27. FrameLayout#onLayout • leftの図 FrameLayout View Gravity:LEFT|BOTTOM FrameLayout.bottom FrameLayout.bottom - View.mesuredHeight

  28. 詳解 LinearLayoutの 内部実装

  29. LinearLayout#onMeasure

  30. LinearLayout#onMeasure measureVertical/Horizontal 1. すべての子Viewをmeasure ◦ 子Viewの高さ/幅の合計を記録する 2. weightがある子Viewがある場合 ◦ weightを分配したサイズ+EXACTLYで再度measure

    3. setMeasuredDimension
  31. Weight Tips • 0dp + weightがなぜ早いか ◦ LinearLayoutのMesureSpecがEXACTLYの場合 0dp +

    weightのViewは 1回目のmeasureをskipするため
  32. LinearLayout#onLayout

  33. LinearLayout#onLayout • layoutVertical/Horizontal ◦ LtR対応があるのでhorizontalがやや複雑 • 上/左から順にlayout • orientationと逆方向のGravityが効く ◦

    割愛
  34. 詳解 ConstraintLayoutの 内部実装

  35. ところで

  36. ところで

  37. UIは 一次方程式と 一次不等式で 表せる

  38. 一次方程式と 一次不等式の 最適解を求める

  39. 線形計画問題

  40. 線形計画問題 線型計画問題とは、最適化問題において、 目的関数が線型関数で、 なおかつ線型関数の等式と不等式で 制約条件が記述できる問題である。 (wikipedia)

  41. 線形計画問題 例: 原料A 1gと原料B 2gで60円の商品Xを作れます 原料A 3gと原料B 1gで40円の商品Yを作れます 原料Aが90g、原料Bが50gあるとき 商品XとYをそれぞれいくつ作れば

    売り上げが最大になるでしょう?
  42. 線形計画問題 商品Xをx、商品Yをyとする x + 3y < 90 2x + y

    < 50 x ≧ 0, y ≧ 0 のとき 60x + 40yの最大値を求める
  43. 図 線形計画問題 x + 3y < 90 2x + y

    < 50 40x + 60y = ?
  44. 図 線形計画問題 x + 3y < 90 2x + y

    < 50 (12, 26) 40x + 60y = 2040
  45. 完全に理解しましたね? 

  46. None
  47. シンプレックス法

  48. シンプレックス法 シンプレックス法は、 実行可能解 (超多面体の頂点) の1つから出発して 目的関数の値をなるべく大きく (小さく) するようなところに移動さ せていく動作を繰り返して 最適解を見つけ出す方法である。

    (wikipedia)
  49. 図 シンプレックス法 x + 3y < 90 2x + y

    < 50 (12, 26) 40x + 60y = 2040 ①初期基底解(BFS) ②
  50. シンプレックス法 • シンプレックス法 • 二段階シンプレックス法 ◦ 人工変数と人工的な目的関数を加えることで 初期基底解が簡単に求まらない場合に対応 • 双対シンプレックス法

    ◦ 双対定理を利用し 双対問題を解くことで主問題も解く
  51. 完全に理解しましたね? 

  52. None
  53. Cassowary

  54. Cassowary • ヒクイドリ - 世界一危険な鳥 • 線形方程式、線形不等式の 制約充足問題(constraint solving problem)の解法

    ◦ 2段階+双対シンプレックス法を使う • アプリのUI用に最適化されている ◦ 高速 ◦ 省メモリ ◦ 宣言的 ◦ 差分更新可能
  55. Cassowary • 2001年に開発された ◦ 最初はCSSのレイアウト拡張として • 多くの言語に移植されている ◦ Smalltalk, C++,

    Java, JavaScript, Dart, Python etc.. • 2011年からMac(OS X)やiOS(6~)で使われている ◦ AutoLayout • ConstraintLayoutでも採用!
  56. 完全に理解しましたね? 

  57. None
  58. 参考資料 @inamiyさん iOSDC Japan 2017で「Auto Layoutのアルゴリズム」について発 表しました https://qiita.com/inamiy/items/a6f73438b32896ffa81e AutoLayout Algorithm

    https://speakerdeck.com/inamiy/autolayout-algorithm
  59. ConstraintLayoutを支える技術 名前だけでも覚えて帰ってね • UIの解決 ≒ 線形計画問題 • 線形計画問題の解法 ◦ シンプレックス法(2段階、双対)

    ◦ Cassowary • Advanced ConstraintLayout https://academy.realm.io/posts/360-andev-2017-nicolas-ro ard-advanced-constraintlayout/
  60. ConstraintLayoutの構成

  61. ConstraintLayoutの構成 • constraint-layout ◦ 9クラス ◦ Viewの簡単な実装がメイン • constraint-layout-solver ◦

    本体 ◦ 20クラス ◦ Androidに非依存 ◦ 線形計画問題の最適解を求める
  62. constraint-layout • ConstraintLayout • Constraints, ConstraintSet • ConstraintHelper ◦ Barrier,

    Group, PlaceHolder • Guideline • BuildConfig
  63. constraint-layout-solver android.support.constraint.solver • ArrayLinkedVariable • ArrayRow • GoalRow • LinearSystem

    →線形計画問題の計算 • SolverVariable
  64. constraint-layout-solver android.support.constraint.solver.widgets • ConstraintWidgetContainer (≒ ConstraintLayout) • WidgetContainer (≒ ViewGroup)

    • ConstraintWidget (≒ View) • ConstarintAnchor (≒ Constraints + ConstraintSet)
  65. constraint-layout-solver android.support.constraint.solver.widgets • Helper = ConstrantHelper ◦ Barrier, Group •

    Guideline • Chain
  66. constraint-layout-solver android.support.constraint.solver.widgets • Optimizer

  67. ConstraintLayout#onMeasure

  68. onMeasure概要 1. ConstraintWidgetで同じView構造を作る 2. ConstraintWidgetに値を反映 3. すべての子Viewをmeasure (引数はViewGroup#getChildMeasureSpec) + ConstraintWidgetにwidth/heightを反映

    4. ※solver側でレイアウトする位置を確定する 5. solverからViewに値を戻す 6. setMeasuredDimensionsで自身のサイズを決定する
  69. レイアウト位置の確定 1. ConstraintWidgetContainter#layout 2. 自身のサイズに依存する子Viewがある場合 ◦ その子Viewを再度measure ◦ その結果とlayoutの結果が合わない場合 ▪

    再度ConstraintWidgetContainter#layout ▪ ConstraintLayoutがminWidth/Heightより小さい場合 • widgetのsizeを設定して 再度ConstraintWidgetContainter#layout
  70. ConstraintWidgetContainer #layout

  71. ConstraintWidgetContainer#layout 線形計画問題の最適解を求めて子ConstraintWidgetに反映 1. whileループ レイアウト位置を確定できるまで繰り返す 2. 自身の幅を設定 3. whileの中でいじった値を元に戻す ListDimensionBehaviors

    ≒ MeasureSpec.Mode 4. 子ConstraintWidgetのレイアウト位置を設定
  72. #layout whileループ 解決できるまで以下のループを繰り返す 1. Optimizerを使えるか試す 使えない場合、線形計画問題の最適解を求める 2. 一旦解決済みとする

  73. whileループ 3. ループが8回目未満 かつ   子Viewが制約のせいで小さくレイアウトされている かつ   ConstraintLayoutがWrapContent かつ

      子ViewのサイズがConstraintLayoutより大きい場合   ・子Viewのサイズ+WrapContentにして、もう1回ループ
  74. whileループ 4. minWidth/Heightより小さい場合  ・widthとheightを   minWidth/Heightの固定値にして、もう1回ループ

  75. whileループ 5. 3.と4.に当てはまらず、WrapContent、   子Viewの変更がないのに   前回よりもwidth/heightが大きかった場合   ・measuredTooSmallフラグを立てる  

    ・自身を前回のwidth/Heightの固定値にしてもう1回ループ ※恐らく、レイアウトの微調節をしていたら  自身のサイズが予定外に変更されてしまった場合。  元のサイズで再計算する。
  76. ConstraintLayout#onLayout

  77. onLayout概要 1. ConstraintHelper#updatePostLayout 2. すべての子ViewをConstraintWidgetの値通りにレイアウト ◦ measureでConstraintWidgetに値が設定されている 3. 以上!

  78. What is Optimizer?

  79. Optimizer • 前提:ConstraintLayoutは高速! • とはいえシンプルなレイアウトには過剰な計算 • シンプルなレイアウトはOptimizerで解決 ◦ 線形問題の解決を行わない

  80. Optimizerが発動する条件 • Viewの位置が簡単に決定できること ◦ 両端が親Viewと一緒+View幅が固定値 ◦ 片端が親Viewと一緒+View幅が固定値           or WrapConent •

    すべての辺が解決しないとOptimise失敗 • すべてのViewが解決しないとOptimise失敗
  81. ConstraintLayoutを 遅くさせないtips

  82. 遅くさせないtips • ConstraintLayoutをwrap_contentにしない • 幅が確定していないViewのpercentを使わない ◦ 依存するVIewののサイズが決まらないと percentを使ったViewサイズを決めれない • Optimizerを使えるレイアウトにする

  83. 以上!

  84. 付録 • RelativeLayoutの内部実装 https://speakerdeck.com/seto_hi/xiang-jie-relativelayoutfalsenei-bu-shi-zhuang • ConstraintLayoutの機能実現 https://speakerdeck.com/seto_hi/constraintlayoutfalseji-neng-falseshi-xian • ノハナ社のレイアウト戦略 https://speakerdeck.com/seto_hi/falsehanashe-falsereiautozhan-lue