参照鸿洋大神的Android 自定义ViewGroup手把手教你实现ArcMenu
然后自己写了一个位于底部的ArcMenu,功能上和效果上都以实现!
主要思路:
1.计算出中心菜单的位置和其他子菜单的位置
2.设置中心菜单的点击事件(动画)
3.设置子菜单的点击监听和动画效果!
1)计算中心菜单的位置
if (i == 0) {// 中心菜单
centerChildHeight = mHeight - child.getMeasuredHeight() / 2;
// 中间view的中心坐标y
centerChildWidth = mWidth / 2;
// 中间view的中心坐标y
child.layout((mWidth - childWidth) / 2, mHeight - childHeight, (mWidth + childWidth) / 2, mHeight);
child.setOnClickListener(this);
中心菜单的中心位置的x坐标为,view的宽度的一半
中心菜单的中心位置的y坐标为,view的高度减去一半中心菜单自身高度的
所以中心菜单的左上右下坐标位置就可以算出来了!
2)计算子菜单的位置
child.setVisibility(View.GONE);
double temA = Math.PI / (childCount - 2) * (i - 1);
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
left = (int) (centerChildWidth + radius * Math.cos(temA) - childWidth / 2);
top = (int) (centerChildHeight - radius * Math.sin(temA) - childHeight / 2);
right = (int) (centerChildWidth + radius * Math.cos(temA) + childWidth / 2);
bottom = (int) (centerChildHeight - radius * Math.sin(temA) + childHeight / 2);
child.layout(left, top, right, bottom);
其他子菜单的位置的计算分两步:
1.根据中心坐标的中心点位置根据弧度/sin/cos分别计算出其他子菜单的中心点位置
2.根据计算所得的中心点坐标再根据子菜单自身的宽高计算出子菜单的左上右下坐标
位置计算好了基本上相当于ArcMenu完成了一大部分,其他的部分就是设置动画效果了
下面贴出来全部的代码
package com.example.arcmenu;
import android.content.Context;
import android.content.res.TypedArray;
import android.provider.AlarmClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.RelativeLayout;
import android.view.View.OnClickListener;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.OvershootInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
public class ArcMenuGroup extends RelativeLayout implements OnClickListener {
private static String TAG = "ArcMenuGroup";
private int position = 1;// 标识菜单的位置 0:上 1:下(当前代码未使用,仅开发了下方的菜单)
private int radius = dip2px(getContext(), 100);// 菜单的半径
private int mHeight;
private int mWidth;
private int centerChildHeight;
private int centerChildWidth;
private View mMenu;
private boolean mStatus = false;// true表示打开,false表示关闭
private OnItemClicklistener mOnItemClickListener;
private int childCount;
private AnimationSet mItemAnimationSet;//菜单项被点击的动画集合
public ArcMenuGroup(Context context) {
this(context, null);
}
public ArcMenuGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ArcMenuGroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArcMenu, defStyle, 0);
int count = array.getIndexCount();
for (int i = 0; i < count; i++) {
int index = array.getIndex(i);
switch (index) {
case R.styleable.ArcMenu_position:
position = array.getInteger(index, 1);
break;
case R.styleable.ArcMenu_radius:
radius = (int) array.getDimension(index, dip2px(context, 100));
default:
break;
}
}
array.recycle();
/**
* 完成菜单项被点击的动画的初始化
*/
mItemAnimationSet = new AnimationSet(false);//false标识动画集中的动画各自使用自己的动画插入器
//创建一个形变动画,从相对于自身中心点由1倍体积扩大到3倍体积
ScaleAnimation mScaleAnimation = new ScaleAnimation(1.0f, 3.0f, 1.0f, 3.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
mScaleAnimation.setDuration(400);
//创建一个透明度动画,透明度从不透明到完全透明
AlphaAnimation mAlphaAnimation = new AlphaAnimation(1.0f, 0.0f);
mAlphaAnimation.setDuration(400);
mItemAnimationSet.addAnimation(mAlphaAnimation);
mItemAnimationSet.addAnimation(mScaleAnimation);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mHeight = getMeasuredHeight();
mWidth = getMeasuredWidth();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
childCount = getChildCount();
if (childCount <= 2) {//菜单项不能少于两个
return;
}
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
int childHeight = child.getMeasuredHeight();
int childWidth = child.getMeasuredWidth();
if (i == 0) {// 中心菜单
centerChildHeight = mHeight - child.getMeasuredHeight() / 2;// 中间view的中心坐标y
centerChildWidth = mWidth / 2;// 中间view的中心坐标y
child.layout((mWidth - childWidth) / 2, mHeight - childHeight, (mWidth + childWidth) / 2, mHeight);
child.setOnClickListener(this);
} else {//根据弧度计算出每个子菜单的位置
child.setVisibility(View.GONE);
double temA = Math.PI / (childCount - 2) * (i - 1);
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
left = (int) (centerChildWidth + radius * Math.cos(temA) - childWidth / 2);
top = (int) (centerChildHeight - radius * Math.sin(temA) - childHeight / 2);
right = (int) (centerChildWidth + radius * Math.cos(temA) + childWidth / 2);
bottom = (int) (centerChildHeight - radius * Math.sin(temA) + childHeight / 2);
child.layout(left, top, right, bottom);
Log.e(TAG, "i=" + i + " left = " + left + " top = " + top + " right = " + right + " bottom = " + bottom);
}
}
}
}
@Override
public void onClick(View v) {
startCenterMenuAnimation();
startItemMenuAnimation();
changeStatus();
}
/**
* 设置子菜单展开和收回的动画
*/
private void startItemMenuAnimation() {
final int childCount = getChildCount();
TranslateAnimation animation = null;
for (int i = 1; i < childCount; i++) {
final int position = i;
double temA = Math.PI / (childCount - 2) * (i - 1);
final View child = getChildAt(i);
if (mStatus) {
animation = new TranslateAnimation(0, -(float) (radius * Math.cos(temA)), 0, (float) (radius * Math.sin(temA)));
child.setVisibility(View.GONE);
animation.setDuration(300);
} else {
animation = new TranslateAnimation(-(float) (radius * Math.cos(temA)), 0, (float) (radius * Math.sin(temA)), 0);
animation.setInterpolator(new OvershootInterpolator(3f));
child.setVisibility(View.VISIBLE);
animation.setDuration(500);
}
child.startAnimation(animation);
child.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startItemClickAnimation(position);
changeStatus(false);
if (mOnItemClickListener != null) {
mOnItemClickListener.onClick(child, position);
}
}
});
}
}
/**
* 中间主菜单项的动画效果
*/
private void startCenterMenuAnimation() {
mMenu = getChildAt(0);
RotateAnimation animation = new RotateAnimation(0f, 360F, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animation.setFillAfter(true);
animation.setDuration(500);
mMenu.startAnimation(animation);
}
/**
* 改变菜单的状态
*/
private void changeStatus() {
if (mStatus) {
mStatus = false;
} else {
mStatus = true;
}
}
/**
* 改变菜单的状态
*/
private void changeStatus(boolean b) {
mStatus = b;
}
/**
* 设置菜单项被点击的动画
*/
private void startItemClickAnimation(int positon) {
for (int i = 1; i < childCount; i++) {
View child = getChildAt(i);
if (i == positon) {
child.startAnimation(mItemAnimationSet);
}
child.setVisibility(View.GONE);
}
}
/**
*菜单项的监听
*/
public interface OnItemClicklistener {
void onClick(View v, int position);
}
public OnItemClicklistener getOnItemClickListener() {
return mOnItemClickListener;
}
public void setOnItemClickListener(OnItemClicklistener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
}