百分比很好理解,就是擺放的 view 佔父 view 寬的百分之幾,高佔父 view 的百分之幾。假定父 view 的寬高跟屏幕大小一致,好比 1080 * 1920 分辨率的手機屏幕, 有一個子 view 的高佔屏幕高的50% ,即 1920 * 0.5 = 960px, 寬佔屏幕寬的30%,即 1080 * 0.3 = 324px。android
筆者在開發中曾經遇到設計師將一張圖片做爲背景,在背景圖片上某些位置須要擺放控件,又不能寫死,由於控件內容會改變。這種噁心的佈局若是經過寫死 dp, 基本上適配其餘機型絕對 gg。因此這種場景就很是適合用百分比適配。其思想也是很是簡單,首先咱們必須保證背景圖片的縮放比例必定不能改變,而後獲得每一個擺放控件與圖片背景的比例,這樣咱們就能夠經過計算得出。那若是改變了縮放比例會怎樣? 答案就是再怎麼計算都沒*用。canvas
舉個例子,如今咱們看下以下背景圖片,若是如今告訴你須要在白色的填寫內容,注意:白色區域並不是在佈局居中對齊,而且內部還有 「+」 符號,要求如圖 1-2 所示,咱們只在可編輯區域(填充粉紅色的區域)填寫內容。bash
首先須要找到可編輯區域在原圖(即背景圖)的到左上角的絕對位置,因爲這張背景圖從網上找來的,所以它的點是我經過尺子測量而來。這裏大體測量獲得: mOldLeft = 282; mOldTop = 476; mOldRight = 736; mOldBottom = 1140; mOldWidth = 1080; mOldHeight = 1619; 這幾個值依次是左上右下以及寬高的值。獲得這幾個值後那就很簡單了,咱們只須要在不一樣設備的設備上,按着比例縮放圖片,而後根據公式: mOldLeft / mOldWidth = mLleft / mBitmapWidth。 其中 mBitmapWidth 爲縮放後的圖片寬度, mLleft 爲所求真實屏幕的left 的值。其代碼以下:app
private Bitmap mBackgroundImage;
/**
* 背景圖片的寬度
*/
private int mBitmapWidth;
/**
* 背景圖片的高度
*/
private int mBitmapHeight;
/**
* 原始圖片,白色輸入框在原圖的絕對位置
*/
private int mOldTop = 476;
/**
* 原始圖片,白色輸入框在原圖的絕對位置
*/
private int mOldLeft = 282;
/**
* 原始圖片,白色輸入框在原圖的絕對位置
*/
private int mOldRight = 736;
/**
* 原始圖片,白色輸入框在原圖的絕對位置
*/
private int mOldBottom = 1140;
/**
* 原始圖片的寬
*/
private int mOldWidth = 1080;
/**
* 原始圖片的高
*/
private int mOldHeight = 1619;
private int mleft;
private int mTop;
private int mRight;
private int mBottom;
private Paint mPaint;
public PercentView(Context context) {
this(context, null);
}
public PercentView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PercentView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 對圖片縮放處理.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.background, options);
options.inSampleSize = 4;
options.inJustDecodeBounds = false;
mBackgroundImage = BitmapFactory.decodeResource(getResources(), R.drawable.background, options);
mBitmapWidth = mBackgroundImage.getWidth();
mBitmapHeight = mBackgroundImage.getHeight();
// 根據背景圖片空白框的比列計算位置.
mleft = mBitmapWidth * mOldLeft / mOldWidth;
mTop = mBitmapHeight * mOldTop / mOldHeight;
mRight = mBitmapWidth * mOldRight / mOldWidth;
mBottom = mBitmapHeight * mOldBottom / mOldHeight;
// 初始化畫筆.
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mBitmapWidth, mBitmapHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 繪製背景圖片.
canvas.drawBitmap(mBackgroundImage, 0, 0, null);
// 繪製紅色輸入框邊界.
canvas.drawRect(mleft, mTop, mRight, mBottom, mPaint);
// 繪製白色輸入框中的圓
mPaint.setColor(Color.parseColor("#E2C0D6"));
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle( (float) (mRight - mleft) / 2 + mleft,
(float) (mBottom - mTop) / 2 + mTop,
(float) (mRight - mleft) / 2,
mPaint);
}
複製代碼
效果圖以下: ide
上面是針對手寫代碼來進行適配,通常場景下若是咱們能夠在佈局中愉快的編寫,就不要經過計算來適配。下面我將經過繼承自 RelativeLayout 來實現,其原理就是在 onMeasure 測量的時候注入咱們的比例設置,所謂比例是根據屏幕的實際寬高尺寸進行比例計算獲得。代碼以下:源碼分析
固然最合適的方式確定是在 attrs 中進行配置,這樣用起來也就爽了
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PercentRelativeLayout">
<!-- 寬的比例 -->
<attr name="layout_widthPercent" format="float"/>
<!-- 高的比例-->
<attr name="layout_heightPercent" format="float"/>
<!-- 距離父控件左邊距離比例 -->
<attr name="layout_marginLeftPercent" format="float"/>
<!-- 距離父控件上邊距離比例 -->
<attr name="layout_marginTopPercent" format="float"/>
<!-- 距離父控件右邊距離比例 -->
<attr name="layout_marginRightPercent" format="float"/>
<!-- 距離父控件下邊距離比例 -->
<attr name="layout_marginBottomPercent" format="float"/>
</declare-styleable>
</resources>
// 繼承自 RelativeLayout, 在 onMeasure 注入計算過程.
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 defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
ViewGroup.LayoutParams childLayoutParams = child.getLayoutParams();
// 檢查當前控件的 layoutParams 是不是 PercentRelativeLayoutParams 或者其子類
if (checkLayoutParams(childLayoutParams)) {
float widthPercent = ((PercentRelativeLayoutParams) childLayoutParams).mWidthPercent;
// 在佈局中填寫的寬度佔屏幕寬的比例. 好比 0.5 佔屏幕寬一半
if (widthPercent > 0) {
// 屏幕的寬度按着指定的比例縮放獲得控件的寬度.
childLayoutParams.width = (int) (widthSize * widthPercent);
}
// 餘下的步驟原理同上
float heightPercent = ((PercentRelativeLayoutParams) childLayoutParams).mHeightPercent;
if (heightPercent > 0) {
childLayoutParams.height = (int) (heightSize * heightPercent);
}
float marginLeftPercent = ((PercentRelativeLayoutParams) childLayoutParams).mMarginLeftPercent;
if (marginLeftPercent > 0) {
((MarginLayoutParams)childLayoutParams).leftMargin = (int) (widthSize * marginLeftPercent);
}
float marginTopPercent = ((PercentRelativeLayoutParams) childLayoutParams).mMarginTopPercent;
if (marginTopPercent > 0) {
((MarginLayoutParams)childLayoutParams).topMargin = (int) (heightSize * marginTopPercent);
}
float marginRightPercent = ((PercentRelativeLayoutParams) childLayoutParams).mMarginRightPercent;
if (marginRightPercent > 0) {
((MarginLayoutParams)childLayoutParams).rightMargin = (int) (widthSize * marginRightPercent);
}
float marginBottomPercent = ((PercentRelativeLayoutParams) childLayoutParams).mMarginBottomPercent;
if (marginBottomPercent > 0) {
((MarginLayoutParams)childLayoutParams).bottomMargin = (int) (heightSize * marginBottomPercent);
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof PercentRelativeLayoutParams;
}
@Override
public PercentRelativeLayoutParams generateLayoutParams(AttributeSet attrs) {
return new PercentRelativeLayoutParams(getContext(), attrs);
}
public static class PercentRelativeLayoutParams extends LayoutParams {
public float mWidthPercent;
public float mHeightPercent;
public float mMarginLeftPercent;
public float mMarginTopPercent;
public float mMarginRightPercent;
public float mMarginBottomPercent;
public PercentRelativeLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
// 獲取自定義屬性.
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.PercentRelativeLayout);
try {
mWidthPercent = a.getFloat(R.styleable.PercentRelativeLayout_layout_widthPercent, 0.0f);
mHeightPercent = a.getFloat(R.styleable.PercentRelativeLayout_layout_heightPercent, 0.0f);
mMarginLeftPercent = a.getFloat(R.styleable.PercentRelativeLayout_layout_marginLeftPercent, 0.0f);
mMarginTopPercent = a.getFloat(R.styleable.PercentRelativeLayout_layout_marginTopPercent, 0.0f);
mMarginRightPercent = a.getFloat(R.styleable.PercentRelativeLayout_layout_marginRightPercent, 0.0f);
mMarginBottomPercent = a.getFloat(R.styleable.PercentRelativeLayout_layout_marginBottomPercent, 0.0f);
} finally {
a.recycle();
}
}
}
}
複製代碼
使用以下,將根佈局設置爲咱們自定義的佈局 PercentRelativeLayout。佈局
<?xml version="1.0" encoding="utf-8"?>
<com.hxj.enjoyandroid.ui.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".UIAdapter2Activity">
<!-- 爲屏幕寬高的 80%,-->
<com.hxj.enjoyandroid.ui.PercentRelativeLayout
app:layout_widthPercent="0.8"
app:layout_heightPercent="0.8"
android:background="#ff22ff"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- 爲父控件寬高的 100%,可是有外邊距,爲父控件的 20% -->
<TextView
android:textColor="#ffffff"
android:gravity="center"
android:text="百分比適配"
app:layout_widthPercent="1.0"
app:layout_heightPercent="1.0"
app:layout_marginTopPercent="0.2"
app:layout_marginRightPercent="0.2"
app:layout_marginLeftPercent="0.2"
app:layout_marginBottomPercent="0.2"
android:background="@color/colorAccent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</com.hxj.enjoyandroid.ui.PercentRelativeLayout>
</com.hxj.enjoyandroid.ui.PercentRelativeLayout>
複製代碼
效果以下:ui
若是有疑惑爲何繼承自 RelativeLayout ? 爲何要繼承 LayoutParams 來加載咱們的自定義屬性?請看源碼分析篇 Android 佈局屬性加載過程this