Android常用Layoutソースコードまとめ-FrameLayout

44366 ワード

文書ディレクトリ
  • 前言
  • FrameLayout特徴
  • ソースコード探究
  • 構造関数
  • LayoutParams
  • 測定onMeasure
  • レイアウトonLayout
  • まとめ
  • 前言
    Androidの公式Layoutのソースコードを学ぶことで、AndroidのUIフレームワークシステムをよりよく理解し、内部の便利なパッケージされたAPI呼び出しを理解し、レイアウトの最適化やカスタムviewの実現などの仕事を行うのに役立ちます.ここでは学習結果をブログでまとめることで、将来忘れないように覚えやすくなります.
    このブログのソースコードはAndroid 8.1に基づいています
    FrameLayoutの特徴
    FrameLayoutはAndroid開発で最もよく使われるLayoutの1つで、サブviewたちが積層して上書きされ、後に追加されたサブviewが他のサブviewの上に上書きされるのが特徴です.
    ソース探究
    コンストラクタ
    FrameLayoutのコンストラクション関数は簡単で、FrameLayoutの属性measureAllChildrenを処理します.
    public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs,
            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    
        final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.FrameLayout, defStyleAttr, defStyleRes);
    
        if (a.getBoolean(R.styleable.FrameLayout_measureAllChildren, false)) {
            setMeasureAllChildren(true);
        }
    
        a.recycle();
    }
    

    measureAllChildrenプロパティは、幅の高さを測定するときにすべてのサブviewを計算するかどうかを設定します.デフォルトはfalseです.つまり、measureフェーズではGONEのサブviewは考慮されません.
    LayoutParams
    FrameLayoutで定義されている静的内部クラスLayoutParamsは、メンバーgravityを含むMarginLayoutParamsから継承されます.
    public int gravity = UNSPECIFIED_GRAVITY;
    

    したがって、サブviewで親レイアウトの位置合わせを設定することをサポートします.
    測定onMeasure
    FrameLayoutフレームレイアウトの特徴のため、LinearLayoutやRelativeLayoutのように重みや相対関係などが必要ではなく、サブviewを巡回してchild測定を順次呼び出し、自分のサイズを設定するだけです.ただし細分化が異なる場合もあり、FrameLayoutのMeasureSpecモードがEXACTLYの場合は、通常の流れで行えばよい.モードがAT_の場合MOSTの場合、FrameLayout自体の寸法が不明確であることを意味し、最大のchildの寸法に逆依存する必要があるため、遍歴しながら最大寸法を記録する必要がある.childのLayoutParamsが同時に存在する場合MATCH_PARENTは、childが親レイアウト寸法に依存していることを意味するため、FrameLayoutが自分の寸法を設定した後、もう一度測定する必要があります.
    FrameLayoutでのアスペクト測定は2つの部分に分かれている.上部は、サブviewの最大幅の高さを計算し、自身の幅の高さを設定します.下の部分は二次計算で上の部分では幅の高いサブviewの幅の高さを正確に計算できなかったが,このときchildに渡される測定規格はFrameLayout測定後の幅の高さに基づいて生成される.
    一、上部:最大幅と高さを計算する
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
    
    	//                   view( FrameLayout                    ,  true)。
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        // mMatchParentChildren   ArrayList  ,              view。
        mMatchParentChildren.clear();
    
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
    
    	//    view
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            //   child   GONE,    measureAllChildren  。
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
            	//   child  。
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                // child     ,  child      ,   margin ,       。
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                //   child     。(getMeasuredState   child     。    state  state,
                //      mMeasuredWidth   mMeasuredHeight     8 。      state ,
                //   state     int        , state         ,         int  )
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        //  measureMatchParentChildren true, child LayoutParams        ,
                        //      List , FrameLayout        ,       。
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }
    
        // Account for padding too
        //     padding
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
    
        // Check against our minimum height and width
        //         。         。
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
    
        // Check against our foreground's minimum height and width
        //       (        ),             。
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }
    
    	//   FrameLayout        
    	// (resolveSizeAndState                                    ,            MeasuredState)
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
    
    	//    ...
    }
    

    FrameLayout onMeasureメソッドでは,まずサブviewを巡回し,サブview測定を行い,最大サブviewのアスペクト値を比較するとともに,アスペクト値を正確に設定していないviewをリストキャッシュに加える.そして最大アスペクト値でFrameLayout自身のアスペクトを設定します.
    このうちpaddingの取得、Minimum幅の取得、サイズ値とステータスの組み合わせなどのAPIは測定操作に極めて便利であり、レイアウトをカスタマイズする際に柔軟な呼び出しを学ぶことができる.
    MeasuredState補足説明:mMeasuredWidthとmMeasuredHeightの2つのメンバー変数の上位8ビットはMeasuredStateを格納し、残りの24ビットは寸法値を格納します.MeasureSpec高2ビット格納モードに似ており、残りの30ビットはサイズ値を格納します.MeasuredStateの役割は、viewが自身のアスペクトを測定する場合、アスペクト値が親レイアウトで与えられた測定仕様の寸法を超える場合、stateをMEASURED_に設定できることです.STATE_TOO_SMALLは、親レイアウトにサイズ制限の緩和を要求します.
    二、下段:二次測定サブビューは、FrameLayoutによる自己幅の計算が完了した後、リスト内のサブビューを二次測定する.
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	//    ...
    
    	count = mMatchParentChildren.size();
        if (count > 1) {
        	//         MATCH_PARENT  view        。
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
    			//     child        
                final int childWidthMeasureSpec;
                if (lp.width == LayoutParams.MATCH_PARENT) {
                	//     MATCH_PARENT ,  FrameLayout        ,  padding margin ,
                	//        (   0, 0),         EXACTLY,         。
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                	//        px、dp  WRAP_CONTENT ,                。
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }
    
    			//           ,       。
                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
    
    			//   child    。
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }
    

    このセクションでは、キャッシュセット内のサブviewを巡り、新しい測定仕様を順次生成し、サブviewを呼び出して再測定します.ここでgetChildMeasureSpecメソッドは、親レイアウトから入力された測定仕様とpaddingおよびchildのLayoutParams値に基づいて新しい測定仕様を生成する責任を負います.
    getChildMeasureSpecメソッドの紹介:
    /**
     * @param spec           
     * @param padding padding margin  
     * @param childDimension LayoutParams  width、height  
     * @return       
     */
    int getChildMeasureSpec(int spec, int padding, int childDimension)
    

    この方法ではまず親レイアウト測定規則からspecModeとspecSizeを取り出し,specSizeからpaddingを減算してsizeを求める(0未満の場合は0をとる).その後、specMode、size、childDimensionの結合条件に基づいて、新しい測定規格を生成する.
    specMode⬇️\childDimension➡️
    MATCH_PARENT
    WRAP_CONTENT
    x px/dp
    EXACTLY
    EXACTLY+size
    AT_MOST+size
    EXACTLY+childDimension
    AT_MOST
    AT_MOST+size
    AT_MOST+size
    EXACTLY+childDimension
    UNSPECIFIED
    UNSPECIFIED+size
    UNSPECIFIED+size
    EXACTLY+childDimension
    注意:FrameLayoutは、少なくとも2つのLayoutParamsのwidthまたはheightがMATCHである場合のみです.PARENTのサブビューの場合,これらのサブビューはFrameLayout測定後のアスペクト値を用いて二次測定される.ビューが1つしかない場合は、FrameLayoutの親レイアウトで入力された測定仕様で、これを測定し、二次測定は行わない.
    レイアウト
    FrameLayoutのonLayoutでは親レイアウトによって与えられた上下左右に,サブviewのgravity,アスペクト,marginなどを組み合わせてサブviewをレイアウトする.
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }
    
    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();
    
    	//     padding  l、t、r、b
        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();
    
        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
    
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            //   GONE child
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    
    			//   child      
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();
    
                int childLeft;
                int childTop;
    
                int gravity = lp.gravity;
                if (gravity == -1) {
                	//       (   )
                    gravity = DEFAULT_CHILD_GRAVITY;
                }
    
    			//       (       )
                final int layoutDirection = getLayoutDirection();
                //         (  Gravity.START Gravity.END,         LEFT RIGHT)
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                //           
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
    
    			//            
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                    	//     
                    	//   child    (       margin,     margin    ,    ,     )
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                    	//     
                    	//           (  false,    )
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                    	//   、      
                        childLeft = parentLeft + lp.leftMargin;
                }
    
    			//           
                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                    	//     (         )
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }
    
    			//   child  
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }
    

    LayoutChildrenレイアウト方法では,ビット演算によりintから位置合わせ方式を取り出し,STARTとENDを設定するとRTL,LTRに応じて対応するLEFTとRIGHTに変換し,その後水平方向の位置合わせ方式と垂直方向の位置合わせ方式を順次判断する.childのwidth,heightが決定するので,水平方向はchildLeftを計算するだけで,垂直方向はchildTopを計算するだけでよい.
    まとめ
    FrameLayoutのコアロジックであるonMeasureとonLayoutメソッド.onMeasure法ではchild測定を配布しながらchildの最大幅と高さを比較し,childのあるLayoutParamsにMATCH_を設定した.PARENTは、親レイアウトの寸法に依存する必要があることを意味し、親レイアウトの測定モードが明確な寸法を示さない場合は、childを測定対象リストに追加します.childを巡回した後、自身の幅を設定します.その後、測定待ち判定リストにキャッシュされたchildの数が少なくとも2つであれば、FrameLayout自身のアスペクトを用いて測定仕様を再生成し、child二次測定を呼び出す.onLayoutメソッドではchildを巡回し,整列方式に従ってchildLeftとchildTopを順次変更し,最後にchildレイアウトを呼び出す.