PercentRelativeLayout 源码解析

ÎÄÕ³ö´¦£ºhttp://www.jianshu.com/p/05bc040841cd 

com.android.support:percent:22.2.0 :google ³öÁ˸ö°üÀ´Ö§³Ö°´¸¸¿Ø¼þ°Ù·Ö±ÈµÄÏÔʾ.
ÎÒÃÇGradle µ¼Èë

compile 'com.android.support:percent:22.2.0'

¿´Ò»ÏÂ,ÆäʵÕâ¸ö¿âÌرð¼òµ¥Ö»ÓÐ3¸ö class Àà

declare-styleable ÓÐ:

1 ¼òÊö

ÎÒÃÇÄÜÓõĿؼþ?

ÓÐ PrecentFrameLayout ºÍ PrecentRelativeLayout
´ÓÕâ¸öÃû³ÆÉÏÃæÎÒÃÇ¿ÉÒÔºÜÇå³þµÄÖªµÀPrecentFrameLayout ¼Ì³Ð FrameLayout PrecentRelativeLayout ¼Ì³Ð RelativeLayout µÄ.

PrecentLayoutHelper ÊÇʲô?

ÆäʵÄã¿ÉÒÔ¿´³öPrecentFrameLayout ºÍPrecentRelativeLayout ÖÐʵÏÖЧ¹ûÀàËÆ,ÓÉ´Ë¿ÉÒÔ¿´³öÁ½¸öÀàÓÐ×ÅÌ«¶àÏàËƵĴúÂë,ËùÒÔ½«²Ù×÷·ÅÔÚHelperÖÐ,²»½öʹÂß¼­¸üÇåÎú,ͬʱ¿ÉÒÔ½øÐÐͳһµÄά»¤,Ò»´ÎÐÞ¸Ä.Á½±ßÉúЧ.

2 Ô´Âë·ÖÎö

ÎÒÃDz鿴PercentRelativeLayout Õâ¸öÀà.ÎÒÃÇÖ±½ÓʹÓà as µÄ ctrl + Êó±ê×ó¼ü,Ö±½Ó½øÈëÔ´ÂëÈ¥²é¿´ (as Õæ°ô)

PercentRelativeLayout.class

public class PercentRelativeLayout extends RelativeLayout {
    private final PercentLayoutHelper mHelper = new PercentLayoutHelper(this);
    public PercentRelativeLayout(Context context) {
        super(context);
    }
    public PercentRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public PercentRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    public PercentRelativeLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new PercentRelativeLayout.LayoutParams(this.getContext(), attrs);
    }
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //ÐÞ¸ÄchildView µÄ LayoutParams
        this.mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(this.mHelper.handleMeasuredStateTooSmall()) {
            //that indicates the measured size is smaller that the space the view would like to have.
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        //»¹Ô­ÉÏÒ»¸ö ×ÓÀàµÄµÄ LayoutParams?  Õâ±ßÎÒÓиöÎÊÌâ?»áÔÚÏÂÃæÌá³ö.
        this.mHelper.restoreOriginalParams();
    }
    public static class LayoutParams extends android.widget.RelativeLayout.LayoutParams implements PercentLayoutParams {
        private PercentLayoutInfo mPercentLayoutInfo;
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            //Ö÷ÒªÊÇ»ñÈ¡ÎÒÃÇ֮ǰÔÚ xml ÉèÖõıÈÀý
            this.mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);
        }
        public LayoutParams(int width, int height) {
            super(width, height);
        }
        public LayoutParams(android.view.ViewGroup.LayoutParams source) {
            super(source);
        }
        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }
        public PercentLayoutInfo getPercentLayoutInfo() {
             //ʵÏÖ½Ó¿Ú·µ»ØmPercentLayoutInfo 
            return this.mPercentLayoutInfo;
        }
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);
        }
    }
}

Ö÷ÒªµÄµÄÄÚÈÝ:

¸´Ð´ÁËgenerateLayoutParams ·½·¨,Õâ¸ö·½·¨ÏµÍ³×Ô¶¯µ÷ÓÃ.ÔÚÌí¼Ó childView µÄµ÷ÓÃ.ͬʱ
Õâ¸ö·½·¨ÊÇ¿ÉÒÔ»ñÈ¡µ½×ÓÀàµÄAttributeSet, ¶øÎÒÃÇÉèÖõıÈÀý¾ÍÔÚAttributeSetÖÐ.
ÎÒÃÇ¿ÉÒÔ¿´µ½generateLayoutParams ·½·¨·µ»ØÁËÒ»¸öеÄLayoutParams ,Õâ¸öLayoutParams ¼Ì³ÐÁËandroid.widget.RelativeLayout.LayoutParams ʵÏÖÁËPercentLayoutParamsÕâ¸ö½Ó¿Ú.

ΪʲôҪʵÏÖÕâ¸ö½Ó¿Ú?

ΪÁ˶à̬,Ëü½«ÎÒÃÇÉèÖõÄÌõ¼þ·ÅÔÚmPercentLayoutInfoÕâ¸öÀàÖÐ,ͨ¹ýÒ»¸ö½Ó¿Ú·µ»Ø¸øÎÒÃÇ.ÒòΪÎÒÃÇÓжà¸ö²»Í¬ÀàÐ͵ĵÄLayoutParams,ͨ¹ýÕâ¸ö½Ó¿Ú¿ÉÒÔʹÓöà̬,ͬʱÓÐÀûÓÚÎÒÃÇ»ùÓÚ Help ½øÐÐÀ©Õ¹.

PercentLayoutHelper.class

Õâ¸ö·½·¨Óе㳤.²»Ì«¿ÉÄÜ°ÑËùÓеĴúÂëÌù³öÀ´,ÎÒÌùÖØÒªµÄ.

 public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) {
        if(Log.isLoggable("PercentLayout", 3)) {
            Log.d("PercentLayout", "adjustChildren: " + this.mHost + " widthMeasureSpec: " + MeasureSpec.toString(widthMeasureSpec) + " heightMeasureSpec: " + MeasureSpec.toString(heightMeasureSpec));
        }
       //»ñÈ¡×ÔÉíµÄ¿í¶ÈµÄ´óС
        int widthHint = MeasureSpec.getSize(widthMeasureSpec);
       //»ñÈ¡×ÔÉí¸ß¶ÈµÄ´óС
        int heightHint = MeasureSpec.getSize(heightMeasureSpec);
        int i = 0;
        for(int N = this.mHost.getChildCount(); i < N; ++i) {
            View view = this.mHost.getChildAt(i);
            LayoutParams params = view.getLayoutParams();
            if(Log.isLoggable("PercentLayout", 3)) {
                Log.d("PercentLayout", "should adjust " + view + " " + params);
            }
            if(params instanceof PercentLayoutHelper.PercentLayoutParams) {
                //»ñÈ¡µ½PercentLayoutInfo ÎÒÃÇÉèÖõıÈÀý¾ÍÔÚÀïÃæ.
                PercentLayoutHelper.PercentLayoutInfo info = ((PercentLayoutHelper.PercentLayoutParams)params).getPercentLayoutInfo();
                if(Log.isLoggable("PercentLayout", 3)) {
                    Log.d("PercentLayout", "using " + info);
                }
                if(info != null) {
                    //Õâ±äΪʲôҪÅжÏLayoutParam µÄÀàÐÍ?
                   //·½·¨ÄÜ×ßµ½ÕâÀïµÄʱºòÒѾ­ËµÃ÷ÁËinfo != null ,¾ÍÊÇ˵ params Õâ¸öʵÏÖÁËPercentLayoutParamsÕâ¸ö½Ó¿Ú,¶øÎÒÃǵĠPercentLayoutParams µÄʵÏÖÀ඼ÊÇ MarginLayoutParams µÄ×ÓÀà.ÄÇÎÒÃÇΪʲô»¹Òª×öÕâ¸öÅжÏÄØ?ΪÁËÀ©Õ¹.Èç¹ûÄãµÄҪʵÏֵIJ»ÊÇ»ù±¾²¼¾ÖÀàÐÍ,¶øÊÇ ViewGrop,ËüµÄ layoutParam ÊÇViewGroup.LayoutParams,¶øÄãҪʵÏÖÕâ¸öÕâ¸öЧ¹û.ÄÇôÄãÈç¹ûÖ±½ÓǿתMarginLayoutParams ÊÇ»á±ÀµôµÄ.
                    if(params instanceof MarginLayoutParams) {
                        info.fillMarginLayoutParams((MarginLayoutParams)params, widthHint, heightHint);
                    } else {
                        info.fillLayoutParams(params, widthHint, heightHint);
                    }
                }
            }
        }
    }

adjustChildren ·½·¨:

ͨ¹ýÐÞ¸ÄfillMarginLayoutParams ºÍ fillLayoutParams ÐÞ¸ÄchildView µÄ LayoutParam µÄ¿í¸ß,±ß¾à,²¢½«ÐÞ¸ÄÇ°µÄÖµ±£´æÔÚPercentLayoutInfo µÄmPreservedParamsÖÐ.

    public boolean handleMeasuredStateTooSmall() {
        boolean needsSecondMeasure = false;
        int i = 0;
        for(int N = this.mHost.getChildCount(); i < N; ++i) {
            View view = this.mHost.getChildAt(i);
            LayoutParams params = view.getLayoutParams();
            if(Log.isLoggable("PercentLayout", 3)) {
                Log.d("PercentLayout", "should handle measured state too small " + view + " " + params);
            }
            if(params instanceof PercentLayoutHelper.PercentLayoutParams) {
                PercentLayoutHelper.PercentLayoutInfo info = ((PercentLayoutHelper.PercentLayoutParams)params).getPercentLayoutInfo();
                if(info != null) {
                    if(shouldHandleMeasuredWidthTooSmall(view, info)) {
                        needsSecondMeasure = true;
                        params.width = -2;
                    }
                    if(shouldHandleMeasuredHeightTooSmall(view, info)) {
                        needsSecondMeasure = true;
                        params.height = -2;
                    }
                }
            }
        }
        if(Log.isLoggable("PercentLayout", 3)) {
            Log.d("PercentLayout", "should trigger second measure pass: " + needsSecondMeasure);
        }
        return needsSecondMeasure;
    }
   private static boolean shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info)
    {
        int state = ViewCompat.getMeasuredWidthAndState(view) & ViewCompat.MEASURED_STATE_MASK;
        return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.widthPercent.percent >= 0 &&
                info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT;
    }
  private static boolean shouldHandleMeasuredHeightTooSmall(View view, PercentLayoutInfo info)
    {
        int state = ViewCompat.getMeasuredHeightAndState(view) & ViewCompat.MEASURED_STATE_MASK;
        return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.heightPercent.percent >= 0 &&
                info.mPreservedParams.height == ViewGroup.LayoutParams.WRAP_CONTENT;
    }

handleMeasuredStateTooSmall ·½·¨ :

Õâ¸ö·½·¨ÊǼì²é childView ´óС¼ÆËãÊÇ·ñ²»ºÏÀí.
Õâ±ßViewCompat.getMeasuredWidthAndState(view)·µ»ØÖµÓÐ3¸ö

ÎÒµÄÒÉ»ó:

ÎÊÌâ1:

ÎÒµÄÎÊÌâÊÇÓÃ3¸ö·µ»ØÖµ·Ö±ðÈ¥¸úMEASURED_STATE_MASKÏàÓë·¢ÏÖÖ»ÓÐMEASURED_STATE_TOO_SMALL ¿ÉÒÔʹ state == ViewCompat.MEASURED_STATE_TOO_SMALL,
ÄÇËüΪʲô»¹Òª¸úMEASURED_STATE_TOO_SMALL ÏàÓë?

ÎÊÌâ2:

ÔÚPercentRelativeLayoutÖеÄonLayout Öе÷ÓÃÁË this.mHelper.restoreOriginalParams()·½·¨,´Ó·½·¨ÃûºÍÌøת½øÈ¥¿´Ô´ÂëÄã·¢ÏÖËüÊǽ«Ö®Ç°µÄ±£´æÔÚPercentLayoutInfo µÄmPreservedParams ÖØи³Öµ¸ø childView µÄÏë·¨.µ«ÊÇÕæµÄÓÐÓÃÂð,ÒòΪµ±ËüÁ¬Ðø2´ÎµÄµ÷ÓÃonMeasure()·½·¨µÄʱºò,PercentLayoutInfo µÄmPreservedParams »á±»Ìæ»»µô,µ¼ÖÂPercentLayoutInfo µÄmPreservedParamsºÍ childView ÖÐµÄ layoutParamÊÇÒ»ÑùµÄ.ËùÒÔÕâ¸öÕæµÄÓÐÓÃÂð.

onMeasure Ϊʲô»áµ÷Óöà´Î?
¿´ How Android Draws Views »ò ÖÐÎÄ°æ