尊重原創轉載請註明:From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige 侵權必究!
炮兵鎮樓
又要開始雞凍人心的一刻了有木有!有木有雞凍! = = ……通過上一節的講解呢我們大致對Android測量控件有個初步的瞭解,而此後呢也有不少盆友Q小窗我問了不少問題,不過其實這些問題大多都不是問題,至於到底是不是問題呢,還要等我研究下究竟可不可以把這些問題歸爲問題……稍等、我吃個藥先。大多數盆友的反應是在對控件測量的具體方法還不是很瞭解,不過不要着急,上一節的內容就當飯前甜點,接下來我們會用一個例子來說明這一切不是問題的問題,這個例子中的控件呢我稱其爲SquareLayout,意爲方形佈局(注:該例子僅作演示,實際應用意義並不大),我們將置於該佈局下的所有子元素都強制變爲一個正方形~~說起簡單,但是如我上一節所說控件的設計要儘可能考慮到所有的可能性才能趨於完美~~但是沒有絕對的完美……在5/12時我曾說過不要將自己當作一個coder而要把自己看成一個designer,控件是我們設計出來的而不是敲出來的,在我們code之前就該對這個控件有一個較爲perfect的design,考慮到控件的屬性設計、行爲設計、交互設計等等,這裏呢我也對我們的SquareLayout做了一個簡單的設計:
非常簡單,如上我們所說,SquareLayout內部的子元素都會以正方形的形狀顯示,我們可以給其定義一個orientation屬性來表示子元素排列方式,如上是orientation爲橫向時的排列方式,而下面則是縱向的排列方式:
指定了排列方式後我們的子元素就會以此爲基準排列下去,但是如果子元素超出了父容器的區域怎麼辦呢?這時我們可以指定兩種處理方式:一、不管,任由子元素被父容器的邊距裁剪;二、強制被裁剪的子元素捨棄其原有佈局重新佈局。暫時我們先默認第一種吧。接着看,如果我們的子元素只能是橫着一排或豎着一排着實單調,我們可以考慮定義兩個屬性控制其最大排列個數,比如縱向排列時,我們可以指定一個max_row屬性,當排列的行數超過該值時自動換列:
當然我們也可以在子元素橫向排列時爲其指定max_column屬性,當橫向排列列數超過該值時自動換行:
仔細想想,當max_column爲1時,我們的排列方式其實就是縱向的而當max_row爲1時就是橫向的,那麼我們的orientation屬性豈不是成了擺設?會不會呢?留給各位去想。好吧、暫時就先定義這倆屬性,光這倆已經夠折騰的了,來來來創建我們的佈局:
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- private int mMaxRow;// 最大行數
- private int mMaxColumn;// 最大列數
- private int mOrientation;// 排列方向
- public SquareLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- }
- }
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- // 省去各種蛋疼的成員變量…………
- // 省去構造方法…………
- // 省去onLayout方法…………
- @Override
- protected LayoutParams generateDefaultLayoutParams() {
- return new MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- }
- @Override
- protected android.view.ViewGroup.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams p) {
- return new MarginLayoutParams(p);
- }
- @Override
- public android.view.ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new MarginLayoutParams(getContext(), attrs);
- }
- }
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- // 省去各種蛋疼的成員變量…………
- // 省去構造方法…………
- // 省去onLayout方法…………
- // 省去三個屌毛方法……
- @Override
- protected boolean checkLayoutParams(android.view.ViewGroup.LayoutParams p) {
- return p instanceof MarginLayoutParams;
- }
- }
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- private static final int ORIENTATION_HORIZONTAL = 0, ORIENTATION_VERTICAL = 1;// 排列方向的常量標識值
- private static final int DEFAULT_MAX_ROW = Integer.MAX_VALUE, DEFAULT_MAX_COLUMN = Integer.MAX_VALUE;// 最大行列默認值
- private int mMaxRow = DEFAULT_MAX_ROW;// 最大行數
- private int mMaxColumn = DEFAULT_MAX_COLUMN;// 最大列數
- private int mOrientation = ORIENTATION_HORIZONTAL;// 排列方向默認橫向
- // 省去構造方法…………
- @SuppressLint("NewApi")
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- /*
- * 聲明臨時變量存儲父容器的期望值
- * 該值應該等於父容器的內邊距加上所有子元素的測量寬高和外邊距
- */
- int parentDesireWidth = 0;
- int parentDesireHeight = 0;
- // 聲明臨時變量存儲子元素的測量狀態
- int childMeasureState = 0;
- /*
- * 如果父容器內有子元素
- */
- if (getChildCount() > 0) {
- /*
- * 那麼就遍歷子元素
- */
- for (int i = 0; i < getChildCount(); i++) {
- // 獲取對應遍歷下標的子元素
- View child = getChildAt(i);
- /*
- * 如果該子元素沒有以“不佔用空間”的方式隱藏則表示其需要被測量計算
- */
- if (child.getVisibility() != View.GONE) {
- // 測量子元素並考量其外邊距
- measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
- // 比較子元素測量寬高並比較取其較大值
- int childMeasureSize = Math.max(child.getMeasuredWidth(), child.getMeasuredHeight());
- // 重新封裝子元素測量規格
- int childMeasureSpec = MeasureSpec.makeMeasureSpec(childMeasureSize, MeasureSpec.EXACTLY);
- // 重新測量子元素
- child.measure(childMeasureSpec, childMeasureSpec);
- // 獲取子元素佈局參數
- MarginLayoutParams mlp = (MarginLayoutParams) child.getLayoutParams();
- /*
- * 考量外邊距計算子元素實際寬高
- */
- int childActualWidth = child.getMeasuredWidth() + mlp.leftMargin + mlp.rightMargin;
- int childActualHeight = child.getMeasuredHeight() + mlp.topMargin + mlp.bottomMargin;
- /*
- * 如果爲橫向排列
- */
- if (mOrientation == ORIENTATION_HORIZONTAL) {
- // 累加子元素的實際寬度
- parentDesireWidth += childActualWidth;
- // 獲取子元素中高度最大值
- parentDesireHeight = Math.max(parentDesireHeight, childActualHeight);
- }
- /*
- * 如果爲豎向排列
- */
- else if (mOrientation == ORIENTATION_VERTICAL) {
- // 累加子元素的實際高度
- parentDesireHeight += childActualHeight;
- // 獲取子元素中寬度最大值
- parentDesireWidth = Math.max(parentDesireWidth, childActualWidth);
- }
- // 合併子元素的測量狀態
- childMeasureState = combineMeasuredStates(childMeasureState, child.getMeasuredState());
- }
- }
- /*
- * 考量父容器內邊距將其累加到期望值
- */
- parentDesireWidth += getPaddingLeft() + getPaddingRight();
- parentDesireHeight += getPaddingTop() + getPaddingBottom();
- /*
- * 嘗試比較父容器期望值與Android建議的最小值大小並取較大值
- */
- parentDesireWidth = Math.max(parentDesireWidth, getSuggestedMinimumWidth());
- parentDesireHeight = Math.max(parentDesireHeight, getSuggestedMinimumHeight());
- }
- // 確定父容器的測量寬高
- setMeasuredDimension(resolveSizeAndState(parentDesireWidth, widthMeasureSpec, childMeasureState),
- resolveSizeAndState(parentDesireHeight, heightMeasureSpec, childMeasureState << MEASURED_HEIGHT_STATE_SHIFT));
- }
- // 省去onLayout方法…………
- // 省去四個屌毛方法……
- }
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/15
- *
- */
- public class CustomLayout extends ViewGroup {
- // 省去N多代碼
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // 省省省………………
- // 設置最終測量值
- setMeasuredDimension(resolveSize(parentDesireWidth, widthMeasureSpec), resolveSize(parentDesireHeight, heightMeasureSpec));
- }
- // 省去N+1多代碼
- }
- public static int resolveSize(int size, int measureSpec) {
- return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
- }
- public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
- int result = size;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
- switch (specMode) {
- case MeasureSpec.UNSPECIFIED:
- result = size;
- break;
- case MeasureSpec.AT_MOST:
- if (specSize < size) {
- result = specSize | MEASURED_STATE_TOO_SMALL;
- } else {
- result = size;
- }
- break;
- case MeasureSpec.EXACTLY:
- result = specSize;
- break;
- }
- return result | (childMeasuredState&MEASURED_STATE_MASK);
- }
這些標識位上面的代碼中我們都有用到,而官方文檔對其作用的說明也是模棱兩可,源碼裏的運用也不明朗,比如說我們看其它幾個與其相關的幾個方法:
- public final int getMeasuredWidth() {
- return mMeasuredWidth & MEASURED_SIZE_MASK;
- }
- public final int getMeasuredHeight() {
- return mMeasuredHeight & MEASURED_SIZE_MASK;
- }
- public final int getMeasuredState() {
- return (mMeasuredWidth&MEASURED_STATE_MASK)
- | ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
- & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
- }
- protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
- // 省去無關代碼……
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
- // 省去一行代碼……
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // 省去海量代碼…………
- widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
- heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
- // 省去一點代碼…………
- }
大概意思就是當控件的測量尺寸比其父容器大時將會設置MEASURED_STATE_TOO_SMALL這個二進制值,而另一個stackoverflow的回答就更官方了:
注意右下角的用戶名和頭像,你就知道爲什麼這個回答有權威性了,鄙人是他腦殘粉。來我們好好翻一下Romain這段話的意思:“childMeasuredState這個值呢由View.getMeasuredState()這個方法返回,一個佈局(或者按我的說法父容器)通過View.combineMeasuredStates()這個方法來統計其子元素的測量狀態。在大多數情況下你可以簡單地只傳遞0作爲參數值,而子元素狀態值目前的作用只是用來告訴父容器在對其進行測量得出的測量值比它自身想要的尺寸要小,如果有必要的話一個對話框將會根據這個原因來重新校正它的尺寸。”So、可以看出,測量狀態對谷歌官方而言也還算個測試性的功能,具體鄙人也沒有找到很好的例證,如果大家誰找到了其具體的使用方法可以分享一下,這裏我們還是就按照谷歌官方的建議依葫蘆畫瓢。好了這個問題就先到這裏爲止,我們繼續看,在測量子元素尺寸時我分了兩種情況:
- /*
- * 如果爲橫向排列
- */
- if (mOrientation == ORIENTATION_HORIZONTAL) {
- // 累加子元素的實際寬度
- parentDesireWidth += childActualWidth;
- // 獲取子元素中高度最大值
- parentDesireHeight = Math.max(parentDesireHeight, childActualHeight);
- }
- /*
- * 如果爲豎向排列
- */
- else if (mOrientation == ORIENTATION_VERTICAL) {
- // 累加子元素的實際高度
- parentDesireHeight += childActualHeight;
- // 獲取子元素中寬度最大值
- parentDesireWidth = Math.max(parentDesireWidth, childActualWidth);
- }
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- private static final int ORIENTATION_HORIZONTAL = 0, ORIENTATION_VERTICAL = 1;// 排列方向的常量標識值
- private static final int DEFAULT_MAX_ROW = Integer.MAX_VALUE, DEFAULT_MAX_COLUMN = Integer.MAX_VALUE;// 最大行列默認值
- private int mMaxRow = DEFAULT_MAX_ROW;// 最大行數
- private int mMaxColumn = DEFAULT_MAX_COLUMN;// 最大列數
- private int mOrientation = ORIENTATION_HORIZONTAL;// 排列方向默認橫向
- // 省去構造方法…………
- // 省去上面已經給過的onMeasure方法…………
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- /*
- * 如果父容器下有子元素
- */
- if (getChildCount() > 0) {
- // 聲明臨時變量存儲寬高倍增值
- int multi = 0;
- /*
- * 遍歷子元素
- */
- for (int i = 0; i < getChildCount(); i++) {
- // 獲取對應遍歷下標的子元素
- View child = getChildAt(i);
- /*
- * 如果該子元素沒有以“不佔用空間”的方式隱藏則表示其需要被測量計算
- */
- if (child.getVisibility() != View.GONE) {
- // 獲取子元素佈局參數
- MarginLayoutParams mlp = (MarginLayoutParams) child.getLayoutParams();
- // 獲取控件尺寸
- int childActualSize = child.getMeasuredWidth();// child.getMeasuredHeight()
- /*
- * 如果爲橫向排列
- */
- if (mOrientation == ORIENTATION_HORIZONTAL) {
- // 確定子元素左上、右下座標
- child.layout(getPaddingLeft() + mlp.leftMargin + multi, getPaddingTop() + mlp.topMargin, childActualSize + getPaddingLeft()
- + mlp.leftMargin + multi, childActualSize + getPaddingTop() + mlp.topMargin);
- // 累加倍增值
- multi += childActualSize + mlp.leftMargin + mlp.rightMargin;
- }
- /*
- * 如果爲豎向排列
- */
- else if (mOrientation == ORIENTATION_VERTICAL) {
- // 確定子元素左上、右下座標
- child.layout(getPaddingLeft() + mlp.leftMargin, getPaddingTop() + mlp.topMargin + multi, childActualSize + getPaddingLeft()
- + mlp.leftMargin, childActualSize + getPaddingTop() + mlp.topMargin + multi);
- // 累加倍增值
- multi += childActualSize + mlp.topMargin + mlp.bottomMargin;
- }
- }
- }
- }
- }
- // 省去四個屌毛方法……
- }
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:background="#ffffff" >
- <com.aigestudio.customviewdemo.views.SquareLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingLeft="5dp"
- android:paddingTop="12dp"
- android:layout_margin="5dp"
- android:paddingRight="7dp"
- android:paddingBottom="20dp"
- android:layout_gravity="center"
- android:background="#679135" >
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="#125793"
- android:text="tomorrow"
- android:textSize="24sp"
- android:textStyle="bold"
- android:typeface="serif" />
- <Button
- android:layout_width="50dp"
- android:layout_height="100dp"
- android:layout_marginBottom="5dp"
- android:layout_marginLeft="10dp"
- android:layout_marginRight="20dp"
- android:layout_marginTop="30dp"
- android:background="#495287"
- android:text="AigeStudio" />
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="50dp"
- android:layout_marginLeft="5dp"
- android:layout_marginRight="20dp"
- android:layout_marginTop="15dp"
- android:background="#976234"
- android:scaleType="centerCrop"
- android:src="@drawable/lovestory_little" />
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="#594342"
- android:text="AigeStudio" />
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="#961315"
- android:text="AigeStudio" />
- </com.aigestudio.customviewdemo.views.SquareLayout>
- </LinearLayout>
下面是運行後顯示的效果:
將排列方式改爲縱向排列:
- private int mOrientation = ORIENTATION_VERTICAL;// 排列方向默認橫向
在運行看看:
看樣子目測還是很完美,不過這只是我們偉大的第一步而已!如我多次強調,控件的測量一定要儘可能地考慮到所有因素,這樣你的控件才能立於N次不倒的暴力測試中,現在開始我們的第二步,max_row和max_column屬性的計算:
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- private static final int ORIENTATION_HORIZONTAL = 0, ORIENTATION_VERTICAL = 1;// 排列方向的常量標識值
- private static final int DEFAULT_MAX_ROW = Integer.MAX_VALUE, DEFAULT_MAX_COLUMN = Integer.MAX_VALUE;// 最大行列默認值
- private int mMaxRow = DEFAULT_MAX_ROW;// 最大行數
- private int mMaxColumn = DEFAULT_MAX_COLUMN;// 最大列數
- private int mOrientation = ORIENTATION_HORIZONTAL;// 排列方向默認橫向
- public SquareLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- // 初始化最大行列數
- mMaxRow = mMaxColumn = 2;
- }
- // 省去onMeasure方法…………
- // 省去onLayout方法…………
- // 省去四個屌毛方法……
- }
- // 初始化最大行列數
- mMaxRow = mMaxColumn = 2;
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- private static final int ORIENTATION_HORIZONTAL = 0, ORIENTATION_VERTICAL = 1;// 排列方向的常量標識值
- private static final int DEFAULT_MAX_ROW = Integer.MAX_VALUE, DEFAULT_MAX_COLUMN = Integer.MAX_VALUE;// 最大行列默認值
- private int mMaxRow = DEFAULT_MAX_ROW;// 最大行數
- private int mMaxColumn = DEFAULT_MAX_COLUMN;// 最大列數
- private int mOrientation = ORIENTATION_HORIZONTAL;// 排列方向默認橫向
- // 省去構造方法…………
- @SuppressLint("NewApi")
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- /*
- * 聲明臨時變量存儲父容器的期望值
- * 該值應該等於父容器的內邊距加上所有子元素的測量寬高和外邊距
- */
- int parentDesireWidth = 0;
- int parentDesireHeight = 0;
- // 聲明臨時變量存儲子元素的測量狀態
- int childMeasureState = 0;
- /*
- * 如果父容器內有子元素
- */
- if (getChildCount() > 0) {
- // 聲明兩個一維數組存儲子元素寬高數據
- int[] childWidths = new int[getChildCount()];
- int[] childHeights = new int[getChildCount()];
- /*
- * 那麼就遍歷子元素
- */
- for (int i = 0; i < getChildCount(); i++) {
- // 獲取對應遍歷下標的子元素
- View child = getChildAt(i);
- /*
- * 如果該子元素沒有以“不佔用空間”的方式隱藏則表示其需要被測量計算
- */
- if (child.getVisibility() != View.GONE) {
- // 測量子元素並考量其外邊距
- measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
- // 比較子元素測量寬高並比較取其較大值
- int childMeasureSize = Math.max(child.getMeasuredWidth(), child.getMeasuredHeight());
- // 重新封裝子元素測量規格
- int childMeasureSpec = MeasureSpec.makeMeasureSpec(childMeasureSize, MeasureSpec.EXACTLY);
- // 重新測量子元素
- child.measure(childMeasureSpec, childMeasureSpec);
- // 獲取子元素佈局參數
- MarginLayoutParams mlp = (MarginLayoutParams) child.getLayoutParams();
- /*
- * 考量外邊距計算子元素實際寬高並將數據存入數組
- */
- childWidths[i] = child.getMeasuredWidth() + mlp.leftMargin + mlp.rightMargin;
- childHeights[i] = child.getMeasuredHeight() + mlp.topMargin + mlp.bottomMargin;
- // 合併子元素的測量狀態
- childMeasureState = combineMeasuredStates(childMeasureState, child.getMeasuredState());
- }
- }
- // 聲明臨時變量存儲行/列寬高
- int indexMultiWidth = 0, indexMultiHeight = 0;
- /*
- * 如果爲橫向排列
- */
- if (mOrientation == ORIENTATION_HORIZONTAL) {
- /*
- * 如果子元素數量大於限定值則進行折行計算
- */
- if (getChildCount() > mMaxColumn) {
- // 計算產生的行數
- int row = getChildCount() / mMaxColumn;
- // 計算餘數
- int remainder = getChildCount() % mMaxColumn;
- // 聲明臨時變量存儲子元素寬高數組下標值
- int index = 0;
- /*
- * 遍歷數組計算父容器期望寬高值
- */
- for (int x = 0; x < row; x++) {
- for (int y = 0; y < mMaxColumn; y++) {
- // 單行寬度累加
- indexMultiWidth += childWidths[index];
- // 單行高度取最大值
- indexMultiHeight = Math.max(indexMultiHeight, childHeights[index++]);
- }
- // 每一行遍歷完後將該行寬度與上一行寬度比較取最大值
- parentDesireWidth = Math.max(parentDesireWidth, indexMultiWidth);
- // 每一行遍歷完後累加各行高度
- parentDesireHeight += indexMultiHeight;
- // 重置參數
- indexMultiWidth = indexMultiHeight = 0;
- }
- /*
- * 如果有餘數表示有子元素未能佔據一行
- */
- if (remainder != 0) {
- /*
- * 遍歷剩下的這些子元素將其寬高計算到父容器期望值
- */
- for (int i = getChildCount() - remainder; i < getChildCount(); i++) {
- indexMultiWidth += childWidths[i];
- indexMultiHeight = Math.max(indexMultiHeight, childHeights[i]);
- }
- parentDesireWidth = Math.max(parentDesireWidth, indexMultiWidth);
- parentDesireHeight += indexMultiHeight;
- indexMultiWidth = indexMultiHeight = 0;
- }
- }
- /*
- * 如果子元素數量還沒有限制值大那麼直接計算即可不須折行
- */
- else {
- for (int i = 0; i < getChildCount(); i++) {
- // 累加子元素的實際高度
- parentDesireHeight += childHeights[i];
- // 獲取子元素中寬度最大值
- parentDesireWidth = Math.max(parentDesireWidth, childWidths[i]);
- }
- }
- }
- /*
- * 如果爲豎向排列
- */
- else if (mOrientation == ORIENTATION_VERTICAL) {
- if (getChildCount() > mMaxRow) {
- int column = getChildCount() / mMaxRow;
- int remainder = getChildCount() % mMaxRow;
- int index = 0;
- for (int x = 0; x < column; x++) {
- for (int y = 0; y < mMaxRow; y++) {
- indexMultiHeight += childHeights[index];
- indexMultiWidth = Math.max(indexMultiWidth, childWidths[index++]);
- }
- parentDesireHeight = Math.max(parentDesireHeight, indexMultiHeight);
- parentDesireWidth += indexMultiWidth;
- indexMultiWidth = indexMultiHeight = 0;
- }
- if (remainder != 0) {
- for (int i = getChildCount() - remainder; i < getChildCount(); i++) {
- indexMultiHeight += childHeights[i];
- indexMultiWidth = Math.max(indexMultiHeight, childWidths[i]);
- }
- parentDesireHeight = Math.max(parentDesireHeight, indexMultiHeight);
- parentDesireWidth += indexMultiWidth;
- indexMultiWidth = indexMultiHeight = 0;
- }
- } else {
- for (int i = 0; i < getChildCount(); i++) {
- // 累加子元素的實際寬度
- parentDesireWidth += childWidths[i];
- // 獲取子元素中高度最大值
- parentDesireHeight = Math.max(parentDesireHeight, childHeights[i]);
- }
- }
- }
- /*
- * 考量父容器內邊距將其累加到期望值
- */
- parentDesireWidth += getPaddingLeft() + getPaddingRight();
- parentDesireHeight += getPaddingTop() + getPaddingBottom();
- /*
- * 嘗試比較父容器期望值與Android建議的最小值大小並取較大值
- */
- parentDesireWidth = Math.max(parentDesireWidth, getSuggestedMinimumWidth());
- parentDesireHeight = Math.max(parentDesireHeight, getSuggestedMinimumHeight());
- }
- // 確定父容器的測量寬高
- setMeasuredDimension(resolveSizeAndState(parentDesireWidth, widthMeasureSpec, childMeasureState),
- resolveSizeAndState(parentDesireHeight, heightMeasureSpec, childMeasureState << MEASURED_HEIGHT_STATE_SHIFT));
- }
- // 省去onLayout方法…………
- // 省去四個屌毛方法……
- }
- @SuppressLint("NewApi")
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // 省去幾行代碼…………
- /*
- * 如果父容器內有子元素
- */
- if (getChildCount() > 0) {
- // 聲明兩個一維數組存儲子元素寬高數據
- int[] childWidths = new int[getChildCount()];
- int[] childHeights = new int[getChildCount()];
- /*
- * 那麼就遍歷子元素
- */
- for (int i = 0; i < getChildCount(); i++) {
- // 省省省……
- /*
- * 如果該子元素沒有以“不佔用空間”的方式隱藏則表示其需要被測量計算
- */
- if (child.getVisibility() != View.GONE) {
- // 省去N行代碼……
- /*
- * 考量外邊距計算子元素實際寬高並將數據存入數組
- */
- childWidths[i] = child.getMeasuredWidth() + mlp.leftMargin + mlp.rightMargin;
- childHeights[i] = child.getMeasuredHeight() + mlp.topMargin + mlp.bottomMargin;
- // 省去一行代碼……
- }
- }
- // 聲明臨時變量存儲行/列寬高
- int indexMultiWidth = 0, indexMultiHeight = 0;
- // 省去無數行代碼……………………
- }
- // 省去一行代碼……
- }
- /*
- * 如果爲橫向排列
- */
- if (mOrientation == ORIENTATION_HORIZONTAL) {
- /*
- * 如果子元素數量大於限定值則進行折行計算
- */
- if (getChildCount() > mMaxColumn) {
- // 計算產生的行數
- int row = getChildCount() / mMaxColumn;
- // 計算餘數
- int remainder = getChildCount() % mMaxColumn;
- // 聲明臨時變量存儲子元素寬高數組下標值
- int index = 0;
- /*
- * 遍歷數組計算父容器期望寬高值
- */
- for (int x = 0; x < row; x++) {
- for (int y = 0; y < mMaxColumn; y++) {
- // 單行寬度累加
- indexMultiWidth += childWidths[index];
- // 單行高度取最大值
- indexMultiHeight = Math.max(indexMultiHeight, childHeights[index++]);
- }
- // 每一行遍歷完後將該行寬度與上一行寬度比較取最大值
- parentDesireWidth = Math.max(parentDesireWidth, indexMultiWidth);
- // 每一行遍歷完後累加各行高度
- parentDesireHeight += indexMultiHeight;
- // 重置參數
- indexMultiWidth = indexMultiHeight = 0;
- }
- /*
- * 如果有餘數表示有子元素未能佔據一行
- */
- if (remainder != 0) {
- /*
- * 遍歷剩下的這些子元素將其寬高計算到父容器期望值
- */
- for (int i = getChildCount() - remainder; i < getChildCount(); i++) {
- indexMultiWidth += childWidths[i];
- indexMultiHeight = Math.max(indexMultiHeight, childHeights[i]);
- }
- parentDesireWidth = Math.max(parentDesireWidth, indexMultiWidth);
- parentDesireHeight += indexMultiHeight;
- indexMultiWidth = indexMultiHeight = 0;
- }
- }
- /*
- * 如果子元素數量還沒有限制值大那麼直接計算即可不須折行
- */
- else {
- for (int i = 0; i < getChildCount(); i++) {
- // 累加子元素的實際高度
- parentDesireHeight += childHeights[i];
- // 獲取子元素中寬度最大值
- parentDesireWidth = Math.max(parentDesireWidth, childWidths[i]);
- }
- }
- }
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- private static final int ORIENTATION_HORIZONTAL = 0, ORIENTATION_VERTICAL = 1;// 排列方向的常量標識值
- private static final int DEFAULT_MAX_ROW = Integer.MAX_VALUE, DEFAULT_MAX_COLUMN = Integer.MAX_VALUE;// 最大行列默認值
- private int mMaxRow = DEFAULT_MAX_ROW;// 最大行數
- private int mMaxColumn = DEFAULT_MAX_COLUMN;// 最大列數
- private int mOrientation = ORIENTATION_HORIZONTAL;// 排列方向默認橫向
- // 省去構造方法…………
- // 省去上面已經給過的onMeasure方法…………
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- /*
- * 如果父容器下有子元素
- */
- if (getChildCount() > 0) {
- // 聲明臨時變量存儲寬高倍增值
- int multi = 0;
- // 指數倍增值
- int indexMulti = 1;
- // 聲明臨時變量存儲行/列寬高
- int indexMultiWidth = 0, indexMultiHeight = 0;
- // 聲明臨時變量存儲行/列臨時寬高
- int tempHeight = 0, tempWidth = 0;
- /*
- * 遍歷子元素
- */
- for (int i = 0; i < getChildCount(); i++) {
- // 獲取對應遍歷下標的子元素
- View child = getChildAt(i);
- /*
- * 如果該子元素沒有以“不佔用空間”的方式隱藏則表示其需要被測量計算
- */
- if (child.getVisibility() != View.GONE) {
- // 獲取子元素佈局參數
- MarginLayoutParams mlp = (MarginLayoutParams) child.getLayoutParams();
- // 獲取控件尺寸
- int childActualSize = child.getMeasuredWidth();// child.getMeasuredHeight()
- /*
- * 如果爲橫向排列
- */
- if (mOrientation == ORIENTATION_HORIZONTAL) {
- /*
- * 如果子元素數量比限定值大
- */
- if (getChildCount() > mMaxColumn) {
- /*
- * 根據當前子元素進行佈局
- */
- if (i < mMaxColumn * indexMulti) {
- child.layout(getPaddingLeft() + mlp.leftMargin + indexMultiWidth, getPaddingTop() + mlp.topMargin + indexMultiHeight,
- childActualSize + getPaddingLeft() + mlp.leftMargin + indexMultiWidth, childActualSize + getPaddingTop()
- + mlp.topMargin + indexMultiHeight);
- indexMultiWidth += childActualSize + mlp.leftMargin + mlp.rightMargin;
- tempHeight = Math.max(tempHeight, childActualSize) + mlp.topMargin + mlp.bottomMargin;
- /*
- * 如果下一次遍歷到的子元素下標值大於限定值
- */
- if (i + 1 >= mMaxColumn * indexMulti) {
- // 那麼累加高度到高度倍增值
- indexMultiHeight += tempHeight;
- // 重置寬度倍增值
- indexMultiWidth = 0;
- // 增加指數倍增值
- indexMulti++;
- }
- }
- } else {
- // 確定子元素左上、右下座標
- child.layout(getPaddingLeft() + mlp.leftMargin + multi, getPaddingTop() + mlp.topMargin, childActualSize
- + getPaddingLeft() + mlp.leftMargin + multi, childActualSize + getPaddingTop() + mlp.topMargin);
- // 累加倍增值
- multi += childActualSize + mlp.leftMargin + mlp.rightMargin;
- }
- }
- /*
- * 如果爲豎向排列
- */
- else if (mOrientation == ORIENTATION_VERTICAL) {
- if (getChildCount() > mMaxRow) {
- if (i < mMaxRow * indexMulti) {
- child.layout(getPaddingLeft() + mlp.leftMargin + indexMultiWidth, getPaddingTop() + mlp.topMargin + indexMultiHeight,
- childActualSize + getPaddingLeft() + mlp.leftMargin + indexMultiWidth, childActualSize + getPaddingTop()
- + mlp.topMargin + indexMultiHeight);
- indexMultiHeight += childActualSize + mlp.topMargin + mlp.bottomMargin;
- tempWidth = Math.max(tempWidth, childActualSize) + mlp.leftMargin + mlp.rightMargin;
- if (i + 1 >= mMaxRow * indexMulti) {
- indexMultiWidth += tempWidth;
- indexMultiHeight = 0;
- indexMulti++;
- }
- }
- } else {
- // 確定子元素左上、右下座標
- child.layout(getPaddingLeft() + mlp.leftMargin, getPaddingTop() + mlp.topMargin + multi, childActualSize
- + getPaddingLeft() + mlp.leftMargin, childActualSize + getPaddingTop() + mlp.topMargin + multi);
- // 累加倍增值
- multi += childActualSize + mlp.topMargin + mlp.bottomMargin;
- }
- }
- }
- }
- }
- }
- // 省去四個屌毛方法……
- }
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:background="#ffffff" >
- <com.aigestudio.customviewdemo.views.SquareLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_margin="5dp"
- android:background="#679135"
- android:paddingBottom="20dp"
- android:paddingLeft="5dp"
- android:paddingRight="7dp"
- android:paddingTop="12dp" >
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="#125793"
- android:text="tomorrow"
- android:textSize="24sp"
- android:textStyle="bold"
- android:typeface="serif" />
- <Button
- android:layout_width="50dp"
- android:layout_height="100dp"
- android:layout_marginBottom="5dp"
- android:layout_marginLeft="10dp"
- android:layout_marginRight="20dp"
- android:layout_marginTop="30dp"
- android:background="#495287"
- android:text="AigeStudio" />
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="50dp"
- android:layout_marginLeft="5dp"
- android:layout_marginRight="20dp"
- android:layout_marginTop="15dp"
- android:background="#976234" >
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:scaleType="centerCrop"
- android:src="@drawable/lovestory_little" />
- </LinearLayout>
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="#594342"
- android:text="AigeStudio" />
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="#961315"
- android:text="Welcome AigeStudio" />
- </com.aigestudio.customviewdemo.views.SquareLayout>
- </LinearLayout>
運行後的顯示效果:
換成縱向排列看看:
運行後的效果:
嘗試更改下縱向排列時的限制值:
- // 初始化最大行列數
- mMaxRow = 2;
- mMaxColumn =3;
表示暫時木有發現什麼大問題,OK,這兩個屬性值的實現就到這裏,雖然只有兩個屬性值 = = TMD實在是菊緊啊,可想而知LinearLayout等佈局這麼多屬性控制是有多蛋疼了麼,不過如我文章開頭所說,我們的這個自定義佈局實用意義不大,主要還是給大家演示瞭解下自定義佈局是有多麼蛋疼、啊不……是由多麼複雜,像系統自帶的那些佈局控件都是經過N多update版本纔有今天,即便如此,依然還有很多BUG,不過大多不會影響實際使用我們也可以很好地解決,所以,再次強調、控件的測量是一個極爲嚴謹縝密的過程,稍有不慎你的控件便到處都會是說不出的BUG~~~~~上一節我們爲了能讓我們的自定義佈局能對外邊距進行計算,我們定義了一個內部類LayoutParams繼承於MarginLayoutParams但是其中什麼也沒做,而這一節呢我們沒有定義這麼一個內部類而是直接返回MarginLayoutParams的實例,我們之所以能從佈局參數中獲取到外邊距的屬性值,比如:
- // 獲取子元素佈局參數
- MarginLayoutParams mlp = (MarginLayoutParams) child.getLayoutParams();
- mlp.leftMargin
- mlp.topMargin
- mlp.rightMargin
- mlp.bottomMargin
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- // 省去無數代碼…………
- public static class LayoutParams extends MarginLayoutParams {
- public int mGravity;// 對齊方式
- public LayoutParams(MarginLayoutParams source) {
- super(source);
- }
- public LayoutParams(android.view.ViewGroup.LayoutParams source) {
- super(source);
- }
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
- }
- public LayoutParams(int width, int height) {
- super(width, height);
- }
- }
- }
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- // 省去無數代碼…………
- @Override
- protected LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- }
- @Override
- protected android.view.ViewGroup.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams p) {
- return new LayoutParams(p);
- }
- @Override
- public android.view.ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
- @Override
- protected boolean checkLayoutParams(android.view.ViewGroup.LayoutParams p) {
- return p instanceof LayoutParams;
- }
- // 省去LayoutParams的定義…………
- }
- // 獲取子元素佈局參數
- LayoutParams mlp = (LayoutParams) child.getLayoutParams();
- if (mlp.mGravity == xxxxxxx) {
- ………………………………………………………………
- }
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="#125793"
- android:text="tomorrow"
- android:textSize="24sp"
- android:textStyle="bold"
- android:typeface="serif"
- <!-- http://blog.csdn.net/aigestudio -->
- <declare-styleable name="SquareLayout">
- <attr name="my_gravity" format="enum">
- <enum name="left" value="0" />
- <enum name="right" value="1" />
- <enum name="center" value="2" />
- <enum name="top" value="3" />
- <enum name="bottom" value="4" />
- </attr>
- </declare-styleable>
- <Button
- xmlns:aigestudio="http://schemas.android.com/apk/res/com.aigestudio.customviewdemo"
- aigestudio:my_gravity="left" />
- xmlns:你想要的名字="http://schemas.android.com/apk/res/完整包名"
- xmlns:你想要的名字="http://schemas.android.com/apk/res-auto"
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- // 省去無數代碼…………
- public static class LayoutParams extends MarginLayoutParams {
- public int mGravity;// 對齊方式
- public LayoutParams(MarginLayoutParams source) {
- super(source);
- }
- public LayoutParams(android.view.ViewGroup.LayoutParams source) {
- super(source);
- }
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
- /*
- * 獲取xml對應屬性
- */
- TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.SquareLayout);
- mGravity = a.getInt(R.styleable.SquareLayout_my_gravity, 0);
- }
- public LayoutParams(int width, int height) {
- super(width, height);
- }
- }
- }
- <!-- http://blog.csdn.net/aigestudio -->
- <declare-styleable name="AttrView">
- <!-- 引用資源 -->
- <attr name="image" format="reference" />
- <!-- 顏色 -->
- <attr name="text_color" format="color" />
- <!-- 布爾值 -->
- <attr name="text_display" format="boolean" />
- <!-- 尺寸大小 -->
- <attr name="temp1" format="dimension" />
- <!-- 浮點值 -->
- <attr name="temp2" format="float" />
- <!-- 整型值 -->
- <attr name="temp3" format="integer" />
- <!-- 字符串 -->
- <attr name="text" format="string" />
- <!-- 百分比 -->
- <attr name="alpha" format="fraction" />
- <!-- 枚舉 -->
- <attr name="text_align" format="integer">
- <enum name="left" value="0" />
- <enum name="right" value="1" />
- <enum name="center" value="2" />
- </attr>
- <!-- 位運算 -->
- <attr name="text_optimize" format="integer">
- <flag name="anti" value="0x001" />
- <flag name="dither" value="0x002" />
- <flag name="linear" value="0x004" />
- </attr>
- </declare-styleable>
- getFraction(int index, int base, int pbase, float defValue)
- aigestudio:alpha="10%"
- aigestudio:alpha="10%p"
- aigestudio:text_optimize="anti|dither"
- /*
- * 畫筆優化的標識位們
- */
- private static final int OPTIMIZE_ANTI = 0x001, OPTIMIZE_DITHER = 0x002, OPTIMIZE_LINEAR = 0x004, OPTIMIZE_ANTI_DITHER = 0x003, OPTIMIZE_ANTI_LINEAR = 0x005, OPTIMIZE_DITHER_LINEAR = 0x006, OPTIMIZE_ALL = 0x007;
- /**
- *
- * @author AigeStudio {@link http://blog.csdn.net/aigestudio}
- * @since 2015/1/23
- *
- */
- public class SquareLayout extends ViewGroup {
- // 省去無數代碼…………
- public static class LayoutParams extends MarginLayoutParams {
- public int mGravity = Gravity.LEFT | Gravity.RIGHT;// 對齊方式
- // 省去沒變的代碼…………
- }
- }
- <declare-styleable name="SquareLayout">
- <attr name="android:layout_gravity" />
- </declare-styleable>
- public static void apply(int gravity, int w, int h, Rect container, Rect outRect)
- @Override
- public boolean shouldDelayChildPressedState() {
- return false;
- }
源碼下載:傳送門