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

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

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

HiroYUKI Seto

February 09, 2018
Tweet

More Decks by HiroYUKI Seto

Other Decks in Technology

Transcript

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

    View Slide

  2. 自己紹介
    ● 瀬戸優之 Seto HiroYUKI @seto_hi
    ● Androidエンジニア & アプリデザイン
    ● 株式会社ノハナ
    ○ 2年連続DroidKaigiスポンサー!
    ○ 一組でも多くの家族に笑顔を届ける
    ○ 絶賛採用中
    ● Material Design大好き
    ● 好きなAPIはCanvas#saveとViewGroup#layout

    View Slide

  3. おしながき
    ● Viewってどうやってレイアウトされているの?
    ○ measure, layout
    ● 詳解FrameLayoutの内部実装
    ● 詳解LinearLayoutの内部実装
    ● 詳解ConstraintLayoutの内部実装
    ○ ConstraintLayoutを支える技術
    ○ Optimizer
    ○ 各機能の実現→付録にあります
    ● 付録

    View Slide

  4. 参考環境
    コードを読んだ環境
    ● FrameLayout, LinearLayout(, RelativeLayout)
    ○ Android 8.0、一部Android8.1
    ● CosntraintLayout
    ○ constraint-layout:1.1.0-beta3, beta4, beta5
    ○ 一部beta2

    View Slide

  5. Viewってどうやって
    レイアウトされているの?

    View Slide

  6. おしながき
    ● レイアウトの流れ
    ● measure
    ○ measure, onMeasure
    ○ MeasureSpec
    ○ measuredWidth, measuredHeight
    ● layout
    ○ layout, onLayout

    View Slide

  7. レイアウトの流れ
    1. レイアウトが要求される
    ○ View#requestLayout
    2. Viewのサイズを計算する
    ○ measure, onMeasure
    3. Viewのレイアウト
    ○ measureしたサイズを参考にする
    ○ layout, onLayout

    View Slide

  8. View
    View
    Group
    View
    Gruop
    View
    Group
    View View
    request
     Layout
    Handler
    Traversal
    Runnable
    View
    Root
    Impl
    request
     Layout
    request
     Layout

    View Slide

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

    View Slide

  10. View
    View
    Group
    View
    Gruop
    View
    Group
    View View
    layout
    View
    Root
    Impl
    layout
    layout
    layout
    layout
    layout

    View Slide

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

    View Slide

  12. Viewのmeasure

    View Slide

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

    View Slide

  14. onMeasure
    ● Viewの幅と高さを計測
    ○ ViewGroupは子Viewをmeasureする
    ● 自身のサイズも設定
    ○ setMeasuredDimensionsで
    measuredWidth/Heightを設定
    ■ 呼ばないと例外

    View Slide

  15. measuredWidth, measuredHeight
    ● onLayoutで使う
    ● setMeasureDimensionsで設定した値※
    ● View#getWidth/getHeightはlayout後の値
    ○ View.mLeft-View.mRight
    ● layout以降は使ってはならない
    ○ measuredWidth/Height通りにlayoutされるとは限らない
    ○ layout以降はgetWidth/Heightを使う
    ※ 厳密にはMeasureSpec

    View Slide

  16. MeasureSpec
    ● onMesureの引数
    ● measureの条件(Mode)とSizeをintで表現したもの
    ○ Modeが上位2byte、Sizeが下位30bit
    Mode 意味
    UNSPECIFIED Viewの好きなサイズにして良い
    EXACTLY 指定されたサイズにすべき
    AT_MOST Viewの好きなサイズにして良い
    でも指定されたサイズは超さないように

    View Slide

  17. Viewのlayout

    View Slide

  18. layoutとonLayout
    ● layoutがonLayoutを呼ぶ
    ● ViewGroup#layoutはfinalメソッド
    ● layoutが呼び出す用
    ● onLayoutがoverrideする用
    ● 各ViewGroupがonLayoutをoverrideする
    ○ ViewはonLayoutをoverrideする必要はない

    View Slide

  19. ViewGroup#onLayout
    ● 子Viewのレイアウトを行う
    ○ ViewGroupに対する相対座標
    ● measureした結果を使う
    ● measuredWidth/Height通りにlayoutすべき
    ○ 例:TextViewはmeasuredWidth/Height通りの
    文字改行がされている

    View Slide

  20. 詳解
    FrameLayoutの
    内部実装

    View Slide

  21. FrameLayout#onMeasure

    View Slide

  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の幅

    View Slide

  23. FrameLayout#onMeasure
    FrameLayout
    View(w:match_parent,
      h:warp_conten)
    View#measuredWidth
    MeasureSpecのsize

    View Slide

  24. FrameLayout#onLayout

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. 詳解
    LinearLayoutの
    内部実装

    View Slide

  29. LinearLayout#onMeasure

    View Slide

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

    View Slide

  31. Weight Tips
    ● 0dp + weightがなぜ早いか
    ○ LinearLayoutのMesureSpecがEXACTLYの場合
    0dp + weightのViewは
    1回目のmeasureをskipするため

    View Slide

  32. LinearLayout#onLayout

    View Slide

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

    View Slide

  34. 詳解
    ConstraintLayoutの
    内部実装

    View Slide

  35. ところで

    View Slide

  36. ところで

    View Slide

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

    View Slide

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

    View Slide

  39. 線形計画問題

    View Slide

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

    View Slide

  41. 線形計画問題
    例:
    原料A 1gと原料B 2gで60円の商品Xを作れます
    原料A 3gと原料B 1gで40円の商品Yを作れます
    原料Aが90g、原料Bが50gあるとき
    商品XとYをそれぞれいくつ作れば
    売り上げが最大になるでしょう?

    View Slide

  42. 線形計画問題
    商品Xをx、商品Yをyとする
    x + 3y < 90
    2x + y < 50
    x ≧ 0, y ≧ 0
    のとき
    60x + 40yの最大値を求める

    View Slide


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

    View Slide


  44. 線形計画問題
    x + 3y < 90
    2x + y < 50
    (12, 26)
    40x + 60y = 2040

    View Slide

  45. 完全に理解しましたね? 

    View Slide

  46. View Slide

  47. シンプレックス法

    View Slide

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

    View Slide


  49. シンプレックス法
    x + 3y < 90
    2x + y < 50
    (12, 26)
    40x + 60y = 2040
    ①初期基底解(BFS)

    View Slide

  50. シンプレックス法
    ● シンプレックス法
    ● 二段階シンプレックス法
    ○ 人工変数と人工的な目的関数を加えることで
    初期基底解が簡単に求まらない場合に対応
    ● 双対シンプレックス法
    ○ 双対定理を利用し
    双対問題を解くことで主問題も解く

    View Slide

  51. 完全に理解しましたね? 

    View Slide

  52. View Slide

  53. Cassowary

    View Slide

  54. Cassowary
    ● ヒクイドリ - 世界一危険な鳥
    ● 線形方程式、線形不等式の
    制約充足問題(constraint solving problem)の解法
    ○ 2段階+双対シンプレックス法を使う
    ● アプリのUI用に最適化されている
    ○ 高速
    ○ 省メモリ
    ○ 宣言的
    ○ 差分更新可能

    View Slide

  55. Cassowary
    ● 2001年に開発された
    ○ 最初はCSSのレイアウト拡張として
    ● 多くの言語に移植されている
    ○ Smalltalk, C++, Java, JavaScript, Dart, Python etc..
    ● 2011年からMac(OS X)やiOS(6~)で使われている
    ○ AutoLayout
    ● ConstraintLayoutでも採用!

    View Slide

  56. 完全に理解しましたね? 

    View Slide

  57. View Slide

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

    View Slide

  59. ConstraintLayoutを支える技術
    名前だけでも覚えて帰ってね
    ● UIの解決 ≒ 線形計画問題
    ● 線形計画問題の解法
    ○ シンプレックス法(2段階、双対)
    ○ Cassowary
    ● Advanced ConstraintLayout
    https://academy.realm.io/posts/360-andev-2017-nicolas-ro
    ard-advanced-constraintlayout/

    View Slide

  60. ConstraintLayoutの構成

    View Slide

  61. ConstraintLayoutの構成
    ● constraint-layout
    ○ 9クラス
    ○ Viewの簡単な実装がメイン
    ● constraint-layout-solver
    ○ 本体
    ○ 20クラス
    ○ Androidに非依存
    ○ 線形計画問題の最適解を求める

    View Slide

  62. constraint-layout
    ● ConstraintLayout
    ● Constraints, ConstraintSet
    ● ConstraintHelper
    ○ Barrier, Group, PlaceHolder
    ● Guideline
    ● BuildConfig

    View Slide

  63. constraint-layout-solver
    android.support.constraint.solver
    ● ArrayLinkedVariable
    ● ArrayRow
    ● GoalRow
    ● LinearSystem →線形計画問題の計算
    ● SolverVariable

    View Slide

  64. constraint-layout-solver
    android.support.constraint.solver.widgets
    ● ConstraintWidgetContainer (≒ ConstraintLayout)
    ● WidgetContainer (≒ ViewGroup)
    ● ConstraintWidget (≒ View)
    ● ConstarintAnchor (≒ Constraints + ConstraintSet)

    View Slide

  65. constraint-layout-solver
    android.support.constraint.solver.widgets
    ● Helper = ConstrantHelper
    ○ Barrier, Group
    ● Guideline
    ● Chain

    View Slide

  66. constraint-layout-solver
    android.support.constraint.solver.widgets
    ● Optimizer

    View Slide

  67. ConstraintLayout#onMeasure

    View Slide

  68. onMeasure概要
    1. ConstraintWidgetで同じView構造を作る
    2. ConstraintWidgetに値を反映
    3. すべての子Viewをmeasure
    (引数はViewGroup#getChildMeasureSpec)
    + ConstraintWidgetにwidth/heightを反映
    4. ※solver側でレイアウトする位置を確定する
    5. solverからViewに値を戻す
    6. setMeasuredDimensionsで自身のサイズを決定する

    View Slide

  69. レイアウト位置の確定
    1. ConstraintWidgetContainter#layout
    2. 自身のサイズに依存する子Viewがある場合
    ○ その子Viewを再度measure
    ○ その結果とlayoutの結果が合わない場合
    ■ 再度ConstraintWidgetContainter#layout
    ■ ConstraintLayoutがminWidth/Heightより小さい場合

    widgetのsizeを設定して
    再度ConstraintWidgetContainter#layout

    View Slide

  70. ConstraintWidgetContainer
    #layout

    View Slide

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

    View Slide

  72. #layout whileループ
    解決できるまで以下のループを繰り返す
    1. Optimizerを使えるか試す
    使えない場合、線形計画問題の最適解を求める
    2. 一旦解決済みとする

    View Slide

  73. whileループ
    3. ループが8回目未満 かつ
      子Viewが制約のせいで小さくレイアウトされている かつ
      ConstraintLayoutがWrapContent かつ
      子ViewのサイズがConstraintLayoutより大きい場合
      ・子Viewのサイズ+WrapContentにして、もう1回ループ

    View Slide

  74. whileループ
    4. minWidth/Heightより小さい場合
     ・widthとheightを
      minWidth/Heightの固定値にして、もう1回ループ

    View Slide

  75. whileループ
    5. 3.と4.に当てはまらず、WrapContent、
      子Viewの変更がないのに
      前回よりもwidth/heightが大きかった場合
      ・measuredTooSmallフラグを立てる
      ・自身を前回のwidth/Heightの固定値にしてもう1回ループ
    ※恐らく、レイアウトの微調節をしていたら
     自身のサイズが予定外に変更されてしまった場合。
     元のサイズで再計算する。

    View Slide

  76. ConstraintLayout#onLayout

    View Slide

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

    View Slide

  78. What is Optimizer?

    View Slide

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

    View Slide

  80. Optimizerが発動する条件
    ● Viewの位置が簡単に決定できること
    ○ 両端が親Viewと一緒+View幅が固定値
    ○ 片端が親Viewと一緒+View幅が固定値
              or WrapConent
    ● すべての辺が解決しないとOptimise失敗
    ● すべてのViewが解決しないとOptimise失敗

    View Slide

  81. ConstraintLayoutを
    遅くさせないtips

    View Slide

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

    View Slide

  83. 以上!

    View Slide

  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

    View Slide