自定義控制元件(14)---ViewGroup繪製的Padding、margin注意
阿新 • • 發佈:2019-01-03
ViewGroup測量子元素有關,其中measureChildWithMargins和measureChildren類似只是加入了對Margins外邊距的處理,ViewGroup提供對子元素測量的方法從measureChildren開始:
measureChildren的邏輯很簡單,通過父容器傳入的widthMeasureSpec和heightMeasureSpec遍歷子元素並呼叫measureChild方法去測量每一個子元素的寬高:
View的大小由其父容器的測量規格MeasureSpec和View本身的佈局引數LayoutParams共同決定
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { // 獲取子元素的佈局引數 final LayoutParams lp = child.getLayoutParams(); /* * 將父容器的測量規格已經上下和左右的邊距還有子元素本身的佈局引數傳入getChildMeasureSpec方法計算最終測量規格 */ final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); // 呼叫子元素的measure傳入計算好的測量規格 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
這裡我們主要就是看看getChildMeasureSpec方法是如何確定最終測量規格的:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { // 獲取父容器的測量模式和尺寸大小 int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); // 這個尺寸應該減去內邊距的值 int size = Math.max(0, specSize - padding); // 宣告臨時變數存值 int resultSize = 0; int resultMode = 0; /* * 根據模式判斷 */ switch (specMode) { case MeasureSpec.EXACTLY: // 父容器尺寸大小是一個確定的值 /* * 根據子元素的佈局引數判斷 */ if (childDimension >= 0) { //如果childDimension是一個具體的值 // 那麼就將該值作為結果 resultSize = childDimension; // 而這個值也是被確定的 resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //如果子元素的佈局引數為MATCH_PARENT // 那麼就將父容器的大小作為結果 resultSize = size; // 因為父容器的大小是被確定的所以子元素大小也是可以被確定的 resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果子元素的佈局引數為WRAP_CONTENT // 那麼就將父容器的大小作為結果 resultSize = size; // 但是子元素的大小包裹了其內容後不能超過父容器 resultMode = MeasureSpec.AT_MOST; } break; case MeasureSpec.AT_MOST: // 父容器尺寸大小擁有一個限制值 /* * 根據子元素的佈局引數判斷 */ if (childDimension >= 0) { //如果childDimension是一個具體的值 // 那麼就將該值作為結果 resultSize = childDimension; // 而這個值也是被確定的 resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //如果子元素的佈局引數為MATCH_PARENT // 那麼就將父容器的大小作為結果 resultSize = size; // 因為父容器的大小是受到限制值的限制所以子元素的大小也應該受到父容器的限制 resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果子元素的佈局引數為WRAP_CONTENT // 那麼就將父容器的大小作為結果 resultSize = size; // 但是子元素的大小包裹了其內容後不能超過父容器 resultMode = MeasureSpec.AT_MOST; } break; case MeasureSpec.UNSPECIFIED: // 父容器尺寸大小未受限制 /* * 根據子元素的佈局引數判斷 */ if (childDimension >= 0) { //如果childDimension是一個具體的值 // 那麼就將該值作為結果 resultSize = childDimension; // 而這個值也是被確定的 resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //如果子元素的佈局引數為MATCH_PARENT // 因為父容器的大小不受限制而對子元素來說也可以是任意大小所以不指定也不限制子元素的大小 resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果子元素的佈局引數為WRAP_CONTENT // 因為父容器的大小不受限制而對子元素來說也可以是任意大小所以不指定也不限制子元素的大小 resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } // 返回封裝後的測量規格 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
家注意到ViewGroup的onLayout方法的簽名列表中有五個引數,其中boolean changed表示是否與上一次位置不同,其具體值在View的layout方法中通過setFrame等方法確定:
public void layout(int l, int t, int r, int b) { // 省略一些程式碼…… boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); // 省略大量程式碼…… }
而剩下的四個引數則表示當前View與父容器的相對距離,如下圖:
activity_main.xml
<com.aigestudio.customviewdemo.views.CustomLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="30dp"
android:background="#FFFFFFFF"
android:orientation="vertical"
android:padding="30dp" >
<com.aigestudio.customviewdemo.views.IconView
android:id="@+id/main_pv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="30dp" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AigeStudio" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AigeStudio" />
</com.aigestudio.customviewdemo.views.CustomLayout>
MainActivity
package com.aigestudio.customviewdemo.activities;
import android.app.Activity;
import android.os.Bundle;
import com.aigestudio.customviewdemo.R;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
CustomLayout
package com.aigestudio.customviewdemo.views;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class CustomLayout extends ViewGroup {
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/*
* 如果有子元素
*/
if (getChildCount() > 0) {
// 那麼對子元素進行測量
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
}
/***
* new!!!!!!
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 獲取父容器內邊距
int parentPaddingLeft = getPaddingLeft();
int parentPaddingTop = getPaddingTop();
/*
* 如果有子元素
*/
if (getChildCount() > 0) {
// 宣告一個臨時變數儲存高度倍增值
int mutilHeight = 0;
// 那麼遍歷子元素並對其進行定位佈局
for (int i = 0; i < getChildCount(); i++) {
// 獲取一個子元素
View child = getChildAt(i);
// 通知子元素進行佈局
// 此時考慮父容器內邊距的影響
child.layout(parentPaddingLeft, mutilHeight + parentPaddingTop,
child.getMeasuredWidth() + parentPaddingLeft,
child.getMeasuredHeight() + mutilHeight
+ parentPaddingTop);
// 改變高度倍增值
mutilHeight += child.getMeasuredHeight();
}
}
}
}
IconView
package com.aigestudio.customviewdemo.views;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import com.aigestudio.customviewdemo.R;
public class IconView extends View {
private Bitmap mBitmap;// 點陣圖
private enum Ratio {
WIDTH, HEIGHT
}
public IconView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化
init();
}
/**
* 初始化
*/
private void init() {
/*
* 獲取Bitmap
*/
if (null == mBitmap) {
mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.logo);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 宣告一個臨時變數來儲存計算出的測量值
int resultWidth = 0;
// 獲取寬度測量規格中的mode
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
// 獲取寬度測量規格中的size
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
/*
* 如果爹心裡有數
*/
if (modeWidth == MeasureSpec.EXACTLY) {
// 那麼兒子也不要讓爹難做就取爹給的大小吧
resultWidth = mBitmap.getWidth() + getPaddingLeft() + getPaddingRight();
}
/*
* 如果爹心裡沒數
*/
else {
// 那麼兒子可要自己看看自己需要多大了
resultWidth = mBitmap.getWidth() + getPaddingLeft() + getPaddingRight();
/*
* 如果爹給兒子的是一個限制值
*/
if (modeWidth == MeasureSpec.AT_MOST) {
// 那麼兒子自己的需求就要跟爹的限制比比看誰小要誰
resultWidth = Math.min(resultWidth, sizeWidth);
}
}
int resultHeight = 0;
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
if (modeHeight == MeasureSpec.EXACTLY) {
resultHeight = sizeHeight;
} else {
resultHeight = mBitmap.getHeight() + getPaddingTop() + getPaddingBottom();
if (modeHeight == MeasureSpec.AT_MOST) {
resultHeight = mBitmap.getHeight() + getPaddingTop() + getPaddingBottom();
}
}
// 設定測量尺寸
setMeasuredDimension(resultWidth, resultHeight);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, getPaddingLeft(), getPaddingTop(), null);
}
}