创建一个宽高比固定的视图容器

有时候,我们需要将一个View固定为指定比例,比如16:9,4:3,1:1等等。
一般的做法有两种:

  • 一种是预先知道宽高的值,直接在xml中给出指定的dp值
  • 一种是根据当前的宽/高值,根据给定的比例值动态计算另一个值

在Android开发中,可以通过重写ViewonMeasure方法来重新计算宽高值,系统在布局的时候会根据相应的计算结果来重新计算,然后给视图分配出指定的宽高。
一般情况下,我们希望在xml中指定宽高比例,以及基准的边(即宽不变,高对应重新计算;或者高不变,宽重新计算)。因此,我们需要在style中声明指定的style,在xml中设置对应的值,在View的构造函数中获取指定的值,然后在onMeasure方法中计算。
下面按照这个步骤来写出一个自定义的FrameLayout (你可以实现自定义的各种View,比如RelativeLayout,或者ImageView,建议是自定义ViewGroup类型,推荐FrameLayout或者RelativeLayout, 这样该控件之内的其他控件都会固定在这个范围之内,同时在其他地方使用时也可直接使用)

1. 新建一个attrs_fixed_aspect_ratio_framelayout.xml文件,放在res/values路径下,内容如下:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FixedAspectRatioView">
<attr name="aspectRatioWidth" format="integer"/>
<attr name="aspectRatioHeight" format="integer"/>
<attr name="fixedAspect" format="enum">
<enum name="width" value="0"/>
<enum name="height" value="1"/>
</attr>
</declare-styleable>
</resources>

其中:

fixedAspect: 枚举类型,标识需要固定的边是宽还是高
aspectRationWidth: 整型值,标识宽高比中的宽的数值
aspectRatioHeight: 整形值,标识宽高比中的高的数值

2.新建一个类:FixedAspectRatioFrameLayout,继承自FrameLayout,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@SuppressWarnings("unused")
public class FixedAspectRatioFrameLayout extends FrameLayout {
private static final int FIXED_WIDTH = 0;
private static final int FIXED_HEIGHT = 1;
private int mAspectRatioWidth = 0;
private int mAspectRatioHeight = 0;
private int mFixedAspect;

public FixedAspectRatioFrameLayout(Context context) {
super(context);
}

public FixedAspectRatioFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}

public FixedAspectRatioFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}

@TargetApi(21)
public FixedAspectRatioFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}

private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FixedAspectRatioView);
mAspectRatioWidth = a.getInt(R.styleable.FixedAspectRatioView_aspectRatioWidth, 0);
mAspectRatioHeight = a.getInt(R.styleable.FixedAspectRatioView_aspectRatioHeight, 0);
mFixedAspect = a.getInt(R.styleable.FixedAspectRatioView_fixedAspect, FIXED_WIDTH);
a.recycle();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mAspectRatioHeight == 0 || mAspectRatioWidth == 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
else {
int originalWidth = MeasureSpec.getSize(widthMeasureSpec);
int originalHeight = MeasureSpec.getSize(heightMeasureSpec);
int calculatedHeight = originalWidth * mAspectRatioHeight / mAspectRatioWidth;
int expectedWidth, expectedHeight;
if (mFixedAspect == FIXED_WIDTH) {
expectedWidth = originalWidth;
expectedHeight = calculatedHeight;
}
else {
expectedWidth = originalHeight * mAspectRatioWidth / mAspectRatioHeight;
expectedHeight = originalHeight;
}
super.onMeasure(MeasureSpec.makeMeasureSpec(expectedWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(expectedHeight, MeasureSpec.EXACTLY));
}
}
}

说明

类中声明了对应的成员变量
构造方法中从xml中读取属性对应的值
onMeasure方法中,根据对应的值重新计算宽和高

3.布局使用中设置对应的属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<com.mydomain.widget.FixedAspectRatioFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/llStockContent"
app:aspectRatioWidth="545"
app:aspectRatioHeight="300"
app:fixedAspect="width"
>

<android.support.v4.view.ViewPager
android:id="@+id/vpImageDaily"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</mobi.imuse.xbstock.widget.FixedAspectRatioFrameLayout>

这样就可以将ViewPager的宽高比例限制在545x300了,宽度适配屏幕宽度