Androidスクリーンアダプティブ

26024 ワード

プロジェクトのアドレス:https://github.com/hongyangAndroid/android-percent-support-extend
原文の出典:
http://blog.csdn.net/lmj623565791/article/details/46695347
少し前、Androidスクリーンの適合案を送ったことを覚えています.このブログはWebページの設計で適合案を引き出し、最終的な目的はコントロールのサイズをパーセンテージで制御することです.もちろん、いくつかの問題があります.例えば、
  • スクリーンサイズを考慮していない場合、予期せぬ状況が発生する可能性があります.
  • apkのサイズが増加します.

  • もちろんandroid-percent-supportというライブラリは、基本的に上記の問題を解決することができますが、少し興奮しているのではないでしょうか.ちょっと待って、このsupport-libについて説明します.
    このライブラリには次のものがあります.
  • の2つのレイアウトは、PercentRelativeLayoutPercentFrameLayoutの2つの容器類から受け継がれていることが名前でわかります.
  • でサポートされているプロパティは、
  • です.FrameLayoutRelativeLayoutlayout_widthPercentlayout_heightPercentlayout_marginPercentlayout_marginLeftPercentlayout_marginTopPercentlayout_marginRightPercentlayout_marginBottomPercent .
    サポート幅とmarginが表示されます.
    つまり,layout_marginStartPercent,layout_marginEndPercentを開発中にPercentRelativeLayout,PercentFrameLayoutに置き換えるだけでよい.
    簡単ではありませんが、Linearlayoutはないようです.Linearlayoutにはweight属性があると言う人がいますね.しかし、weightプロパティは一方向しかサポートできません.ああ、大丈夫です.ちょうどFrameLayoutをカスタマイズする機会を与えてくれました.
    では、本稿は3つの部分に分けられます.
  • RelativeLayoutPercentLinearLayoutの使用
  • 上記コントロールソースコード分析
  • カスタムPercentRelativeLayout
  • 二、使用
    使用については、実は簡単で、githubにも例があります.android-percent-support-lib-sampleです.簡単に説明します.
    まず覚えておいてください.gradle追加:
     compile 'com.android.support:percent:22.2.0'

    (一)PercentFrameLayout
    
    
    
        
    
        
    
    
        
    
    
    

    3つのTextView、とても簡単で、直接効果図を見ます:

    (二)PercentRelativeLayout
    
    
    
        
    
        
    
    
        
    
        
    
    
    

    OK、依然として効果図を直接見ています.

    使うことは何も言うことはありませんが、直感的に見てみましょう.
    三、ソース分析
    実際に考えてみると、Googleは私たちがよく知っていたRelativeLayoutとFrameLayoutの機能の拡張にすぎず、percent関連の属性をサポートしています.
    では、この拡張子を追加したらどうするか考えてみましょう.
  • LayoutParamsによりchild設定のpercent関連属性の値
  • を取得する
  • onMeasureの場合、childのwidth,heightの値を、取得したカスタム属性の値で計算し(eg:コンテナの幅*fraction)、計算後child.measure(w,h)、

  • OK、上の予想があったら、PercentFrameLayoutのソースコードを直接見ます.
    public class PercentFrameLayout extends FrameLayout {
        private final PercentLayoutHelper mHelper = new PercentLayoutHelper(this);
    
        //   ,      
    
        public PercentFrameLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
    
    
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new LayoutParams(getContext(), attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            if (mHelper.handleMeasuredStateTooSmall()) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
    
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            mHelper.restoreOriginalParams();
        }
    
        public static class LayoutParams extends FrameLayout.LayoutParams
                implements PercentLayoutHelper.PercentLayoutParams {
            private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;
    
            public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
                mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);
            }
            //       ...
    
            @Override
            public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() {
                return mPercentLayoutInfo;
            }
    
            @Override
            protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
                PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);
            }
        }
    }

    コードはかなり短くて、PercentLinearLayoutの中でまずgenerateLayoutParamsの方法を書き直したことを見ることができて、もちろん、いくつかの新しいlayout_をサポートしたためですプロパティは、対応するLayoutParamsを定義する必要があります.
    (一)percent関連属性の取得
    PercentFrameLayoutが見えますLayoutParamsは元のFrameLayoutでLayoutParamsをベースに、PercentLayoutHelperを実現しました.PercentLayoutParamsインタフェース.
    このインタフェースは簡単で、1つの方法しかありません.
    public interface PercentLayoutParams {
            PercentLayoutInfo getPercentLayoutInfo();
        }
     
      

    , , :return mPercentLayoutInfo;, mPercentLayoutInfo ?

    PercentFrameLayout.LayoutParams :

    public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
                mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);
            }
     
      

    , attrs getPercentLayoutInfo , , , , PercentLayoutInfo , 。

    public static PercentLayoutInfo getPercentLayoutInfo(Context context,
                AttributeSet attrs) {
            PercentLayoutInfo info = null;
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout);
            float value = array.getFraction(R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1,
                    -1f);
            if (value != -1f) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "percent width: " + value);
                }
                info = info != null ? info : new PercentLayoutInfo();
                info.widthPercent = value;
            }
            value = array.getFraction(R.styleable.PercentLayout_Layout_layout_heightPercent, 1, 1, -1f);
            if (value != -1f) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "percent height: " + value);
                }
                info = info != null ? info : new PercentLayoutInfo();
                info.heightPercent = value;
            }
            value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginPercent, 1, 1, -1f);
            if (value != -1f) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "percent margin: " + value);
                }
                info = info != null ? info : new PercentLayoutInfo();
                info.leftMarginPercent = value;
                info.topMarginPercent = value;
                info.rightMarginPercent = value;
                info.bottomMarginPercent = value;
            }
            value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginLeftPercent, 1, 1,
                    -1f);
            if (value != -1f) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "percent left margin: " + value);
                }
                info = info != null ? info : new PercentLayoutInfo();
                info.leftMarginPercent = value;
            }
            value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginTopPercent, 1, 1,
                    -1f);
            if (value != -1f) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "percent top margin: " + value);
                }
                info = info != null ? info : new PercentLayoutInfo();
                info.topMarginPercent = value;
            }
            value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginRightPercent, 1, 1,
                    -1f);
            if (value != -1f) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "percent right margin: " + value);
                }
                info = info != null ? info : new PercentLayoutInfo();
                info.rightMarginPercent = value;
            }
            value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginBottomPercent, 1, 1,
                    -1f);
            if (value != -1f) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "percent bottom margin: " + value);
                }
                info = info != null ? info : new PercentLayoutInfo();
                info.bottomMarginPercent = value;
            }
            value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginStartPercent, 1, 1,
                    -1f);
            if (value != -1f) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "percent start margin: " + value);
                }
                info = info != null ? info : new PercentLayoutInfo();
                info.startMarginPercent = value;
            }
            value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginEndPercent, 1, 1,
                    -1f);
            if (value != -1f) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "percent end margin: " + value);
                }
                info = info != null ? info : new PercentLayoutInfo();
                info.endMarginPercent = value;
            }
            array.recycle();
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "constructed: " + info);
            }
            return info;
        }

    たちの の と ているのではないでしょうか.すべての は にPercentLayoutInfoオブジェクトにカプセル されます.
    OK、ここで たちの を します.これらの があれば、onMeasureで うのではないでしょうか.
    ( )onMeasueでchildの を する
    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            if (mHelper.handleMeasuredStateTooSmall()) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
     
      

    onMeasure , mHelper , mHelper.adjustChildren 。

    /**
         * Iterates over children and changes their width and height to one calculated from percentage
         * values.
         * @param widthMeasureSpec Width MeasureSpec of the parent ViewGroup.
         * @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup.
         */
        public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) {
            //...
            int widthHint = View.MeasureSpec.getSize(widthMeasureSpec);
            int heightHint = View.MeasureSpec.getSize(heightMeasureSpec);
            for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
                View view = mHost.getChildAt(i);
                ViewGroup.LayoutParams params = view.getLayoutParams();
    
                if (params instanceof PercentLayoutParams) {
                    PercentLayoutInfo info =
                            ((PercentLayoutParams) params).getPercentLayoutInfo();
                    if (Log.isLoggable(TAG, Log.DEBUG)) {
                        Log.d(TAG, "using " + info);
                    }
                    if (info != null) {
                        if (params instanceof ViewGroup.MarginLayoutParams) {
                            info.fillMarginLayoutParams((ViewGroup.MarginLayoutParams) params,
                                    widthHint, heightHint);
                        } else {
                            info.fillLayoutParams(params, widthHint, heightHint);
                        }
                    }
                }
            }
        }

    この では、すべての を し、パーセントのプロパティで と さを することもできます.
    まずwidthHint、heightHintで の 、 さを し、その すべての を し、LayoutParamsがPercentLayoutParamsタイプかどうかを し、もしそうであればparams.getPercentLayoutInfoはinfoオブジェクトを り します.
    の でPercentLayoutInfoはpercent の を したことを えていますか.
    infoがnullでない 、marginを する があるかどうかを する.fillLayoutParamsメソッドを てみましょう(marginの も ています).
     /**
             * Fills {@code ViewGroup.LayoutParams} dimensions based on percentage values.
             */
            public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,
                    int heightHint) {
                // Preserve the original layout params, so we can restore them after the measure step.
                mPreservedParams.width = params.width;
                mPreservedParams.height = params.height;
    
                if (widthPercent >= 0) {
                    params.width = (int) (widthHint * widthPercent);
                }
                if (heightPercent >= 0) {
                    params.height = (int) (heightHint * heightPercent);
                }
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")");
                }
            }

    まず のwidthとheightを し、paramsのwidthとheightをリセットします.PercentFrameLayoutおよびPercentFrameLayout .
    これで、 は たちのパーセンテージ は わり、 にはパーセンテージのサポートが しましたが、Googleはいくつかの を しています.
    onMeasureメソッドに ります.
    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            if (mHelper.handleMeasuredStateTooSmall()) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
     
      

    mHelper.handleMeasuredStateTooSmall , , , MeasuredSize , 。 :

    public boolean handleMeasuredStateTooSmall() {
            boolean needsSecondMeasure = false;
            for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
                View view = mHost.getChildAt(i);
                ViewGroup.LayoutParams params = view.getLayoutParams();
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "should handle measured state too small " + view + " " + params);
                }
                if (params instanceof PercentLayoutParams) {
                    PercentLayoutInfo info =
                            ((PercentLayoutParams) params).getPercentLayoutInfo();
                    if (info != null) {
                        if (shouldHandleMeasuredWidthTooSmall(view, info)) {
                            needsSecondMeasure = true;
                            params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
                        }
                        if (shouldHandleMeasuredHeightTooSmall(view, info)) {
                            needsSecondMeasure = true;
                            params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
                        }
                    }
                }
            }
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "should trigger second measure pass: " + needsSecondMeasure);
            }
            return needsSecondMeasure;
        }

    まずすべての を り、 のlayoutparamsを り し、PercentLayoutParamsのインスタンスであればinfoを り します.infoがnullでない は び します(int) (widthHint * widthPercent) :
    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 >= 0 &&
                    info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT;
        }
     
      

    , measuredWidth measureHeight , layout_w/h WRAP_CONTENT , params.width / height= ViewGroup.LayoutParams.WRAP_CONTENT, 。

    ,onMeasure ~~~ , , ,but, onLayout , layout , onLayout :

    @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            mHelper.restoreOriginalParams();
        }
     
      

    mHelper.restoreOriginalParams

     /**
         * Iterates over children and restores their original dimensions that were changed for
         * percentage values. Calling this method only makes sense if you previously called
         * {@link PercentLayoutHelper#adjustChildren(int, int)}.
         */
        public void restoreOriginalParams() {
            for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
                View view = mHost.getChildAt(i);
                ViewGroup.LayoutParams params = view.getLayoutParams();
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "should restore " + view + " " + params);
                }
                if (params instanceof PercentLayoutParams) {
                    PercentLayoutInfo info =
                            ((PercentLayoutParams) params).getPercentLayoutInfo();
                    if (Log.isLoggable(TAG, Log.DEBUG)) {
                        Log.d(TAG, "using " + info);
                    }
                    if (info != null) {
                        if (params instanceof ViewGroup.MarginLayoutParams) {
                            info.restoreMarginLayoutParams((ViewGroup.MarginLayoutParams) params);
                        } else {
                            info.restoreLayoutParams(params);
                        }
                    }
                }
            }
        }

    ふふ、 は の を に し して、つまりonMeasureの の を えて、 が わった です.ここでは、レイアウトファイルの が0である に を します.リカバリは です.
    public void restoreLayoutParams(ViewGroup.LayoutParams params) {
                params.width = mPreservedParams.width;
                params.height = mPreservedParams.height;
            }
     
      

    ~ , Ctrl+F ‘mPreservedParams.width’ 。

    , , view v.getLayoutParams().width, 0。

    ~ 0 , ~~

    , , :

  • LayoutParamsにおける の
  • onMeasureでparamsを する.widthはパーセンテージ であり、
  • が さすぎてw/hがwrap_である content,
  • onLayoutでparamsをリセットする.w/hはレイアウトファイルに された
  • である.
    RelativeLayout、FrameLayoutの があって、 にもLinearLayoutのいくつかの がありません. いなことに、 たちのコアコードは(int) (heightHint * heightPercent);でパッケージされており、 でLinearLayoutを しても ではありません.
    、PercentLinearlayoutの
    weightがあると う もいるかもしれませんが、weightは と さを にパーセントで えることができますか?
    はい、コードは です. のようにします.
    ( )PercentLinearLayout
    package com.juliengenoud.percentsamples;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.support.percent.PercentLayoutHelper;
    import android.util.AttributeSet;
    import android.view.ViewGroup;
    import android.widget.LinearLayout;
    
    /**
     * Created by zhy on 15/6/30.
     */
    public class PercentLinearLayout extends LinearLayout
    {
    
        private PercentLayoutHelper mPercentLayoutHelper;
    
        public PercentLinearLayout(Context context, AttributeSet attrs)
        {
            super(context, attrs);
    
            mPercentLayoutHelper = new PercentLayoutHelper(this);
        }
    
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
        {
            mPercentLayoutHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            if (mPercentLayoutHelper.handleMeasuredStateTooSmall())
            {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b)
        {
            super.onLayout(changed, l, t, r, b);
            mPercentLayoutHelper.restoreOriginalParams();
        }
    
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs)
        {
            return new LayoutParams(getContext(), attrs);
        }
    
    
        public static class LayoutParams extends LinearLayout.LayoutParams
                implements PercentLayoutHelper.PercentLayoutParams
        {
            private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;
    
            public LayoutParams(Context c, AttributeSet attrs)
            {
                super(c, attrs);
                mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);
            }
    
            @Override
            public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo()
            {
                return mPercentLayoutInfo;
            }
    
            @Override
            protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr)
            {
                PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);
            }
    
            public LayoutParams(int width, int height) {
                super(width, height);
            }
    
    
            public LayoutParams(ViewGroup.LayoutParams source) {
                super(source);
            }
    
            public LayoutParams(MarginLayoutParams source) {
                super(source);
            }
    
        }
    
    }
    

    のソース を しく たら、このコードは されていないのではないでしょうか.
    ( )テストレイアウト
    
    
    
    
    
        
    
        
        
        
    
        
    
    
    

    に したいくつかのTextViewは,それぞれ / さをパーセンテージとし, を5%pとした.
    ( )

    OK、これで、ソース 、 PercentLinearLayoutを して わります.
    PercentLinearLayoutの のアドレス:クリックして
    ダウンロード:android-percent-support-extendにはandroid studio、eclipseプロジェクト、および のソースコードが まれています.