转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907

逛eoe发现这种UI效果,感觉非常不错,后来知道github上有这么个开源项目~~~~当然本篇不是教你怎样使用这个开源项目,而是教你怎样自己通过自己定义ViewGroup写这种效果,自己定义ViewGroup也是我的痛楚。嘿嘿。希望以此能够抛砖引玉~~

效果图:

1、实现思路

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

a、动画效果怎样实现

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

b、怎样确定位置呢?

这可能须要一点数学上的知识。我画了一张草图(冰天雪地。跪玻璃碴子求画以下这些图的工具):

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

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

总体我通过自己定义一个ViewGroup,这个ViewGroup中第一个子元素为点击的button(你能够随便布局。随便用什么控件),接下来的子元素我觉得是菜单项。依据效果图,决定展开半径和显示的位置,让用户自己去定制。

以下看详细实现:

2、自己定义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>

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

Arcmenu.java

/**
* @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;
/**
* 用户点击的button
*/
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();
}

4、计算子元素的大小:
@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);
}

5、确定子元素的位置:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
if (changed)
{ layoutButton();
int count = getChildCount();
/**
* 设置全部孩子的位置 比如(第一个为button): 左上时,从左到右 ] 第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中对button位置即可设置,以及初始化点击事件;然后从第二个子元素開始为菜单项,分别设置其位置,计算的原理就是上面我画的草图,能够再去细致看看,动手在纸上画一画。

	/**
* 第一个子元素为button,为button布局且初始化点击事件
*/
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的整个控件的样子已经实现了,接下来就是点击事件。已经效果动画的实现了。

6、设置button点击事件
/**
* 为button加入点击事件
*/
@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);
}

/**
* button的旋转动画
*
* @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动画。从定点向外扩展。也给点击button加入了一个旋转动画。每一个子菜单项相同加入了旋转动画,且假设用户设置回调。调用回调接口。设置子菜单的点击事件。总体就是点击然后动画效果~~

7、设置子菜单的点击事件
/**
* 開始菜单动画。点击的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了~

8、布局文件:
<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>

嗯。第一个元素为button,其它的都是菜单项了~~喜欢用代码的,也能够代码生成,我是比較喜欢布局文件里写这些玩意~

9、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 自己定义ViewGroup手把手教你实现ArcMenu的更多相关文章

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

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

  2. Android 自己定义ViewGroup 实战篇 -&gt; 实现FlowLayout

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38352503 .本文出自[张鸿洋的博客] 1.概述 上一篇已经基本给大家介绍了怎 ...

  3. Android自己定义ViewGroup打造各种风格的SlidingMenu

    看鸿洋大大的QQ5.0側滑菜单的视频课程,对于側滑的时的动画效果的实现有了新的认识,似乎打通了任督二脉.眼下能够实现随意效果的側滑菜单了.感谢鸿洋大大!! 鸿洋大大用的是HorizontalScrol ...

  4. android 自己定义ViewGroup实现可记载并呈现选择的ListView

    转载请注明出处:王亟亟的大牛之路 之前也做过一些用TextView之类的记录ListView选项的东西.可是总认为好难看.发现个不错的实现就贴给大家. 项目文件夹 执行效果: 自己定义视图: @Tar ...

  5. Android应用系列:手把手教你做一个小米通讯录(附图附源码)

    前言 最近心血来潮,突然想搞点仿制品玩玩,很不幸小米成为我苦逼的第一个试验品.既然雷布斯的MIUI挺受欢迎的(本人就是其的屌丝用户),所以就拿其中的一些小功能做一些小demo来玩玩.小米的通讯录大家估 ...

  6. Android自己定义ViewGroup(二)——带悬停标题的ExpandableListView

    项目里要加一个点击可收缩展开的列表,要求带悬停标题,详细效果例如以下图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fon ...

  7. Android系统裁剪:手把手教你如何进行系统裁剪

    前言:android系统裁剪优化一直是各个厂商定制产品的关键步骤,包括浅层次的去除不必要的apk(android apk裁剪定制 )和深层次的裁剪整个编译系统和框架层.   android作为开源系统 ...

  8. Android:手把手教你打造可缩放移动的ImageView(下)

    在上一篇Android:手把手教你打造可缩放移动的ImageView最后提出了一个注意点:当自定义的MatrixImageView如ViewPager.ListView等带有滑动效果的ViewGrou ...

  9. 手把手教你Android手机与BLE终端通信--连接,发送和接收数据

    假设你还没有看上一篇 手把手教你Android手机与BLE终端通信--搜索,你就先看看吧,由于这一篇要接着讲搜索到蓝牙后的连接.和连接后的发送和接收数据. 评论里有非常多人问假设一条信息特别长,怎么不 ...

随机推荐

  1. css样式介绍

    1 css之选择器 1.1 基本选择器 1.2 组合选择器 E,F      多元素选择器,同时匹配所有E元素或F元素,E和F之间用逗号分隔:div,p { color:#f00; } E F     ...

  2. Delphi 实现多线程编程的线程类 TThread

    http://blog.csdn.net/henreash/article/details/3183119 Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉 ...

  3. Visio中旋转文本框与箭头平行

    如图想要让文本框和箭头平行,按住shift,可以画出水平或者垂直的线,线是斜的,用文本框来标识,要框和线平行,那可以这样做: 打开视图  -  任务窗格 - 大小和位置,然后先单击选中斜线 左下角倒数 ...

  4. WebService基于SoapHeader实现安全认证(二)

    支持通过Http请求方法调用webservice,同时支持SoapHeader验证. using Globalegrow.Common; using Globalegrow.Model; using ...

  5. java比较客户端版本号

    参考文章:http://www.jb51.net/article/70317.htm 关键点 为什么不能使用String.compareTo方法来比较客户端版本号? 举个例子,之前客户端版本号为:9. ...

  6. JIRA Service Desk 3.9.2 没有许可证

    https://my.atlassian.com/license/evaluation Server ID BFHT-0XFL-3NM8-3KRF SEN SEN-L10880225 License ...

  7. meteor学习-- #一 安装meteor快速使用

    下载安装 curl https://install.meteor.com/ | sh meteor会自动下载mongodb和其他需要依赖的库,不需要手动安装. 如果是Windows 的用户,请使用 m ...

  8. Netty游戏服务器二

    上节我们写个server主类,那么发现什么事情都干不了,是的,我们还没有做任何的业务处理. 接着我们开始写处理客户端连接,发送接收数据的类ServerHandler. public class Ser ...

  9. python3 str和bytes之间转换

    a bytes-like object is required, not 'str' 碰到 这个错误 ,是因为需要是的bytes,不是str bytes -> str: 1  str-> ...

  10. [转]SSIS: Execute Package via Stored Procedure

    本文转自:http://sqlblog.de/blog/2009/09/ssis-execute-package-via-stored-procedure/ There are two options ...