概述
平时android开发时,头疼不已的也就是界面了,各式各样的控件组合在一个小小的屏幕上面,就会发现自己写出来的界面在不同像素的设备上面,会出现错乱不堪的布局,是不是很头痛?控件的大小不好控制,布局时如果用LinearLayout权重布局,也可以解决这一问题,但是有时候权重也比较坑,况且布局不全用LinearLayout吧,再说LinearLayout嵌套多了,资源也很浪费。所以也就研究了重写容器,达到容器内的控件百分百布局。
1、自定义属性
我们平时在布局文件中写控件,控件里面都会用各式各样的控件属性,比如android:layout_width、android:text,不单有属性名还有对应格式的参数,例如android:layout_width=”wrap_content”,所以,我们也可以定义自己的属性,作用于百分比。先在资源文件夹下values中建下attr文件用户存放属性,在文件中就写下我们需要定义的属性,
<?xml version="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,我们可以理解为对象名,可以随意命名,合法就行,
这里我们就重写一个RelativeLayout名曰PercentRelativeLayout的自定义相对布局,文件内就重写几个构造方法,再在布局文件使用!
在容器里面随意写上两个控件
<?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方法,点开返回的那个布局参数定位到这个参数类,我们在构造方法中一下就看见了经常玩自定义属性的同学都知道,没错,context通过调用obtainStyledAttributes方法来获取一个TypeArray,而TypeArray来对属性进行设置,那怎么给属性名设值呢?那我们继续看源码,发现
这下就好办了我们可以按照老大哥的脚印一步一步的来,怎么写下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);
必不可少的步骤;我们要设置子控件的大小,也就要写一个循环去得到容器里面的view,用view.getLayoutParams();得到ViewGroup.LayoutParams,这个时候就就把子布局参数和RelativeLayoutParams对比,ture就在if中获得我们在布局文件中设置的百分比宽高,直接用RelativeLayoutParams对象get就行了,float widtChild = ((RelativeLayoutParams) params).getWidthPercent()和float heightChild = ((RelativeLayoutParams) params).getHeightPercent();这样就获取到了在控件设置的浮点数,最后就设置这控件的大小了,很简单,用父容器宽高和自己设置的浮点数相乘就是我们想要得到的效果
int heightnum = MeasureSpec.getSize(heightMeasureSpec);
@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);
}
}
运行效果:
但是这样还没有写完,如果我们想在控件里面不设置layout_percentheight和layout_percentwidth,那岂不是widtChild和heightChild都为0,因为从RelativeLayoutParams对象中get不出来嘛,这样设置出来的控件宽高都会为0,自然就显示不出来,所以我们在onMeasure里for循环最后添加判断,if (widtChild == 0 && heightChild == 0) {
这样就解决问题了,如果单设置layout_percentwidth或layout_percentheight,一样的逻辑,widtChild和heightChild谁不等于0就设置谁。
continue;
}
代码:
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也是大同小异,这里就不解释了。
PS 第一次写博文,语言描述有什么模糊不理解的请见谅!
附上github项目地址
PercentLayout-master用法参考本文,或者README。
源码下载地址:
PercentLayout