`
king_tt
  • 浏览: 2224990 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

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

 
阅读更多

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

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

效果图:



1、实现思路

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

a、动画效果如何实现

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

b、如何确定位置呢?

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


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

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


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

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;
	/**
	 * 用户点击的按钮
	 */
	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();
			/**
			 * 设置所有孩子的位置 例如(第一个为按钮): 左上时,从左到右 ] 第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的整个控件的样子已经实现了,接下来就是点击事件,已经效果动画的实现了。

6、设置按钮点击事件

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

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>

嗯,第一个元素为按钮,其他的都是菜单项了~~喜欢用代码的,也可以代码生成,我是比较喜欢布局文件中写这些玩意~

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

    在Android开发中,自定义ViewGroup是实现独特用户界面效果的重要手段。ArcMenu是一个非常吸引人的设计,它呈现了一个扇形的菜单布局,通常用于显示一些可点击的图标或者选项,这种设计通常会给人一种优雅而直观的...

    Android 手把手教您自定义ViewGroup(一)

    我们将通过分析标题"Android 手把手教您自定义ViewGroup(一)"以及提供的博客链接,深入探讨这个主题。 首先,自定义ViewGroup意味着你需要扩展`android.view.ViewGroup`类,这是一个容器,可以包含多个子视图...

    Android自定义ViewGroup实现Title自动隐藏功能.rar

    今天看到很多app都有Title自动隐藏功能,1.这是一个Gradle工程,Gradle的版本是2.10,项目的Compile sdk Version是 23 , Build ...Android自定义ViewGroup实现Title自动隐藏功能源代码分享,Android开发者必看示例。

    android自定义viewgroup实现slidingMenu

    在Android开发中,自定义ViewGroup是实现复杂布局和交互效果的重要手段。SlidingMenu是一种常见的界面组件,常用于实现侧滑菜单效果,类似于许多应用(如Google Play、Facebook等)中的导航栏。本资源提供的就是一个...

    自定义VIewGroup实现仿淘宝商品详情页

    在Android开发中,自定义ViewGroup是实现复杂布局和交互效果的重要手段。本教程将深入讲解如何通过自定义ViewGroup来实现类似淘宝商品详情页的滚动效果,即当页面滑动到底部时出现粘滞效果,继续上拉则切换至下一页...

    Android自定义ViewGroup-----流式布局

    在Android开发中,自定义ViewGroup是实现复杂布局或创新交互设计的重要手段。本文将深入探讨如何创建一个自定义的“流式布局”(MyFlowLayout),这个布局会根据屏幕尺寸自动调整子视图的位置,使其从左到右、从上到...

    Android 自定义ViewGroup 实战篇 -> 实现FlowLayout源码程序

    在Android开发中,自定义ViewGroup是实现复杂布局和交互的关键技术之一。本实战篇将聚焦于如何实现一个名为FlowLayout的自定义布局,它允许子视图按行排列,类似于HTML中的`&lt;div&gt;`标签。在Android应用设计中,...

    android自定义viewgroup实现等分格子布局

    在Android开发中,自定义ViewGroup是创建复杂布局或实现特定功能的重要手段。"android自定义viewgroup实现等分格子布局"这个话题涉及到如何利用自定义ViewGroup类来创建一个能够平均分配屏幕空间的网格布局。下面...

    自定义ViewGroup实现瀑布流

    在Android开发中,自定义ViewGroup来实现瀑布流是一项技术挑战,但也是实现个性化布局的关键。 首先,我们需要理解Android中的ViewGroup。ViewGroup是所有布局容器的基类,如LinearLayout、RelativeLayout等。...

    Android自定义ViewGroup的实现方法

    在Android开发中,自定义ViewGroup是扩展布局功能的关键手段,尤其当系统提供的LinearLayout、RelativeLayout、FrameLayout等标准布局无法满足复杂界面设计时。本文将详细介绍如何实现一个自定义的ViewGroup,以...

    自定义ViewGroup 显示一个TextView到ViewGroup

    在Android开发中,自定义ViewGroup是实现复杂布局和交互效果的重要手段。本文将深入探讨如何创建一个自定义的ViewGroup,并将一个TextView显示在其中。我们将基于标题“自定义ViewGroup 显示一个TextView到ViewGroup...

    android自定义ViewGroup总结

    在Android开发中,自定义ViewGroup是实现复杂布局和交互效果的重要手段。本文将深入探讨如何自定义ViewGroup,从基础概念到实际操作,再到优化技巧,帮助开发者更好地理解和运用这一技术。 首先,理解ViewGroup的...

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

    总之,通过自定义ViewGroup来实现SlidingMenu,不仅需要理解Android的测量和布局机制,还需要熟悉触摸事件处理和动画实现。这是一种高级的Android开发技能,能帮助开发者创造出更富有创意和个性化的用户界面。学习和...

    android 自动换行的自定义viewgroup

    在Android开发中,自定义ViewGroup是实现复杂布局和交互的关键技术之一。标题"android 自动换行的自定义viewgroup"所指的,就是创建一个能够根据子视图的数量和大小自动调整布局,实现自动换行效果的自定义 ...

    Android 自定义ViewGroup实现整个Item布局竖直跑马灯效果

    在Android开发中,自定义ViewGroup是实现复杂布局和动画效果的重要手段。本文将深入探讨如何利用自定义ViewGroup来实现一个独特的“竖直跑马灯”效果,这种效果常见于各种信息展示或广告轮播场景,使得内容能沿着...

    Android自定义ViewGroup(一)

    在Android开发中,自定义ViewGroup是提升应用用户体验和界面设计灵活性的重要手段。这篇博客文章“Android自定义ViewGroup(一)”详细介绍了如何从零开始创建一个自定义的ViewGroup,以及在这个过程中可能遇到的...

    Android自定义ViewGroup实现竖向引导界面

    总结来说,Android自定义ViewGroup实现竖向引导界面的关键在于自定义布局类,处理触摸事件并判断滑动方向,以及实现页面切换的逻辑。通过这种方式,开发者可以创建出符合自己应用风格的个性化引导界面,提供更好的...

    自定义ViewGroup实现滑屏等动作

    在Android开发中,自定义ViewGroup是实现复杂交互和独特界面设计的重要手段。这篇博客“自定义ViewGroup实现滑屏等动作”显然探讨了如何利用自定义ViewGroup来实现滑动和其他动态效果,这对于增强用户体验和创新应用...

    自定义ViewGroup组合控件

    自定义ViewGroup意味着扩展Android的 ViewGroup 类或其子类,如 LinearLayout、RelativeLayout 或 FrameLayout,以实现特定的功能或视觉效果。本知识点主要围绕如何设计和实现自定义ViewGroup,以及如何处理其中的...

    继承ViewGroup自定义ViewPager

    在Android开发中,自定义控件是提升应用独特性和用户体验的重要手段。`ViewGroup`是Android UI组件的基础,它作为容器可以包含多个`View`或`ViewGroup`,形成了我们看到的各种复杂的布局。`ViewPager`则是一种常见的...

Global site tag (gtag.js) - Google Analytics