最近看到一个比较不错的菜单效果,大家都叫它卫星菜单。效果就是点击一个按钮在它的周围弹射出子菜单,效果如图:
如何封装一个这个这样的菜单?
1.创建一个类继承ViewGroup,先贴上ArcMenu类的所有代码,下面仔细讲解
package com.def.view;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import com.example.arcmenudemo.R;
public class ArcMenu extends ViewGroup implements OnClickListener {
private static final String TAG = "quw.ArcMenu";
private static final int POS_LEFT_TOP = 0;
private static final int POS_LEFT_BUTTOM = 1;
private static final int POS_RIGHT_TOP = 2;
private static final int POS_RIGHT_BUTTOM = 3;
private static final int STATUS_CLOSE = 0;
private static final int STATUS_OPEN = 1;
private int mPosition = POS_RIGHT_BUTTOM;
private int mRadius = 0;
private int mStatus = STATUS_CLOSE;
/**
* 启动菜单的主按钮
*/
private View mCButton;
private OnMenuItemClickListener mMenuItemClickListener;
public void setOnMenuItemClickListener(
OnMenuItemClickListener l) {
this.mMenuItemClickListener = l;
}
public ArcMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
100, getResources().getDisplayMetrics());
TypedArray typedArray = context.getTheme().obtainStyledAttributes(
attrs, R.styleable.ArcMenu, defStyle, 0);
mPosition = typedArray.getInt(R.styleable.ArcMenu_position,
POS_RIGHT_BUTTOM);
mRadius = (int) typedArray.getDimension(R.styleable.ArcMenu_radius,
mRadius);
typedArray.recycle();
Log.i(TAG, "mPosition = " + mPosition + ", mRadius = " + mRadius);
}
public ArcMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO Auto-generated constructor stub
}
public ArcMenu(Context context) {
this(context, null, 0);
// TODO Auto-generated constructor stub
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
int count = getChildCount();
for (int i = 0; i < count; i++) {
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
if (changed) {
layoutButton();
}
}
private void layoutButton() {
/**
* 计算所有按钮的初始放置位置
*/
int count = getChildCount();
mCButton = getChildAt(count - 1);
int l = 0;
int t = 0;
int width = mCButton.getMeasuredWidth();
int height = mCButton.getMeasuredHeight();
switch (mPosition) {
case POS_LEFT_TOP:
break;
case POS_LEFT_BUTTOM:
t = getMeasuredHeight() - height;
break;
case POS_RIGHT_TOP:
l = getMeasuredWidth() - width;
break;
case POS_RIGHT_BUTTOM:
l = getMeasuredWidth() - width;
t = getMeasuredHeight() - height;
break;
}
/**
* 放置菜单按钮
*/
mCButton.layout(l, t, l + width, t + height);
mCButton.setClickable(true);
mCButton.setFocusable(true);
mCButton.setOnClickListener(this);
/**
* 放置子按钮
*/
for (int i = 0; i < count - 1; i++) {
View childView = getChildAt(i);
childView.layout(l, t, l + width, t + height);
childView.setVisibility(View.GONE);
final int pos = i;
childView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
clickItemAnim(pos);
changeStatus();
if (mMenuItemClickListener != null) {
mMenuItemClickListener.onItemClick(v, pos);
}
}
});
}
}
public interface OnMenuItemClickListener {
void onItemClick(View view, int position);
}
@Override
public void onClick(View v) {
rotateCButton();
toggleMenu();
}
private void toggleMenu() {
int itemCount = getChildCount() - 1;
for (int i = 0; i < itemCount; i++) {
final View child = getChildAt(i);
child.setVisibility(View.VISIBLE);
child.setClickable(true);
child.setFocusable(true);
/**
* 计算出子按钮位移距离
*/
int x = (int) (mRadius * Math.sin(Math.PI / 2 / (itemCount - 1)
* i));
int y = (int) (mRadius * Math.cos(Math.PI / 2 / (itemCount - 1)
* i));
/**
* 判断出位移方向
*/
int xflag = 1;
int yflag = 1;
if (mPosition == POS_RIGHT_TOP || mPosition == POS_RIGHT_BUTTOM) {
xflag = -1;
}
if (mPosition == POS_LEFT_BUTTOM || mPosition == POS_RIGHT_BUTTOM) {
yflag = -1;
}
ObjectAnimator transXAnimator = null;
ObjectAnimator transYAnimator = null;
ObjectAnimator rotationAnimator = null;
AnimatorSet animatorSet = new AnimatorSet();
if (mStatus == STATUS_CLOSE) {// 设置弹出动画
transXAnimator = ObjectAnimator.ofFloat(child, "translationX",
0,
x * xflag);
transYAnimator = ObjectAnimator.ofFloat(child, "translationY",
0,
y * yflag);
} else {// 设置收回的动画
transXAnimator = ObjectAnimator.ofFloat(child, "translationX",
x
* xflag, 0);
transYAnimator = ObjectAnimator.ofFloat(child, "translationY",
y
* yflag, 0);
}
rotationAnimator = ObjectAnimator.ofFloat(child, "rotation", 0,
720f);
animatorSet.playTogether(transXAnimator, transYAnimator,
rotationAnimator);
animatorSet.setDuration(300);
animatorSet.setStartDelay(25 * i);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
if (mStatus == STATUS_CLOSE) {
child.setVisibility(View.GONE);
child.setClickable(false);
child.setFocusable(false);
}
super.onAnimationEnd(animation);
}
});
animatorSet.start();
}
changeStatus();
}
private void clickItemAnim(int pos) {
// TODO Auto-generated method stub
int itemCount = getChildCount() - 1;
for (int i = 0; i < itemCount; i++)
{
final View childView = getChildAt(i);
AnimatorSet animatorSet = null;
if (i == pos) {
animatorSet = scaleBigAnim(childView);
} else {
animatorSet = scaleSmallAnim(childView);
}
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
/**
* 属性动画在执行之后会将View的各个属性确实更改,必须将他们还原
*/
childView.setScaleX(1.0f);
childView.setScaleY(1.0f);
childView.setAlpha(1.0f);
childView.setX(0f);
childView.setTranslationX(0f);
childView.setTranslationY(0f);
if (mStatus == STATUS_CLOSE) {
childView.setVisibility(View.GONE);
}
super.onAnimationEnd(animation);
}
});
animatorSet.start();
childView.setClickable(false);
childView.setFocusable(false);
}
}
private AnimatorSet scaleBigAnim(final View v) {
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(v, "scaleX",
2.0f, 1.0f);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(v, "scaleY",
2.0f, 1.0f);
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha",
1.0f,
0f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleXAnimator, scaleYAnimator,
alphaAnimator);
animatorSet.setDuration(500);
return animatorSet;
}
private AnimatorSet scaleSmallAnim(View v) {
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(v, "scaleX",
1.0f, 0f);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(v, "scaleY",
1.0f, 0f);
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha",
1.0f,
0f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleXAnimator, scaleYAnimator,
alphaAnimator);
animatorSet.setDuration(500);
return animatorSet;
}
private void changeStatus() {
// TODO Auto-generated method stub
mStatus = (mStatus == STATUS_CLOSE ? STATUS_OPEN : STATUS_CLOSE);
}
private void rotateCButton() {
ObjectAnimator anim = ObjectAnimator
.ofFloat(mCButton, "rotation", 0, 360f);
anim.setDuration(500);
anim.start();
}
}
2.自定义卫星菜单的位置、半径属性
valus文件夹下创建attr.xml,添加以下内容
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="position">
<enum name="left_top" value="0"></enum>
<enum name="left_bottom" value="1"></enum>
<enum name="right_top" value="2"></enum>
<enum name="right_bottom" value="3"></enum>
</attr>
<attr name="radius" format="dimension"></attr>
<declare-styleable name="ArcMenu">
<attr name="position"></attr>
<attr name="radius"></attr>
</declare-styleable>
</resources>
先定义了position、radius属性,然后利用declare-styleable标签申明“为ArcMenu添加positon、radius属性”,然后就在layout文件中进行使用
<?xml version="1.0" encoding="utf-8"?>
<com.def.view.ArcMenu
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:def="http://schemas.android.com/apk/res/com.example.arcmenudemo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
def:position="right_bottom"
def:radius="200dp" >
利用“xmlns:def”申明自定义属性的命名空间,利用“def:属性”对自定义属性进行调用。
在ArcMenu类中对属性值进行获取
// 设置默认半径为100dp,根据需要自行修改
mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
100, getResources().getDisplayMetrics());
/**
* 获取xml文件中设置的属性值
*/
TypedArray typedArray = context.getTheme().obtainStyledAttributes(
attrs, R.styleable.ArcMenu, defStyle, 0);
mPosition = typedArray.getInt(R.styleable.ArcMenu_position,
POS_RIGHT_BUTTOM);
mRadius = (int) typedArray.getDimension(R.styleable.ArcMenu_radius,
mRadius);
// 一定记得释放
typedArray.recycle();
3.重写onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
int count = getChildCount();
for (int i = 0; i < count; i++) {
// 主要对child的大小进行计算
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
4.重写onLayout方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
if (changed) {
layoutButton();
}
}
private void layoutButton() {
/**
* 计算所有按钮的初始放置位置
*/
int count = getChildCount();
mCButton = getChildAt(count - 1);
int l = 0;
int t = 0;
int width = mCButton.getMeasuredWidth();
int height = mCButton.getMeasuredHeight();
switch (mPosition) {
case POS_LEFT_TOP:
break;
case POS_LEFT_BUTTOM:
t = getMeasuredHeight() - height;
break;
case POS_RIGHT_TOP:
l = getMeasuredWidth() - width;
break;
case POS_RIGHT_BUTTOM:
l = getMeasuredWidth() - width;
t = getMeasuredHeight() - height;
break;
}
/**
* 放置菜单按钮
*/
mCButton.layout(l, t, l + width, t + height);
mCButton.setClickable(true);
mCButton.setFocusable(true);
mCButton.setOnClickListener(this);
/**
* 放置子按钮
*/
for (int i = 0; i < count - 1; i++) {
View childView = getChildAt(i);
childView.layout(l, t, l + width, t + height);
childView.setVisibility(View.GONE);
final int pos = i;
childView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
clickItemAnim(pos);
changeStatus();
if (mMenuItemClickListener != null) {
mMenuItemClickListener.onItemClick(v, pos);
}
}
});
}
}
对按钮的初始放置位置进行计算,中心按钮注意要单独考虑
5.添加中心按钮的点击动画
private void rotateCButton() {
ObjectAnimator anim = ObjectAnimator
.ofFloat(mCButton, "rotation", 0, 360f);
anim.setDuration(500);
anim.start();
}
中心按钮的旋转动画很简单,在其点击事件中进行调用
6.添加子按钮的弹出和回收动画
private void toggleMenu() {
int itemCount = getChildCount() - 1;
for (int i = 0; i < itemCount; i++) {
final View child = getChildAt(i);
child.setVisibility(View.VISIBLE);
child.setClickable(true);
child.setFocusable(true);
/**
* 计算出子按钮位移距离
*/
int x = (int) (mRadius * Math.sin(Math.PI / 2 / (itemCount - 1)
* i));
int y = (int) (mRadius * Math.cos(Math.PI / 2 / (itemCount - 1)
* i));
/**
* 判断出位移方向
*/
int xflag = 1;
int yflag = 1;
if (mPosition == POS_RIGHT_TOP || mPosition == POS_RIGHT_BUTTOM) {
xflag = -1;
}
if (mPosition == POS_LEFT_BUTTOM || mPosition == POS_RIGHT_BUTTOM) {
yflag = -1;
}
ObjectAnimator transXAnimator = null;
ObjectAnimator transYAnimator = null;
ObjectAnimator rotationAnimator = null;
AnimatorSet animatorSet = new AnimatorSet();
if (mStatus == STATUS_CLOSE) {// 设置弹出动画
transXAnimator = ObjectAnimator.ofFloat(child, "translationX",
0,
x * xflag);
transYAnimator = ObjectAnimator.ofFloat(child, "translationY",
0,
y * yflag);
} else {// 设置收回的动画
transXAnimator = ObjectAnimator.ofFloat(child, "translationX",
x
* xflag, 0);
transYAnimator = ObjectAnimator.ofFloat(child, "translationY",
y
* yflag, 0);
}
rotationAnimator = ObjectAnimator.ofFloat(child, "rotation", 0,
720f);
animatorSet.playTogether(transXAnimator, transYAnimator,
rotationAnimator);
animatorSet.setDuration(300);
animatorSet.setStartDelay(25 * i);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
if (mStatus == STATUS_CLOSE) {
child.setVisibility(View.GONE);
child.setClickable(false);
child.setFocusable(false);
}
super.onAnimationEnd(animation);
}
});
animatorSet.start();
}
changeStatus();
}
private void changeStatus() {
// TODO Auto-generated method stub
mStatus = (mStatus == STATUS_CLOSE ? STATUS_OPEN : STATUS_CLOSE);
}
7.添加子按钮点击动画
private void clickItemAnim(int pos) {
// TODO Auto-generated method stub
int itemCount = getChildCount() - 1;
for (int i = 0; i < itemCount; i++)
{
final View childView = getChildAt(i);
AnimatorSet animatorSet = null;
if (i == pos) {
animatorSet = scaleBigAnim(childView);
} else {
animatorSet = scaleSmallAnim(childView);
}
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
/**
* 属性动画在执行之后会将View的各个属性确实更改,必须将他们还原
*/
childView.setScaleX(1.0f);
childView.setScaleY(1.0f);
childView.setAlpha(1.0f);
childView.setX(0f);
childView.setTranslationX(0f);
childView.setTranslationY(0f);
if (mStatus == STATUS_CLOSE) {
childView.setVisibility(View.GONE);
}
super.onAnimationEnd(animation);
}
});
animatorSet.start();
childView.setClickable(false);
childView.setFocusable(false);
}
}
private AnimatorSet scaleBigAnim(final View v) {
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(v, "scaleX",
2.0f, 1.0f);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(v, "scaleY",
2.0f, 1.0f);
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha",
1.0f,
0f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleXAnimator, scaleYAnimator,
alphaAnimator);
animatorSet.setDuration(500);
return animatorSet;
}
private AnimatorSet scaleSmallAnim(View v) {
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(v, "scaleX",
1.0f, 0f);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(v, "scaleY",
1.0f, 0f);
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha",
1.0f,
0f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleXAnimator, scaleYAnimator,
alphaAnimator);
animatorSet.setDuration(500);
return animatorSet;
}
子按钮点击效果的调用在onLayout方法中注册
8.添加子按钮的事件处理
public interface OnMenuItemClickListener {
void onItemClick(View view, int position);
}
创建Item点击的外部接口,在layoutButton中对接口进行实际调用,外部类通过实现该接口进行对Item点击的监听