700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > android自定义弧形 Android 自定义弧形旋转菜单栏——卫星菜单

android自定义弧形 Android 自定义弧形旋转菜单栏——卫星菜单

时间:2024-05-17 04:53:35

相关推荐

android自定义弧形 Android 自定义弧形旋转菜单栏——卫星菜单

概述

现在很多App会在入口比较浅的页面添加一些快捷操作入口,一方面是为了方便用户操作,一方面是为了提高产品一些关键入口的使用率,让用户能够在浏览信息流的过程中能快速切换至其他一些功能页面。例如豆瓣的首页 (右下角红框选中部分):

豆瓣菜单

本文将仿照这种菜单效果进行实现,最终效果如下:

弧形菜单效果图

需要定制的特性

1.菜单展开半径

2.设置菜单主按钮Icon

3.设置菜单子项的各个Icon

4.展开和收缩的动画时长

5.所有菜单按钮的宽高

6.是否在展开收缩的同时旋转主按钮

注:理论上可以设置无数个菜单项,但是会出现重叠情况(空间有限),这种情况得自行调整按钮数量和宽高。

实现思路

可以看到这个菜单是由多个按钮组合而成,所以可以考虑用ViewGroup来作为载体,其中的子View再通过属性动画进行配合达成效果,而各个菜单项的弹出角度可以针对90°来进行弧度平分,再通过三角函数得到最终展开的目标坐标,关键要注意View的宽高边距的计算,否则可能会出现超出边界的情况。

1)初始化基本框架

由于菜单是由多个按钮叠加在一个平面,所以可以考虑采用继承FrameLayout,然后根据设置的Icon资源Id的数量来作为按钮的数量进行初始化,代码如下:ListmImgViews=newArrayList<>();

ListmMenuItemResIds=newArrayList<>();/**

*初始化主按钮

*@paramcontext

*/

privatevoidinitMenuView(Contextcontext){

mMenuIv=newImageView(context);

mMenuIv.setImageResource(mMenuResId);

FrameLayout.LayoutParamsparams=newLayoutParams(mMenuWidth,mMenuWidth);

params.bottomMargin=mMenuItemWidth/2;

params.rightMargin=mMenuItemWidth/2;

params.gravity=Gravity.BOTTOM|Gravity.RIGHT;

addView(mMenuIv,params);

mMenuIv.setOnClickListener(this);

}/**

*初始化菜单子项按钮

*@paramcontext

*/

privatevoidinitMenuItemViews(Contextcontext){

mImgViews.clear();for(intindex=0;index

ImageViewmenuItem=newImageView(context);

menuItem.setImageResource(mMenuItemResIds.get(index));

FrameLayout.LayoutParamsparams=newLayoutParams(mMenuItemWidth,mMenuItemWidth);

params.bottomMargin=mMenuItemWidth/2;

params.rightMargin=mMenuItemWidth/2;

params.gravity=Gravity.BOTTOM|Gravity.RIGHT;

menuItem.setTag(index);

menuItem.setOnClickListener(this);

addView(menuItem,params);

menuItem.setScaleX(0f);

menuItem.setScaleY(0f);

mImgViews.add(menuItem);

}

}

可以看到都设置在了父容器的右下角,且设置了margin值,这是由于我们点击菜单瞬间有放大双倍的效果,所以这里需要为其边缘腾出一点空间,否则处于边缘的菜单项放大时,会有部分被切掉影响观感,记得为每个子项设置Tag(这里设置为下标),后面触发点击事件时会用到。

2)展开菜单

前面说过了,主要是根据平分弧度的思路来计算,从效果图中可以看出,我们的整个展开角度是90°,那么每个菜单项的角度应该是90°/(菜单的数量-1),计算出这个角度有什么作用呢?可以先通过下图帮忙理解:

弧形菜单弹出距离计算示意图

可以看到,要做弹出动画,就需要计算出弹出的横向距离和纵向距离,刚才计算出来的角度在这就派上用场啦,利用三角函数可以得到:

tranX = 弹出半径*sin(90 * i / (count - 1));

tranY = 弹出半径*cos(90 * i / (count - 1));

再结合透明度和大小的变化,代码如下:/**

*菜单展开动画

*/

privatevoidstartOpenAnim(){intcount=mMenuItemResIds.size();

Listanimators=newArrayList<>();for(inti=0;i

ObjectAnimatoranimatorX=ObjectAnimator.ofFloat(mImgViews.get(i),"translationX",0f,tranX);

ObjectAnimatoranimatorY=ObjectAnimator.ofFloat(mImgViews.get(i),"translationY",0f,tranY);

ObjectAnimatoralpha=ObjectAnimator.ofFloat(mImgViews.get(i),"alpha",0,1);

ObjectAnimatorscaleX=ObjectAnimator.ofFloat(mImgViews.get(i),"scaleX",0.1f,1);

ObjectAnimatorscaleY=ObjectAnimator.ofFloat(mImgViews.get(i),"scaleY",0.1f,1);

animators.add(animatorX);

animators.add(animatorY);

animators.add(alpha);

animators.add(scaleX);

animators.add(scaleY);

}

AnimatorSetanimatorSet=newAnimatorSet();

animatorSet.setDuration(mDuration);

animatorSet.playTogether(animators);

animatorSet.start();

}

3)收回菜单

上一步已经理解了如何展开菜单,回收菜单自然就容易多了,没错,就是反其道而行之:/**

*菜单收回动画

*/

privatevoidstartCloseAnim(){intcount=mMenuItemResIds.size();

Listanimators=newArrayList<>();for(inti=0;i

ObjectAnimatoranimatorX=ObjectAnimator.ofFloat(mImgViews.get(i),"translationX",tranX,0f);

ObjectAnimatoranimatorY=ObjectAnimator.ofFloat(mImgViews.get(i),"translationY",tranY,0f);

ObjectAnimatoralpha=ObjectAnimator.ofFloat(mImgViews.get(i),"alpha",1,0);

ObjectAnimatorscaleX=ObjectAnimator.ofFloat(mImgViews.get(i),"scaleX",1,0.3f);

ObjectAnimatorscaleY=ObjectAnimator.ofFloat(mImgViews.get(i),"scaleY",1,0.3f);

animators.add(animatorX);

animators.add(animatorY);

animators.add(alpha);

animators.add(scaleX);

animators.add(scaleY);

}

AnimatorSetanimatorSet=newAnimatorSet();

animatorSet.setDuration(mDuration);

animatorSet.playTogether(animators);

animatorSet.start();

}

其实主要就是在做位移动画的时候,从tranX和tranY位移到0,回到原来的位置。

4)菜单子项点击动画

以上完成了菜单的展开和收缩,基本的模样已经出来了,还可以为其子项添加一些点击效果,让整个View更为生动,代码如下:/**

*菜单子项点击动画

*

*@paramindex子项下标

*/

privatevoidstartClickItemAnim(intindex){intcount=mMenuItemResIds.size();

Listanimators=newArrayList<>();//当前被点击按钮放大且逐渐变透明,造成消散效果

ObjectAnimatorclickItemAlpha=ObjectAnimator.ofFloat(mImgViews.get(index),"alpha",1,0);

ObjectAnimatorclickItemScaleX=ObjectAnimator.ofFloat(mImgViews.get(index),"scaleX",1,2);

ObjectAnimatorclickItemScaleY=ObjectAnimator.ofFloat(mImgViews.get(index),"scaleY",1,2);

animators.add(clickItemAlpha);

animators.add(clickItemScaleX);

animators.add(clickItemScaleY);for(inti=0;i

continue;

}//其他选项缩小且变透明

ObjectAnimatoralpha=ObjectAnimator.ofFloat(mImgViews.get(i),"alpha",1,0);

ObjectAnimatorscaleX=ObjectAnimator.ofFloat(mImgViews.get(i),"scaleX",1,0.1f);

ObjectAnimatorscaleY=ObjectAnimator.ofFloat(mImgViews.get(i),"scaleY",1,0.1f);

animators.add(alpha);

animators.add(scaleX);

animators.add(scaleY);

}

AnimatorSetanimatorSet=newAnimatorSet();

animatorSet.setDuration(500);

animatorSet.playTogether(animators);

animatorSet.start();

animatorSet.addListener(newAnimator.AnimatorListener(){@Override

publicvoidonAnimationStart(Animatoranimator){

}@Override

publicvoidonAnimationEnd(Animatoranimator){//点击动画结束之后要将所有子项归位

resetItems();

}@Override

publicvoidonAnimationCancel(Animatoranimator){

}@Override

publicvoidonAnimationRepeat(Animatoranimator){

}

});

}

首先传进来一个index参数,其实就是之前我们在初始化的时候为每个子View设置的Tag,在每次onClick的时候,通过 view.getTag() 获取到对应的下标,传进来之后,循环遍历所有子View,根据这个下标来判断当前点击的是哪个菜单项,将其做放大消散的动画效果,其他菜单项则单纯消散即可。

并且这里注意,要在动画结束时,将所有子项设置回展开之前的位置,否则当再次点击菜单按钮时,菜单项会在圆弧上闪现了一下,体验很差,因此要在onAnimationEnd的回调中重置所有子项,重置代码如下:/**

*重置所有子项位置

*/

privatevoidresetItems(){intcount=mImgViews.size();for(inti=0;i

mImgViews.get(i).setTranslationX(tranX);

mImgViews.get(i).setTranslationY(tranY);

}

mIsOpen=false;

}

5)旋转主菜单按钮

我们还可以在展开收缩的同时,还可以为菜单按钮添加上一些花样,将其旋转一下,使整个动画更加自然:/**

*旋转主菜单按钮

*

*@paramstartAngel起始角度

*@paramendAngel结束角度

*/

privatevoidrotateMenu(intstartAngel,intendAngel){if(!mCanRotate){return;

}

ObjectAnimatorclickItemAlpha=ObjectAnimator.ofFloat(mMenuIv,"rotation",startAngel,endAngel);

clickItemAlpha.setDuration(mDuration);

clickItemAlpha.start();

}

6)添加外部点击监听

提供一个供外界设置banner数据的方法:ClickMenuListenermItemListener;publicvoidsetClickItemListener(ClickMenuListenermItemListener){this.mItemListener=mItemListener;

}publicinterfaceClickMenuListener{voidclickMenuItem(intresId);

}@Override

publicvoidonClick(Viewview){if(view==mMenuIv){

...

}else{

...if(mItemListener!=null&&index

mItemListener.clickMenuItem(mMenuItemResIds.get(index));

}

}

}

就是正常的暴露接口,将菜单对应的资源id传出去,供外界判断点击的是哪个菜单项。

应用

xml布局中引用(这里的宽高由设置的弧长半径决定,只需设置wrap_conetnt即可):

android:id="@+id/arc_menu"

android:layout_width="match_parent"

android:layout_height="wrap_content"

app:spread_radius="150dp"

app:duration="1000"

app:menu_width="64dp"

app:menu_item_width="64dp"

app:can_rotate="true"

app:layout_constraintRight_toRightOf="parent"

app:layout_constraintBottom_toBottomOf="parent"

/>

Acitivity中实例代码如下:mArcMenuView=findViewById(R.id.arc_menu);

ListmenuItems=newArrayList<>();

menuItems.add(R.drawable.ic_menu_camera);

menuItems.add(R.drawable.ic_menu_photo);

menuItems.add(R.drawable.ic_menu_share);

mArcMenuView.setMenuItems(menuItems);

mArcMenuView.setClickItemListener(newYArcMenuView.ClickMenuListener(){@Override

publicvoidclickMenuItem(intresId){switch(resId){caseR.drawable.ic_menu_camera:

Toast.makeText(getApplicationContext(),"点击了相机",Toast.LENGTH_SHORT).show();break;caseR.drawable.ic_menu_photo:

Toast.makeText(getApplicationContext(),"点击了相册",Toast.LENGTH_SHORT).show();break;caseR.drawable.ic_menu_share:

Toast.makeText(getApplicationContext(),"点击了分享",Toast.LENGTH_SHORT).show();break;

}

}

});

作者:Android小Y

链接:/p/220da4460e5d

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。