在前期的繪制系列文章中,我們主要學習如何在
View
内部使用畫筆工具自行繪制,以達到UI稿頁面效果,但并不是所有效果都适合通過
onDraw
實作。
以下圖為例:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICMyYTMvw1dvwlMvwlM3VWaWV2Zh1Wa-cmbw5SN3F2YudXbzwWdvwFO4IjMzgzNtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.png)
界面上有六個按鈕,其中一個位于頁面底部居中,其他五個呈半圓形拱衛。
這種情況下使用繪制系列中相關的知識去實作,就會發現很麻煩,主要展現在以下幾點:
- 繪制的控件點選事件響應
- 在點選事件發生後,外部五個按鈕的收起展開動畫效果
在View繪制系列(3)-自定義View簡介 一文中,我們已經介紹了自定義
View
的常見實作方式以及主要過程,其中就提到我們可以有三種實作自定義
View
的方式:
- 繼承自
View
- 繼承自
ViewGroup
- 繼承已有控件(
,ImageView
,TextView
LinearLayout
等諸如此類系統控件)
這裡我們就可以通過繼承
,重新排布其内部的子ViewGroup
來實作上圖中的UI效果。View
繼承ViewGroup
建立
SemiCircleMenuView
繼承自
ViewGroup
,并重寫構造及
onLayout
方法,代碼如下:
public class SemiCircleMenuView extends ViewGroup {
public SemiCircleMenuView(Context context) {
super(context);
}
public SemiCircleMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SemiCircleMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}
複制
測量子View
重寫
onMeasure
,調用
measureChild
方法進行子
View
大小測量,代碼如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//将布局參數應用到子View,驅動子View進行自身測量
int count = getChildCount();
for (int i = ; i < count; i++) {
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
複制
擷取拱衛中心坐标
從上圖中可以看出,最中心的控件位于(ViewGroup.Width/2,ViewGroup.Height)位置,故建立變量如下:
/**
* ViewGroup的寬度高度
*/
private int mWidth;
private int mHeight;
/**
* 子View圍繞的圓心坐标
*/
private int mChildrenCenterX;
private int mChildrenCenterY;
複制
重寫
onSizeChanged
方法:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
//指派中心點坐标
mChildrenCenterX = w / ;
mChildrenCenterY = h;
}
複制
布局中心View
上一步中已經擷取到了中心
View
的放置坐标(
mChildrenCenterX
,
mChildrenCenterY
),随後調用
layout
方法進行
View
放置,代碼如下:
private void layoutCenterItem() {
View view = getChildAt();
int centerItemWidth = view.getMeasuredWidth();
int centerItemHeight = view.getMeasuredHeight();
view.layout(mChildrenCenterX - centerItemWidth / , mChildrenCenterY - centerItemHeight, mChildrenCenterX + centerItemWidth / , mChildrenCenterY);
}
複制
布局周圍的拱衛View
由于周圍拱衛的菜單
View
位于同一圓周上,假設圓周半徑為
mRadius
,弧度為A,那麼拱衛菜單的位置坐标為:
positionX = mChildrenCenterX + (int)(mRadius * Math.cos(A))
positionY = mChildrenCenterY + (int)(mRadius*Math.sin(A))
由于在
Layout
過程中以水準向右順時針方向為正角,那麼要實作圖上效果,拱衛
View
是分布在弧度為0到-Math.PI的弧段上,進而A的取值為(0,-Math.PI).
布局拱衛菜單
View
的代碼如下:
private void layoutMenuItem() {
int count = getChildCount();
for (int i = ; i < count; i++) {
View menuItem = getChildAt(i);
int menuItemWidth = menuItem.getMeasuredWidth();
int menuItemHeight = menuItem.getMeasuredHeight();
int menuPositionX = (int) (mChildrenCenterX + mRadius * Math.cos(-Math.PI / (count - ) * (i-1)));
int menuPositionY = (int) (mChildrenCenterY + mRadius * Math.sin(-Math.PI / (count - ) * (i-1)));
menuItem.layout(menuPositionX - menuItemWidth / , menuPositionY - menuItemHeight, menuPositionX + menuItemWidth / , menuPositionY);
}
}
複制
這裡count-2的原因在于剩餘count-1個View會将180度的圓周均分成count-2份
這裡i-1的原因在于第一個角度為0度,而我們i取值從1開始
運作
在布局中使用該View:
<com.poseidon.testapplication.SemiCircleMenuView
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round"></ImageButton>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round"></ImageButton>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round"></ImageButton>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round"></ImageButton>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round"></ImageButton>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round"></ImageButton>
</com.poseidon.testapplication.SemiCircleMenuView>
複制
在onlayout中調用布局方法并運作,檢視效果,代碼如下:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildCount() <= ) {
return;
}
//中心位置放置首個子元素
layoutCenterItem();
layoutMenuItem();
}
複制