Android实现自定义的卫星式菜单(弧形菜单)详解

一、前言

Android 实现卫星式菜单也叫弧形菜单,主要要做的工作如下:

1.动画的处理

2.自定义ViewGroup来实现卫星式菜单View

(1)自定义属性

a. 在attrs.xml中定义属性

b. 在布局中使用自定义属性

c. 在自定义View中读取布局文件中的自定义属性

(2)onMeasure 测量 child 即测量主按钮以及菜单项

(3)onLayout 布局 child 即布局主按钮以及菜单项

(4)设置主按钮的选择动画

a.为菜单项menuItem添加平移动画和旋转动画

b.实现菜单项menuItem的点击动画

卫星式菜单效果截图:

二、实现

上面介绍了原理和效果图,下面来看看卫星菜单类的实现:

1.布局文件的实现

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:xcskin="http://schemas.android.com/apk/res/com.xc.xcskin" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" > <com.xc.xcskin.view.XCArcMenuView android:id="@+id/arcmenu" android:layout_width="150dp" android:layout_height="150dp" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" xcskin:position="left_bottom" xcskin:radius="120dp" > <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:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_camera" android:tag="camera" /> <ImageView android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_music" android:tag="music" /> <ImageView android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_place" android:tag="place" /> <ImageView android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_sleep" android:tag="sleep" /> <ImageView android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_thought" android:tag="thought" /> <ImageView android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_with" android:tag="with" /> </com.xc.xcskin.view.XCArcMenuView> <com.xc.xcskin.view.XCArcMenuView android:id="@+id/arcmenu2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" xcskin:position="right_bottom" xcskin:radius="150dp" > <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:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_camera" android:tag="camera" /> <ImageView android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_music" android:tag="music" /> <ImageView android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_place" android:tag="place" /> <ImageView android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_sleep" android:tag="sleep" /> <ImageView android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_thought" android:tag="thought" /> <ImageView android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/composer_with" android:tag="with" /> </com.xc.xcskin.view.XCArcMenuView> </RelativeLayout>

2.卫星菜单类的实现

package com.xc.xcskin.view; import com.xc.xcskin.R; 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 android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.AnimationSet; import android.view.animation.RotateAnimation; import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; /** * 卫星式菜单View * @author caizhiming * */ public class XCArcMenuView extends ViewGroup implements OnClickListener{ private static final int POS_LEFT_TOP = 0; private static final int POS_LEFT_BOTTOM = 1; private static final int POS_RIGHT_TOP = 2; private static final int POS_RIGHT_BOTTOM = 3; private Position mPosition = Position.RIGHT_BOTTOM; private int mRadius; private Status mStatus = Status.CLOSE; //主菜的单按钮 private View mCButton; private OnMenuItemClickListener mOnMenuItemClickListener; /** * 菜单的状态枚举类 * @author caizhiming * */ public enum Status{ OPEN,CLOSE } /** * 菜单的位置枚举类 * @author caizhiming * */ public enum Position{ LEFT_TOP,LEFT_BOTTOM, RIGHT_TOP,RIGHT_BOTTOM } /** * 点击子菜单项的回调接口 * @author caizhiming * */ public interface OnMenuItemClickListener { void onClick(View view, int pos); } public void setOnMenuItemClickListener( OnMenuItemClickListener onMenuItemClickListener) { this.mOnMenuItemClickListener = onMenuItemClickListener; } public XCArcMenuView(Context context) { this(context, null); // TODO Auto-generated constructor stub } public XCArcMenuView(Context context, AttributeSet attrs) { this(context, attrs, 0); // TODO Auto-generated constructor stub } public XCArcMenuView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub //获取自定义属性 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.XCArcMenuView,defStyle,0); int pos = a.getInt(R.styleable.XCArcMenuView_position , POS_RIGHT_BOTTOM); switch (pos) { case POS_LEFT_TOP: mPosition = Position.LEFT_TOP; break; case POS_LEFT_BOTTOM: mPosition = Position.LEFT_BOTTOM; break; case POS_RIGHT_TOP: mPosition = Position.RIGHT_TOP; break; case POS_RIGHT_BOTTOM: mPosition = Position.RIGHT_BOTTOM; break; } mRadius = (int) a.getDimension(R.styleable.XCArcMenuView_radius, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 150, getResources().getDisplayMetrics())); Log.v("czm", "mPosition = " + mPosition + ",mRadius = "+mRadius); a.recycle(); } @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){ layoutCButton(); layoutMenuItems(); } } /** * 布局主菜单项 */ private void layoutCButton() { // TODO Auto-generated method stub mCButton = getChildAt(0); mCButton.setOnClickListener(this); int l = 0; int t = 0; int width = mCButton.getMeasuredWidth(); int height = mCButton.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; default: break; } mCButton.layout(l, t, l + width, t + height); } /** * 布局菜单项 */ private void layoutMenuItems() { // TODO Auto-generated method stub int count = getChildCount(); for (int i = 0; i < count - 1; i++) { View child = getChildAt(i + 1); int l = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i)); int t = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i)); int width = child.getMeasuredWidth(); int height = child.getMeasuredHeight(); // 如果菜单位置在底部 左下,右下 if (mPosition == Position.LEFT_BOTTOM || mPosition == Position.RIGHT_BOTTOM) { t = getMeasuredHeight() - height - t; } // 右上,右下 if (mPosition == Position.RIGHT_TOP || mPosition == Position.RIGHT_BOTTOM) { l = getMeasuredWidth() - width - l; } child.layout(l, t, l + width, t + height); child.setVisibility(View.GONE); } } @Override public void onClick(View v) { // TODO Auto-generated method stub mCButton = findViewById(R.id.id_button); rotateCButton(v,0,360,300); toggleMenu(300); } /** * 切换菜单 */ public void toggleMenu(int duration) { // TODO Auto-generated method stub // 为menuItem添加平移动画和旋转动画 int count = getChildCount(); for (int i = 0; i < count - 1; i++) { final View childView = getChildAt(i + 1); childView.setVisibility(View.VISIBLE); // end 0 , 0 // start int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i)); int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i)); 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; } AnimationSet animset = new AnimationSet(true); Animation tranAnim = null; // to open if (mStatus == Status.CLOSE) { tranAnim = new TranslateAnimation(xflag * cl, 0, yflag * ct, 0); childView.setClickable(true); childView.setFocusable(true); } else // to close { tranAnim = new TranslateAnimation(0, xflag * cl, 0, yflag * ct); childView.setClickable(false); childView.setFocusable(false); } tranAnim.setFillAfter(true); tranAnim.setDuration(duration); tranAnim.setStartOffset((i * 100) / count); tranAnim.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { if (mStatus == Status.CLOSE) { childView.setVisibility(View.GONE); } } }); // 旋转动画 RotateAnimation rotateAnim = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); rotateAnim.setDuration(duration); rotateAnim.setFillAfter(true); animset.addAnimation(rotateAnim); animset.addAnimation(tranAnim); childView.startAnimation(animset); final int pos = i + 1; childView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mOnMenuItemClickListener != null) mOnMenuItemClickListener.onClick(childView, pos); menuItemAnim(pos - 1); changeStatus(); } }); } // 切换菜单状态 changeStatus(); } /** * 选择主菜单按钮 * */ private void rotateCButton(View v, float start, float end, int duration) { // TODO Auto-generated method stub RotateAnimation anim = new RotateAnimation(start, end, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); anim.setDuration(duration); anim.setFillAfter(true); v.startAnimation(anim); } /** * 添加menuItem的点击动画 * */ private void menuItemAnim(int pos) { for (int i = 0; i < getChildCount() - 1; i++) { View childView = getChildAt(i + 1); if (i == pos) { childView.startAnimation(scaleBigAnim(300)); } else { childView.startAnimation(scaleSmallAnim(300)); } childView.setClickable(false); childView.setFocusable(false); } } /** * 为当前点击的Item设置变小和透明度增大的动画 * @param duration * @return */ private Animation scaleSmallAnim(int duration) { AnimationSet animationSet = new AnimationSet(true); ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 0.0f, 1.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); AlphaAnimation alphaAnim = new AlphaAnimation(1f, 0.0f); animationSet.addAnimation(scaleAnim); animationSet.addAnimation(alphaAnim); animationSet.setDuration(duration); animationSet.setFillAfter(true); return animationSet; } /** * 为当前点击的Item设置变大和透明度降低的动画 */ private Animation scaleBigAnim(int duration) { AnimationSet animationSet = new AnimationSet(true); ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); AlphaAnimation alphaAnim = new AlphaAnimation(1f, 0.0f); animationSet.addAnimation(scaleAnim); animationSet.addAnimation(alphaAnim); animationSet.setDuration(duration); animationSet.setFillAfter(true); return animationSet; } /** * 切换菜单状态 */ private void changeStatus() { mStatus = (mStatus == Status.CLOSE ? Status.OPEN : Status.CLOSE); } /** * 是否处于展开状态 * @return */ public boolean isOpen() { return mStatus == Status.OPEN; } }

3.使用卫星式菜单类

package com.xc.xcskin; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Toast; import com.xc.xcskin.view.XCArcMenuView; import com.xc.xcskin.view.XCArcMenuView.OnMenuItemClickListener; import com.xc.xcskin.view.XCGuaguakaView; import com.xc.xcskin.view.XCGuaguakaView.OnCompleteListener; /** * 使用并测试自定义卫星式菜单View * @author caizhiming * */ public class XCArcMenuViewDemo extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.xc_arcmenu_view_demo); XCArcMenuView view = (XCArcMenuView) findViewById(R.id.arcmenu); view.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public void onClick(View view, int pos) { // TODO Auto-generated method stub String tag = (String) view.getTag(); Toast.makeText(XCArcMenuViewDemo.this, tag, Toast.LENGTH_SHORT).show(); } }); } }

三、总结

Android实现自定义的卫星式菜单(弧形菜单)的内容到这就基本结束了,感兴趣的朋友们可以动手操作起来,只有自己实践了才能更深的理解,希望本文对大家能有所帮助。

时间: 2024-10-26 14:29:20

Android实现自定义的卫星式菜单(弧形菜单)详解的相关文章

Android实现自定义的卫星式菜单(弧形菜单)详解_Android

一.前言 Android 实现卫星式菜单也叫弧形菜单,主要要做的工作如下: 1.动画的处理 2.自定义ViewGroup来实现卫星式菜单View (1)自定义属性        a. 在attrs.xml中定义属性        b. 在布局中使用自定义属性        c. 在自定义View中读取布局文件中的自定义属性 (2)onMeasure 测量 child 即测量主按钮以及菜单项 (3)onLayout 布局 child 即布局主按钮以及菜单项 (4)设置主按钮的选择动画       

Android 隐式Intent的实例详解

Android  隐式Intent的实例详解 前言: 顾名思义,隐式意图就是在不明确设置激活对象的前提下寻找最匹配的组件,举个例子,比如有5个人: (1)A:170cm (2)B:160cm (3)C:180cm (4)D:190cm (5)E:200cm 如果是显示意图的话,如果我们要指明选择A的话会说:"我选择A.",但是如果是隐式意图,则会说:"我要选择170cm的人",虽然没有指明要选A,但会寻找条件最匹配的人. 在intent过滤器中类似于上面例子中的&q

Android自定义View中attrs.xml的实例详解

Android自定义View中attrs.xml的实例详解 我们在自定义View的时候通常需要先完成attrs.xml文件 在values中定义一个attrs.xml 然后添加相关属性 这一篇先详细介绍一下attrs.xml的属性. <?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定义公共属性 <attr name="titleText" for

Android中gson、jsonobject解析JSON的方法详解_Android

JSON的定义: 一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性.业内主流技术为其提供了完整的解决方案(有点类似于正则表达式 ,获得了当今大部分语言的支持),从而可以在不同平台间进行数据交换.JSON采用兼容性很高的文本格式,同时也具备类似于C语言体系的行为. JSON对象: JSON中对象(Object)以"{"开始, 以"}"结束. 对象中的每一个item都是一个key-value对, 表现为"key:value"的形式, ke

Android编程之软键盘的隐藏显示实例详解_Android

本文实例分析了Android编程之软键盘的隐藏显示方法.分享给大家供大家参考,具体如下: Android是一个针对触摸屏专门设计的操作系统,当点击编辑框,系统自动为用户弹出软键盘,以便用户进行输入. 那么,弹出软键盘后必然会造成原有布局高度的减少,那么系统应该如何来处理布局的减少?我们能否在应用程序中进行自定义的控制?这些是本文要讨论的重点. 一.软键盘显示的原理 软件盘的本质是什么?软键盘其实是一个Dialog! InputMethodService为我们的输入法创建了一个Dialog,并且将

ListView点击Item展开菜单实现代码详解_Android

一.概述 ListView点击item显示菜单是要实现这样的效果: 需要实现的逻辑如下: 1)点击一个普通item,展开当前菜单,同时关闭其他菜单 2)点击一个已展开的菜单,隐藏当前菜单 3)将展开菜单滑到listview之外,再滑动回来,展开菜单状态不变 4)点击菜单中的按钮,能够根据不同item进行不同的处理 二.实现思路 1.UI布局上,对于这种每个listitem都包含动态显示菜单的场景,可以直接在listitem的xml布局里就包含两部分元素:item本身以及展开菜单 点击item的时

第九篇Bootstrap导航菜单创建步骤详解_javascript技巧

创建一个标签式的导航菜单的步骤是:  •在ul标签上加上class  nav  •再ul标签上加上 class .nav-tabs. 在li标签上加上 active表示激活该项 <ul class="nav nav-tabs"> <li class="active"><a href="#">Home</a></li> <li><a href="#"&

ListView点击Item展开菜单实现代码详解

一.概述 ListView点击item显示菜单是要实现这样的效果: 需要实现的逻辑如下: 1)点击一个普通item,展开当前菜单,同时关闭其他菜单 2)点击一个已展开的菜单,隐藏当前菜单 3)将展开菜单滑到listview之外,再滑动回来,展开菜单状态不变 4)点击菜单中的按钮,能够根据不同item进行不同的处理 二.实现思路 1.UI布局上,对于这种每个listitem都包含动态显示菜单的场景,可以直接在listitem的xml布局里就包含两部分元素:item本身以及展开菜单 点击item的时

Android中gson、jsonobject解析JSON的方法详解

JSON的定义: 一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性.业内主流技术为其提供了完整的解决方案(有点类似于正则表达式 ,获得了当今大部分语言的支持),从而可以在不同平台间进行数据交换.JSON采用兼容性很高的文本格式,同时也具备类似于C语言体系的行为. JSON对象: JSON中对象(Object)以"{"开始, 以"}"结束. 对象中的每一个item都是一个key-value对, 表现为"key:value"的形式, ke