最终效果如下

实现思路

通过效果图,会有几个问题:

a、动画效果如何实现

可以看出动画是从顶点外外发射的,可能有人说,那还不简单,默认元素都在定点位置,然后TraslateAnimation就好了;这样忽略了一点,就是TraslateAnimation虽然有动画效果,但是本质是不会改变按钮的位置,我们的按钮动画结束是要点击的;有人可能会说那使用属性动画,或者改变leftMagin,rightMagin;这样可能比较麻烦,其实我们可以默认让子菜单就已经在目标位置,然后GONE,当点击时还是用TraslateAnimation,把起始位置设为定点,终点位置就是我们隐藏的区域,动画结束VISIBLE.

b、如何确定位置呢?

每次会根据子菜单数量,算出a这个角度,然后通过sin , cos 分别算出每个子菜单的left , top ;

当然这是在左上的情况,如果在右上,则top还是和左上一致的,left则为 (屏幕宽度-左上算出的left) ;其他两个方位同理~

整体我通过自定义一个ViewGroup,这个ViewGroup中第一个子元素为点击的按钮(你可以随便布局,随便用什么控件),接下来的子元素我认为是菜单项。根据效果图,决定展开半径和显示的位置,让用户自己去定制。下面看具体实现:

自定义View的属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="position">
<enum name="left_top" value="0" />
<enum name="right_top" value="1" />
<enum name="right_bottom" value="2" />
<enum name="left_bottom" value="3" />
</attr>
<attr name="radius" format="dimension"></attr> <declare-styleable name="ArcMenu">
<attr name="position" />
<attr name="radius"/>
</declare-styleable> </resources>

在自定义的ViewGroup中获取这些属性

/**
* @author zhy
*/
public class ArcMenu extends ViewGroup implements OnClickListener
{ private static final String TAG = "ArcMenu";
/**
* 菜单的显示位置
*/
private Position mPosition = Position.LEFT_TOP; /**
* 菜单显示的半径,默认100dp
*/
private int mRadius = 100;
/**
* 用户点击的按钮
*/
private View mButton;
/**
* 当前ArcMenu的状态
*/
private Status mCurrentStatus = Status.CLOSE;
/**
* 回调接口
*/
private OnMenuItemClickListener onMenuItemClickListener; /**
* 状态的枚举类
*
* @author zhy
*
*/
public enum Status
{
OPEN, CLOSE
} /**
* 设置菜单现实的位置,四选1,默认右下
*
* @author zhy
*/
public enum Position
{
LEFT_TOP, RIGHT_TOP, RIGHT_BOTTOM, LEFT_BOTTOM;
} public interface OnMenuItemClickListener
{
void onClick(View view, int pos);
} public ArcMenu(Context context)
{
this(context, null);
} public ArcMenu(Context context, AttributeSet attrs)
{
this(context, attrs, 0); } /**
* 初始化属性
*
* @param context
* @param attrs
* @param defStyle
*/
public ArcMenu(Context context, AttributeSet attrs, int defStyle)
{ super(context, attrs, defStyle);
// dp convert to px
mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
mRadius, getResources().getDisplayMetrics());
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.ArcMenu, defStyle, 0); int n = a.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.ArcMenu_position:
int val = a.getInt(attr, 0);
switch (val)
{
case 0:
mPosition = Position.LEFT_TOP;
break;
case 1:
mPosition = Position.RIGHT_TOP;
break;
case 2:
mPosition = Position.RIGHT_BOTTOM;
break;
case 3:
mPosition = Position.LEFT_BOTTOM;
break;
}
break;
case R.styleable.ArcMenu_radius:
// dp convert to px
mRadius = a.getDimensionPixelSize(attr, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100f,
getResources().getDisplayMetrics()));
break; }
}
a.recycle();
}

计算子元素的大小

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int count = getChildCount();
for (int i = 0; i < count; i++)
{
// mesure child
getChildAt(i).measure(MeasureSpec.UNSPECIFIED,
MeasureSpec.UNSPECIFIED);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

确定子元素的位置

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
if (changed)
{ layoutButton();
int count = getChildCount();
/**
* 设置所有孩子的位置 例如(第一个为按钮): 左上时,从左到右 ] 第2个:mRadius(sin0 , cos0)
* 第3个:mRadius(sina ,cosa) 注:[a = Math.PI / 2 * (cCount - 1)]
* 第4个:mRadius(sin2a ,cos2a) 第5个:mRadius(sin3a , cos3a) ...
*/
for (int i = 0; i < count - 1; i++)
{
View child = getChildAt(i + 1);
child.setVisibility(View.GONE); int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2)
* i));
int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2)
* i));
// childview width
int cWidth = child.getMeasuredWidth();
// childview height
int cHeight = child.getMeasuredHeight(); // 右上,右下
if (mPosition == Position.LEFT_BOTTOM
|| mPosition == Position.RIGHT_BOTTOM)
{
ct = getMeasuredHeight() - cHeight - ct;
}
// 右上,右下
if (mPosition == Position.RIGHT_TOP
|| mPosition == Position.RIGHT_BOTTOM)
{
cl = getMeasuredWidth() - cWidth - cl;
} Log.e(TAG, cl + " , " + ct);
child.layout(cl, ct, cl + cWidth, ct + cHeight); }
}
}

首先在layoutButton中对按钮位置就行设置,以及初始化点击事件;然后从第二个子元素开始为菜单项,分别设置其位置,计算的原理就是上面我画的草图,可以再去仔细看看,动手在纸上画一画

/**
* 第一个子元素为按钮,为按钮布局且初始化点击事件
*/
private void layoutButton()
{
View cButton = getChildAt(0); cButton.setOnClickListener(this); int l = 0;
int t = 0;
int width = cButton.getMeasuredWidth();
int height = cButton.getMeasuredHeight();
switch (mPosition)
{
case LEFT_TOP:
l = 0;
t = 0;
break;
case LEFT_BOTTOM:
l = 0;
t = getMeasuredHeight() - height;
break;
case RIGHT_TOP:
l = getMeasuredWidth() - width;
t = 0;
break;
case RIGHT_BOTTOM:
l = getMeasuredWidth() - width;
t = getMeasuredHeight() - height;
break; }
Log.e(TAG, l + " , " + t + " , " + (l + width) + " , " + (t + height));
cButton.layout(l, t, l + width, t + height); }

这是定位Button的代码,此时的代码已经实现了定位,如果你把onLayout中childView.setVisibility(VISIBLE)。ArcMenu的整个控件的样子已经实现了,接下来就是点击事件,已经效果动画的实现了。

设置按钮点击事件

/**
* 为按钮添加点击事件
*/
@Override
public void onClick(View v)
{
mButton = findViewById(R.id.id_button);
if (mButton == null)
{
mButton = getChildAt(0);
}
rotateView(mButton, 0f, 270f, 300);
toggleMenu(300);
}
/**
* 按钮的旋转动画
*
* @param view
* @param fromDegrees
* @param toDegrees
* @param durationMillis
*/
public static void rotateView(View view, float fromDegrees,
float toDegrees, int durationMillis)
{
RotateAnimation rotate = new RotateAnimation(fromDegrees, toDegrees,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
rotate.setDuration(durationMillis);
rotate.setFillAfter(true);
view.startAnimation(rotate);
} public void toggleMenu(int durationMillis)
{
int count = getChildCount();
for (int i = 0; i < count - 1; i++)
{
final View childView = getChildAt(i + 1);
childView.setVisibility(View.VISIBLE); int xflag = 1;
int yflag = 1; if (mPosition == Position.LEFT_TOP
|| mPosition == Position.LEFT_BOTTOM)
xflag = -1;
if (mPosition == Position.LEFT_TOP
|| mPosition == Position.RIGHT_TOP)
yflag = -1; // child left
int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i));
// child top
int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i)); AnimationSet animset = new AnimationSet(true);
Animation animation = null;
if (mCurrentStatus == Status.CLOSE)
{// to open
animset.setInterpolator(new OvershootInterpolator(2F));
animation = new TranslateAnimation(xflag * cl, 0, yflag * ct, 0);
childView.setClickable(true);
childView.setFocusable(true);
} else
{// to close
animation = new TranslateAnimation(0f, xflag * cl, 0f, yflag
* ct);
childView.setClickable(false);
childView.setFocusable(false);
}
animation.setAnimationListener(new AnimationListener()
{
public void onAnimationStart(Animation animation)
{
} public void onAnimationRepeat(Animation animation)
{
} public void onAnimationEnd(Animation animation)
{
if (mCurrentStatus == Status.CLOSE)
childView.setVisibility(View.GONE); }
}); animation.setFillAfter(true);
animation.setDuration(durationMillis);
// 为动画设置一个开始延迟时间,纯属好看,可以不设
animation.setStartOffset((i * 100) / (count - 1));
RotateAnimation rotate = new RotateAnimation(0, 720,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(durationMillis);
rotate.setFillAfter(true);
animset.addAnimation(rotate);
animset.addAnimation(animation);
childView.startAnimation(animset);
final int index = i + 1;
childView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
if (onMenuItemClickListener != null)
onMenuItemClickListener.onClick(childView, index - 1);
menuItemAnin(index - 1);
changeStatus(); }
}); }
changeStatus();
Log.e(TAG, mCurrentStatus.name() +"");
}

点击时,触发TanslateAnimation动画,从定点向外扩展,也给点击按钮添加了一个旋转动画,每个子菜单项同样添加了旋转动画,且如果用户设置回调,调用回调接口;设置子菜单的点击事件。整体就是点击然后动画效果~~

设置子菜单的点击事件

/**
* 开始菜单动画,点击的MenuItem放大消失,其他的缩小消失
* @param item
*/
private void menuItemAnin(int item)
{
for (int i = 0; i < getChildCount() - 1; i++)
{
View childView = getChildAt(i + 1);
if (i == item)
{
childView.startAnimation(scaleBigAnim(300));
} else
{
childView.startAnimation(scaleSmallAnim(300));
}
childView.setClickable(false);
childView.setFocusable(false); } } /**
* 缩小消失
* @param durationMillis
* @return
*/
private Animation scaleSmallAnim(int durationMillis)
{
Animation anim = new ScaleAnimation(1.0f, 0f, 1.0f, 0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
anim.setDuration(durationMillis);
anim.setFillAfter(true);
return anim;
}
/**
* 放大,透明度降低
* @param durationMillis
* @return
*/
private Animation scaleBigAnim(int durationMillis)
{
AnimationSet animationset = new AnimationSet(true); Animation anim = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
Animation alphaAnimation = new AlphaAnimation(1, 0);
animationset.addAnimation(anim);
animationset.addAnimation(alphaAnimation);
animationset.setDuration(durationMillis);
animationset.setFillAfter(true);
return animationset;
}

点击的菜单项变大且慢慢透明消失,未点击的菜单项缩小消失~有兴趣的可以改成自己喜欢的动画~

注:动画效果很多借鉴了eoe上那位仁兄的代码,这类动画也比较简单,就不多说了~

好了,剩下就是些getter,setter了~

布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:zhy="http://schemas.android.com/apk/res/com.example.zhy_arcmenu"
android:layout_width="match_parent"
android:layout_height="match_parent" > <com.example.zhy_arcmenu.ArcMenu
android:id="@+id/id_arcmenu1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
zhy:position="left_top"
zhy:radius="130dp" > <RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/composer_button" > <ImageView
android:id="@+id/id_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_icn_plus" />
</RelativeLayout> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_camera"
android:tag="Camera" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_sun"
android:tag="Sun" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_place"
android:tag="Place" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_sleep"
android:tag="Sleep" /> </com.example.zhy_arcmenu.ArcMenu> <com.example.zhy_arcmenu.ArcMenu
android:layout_width="fill_parent"
android:layout_height="fill_parent"
zhy:position="right_bottom"
zhy:radius="130dp" > <RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/composer_button" > <ImageView
android:id="@+id/id_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_icn_plus" />
</RelativeLayout> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_camera"
android:tag="Camera" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_sun"
android:tag="Sun" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_place"
android:tag="Place" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_sleep"
android:tag="Sleep" />
</com.example.zhy_arcmenu.ArcMenu> <com.example.zhy_arcmenu.ArcMenu
android:layout_width="fill_parent"
android:layout_height="fill_parent"
zhy:position="left_bottom"
zhy:radius="130dp" > <RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/composer_button" > <ImageView
android:id="@+id/id_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_icn_plus" />
</RelativeLayout> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_sun"
android:tag="Sun" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_place"
android:tag="Place" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_sleep"
android:tag="Sleep" />
</com.example.zhy_arcmenu.ArcMenu> <com.example.zhy_arcmenu.ArcMenu
android:layout_width="fill_parent"
android:layout_height="fill_parent"
zhy:position="right_top"
zhy:radius="130dp" > <RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/composer_button" > <ImageView
android:id="@+id/id_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_icn_plus" />
</RelativeLayout> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_camera"
android:tag="Camera" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_sun"
android:tag="Sun" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_place"
android:tag="Place" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/composer_sleep"
android:tag="Sleep" />
</com.example.zhy_arcmenu.ArcMenu> </RelativeLayout>

MainActivity

package com.example.zhy_arcmenu;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.Toast; import com.example.zhy_arcmenu.ArcMenu.OnMenuItemClickListener; public class MainActivity extends Activity
{ private ArcMenu mArcMenuLeftTop; @Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mArcMenuLeftTop = (ArcMenu) findViewById(R.id.id_arcmenu1);
//动态添加一个MenuItem
ImageView people = new ImageView(this);
people.setImageResource(R.drawable.composer_with);
people.setTag("People");
mArcMenuLeftTop.addView(people); mArcMenuLeftTop
.setOnMenuItemClickListener(new OnMenuItemClickListener()
{
@Override
public void onClick(View view, int pos)
{
Toast.makeText(MainActivity.this,
pos + ":" + view.getTag(), Toast.LENGTH_SHORT)
.show();
}
});
} }

注意

本文中用到了android TypedValue.applyDimension,这个方法是转变为标准尺寸的一个函数

参考链接

android TypedValue.applyDimension()的作用 - BuleRiver的专栏 - 博客频道 - CSDN.NET

Animation.setFillAfter and Animation.setFillBefore的作用

动画终止时停留在最后一帧~不然会回到没有执行之前的状态

参考链接

Animation.setFillAfter and Animation.setFillBefore的作用 - PuerTea - 博客园

关于animation的详细介绍

详情参见

【TweenedAnimation】四种动画效果参数详解(自测所得) - 邪天殇 - 博客园

Animation & Property Animation 使用 - Ajian_studio - 博客频道 - CSDN.NET

本文主要参考

Android 自定义ViewGroup手把手教你实现ArcMenu - Hongyang - 博客频道 - CSDN.NET

源代码下载

源代码

自定义viewgroup实现ArcMenu的更多相关文章

  1. Android 自定义ViewGroup手把手教你实现ArcMenu

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这 ...

  2. android自定义viewgroup初步之一----抽屉菜单

    转载请注明出处 http://blog.csdn.net/wingichoy/article/details/47832151 几天前在慕课网上看到鸿洋老师的 自定义卫星菜单,感觉很有意思,于是看完视 ...

  3. (一)自定义ViewGroup绘制出菜单

    从网上学习了hyman大神的卫星菜单实现,自己特意亲自又写了一编代码,对自定义ViewGroup的理解又深入了一点.我坚信只有自己写出来的知识才会有更加好的的掌握.因此也在自己的博客中将这个卫星菜单的 ...

  4. 简单的例子了解自定义ViewGroup(一)

    在Android中,控件可以分为ViewGroup控件与View控件.自定义View控件,我之前的文章已经说过.这次我们主要说一下自定义ViewGroup控件.ViewGroup是作为父控件可以包含多 ...

  5. Android动画效果之自定义ViewGroup添加布局动画

    前言: 前面几篇文章介绍了补间动画.逐帧动画.属性动画,大部分都是针对View来实现的动画,那么该如何为了一个ViewGroup添加动画呢?今天结合自定义ViewGroup来学习一下布局动画.本文将通 ...

  6. Android自定义控件之自定义ViewGroup实现标签云

    前言: 前面几篇讲了自定义控件绘制原理Android自定义控件之基本原理(一),自定义属性Android自定义控件之自定义属性(二),自定义组合控件Android自定义控件之自定义组合控件(三),常言 ...

  7. Android自定义ViewGroup

    视图分类就两类,View和ViewGroup.ViewGroup是View的子类,ViewGroup可以包含所有的View(包括ViewGroup),View只能自我描绘,不能包含其他View. 然而 ...

  8. [Android Pro] Android开发实践:自定义ViewGroup的onLayout()分析

    reference to : http://www.linuxidc.com/Linux/2014-12/110165.htm 前一篇文章主要讲了自定义View为什么要重载onMeasure()方法( ...

  9. android 手把手教您自定义ViewGroup(一)

    1.概述 在写代码之前,我必须得问几个问题: 1.ViewGroup的职责是啥? ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属 ...

随机推荐

  1. EasyUI datagrid 格式化显示数据

    http://blog.163.com/ppy2790@126/blog/static/103242241201512502532379/ 设置formatter属性,是一个函数,格式化函数有3个参数 ...

  2. 【ASP.NET Web API教程】6.1 媒体格式化器

    http://www.cnblogs.com/r01cn/archive/2013/05/17/3083400.html 6.1 Media Formatters6.1 媒体格式化器 本文引自:htt ...

  3. 网络html查看器

    1)演示效果:

  4. 检测ADO.net拼接字符串中非法字符

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Refle ...

  5. 创建gbk编码

        NSStringEncoding gbkEncoding =CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_1803 ...

  6. Unity Shaders Vertex & Fragment Shader入门

    http://blog.csdn.net/candycat1992/article/details/40212735 三个月以前,在一篇讲卡通风格的Shader的最后,我们说到在Surface Sha ...

  7. cmd批处理常用符号详解

    cmd批处理常用符号详解 作者: 字体:[增加 减小] 类型:转载 我们在批处理编写过程中经常遇到各种特殊符号,很多朋友不是很清楚cmd中特殊符号的含义,这里简单的介绍下,方便需要的朋友   1.@一 ...

  8. MyEclipse代码提示快捷键和常用设置

    我使用的是MyEclipse 6.0版本,代码助手(content assist)的快捷键由 Alt + / 改成了 Ctrl + Space,恰好我的输入法快捷键也是 Ctrl + Space .造 ...

  9. MyBatis 用户表记录数查询

    搭建MyBatis开发环境,实现用户表记录数查询 1.在MyEclipse中创建工程,导入MyBatis的jar包

  10. C# 检测操作系统是否空闲,实现系统空闲后做一些操作

    public class CheckComputerFreeState { /// <summary> /// 创建结构体用于返回捕获时间 /// </summary> [St ...