一、先看效果圖
第二張圖是測(cè)試顯示結(jié)果,第一張圖是網(wǎng)上找的,效果大概就和第一張最后的那個(gè)效果一樣。

效果圖一.gif

10AA2DDF198D76A2DE249170A07F4EBA.jpg
二、自定義View的套路
1. 自定義屬性
2. 測(cè)量控件的寬高
3. 擺放控件的位置
4. 繪制控件
5. 用戶交互(事件處理)
三、分析衛(wèi)星菜單特有的屬性
1. 控件是弧形擺放,所以要有一個(gè)弧形的半徑。
2. 控件弧形擺放,從第一個(gè)擺放到最后一個(gè)要有一個(gè)角度。
3. 控件展開的背景色。
四、定義衛(wèi)星菜單特有的屬性
<declare-styleable name="ArcLayout">
<!--擺放的角度:0-180度 -->
<attr name="arcAngle" format="float" />
<!--擺放的半徑-->
<attr name="arcRadius" format="dimension" />
<!--子菜單展開的背景色-->
<attr name="arcSpreadColor" format="color" />
</declare-styleable>
五、創(chuàng)建自定義衛(wèi)星菜單ArcLayout,并獲取特有屬性
public class ArcLayout extends ViewGroup implements View.OnClickListener {
// 擺放的總角度
private float mArcAngle = 100f;
// 擺放的半徑
private float mArcRadius = 120f;
// 展開的背景色
private int mArcSpreadColor = Color.TRANSPARENT;
// 開始的角度
private float mStartAngle;
public ArcLayout(Context context) {
this(context, null);
}
public ArcLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ArcLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ArcLayout);
mArcAngle = array.getFloat(R.styleable.ArcLayout_arcAngle, mArcAngle);
mArcRadius = array.getDimension(R.styleable.ArcLayout_arcRadius, dip2px(mArcRadius));
mArcSpreadColor = array.getColor(R.styleable.ArcLayout_arcSpreadColor, mArcSpreadColor);
// 計(jì)算開始的角度
// rad 是弧度, deg 是角度
// rad(弧度) = deg(角度) * PI / 180
mStartAngle = (float) ((180 - mArcAngle) / 2 * (Math.PI / 180));
array.recycle();
// 設(shè)置背景色
setBackgroundColor(mArcSpreadColor);
mBackground = getBackground();
if (mBackground != null) {
mBackground.setAlpha(0);
}
}
private float dip2px(float value) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getResources().getDisplayMetrics());
}
}
六、測(cè)量子衛(wèi)星菜單View的大小 和計(jì)算每個(gè)子菜單偏移平分的角度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 測(cè)量子控件的寬高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 計(jì)算平分的角度
mSquareAngle = (float) (mArcAngle / (childCount - 2) * (Math.PI / 180));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// 獲取控件的寬高
mWidth = w;
mHeight = h;
}
七、衛(wèi)星菜單控件的擺放
先看擺放示意圖

先看擺放示意圖.png
其中p1x 、p1y是子控件的左上角的坐標(biāo)。
子控件的擺放分兩步走:
- 擺放底部中間的子控件
- 擺放弧形位置的子菜單控件
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
layoutCenter();
layoutMenu();
}
}
/**
* 擺放第一個(gè)控件
*/
private void layoutCenter() {
mCenterChild = getChildAt(0);
// 第一個(gè)子控件的寬高
int width = mCenterChild.getMeasuredWidth();
int height = mCenterChild.getMeasuredHeight();
int l = mWidth / 2 - width / 2;
int t = mHeight - height-getPaddingBottom();
mCenterChild.layout(l, t, l + width, t + height);
// 第一個(gè)子控件的點(diǎn)擊事件
mCenterChild.setOnClickListener(this);
}
/**
* 擺放子菜單
*/
private void layoutMenu() {
int childCount = getChildCount();
if (childCount < 2) {
return;
}
for (int i = 1; i < childCount; i++) {
View child = getChildAt(i);
// 子控件的寬高
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
float Q = mStartAngle + mSquareAngle * (i - 1);
// left = 寬度的一半 - x - 子控件寬度的一半
// top = 高度 - y - 子控件高度的一半
int cl = (int) (mWidth / 2 - Math.cos(Q) * mArcRadius - width / 2);
int ct = (int) (mHeight - Math.sin(Q) * mArcRadius - height / 2-getPaddingBottom());
// 擺放子菜單
child.layout(cl, ct, cl + width, ct + height);
// 默認(rèn)隱藏子菜單
child.setVisibility(GONE);
final int position = i;
// 子菜單的點(diǎn)擊事件
child.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
menuItemClick(v, position);
}
});
}
}
其實(shí)開始的時(shí)候,控件都全部擺放好,默認(rèn)將子控件都隱藏。
八、處理底部中間的子控件的點(diǎn)擊事件
1. 點(diǎn)擊后,記錄點(diǎn)擊狀態(tài),如果菜單隱藏,就展開,如果是展開就隱藏。
2. 創(chuàng)建隱藏和打開子菜單的動(dòng)畫
3. 點(diǎn)擊后改變菜單的狀態(tài)
4. 改變背景色
5. 如果動(dòng)畫執(zhí)行就屏蔽點(diǎn)擊事件等一些細(xì)節(jié)處理
// 默認(rèn)關(guān)閉
private Status mStatus = Status.CLOSE;
// 菜單的狀態(tài)
public enum Status {
CLOSE, OPEN
}
/**
* 關(guān)閉或者打開菜單
*/
public void toggle() {
// 中間主要的View設(shè)置旋轉(zhuǎn)動(dòng)畫
Animation animation = ArcAnimUtil.rotateCenterAnim(mDuration);
mCenterChild.startAnimation(animation);
// 背景動(dòng)畫
bgAnim();
// 中間子View設(shè)置平移和旋轉(zhuǎn)動(dòng)畫
int childCount = getChildCount();
for (int i = 1; i < childCount; i++) {
final View child = getChildAt(i);
child.setVisibility(VISIBLE);
// 子控件的高
int height = child.getMeasuredHeight();
float Q = mStartAngle + mSquareAngle * (i - 1);
// 離當(dāng)前View X坐標(biāo)上的距離
int fromXDelta = (int) (Math.cos(Q) * mArcRadius);
// 離當(dāng)前View Y坐標(biāo)上的距離
int fromYDelta = (int) (Math.sin(Q) * mArcRadius - height / 2-getPaddingBottom());
// 動(dòng)畫
Animation anim = null;
if (mStatus == Status.CLOSE) {
// 展開的動(dòng)畫
anim = ArcAnimUtil
.menuAnim(fromXDelta, 0, fromYDelta, 0, i, mDuration);
child.setEnabled(true);
} else {
// 關(guān)閉的動(dòng)畫
anim = ArcAnimUtil
.menuAnim(0, fromXDelta, 0, fromYDelta, i, mDuration);
child.setEnabled(false);
}
anim.setAnimationListener(new AnimationAdapter() {
@Override
public void onAnimationStart(Animation animation) {
mIsStart = true;
}
@Override
public void onAnimationEnd(Animation animation) {
mIsStart = false;
if (mStatus == Status.CLOSE) {
child.clearAnimation();
child.setVisibility(GONE);
// 關(guān)閉的背景設(shè)置為透明
setClickable(false);
} else {
// 設(shè)置背景
setClickable(true);
}
}
});
child.startAnimation(anim);
}
// 改變菜單的狀態(tài)
changeStatus();
}
/**
* 背景動(dòng)畫
*/
private void bgAnim() {
// 背景動(dòng)畫
ValueAnimator valueAnimator = null;
if (mStatus == Status.CLOSE) {
// 展開
valueAnimator = ObjectAnimator.ofFloat(0, 1);
} else {
// 關(guān)閉
valueAnimator = ObjectAnimator.ofFloat(1, 0);
}
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
int alpha = (int) (value * 255);
if (mBackground != null) {
mBackground.setAlpha(alpha);
}
}
});
valueAnimator.setDuration(mDuration);
valueAnimator.start();
}
/**
* 改變菜單的狀態(tài)
*/
private void changeStatus() {
mStatus = mStatus == Status.OPEN ? Status.CLOSE : Status.OPEN;
}
九、處理子菜單的點(diǎn)擊事件
1. 執(zhí)行用戶回調(diào)監(jiān)聽
2. 執(zhí)行子菜單的點(diǎn)擊動(dòng)畫
3. 執(zhí)行背景動(dòng)畫
4. 改變菜單的狀態(tài)
/**
* 子菜單的點(diǎn)擊事件
*/
private void menuItemClick(View view, int position) {
if (mIsStart) {
return;
}
if (mOnMenuItemClickListener != null) {
mOnMenuItemClickListener.onMenuItemClick(view, position);
}
// 設(shè)置控件不能點(diǎn)擊
setClickable(false);
int childCount = getChildCount();
for (int i = 1; i < childCount; i++) {
View child = getChildAt(i);
Animation animation = null;
if (i == position) {
animation = ArcAnimUtil
.menuItemAnim(1f, 1.2f, 1f, 1.2f, mDuration);
} else {
animation = ArcAnimUtil
.menuItemAnim(1f, 0f, 1f, 0f, mDuration);
}
animation.setAnimationListener(new AnimationAdapter() {
@Override
public void onAnimationStart(Animation animation) {
mIsStart = true;
}
@Override
public void onAnimationEnd(Animation animation) {
mIsStart = false;
}
});
child.startAnimation(animation);
child.setEnabled(false);
}
// 背景動(dòng)畫
bgAnim();
// 改變當(dāng)前狀態(tài)
changeStatus();
}
十、動(dòng)畫的幫助類
/**
* 衛(wèi)星布局--動(dòng)畫的工具類
*/
public class ArcAnimUtil {
/**
* 旋轉(zhuǎn)360度
*/
public static Animation rotateCenterAnim(int duration) {
RotateAnimation ta = new RotateAnimation(0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
ta.setDuration(duration);
ta.setFillAfter(true);
return ta;
}
public static Animation menuAnim(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta, int position, int duration) {
AnimationSet set = new AnimationSet(true);
// romXDelta:動(dòng)畫開始前,離當(dāng)前View X坐標(biāo)上的距離。
// toXDelta:動(dòng)畫結(jié)束后,離當(dāng)前View X坐標(biāo)上的距離。
// fromYDelta:動(dòng)畫開始前,離當(dāng)前View Y坐標(biāo)上的距離。
// toYDelta:動(dòng)畫結(jié)束后,離當(dāng)前View Y坐標(biāo)上的距離。
Animation transAnim = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);
;
transAnim.setStartOffset((position * 50));
RotateAnimation ta = new RotateAnimation(0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
set.setDuration(duration);
set.setFillAfter(true);
set.addAnimation(ta);
set.addAnimation(transAnim);
return set;
}
public static Animation menuItemAnim(float fromX, float toX, float fromY, float toY, int duration) {
AnimationSet set = new AnimationSet(true);
ScaleAnimation sa = new ScaleAnimation(fromX, toX, fromY, toY,
Animation.RELATIVE_TO_SELF, Animation.RELATIVE_TO_SELF);
AlphaAnimation aa = new AlphaAnimation(1.0f, 0);
set.setDuration(duration);
set.setFillAfter(true);
set.addAnimation(sa);
set.addAnimation(aa);
return set;
}
}
動(dòng)畫的代碼沒什么復(fù)雜的,自已去看看就好。
十一、其它的一些處理
1. 菜單展開后,點(diǎn)擊控件關(guān)閉菜單。
2. 菜單展開后,屏蔽掉控件的點(diǎn)擊事件和子菜單的點(diǎn)擊事件
3. 其它一點(diǎn)小細(xì)節(jié)的處理
4. 最后測(cè)試,效果圖在上面開始的時(shí)候
參考了鴻洋大神的代碼,感謝鴻洋大神的
Android 自定義ViewGroup手把手教你實(shí)現(xiàn)ArcMenu