志在指尖
用双手敲打未来

Android自定义View实战

概述
如今很多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<mMenuItemResIds.size();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(90i/(count-1));
tranY=弹出半径cos(90i/(count-1));
再分离透明度和大小的变化,代码如下:
/**
*菜单展开动画
*/
privatevoidstartOpenAnim(){
intcount=mMenuItemResIds.size();
Listanimators=newArrayList<>();
for(inti=0;i<count;i++){
inttranX=-(int)(mRadius*Math.sin(Math.toRadians(90*i/(count-1))));
inttranY=-(int)(mRadius*Math.cos(Math.toRadians(90*i/(count-1))));
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<count;i++){
inttranX=-(int)(mRadius*Math.sin(Math.toRadians(90*i/(count-1))));
inttranY=-(int)(mRadius*Math.cos(Math.toRadians(90*i/(count-1))));
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<count;i++){
if(index==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.size();i++){
inttranX=(int)(mRadius*Math.sin(Math.toRadians(90*i/(count-1))));
inttranY=(int)(mRadius*Math.cos(Math.toRadians(90*i/(count-1))));
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<mMenuItemResIds.size()){
mItemListener.clickMenuItem(mMenuItemResIds.get(index));
}
}
}
就是正常的暴露接口,将菜单对应的资源id传进来,供外界判别点击的是哪个菜单项。
应用
xml规划中援用(这里的宽高由设置的弧长半径决议,只需设置wrap_conetnt即可):
<com.zjywidget.widget.arcmenu.YArcMenuView
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;
}
}
});

未经允许不得转载:IT技术网站 » Android自定义View实战
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

志在指尖 用双手敲打未来

登录/注册IT技术大全

热门IT技术

C#基础入门   SQL server数据库   系统SEO学习教程   WordPress小技巧   WordPress插件   脚本与源码下载