天天看点

android-percent-support-lib-sample

          我们做Android开发,经常会遇到适配的问题!同一个界面,在不同的分辨率的手机上会有不同的显示效果。

为了解决这个问题,我们在标明控件的高宽的时候。通常会用到dp作为单位。dp*density(像素密度)/160=px;这就保证了

用dp确定高宽的控件在不同的分辨率的手机上我固定的大小。在合适大小的手机屏幕上,当然没问题。但是如果手机屏幕过

大或者过小都会有些不合适。现在github上发现一个开源项目:android-percent-support-lib-sample。定义了两个新的控件:

PercentRelativeLayout,和PercentFrameLayout。能够在通过设置子控件的高宽的百分比。来使得子控件在不同分辨率的手机上保持

相对大小。提供了一个很好的解决适配问题的思路!

          下面介绍一下他们的用法:

<com.wang.percentlayout.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:wang="http://schemas.android.com/apk/res/com.wang.percentlayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wang.percentlayout.MainActivity" >
    
    
    <TextView 
        android:id="@+id/tv_left"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:background="#44ff0000"
        android:text="width:30%,height:30%"
        android:gravity="center"
        wang:layout_widthPercent="30%"
        wang:layout_heightPercent="30%"/>
    
    <TextView 
        android:id="@+id/tv_below"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_below="@id/tv_left"
        android:layout_alignParentLeft="true"
        android:background="#440ff000"
        android:text="width:40%,height:50%"
        android:gravity="center"
        wang:layout_widthPercent="40%"
        wang:layout_heightPercent="50%"/>
    
     <TextView 
        android:id="@+id/tv_right"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:background="#4400ff00"
        android:gravity="center"
        android:text="width:70%,height:20%"
        wang:layout_widthPercent="70%"
        wang:layout_heightPercent="20%"/>
     
     <TextView 
        android:id="@+id/tv_below_right"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_below="@id/tv_right"
        android:layout_alignParentRight="true"
        android:background="#44000ff0"
        android:text="width:20%,height:60%"
        android:gravity="center"
        wang:layout_widthPercent="20%"
        wang:layout_heightPercent="60%"/>
     
      <TextView 
        android:id="@+id/tv_bottom"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:background="#770000ff"
        android:gravity="center"
        android:text="width:100%,height:20%"
        wang:layout_widthPercent="100%"
        wang:layout_heightPercent="20%"/>

</com.wang.percentlayout.PercentRelativeLayout>
           

效果图

android-percent-support-lib-sample

那么PercentRelativieLayout相对RelativeLayout又多做了哪些工作呢?

我们看一下源码:

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wang.percentlayout;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.RelativeLayout;

/**
 * Subclass of {@link RelativeLayout} that supports percentage based dimensions and
 * margins.
 *
 * You can specify dimension or a margin of child by using attributes with "Percent" suffix. Follow
 * this example:
 *
 * <pre class="prettyprint">
 * <android.support.percent.PercentRelativeLayout
 *         xmlns:android="http://schemas.android.com/apk/res/android"
 *         xmlns:app="http://schemas.android.com/apk/res-auto"
 *         android:layout_width="match_parent"
 *         android:layout_height="match_parent"/>
 *     <ImageView
 *         app:layout_widthPercent="50%"
 *         app:layout_heightPercent="50%"
 *         app:layout_marginTopPercent="25%"
 *         app:layout_marginLeftPercent="25%"/>
 * </android.support.percent.PercentFrameLayout/>
 * </pre>
 *
 * The attributes that you can use are:
 * <ul>
 *     <li>{@code layout_widthPercent}
 *     <li>{@code layout_heightPercent}
 *     <li>{@code layout_marginPercent}
 *     <li>{@code layout_marginLeftPercent}
 *     <li>{@code layout_marginTopPercent}
 *     <li>{@code layout_marginRightPercent}
 *     <li>{@code layout_marginBottomPercent}
 *     <li>{@code layout_marginStartPercent}
 *     <li>{@code layout_marginEndPercent}
 * </ul>
 *
 * It is not necessary to specify {@code layout_width/height} if you specify {@code
 * layout_widthPercent.} However, if you want the view to be able to take up more space than what
 * percentage value permits, you can add {@code layout_width/height="wrap_content"}. In that case
 * if the percentage size is too small for the View's content, it will be resized using
 * {@code wrap_content} rule.
 */
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);
    }

    @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 RelativeLayout.LayoutParams
            implements PercentLayoutHelper.PercentLayoutParams {
        private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;

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

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }

        @Override
        public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() {
            return mPercentLayoutInfo;
        }

        @Override
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);
        }
    }
}
           

我们可以看到,这里先是自定义一个新的LayoutParams(里面定义了新的属性),然后重写了generateLayoutParams方法。

在自定义的LayoutParams中实现了接口PercentLayoutHelper.PercentLayoutParams;

public interface PercentLayoutParams
    {
        PercentLayoutInfo getPercentLayoutInfo();
    }
           

重写了getPercentLayoutInfo()方法,返回mPercentLayoutInfo:

@Override
        public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() {
            return mPercentLayoutInfo;
        }
           

而,mPercentLayoutInfo中包含了自定义的百分比属性(实在构造方法中取到的):

public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);
        }
           
public static PercentLayoutInfo getPercentLayoutInfo(Context context,
                                                         AttributeSet attrs)
    {
        PercentLayoutInfo info = null;
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout);

        int index = R.styleable.PercentLayout_Layout_layout_widthPercent;
        String sizeStr = array.getString(index);
        PercentLayoutInfo.PercentVal percentVal = getPercentVal(sizeStr, true);
        if (percentVal != null)
        {
            if (Log.isLoggable(TAG, Log.VERBOSE))
            {
                Log.v(TAG, "percent width: " + percentVal.percent);
            }
            info = checkForInfoExists(info);
            info.widthPercent = percentVal;
        }
        sizeStr = array.getString(R.styleable.PercentLayout_Layout_layout_heightPercent);
        percentVal = getPercentVal(sizeStr, false);

        if (sizeStr != null)
        {
            if (Log.isLoggable(TAG, Log.VERBOSE))
            {
                Log.v(TAG, "percent height: " + percentVal.percent);
            }
            info = checkForInfoExists(info);
            info.heightPercent = percentVal;
        }

        // value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginPercent, 1, 1, -1f);
        sizeStr = array.getString(R.styleable.PercentLayout_Layout_layout_marginPercent);
        // just for judge
        percentVal = getPercentVal(sizeStr, false);

        if (percentVal != null)
        {
            if (Log.isLoggable(TAG, Log.VERBOSE))
            {
                Log.v(TAG, "percent margin: " + percentVal.percent);
            }
            info = checkForInfoExists(info);
            info.leftMarginPercent = getPercentVal(sizeStr, true);
            info.topMarginPercent = getPercentVal(sizeStr, false);
            info.rightMarginPercent = getPercentVal(sizeStr, true);
            info.bottomMarginPercent = getPercentVal(sizeStr, false);
        }
        //value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginLeftPercent, 1, 1,
        //      -1f);
        sizeStr = array.getString(R.styleable.PercentLayout_Layout_layout_marginLeftPercent);
        percentVal = getPercentVal(sizeStr, true);
        if (percentVal != null)
        {
            if (Log.isLoggable(TAG, Log.VERBOSE))
            {
                Log.v(TAG, "percent left margin: " + percentVal.percent);
            }
            info = checkForInfoExists(info);
            info.leftMarginPercent = percentVal;
        }

        //  value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginTopPercent, 1, 1,
        //        -1f);
        sizeStr = array.getString(R.styleable.PercentLayout_Layout_layout_marginTopPercent);
        percentVal = getPercentVal(sizeStr, false);
        if (percentVal != null)
        {
            if (Log.isLoggable(TAG, Log.VERBOSE))
            {
                Log.v(TAG, "percent top margin: " + percentVal.percent);
            }
            info = checkForInfoExists(info);
            info.topMarginPercent = percentVal;
        }
        // value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginRightPercent, 1, 1,
        //       -1f);
        sizeStr = array.getString(R.styleable.PercentLayout_Layout_layout_marginRightPercent);
        percentVal = getPercentVal(sizeStr, true);
        if (percentVal != null)
        {
            if (Log.isLoggable(TAG, Log.VERBOSE))
            {
                Log.v(TAG, "percent right margin: " + percentVal.percent);
            }
            info = checkForInfoExists(info);
            info.rightMarginPercent = percentVal;
        }
        //value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginBottomPercent, 1, 1,
        //  -1f);
        sizeStr = array.getString(R.styleable.PercentLayout_Layout_layout_marginBottomPercent);
        percentVal = getPercentVal(sizeStr, false);
        if (percentVal != null)
        {
            if (Log.isLoggable(TAG, Log.VERBOSE))
            {
                Log.v(TAG, "percent bottom margin: " + percentVal.percent);
            }
            info = checkForInfoExists(info);
            info.bottomMarginPercent = percentVal;
        }
        // value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginStartPercent, 1, 1,
        //       -1f);
        sizeStr = array.getString(R.styleable.PercentLayout_Layout_layout_marginStartPercent);
        percentVal = getPercentVal(sizeStr, true);
        if (percentVal != null)
        {
            if (Log.isLoggable(TAG, Log.VERBOSE))
            {
                Log.v(TAG, "percent start margin: " + percentVal.percent);
            }
            info = checkForInfoExists(info);
            info.startMarginPercent = percentVal;
        }
        //value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginEndPercent, 1, 1,
        //      -1f);
        sizeStr = array.getString(R.styleable.PercentLayout_Layout_layout_marginEndPercent);
        percentVal = getPercentVal(sizeStr, true);
        if (percentVal != null)
        {
            if (Log.isLoggable(TAG, Log.VERBOSE))
            {
                Log.v(TAG, "percent end margin: " + percentVal.percent);
            }
            info = checkForInfoExists(info);
            info.endMarginPercent = percentVal;
        }

        //textSizePercent
        sizeStr = array.getString(R.styleable.PercentLayout_Layout_layout_textSizePercent);
        percentVal = getPercentVal(sizeStr, false);
        if (percentVal != null)
        {
            if (Log.isLoggable(TAG, Log.VERBOSE))
            {
                Log.v(TAG, "percent text size: " + percentVal.percent);
            }
            info = checkForInfoExists(info);
            info.textSizePercent = percentVal;
        }

        //maxWidth
        percentVal = getPercentVal(array,
                R.styleable.PercentLayout_Layout_layout_maxWidthPercent,
                true);
        if (percentVal != null)
        {
            checkForInfoExists(info);
            info.maxWidthPercent = percentVal;
        }
        //maxHeight
        percentVal = getPercentVal(array,
                R.styleable.PercentLayout_Layout_layout_maxHeightPercent,
                false);
        if (percentVal != null)
        {
            checkForInfoExists(info);
            info.maxHeightPercent = percentVal;
        }
        //minWidth
        percentVal = getPercentVal(array,
                R.styleable.PercentLayout_Layout_layout_minWidthPercent,
                true);
        if (percentVal != null)
        {
            checkForInfoExists(info);
            info.minWidthPercent = percentVal;
        }
        //minHeight
        percentVal = getPercentVal(array,
                R.styleable.PercentLayout_Layout_layout_minHeightPercent,
                false);
        Log.d(TAG,"minHeight = "+percentVal);
        if (percentVal != null)
        {
            checkForInfoExists(info);
            info.minHeightPercent = percentVal;
        }

        array.recycle();
        if (Log.isLoggable(TAG, Log.DEBUG))
        {
            Log.d(TAG, "constructed: " + info);
        }
        return info;
    }
           

获得属性后,在onMeasure中对器子控件,重新计算高宽。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mHelper.handleMeasuredStateTooSmall()) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
           
public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec)
    {
        if (Log.isLoggable(TAG, Log.DEBUG))
        {
            Log.d(TAG, "adjustChildren: " + mHost + " widthMeasureSpec: "
                    + View.MeasureSpec.toString(widthMeasureSpec) + " heightMeasureSpec: "
                    + View.MeasureSpec.toString(heightMeasureSpec));
        }
        int widthHint = View.MeasureSpec.getSize(widthMeasureSpec);
        int heightHint = View.MeasureSpec.getSize(heightMeasureSpec);

        Log.d(TAG, "widthHint = " + widthHint + " , heightHint = " + heightHint);
        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 adjust " + 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)
                {
                    supportTextSize(widthHint, heightHint, view, info);
                    supportMinOrMaxDimesion(widthHint, heightHint, view, info);

                    if (params instanceof ViewGroup.MarginLayoutParams)
                    {
                        info.fillMarginLayoutParams((ViewGroup.MarginLayoutParams) params,
                                widthHint, heightHint);
                    } else
                    {
                        info.fillLayoutParams(params, widthHint, heightHint);
                    }
                }
            }
        }


    }
           

上面关键代码:

PercentLayoutInfo info =
                        ((PercentLayoutParams) params).getPercentLayoutInfo();
           

继续阅读