概述
平时android开发时,头疼不已的也就是界面了,各式各样的控件组合在一个小小的屏幕上面,就会发现自己写出来的界面在不同像素的设备上面,会出现错乱不堪的布局,是不是很头痛?控件的大小不好控制,布局时如果用LinearLayout权重布局,也可以解决这一问题,但是有时候权重也比较坑,况且布局不全用LinearLayout吧,再说LinearLayout嵌套多了,资源也很浪费。所以也就研究了重写容器,达到容器内的控件百分百布局。


1、自定义属性

我们平时在布局文件中写控件,控件里面都会用各式各样的控件属性,比如android:layout_width、android:text,不单有属性名还有对应格式的参数,例如android:layout_width=”wrap_content”,所以,我们也可以定义自己的属性,作用于百分比。先在资源文件夹下values中建下attr文件用户存放属性,

android 获取百分比 android百分百布局_布局

在文件中就写下我们需要定义的属性,

<?xml versinotallow="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HaoPercentLayout">
<attr name="layout_percentwidth" format="float" />
<attr name="layout_percentheight" format="float" />
</declare-styleable>
</resources>

这里简单解释一下,HaoPercentLayout是这个属性styleable定义了变量的名称,layout_percentwidth布局宽的百分比,类型为浮点,layout_percentheight布局高的百分比,类型为浮点;


2、属性在布局中使用

在布局文件中需要使用我们刚才写上的属性,首先就要引用,在外层布局中填上xmlns:badboy="http://schemas.android.com/apk/res-auto"

这里的badboy,我们可以理解为对象名,可以随意命名,合法就行,

android 获取百分比 android百分百布局_android 获取百分比_02


这里我们就重写一个RelativeLayout名曰PercentRelativeLayout的自定义相对布局,文件内就重写几个构造方法,再在布局文件使用!

android 获取百分比 android百分百布局_布局_03


在容器里面随意写上两个控件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:badboy="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical">
    <com.hao.percentlayout.layout.PercentRelativeLayout
        android:layout_width="match_parent"
        android:layout_height="400dip"
        android:background="@color/aqua">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/teal"
            android:text="宽40%高30%"
            badboy:layout_percentheight="0.3"
            badboy:layout_percentwidth="0.4" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:background="@color/orange"
            android:text="宽56%高40%"
            badboy:layout_percentheight="0.4"
            badboy:layout_percentwidth="0.56" />
    </com.hao.percentlayout.layout.PercentRelativeLayout>
</LinearLayout>

属性的使用就是刚才定义的名字:属性名=“对应类型的参数”badboy:layout_percentheight=”0.4”,这是虽说布局好了,但属性作用并没有写,所以功能自然没有了。


3、重写RelativeLayout

定义好的属性没有写具体功能是没有任何作用的,我们要达到百分比布局,所以就需要重写容器,这里我就拿RelativeLayout来举例。分析:既然是我们需求是让子控件能在容器中百分比摆放,那就是和大小有关咯,写过自定义控件的同学都知道,View中的onMeasure是测量控件大小的,View中绘制流程也是先执行onMeasure再摆放布局和绘制,所以这里我们就要把主要逻辑写在onMeasure中。写到这里了,我们应该怎么拿到我们刚才写在attr文件中的属性?是不是有点无从下手?别慌,Google给我提供了一个方法generateLayoutParams;

@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return super.generateLayoutParams(attrs);
}

从字面意思上面,我们就能知道是生成布局参数。所以这个方法估计能拿到我们写的属性,既然我们知道这个方法,想一想我们平时用RelativeLayout的时候,里面这么多的属性,Google怎么在代码里面实现的呢,我就忍不住去看看源码咯,找到RelativeLayout的generateLayoutParams方法,点开返回的那个布局参数

android 获取百分比 android百分百布局_控件_04

定位到这个参数类,我们在构造方法中一下就看见了

android 获取百分比 android百分百布局_控件_05

经常玩自定义属性的同学都知道,没错,context通过调用obtainStyledAttributes方法来获取一个TypeArray,而TypeArray来对属性进行设置,那怎么给属性名设值呢?那我们继续看源码,发现

android 获取百分比 android百分百布局_控件_06

这下就好办了我们可以按照老大哥的脚印一步一步的来,怎么写下RelativeLayout.LayoutParams那就没什么难度了。这里我直接附上代码

package com.hao.percentlayout.layoutparams;

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

import com.hao.percentlayout.R;

/**
* 百分比相对布局配置文件
* Created by Hao on 2016/9/7.
*/
public class RelativeLayoutParams extends RelativeLayout.LayoutParams {
private float widthPercent;
private float heightPercent;

public RelativeLayoutParams(Context c, AttributeSet attrs) {
    super(c, attrs);
    TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.HaoPercentLayout);
    widthPercent = typedArray.getFloat(R.styleable.HaoPercentLayout_layout_percentwidth, 0);
    heightPercent = typedArray.getFloat(R.styleable.HaoPercentLayout_layout_percentheight, 0);
}

public float getWidthPercent() {
    return widthPercent;
}

public void setWidthPercent(float widthPercent) {
    this.widthPercent = widthPercent;
}

public float getHeightPercent() {
    return heightPercent;
}

public void setHeightPercent(float heightPercent) {
    this.heightPercent = heightPercent;
}

}
参数配置文件就写在这里就结束了;回到PercentRelativeLayout中,直接把在generateLayoutParams方法中返回RelativeLayoutParams;

@Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new RelativeLayoutParams(getContext(), attrs);
    }

接下来就在onMeasure写核心的代码来实现功能了,在方法中,先去拿到容器的宽高int widthnum = MeasureSpec.getSize(widthMeasureSpec);
int heightnum = MeasureSpec.getSize(heightMeasureSpec);
必不可少的步骤;我们要设置子控件的大小,也就要写一个循环去得到容器里面的view,用view.getLayoutParams();得到ViewGroup.LayoutParams,这个时候就就把子布局参数和RelativeLayoutParams对比,ture就在if中获得我们在布局文件中设置的百分比宽高,直接用RelativeLayoutParams对象get就行了,float widtChild = ((RelativeLayoutParams) params).getWidthPercent()和float heightChild = ((RelativeLayoutParams) params).getHeightPercent();这样就获取到了在控件设置的浮点数,最后就设置这控件的大小了,很简单,用父容器宽高和自己设置的浮点数相乘就是我们想要得到的效果

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthnum = MeasureSpec.getSize(widthMeasureSpec);
        int heightnum = MeasureSpec.getSize(heightMeasureSpec);
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            float widtChild = 0;
            float heightChild = 0;
            ViewGroup.LayoutParams params = child.getLayoutParams();
            if (params instanceof RelativeLayoutParams) {
                widtChild = ((RelativeLayoutParams) params).getWidthPercent();
                heightChild = ((RelativeLayoutParams) params).getHeightPercent();
            }
            params.width = (int) (widtChild * widthnum);
            params.height = (int) (heightChild * heightnum);
        }
    }

运行效果:

android 获取百分比 android百分百布局_布局_07

但是这样还没有写完,如果我们想在控件里面不设置layout_percentheight和layout_percentwidth,那岂不是widtChild和heightChild都为0,因为从RelativeLayoutParams对象中get不出来嘛,这样设置出来的控件宽高都会为0,自然就显示不出来,所以我们在onMeasure里for循环最后添加判断,if (widtChild == 0 && heightChild == 0) {
continue;
}
这样就解决问题了,如果单设置layout_percentwidth或layout_percentheight,一样的逻辑,widtChild和heightChild谁不等于0就设置谁。
代码:

package com.hao.percentlayout.layout;

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

import com.hao.percentlayout.layoutparams.RelativeLayoutParams;

/**
 * 百分比相对布局
 * Created by Hao on 2016/9/7.
 */
public class PercentRelativeLayout extends RelativeLayout {
    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
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthnum = MeasureSpec.getSize(widthMeasureSpec);
        int heightnum = MeasureSpec.getSize(heightMeasureSpec);
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            float widtChild = 0;
            float heightChild = 0;
            ViewGroup.LayoutParams params = child.getLayoutParams();
            if (params instanceof RelativeLayoutParams) {
                widtChild = ((RelativeLayoutParams) params).getWidthPercent();
                heightChild = ((RelativeLayoutParams) params).getHeightPercent();
            }
            if (widtChild == 0 && heightChild == 0) {
                continue;
            }
            if (widtChild == 0 && heightChild != 0) {
                params.height = (int) (heightChild * heightnum);
                continue;
            }
            if (widtChild != 0 && heightChild == 0) {
                params.width = (int) (widtChild * widthnum);
                continue;
            }
            params.width = (int) (widtChild * widthnum);
            params.height = (int) (heightChild * heightnum);
        }
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new RelativeLayoutParams(getContext(), attrs);
    }
}

在这里,就差不多写完了,重写LinearLayout也是大同小异,这里就不解释了。