Android開發技巧——實現設計師給出的視覺居中的佈局
本篇主要是對自定義控制元件的測量方法(
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
)在實際場景中的運用。
在移動應用的設計中,經常有這樣的介面:某個介面的元素非常少,比如空列表介面,或者某某操作成功的介面,只有一兩個元素在中間。但是它們在某個佈局裡又不是數學上的那個居中,而是經過設計師調出來的“視覺居中”。這種“視覺居中”內部是怎麼計算的,我大致也不懂,反正結果就是設計師們看起來要顯示的資訊給人有感覺是在中間的(通常是比中間偏上一點)。
既是這樣,那我們在佈局中就不能用gravity="center"
或layout_gravity="center"
等這樣的屬性來設定了。而使用固定的padding或margin來調整它的位置,也難以在不同的螢幕解析度中實現同樣的效果,那就只好按鈕設計圖的標註,按比例來計算它的位置了。
按比例來調整子view與layout中的距離,在約束佈局(ConstraintLayout)中是可以做到的,但是在我個人看來相對這樣簡單的需求,約束佈局有點重了,並且它的依賴在不同方式的編譯下總是很容易出問題(比如在自己電腦編譯通過,在travis-ci上編譯就提示找不到該庫的某個版本),還有拖拽生成的程式碼格式不是很整齊(我有程式碼潔癖),總需要自己再去格式化一下程式碼。那麼自定義實現一下也是可以的嘛。
首先像這樣簡單的介面,通常來說,使用LinearLayout
就足夠了。我們所需要的只是按比例計算出padding然後設進去,那麼內容就能夠按我們想要的位置去顯示了。在這裡,我把這個佈局定義為PaddingWeightLinearLayout
,即可以按權重來設計它的padding。它提供了四個屬性:
<declare-styleable name="PaddingWeightLinearLayout">
<attr name="ppLeftPadding" format="integer"/>
<attr name="ppRightPadding" format="integer"/>
<attr name="ppTopPadding" format="integer"/>
<attr name="ppBottomPadding" format="integer"/>
</declare-styleable>
分別代表每個方向上的padding的權重。
在構造方法裡獲取這些屬性的值:
private final int mTopWeight;
private final int mBottomWeight;
private final int mLeftWeight;
private final int mRightWeight;
public PaddingWeightLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PaddingWeightLinearLayout);
mTopWeight = ta.getInteger(R.styleable.PaddingWeightLinearLayout_ppTopPadding, 0);
mBottomWeight = ta.getInteger(R.styleable.PaddingWeightLinearLayout_ppBottomPadding, 0);
mLeftWeight = ta.getInteger(R.styleable.PaddingWeightLinearLayout_ppLeftPadding, 0);
mRightWeight = ta.getInteger(R.styleable.PaddingWeightLinearLayout_ppRightPadding, 0);
ta.recycle();
}
那麼接下來,我們只需要計算出所有子View所佔的空間,然後計算出水平和豎直方向上剩餘的空間,按比例分給這四個padding就可以了。之所以使用LinearLayout是因為它是線性佈局,子View按線性排列,比較利於計算。如下圖(電腦的圖畫不好,獻醜了):
圖1表示的是水平方向的佈局,那麼它的內容所佔的大小是:
- 寬度為所有子View的寬度加上其左右Margin的總和。
- 高度為子View高度加上其上下Margin中最高的一個。
圖2 是豎直方向的佈局,它的內容所佔的大小是:
- 寬度為子View寬度加上其左右Margin中最大的一個。
- 高度為所有子View的高度加上其上下Margin的總和。
因此,我們要先測量出子View的大小,然後再進行計算。
測試是在onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法中進行的。其中引數分別表示父佈局能夠提供給它的空間。這個int型別的引數分為兩部分,高2位表示的是模式(mode),後面的30位表示的是具體的大小。這裡的mode一共有三個:
-
UNSPECIFIED
父View對子View沒有任何約束,可以隨意指定大小 -
EXACTLY
父View給子View一個固定的大小,子View會被這些邊界限制,不管它自己想要多大 -
AT_MOST
子檢視的大小可以是自己想要的值,但是不能超過指定的值
當我們要計運算元View時,我們需要呼叫子View的measure(widthMeasureSpec, int heightMeasureSpec)
方法,為了能夠得到子View的確定大小,我們需要將widthMeasureSpec
mode
指定為AT_MOST
,程式碼如下(以下程式碼都是在onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法內):
int layoutWidth = MeasureSpec.getSize(widthMeasureSpec);
int layoutHeight = MeasureSpec.getSize(heightMeasureSpec);
int widthSpec = MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.AT_MOST);
int heightSpec = MeasureSpec.makeMeasureSpec(heightMeasureSpec, MeasureSpec.AT_MOST);
然後遍歷所有子View,計運算元View寬高的總和及最大值:
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
child.measure(widthSpec, heightSpec);
LayoutParams params = (LayoutParams) child.getLayoutParams();
int width = child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
int height = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
totalWidth += width;
totalHeight += height;
if (width > maxWidth) {
maxWidth = width;
}
if (height > maxHeight) {
maxHeight = height;
}
}
然後計算出在水平及豎直方向上剩餘的空間:
int spaceHorizontal;
int spaceVertical;
if (getOrientation() == VERTICAL) {
spaceHorizontal = layoutWidth - maxWidth;
spaceVertical = layoutHeight - totalHeight;
} else {
spaceHorizontal = layoutWidth - totalWidth;
spaceVertical = layoutHeight - maxHeight;
}
if (spaceHorizontal < 0) {
spaceHorizontal = 0;
}
if (spaceVertical < 0) {
spaceVertical = 0;
}
最後算出各個方向的padding,設定進去,然後重新呼叫父類的onMeasure(int, int)
方法:
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int horizontalWeight = mLeftWeight + mRightWeight;
if (spaceHorizontal > 0 && horizontalWeight > 0) {
paddingLeft = mLeftWeight * spaceHorizontal / horizontalWeight;
paddingRight = spaceHorizontal - paddingLeft;
}
int verticalWeight = mTopWeight + mBottomWeight;
if (spaceVertical > 0 && verticalWeight > 0) {
paddingTop = mTopWeight * spaceVertical / verticalWeight;
paddingBottom = spaceVertical - paddingTop;
}
setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
下面我們只需要在寫佈局程式碼的時候,按照設計圖填入標註的padding值,就可以按比例計算出內邊距並設定,從而讓我們的內容按照比例位置顯示了:
<com.parkingwang.widget.PaddingWeightLinearLayout
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"
android:background="@android:color/white"
android:orientation="vertical"
android:paddingLeft="@dimen/screen_padding"
android:paddingRight="@dimen/screen_padding"
app:ppBottomPadding="287"
app:ppTopPadding="85">
... 內容佈局程式碼
</com.parkingwang.widget.PaddingWeightLinearLayout>
下面分別是實現的效果以及設計圖,可以看到,在內容區域它們所在的位置是相同的(由於螢幕解析度的關係,大小會有微小差異)。
完整程式碼請參見Github專案:https://github.com/msdx/androidsnippet
相關文章
- 佈局總結-水平居中佈局的實現
- 寫給 Android 開發的小程式佈局指南,Flex 佈局!AndroidFlex
- 視覺化佈局模組開發分享視覺化
- Android 日常開發中,兩個非常實用的佈局技巧Android
- 自律給你自由——Android設計佈局的新姿勢Android
- 水平垂直居中佈局的多種實現方式
- 10個提高Android視覺效果的設計技巧Android視覺
- 基於bootstrap實現視覺化佈局工具boot視覺化
- CSS佈局 --- 居中佈局CSS
- 網頁佈局實現之div垂直居中網頁
- 居中佈局、三欄佈局
- Android開發 - 檢視佈局屬性解析Android
- 自律給你自由——設計佈局的新姿勢
- 【佈局技巧】Flex 佈局下居中溢位滾動截斷問題Flex
- 視覺設計師是怎樣讓前端工程師 100% 實現設計效果的?視覺前端工程師
- CSS 佈局之水平居中佈局CSS
- L – 居中佈局
- 關於css佈局、居中的問題以及一些小技巧CSS
- 網頁設計有哪些佈局的方法和技巧?網頁
- android 相對佈局,程式碼建立imageview,佈局居中問題AndroidView
- Android佈局優化技巧Android優化
- 自由程式設計師的3個開發技巧程式設計師
- 解鎖Android設計佈局的新姿勢Android
- css居中與佈局CSS
- CSS之居中佈局CSS
- Android開發之常用佈局Android
- android: 動態載入碎片佈局的技巧Android
- Android 佈局小技巧彙總Android
- Android 實現一個通用的圓角佈局Android
- Android最佳效能實踐(4):佈局優化技巧Android優化
- 資料視覺化設計的25個小技巧視覺化
- CSS佈局之水平居中和垂直居中CSS
- 佈局技巧:合併佈局
- css經典佈局系列一——垂直居中佈局CSS
- CSS如何佈局與居中CSS
- CSS佈局-各種居中CSS
- 網頁佈局CSS技巧-Web設計必知網頁CSSWeb
- 深入淺出排序學習: 寫給程式設計師的演算法系統開發實踐排序程式設計師演算法