我们做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>
效果图
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2QvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zM5YTN1UDM2ETNxcDM1EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
那么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();