Android仿知乎悬浮功能按钮FloatingActionButton效果

Android仿知乎悬浮功能按钮FloatingActionButton效果,第1张

概述前段时间在看属性动画,恰巧这个按钮效果可以用属性动画实现,所以就来实践实践。效果基本出来了,大家可以自己去完善。

前段时间在看属性动画,恰巧这个按钮的效果可以用属性动画实现,所以就来实践实践。效果基本出来了,大家可以自己去完善。

首先看一下效果图:

我们看到点击floatingActionbutton后会展开一些item,然后会有一个蒙板效果,这都是这个VIEw的功能。那么这整个VIEw肯定是个VIEwGroup,我们一部分一部分来看。


首先是这个最小的Tag:

这个Tag带文字,可以是一个TextVIEw,但为了美观,我们使用CardVIEw,CardVIEw是一个FrameLayout,我们要让它具有显示文字的功能,就继承CardVIEw自定义一个VIEwGroup。

public class TagVIEw extends CardVIEw

内部维护一个TextVIEw,在其构造函数中我们实例化一个TextVIEw用来显示文字,并在外部调用setTagText的时候把TextVIEw添加到这个CardVIEw中。

public class TagVIEw extends CardVIEw { private TextVIEw mTextVIEw; public TagVIEw(Context context) { this(context,null); } public TagVIEw(Context context,AttributeSet attrs) { this(context,attrs,0); } public TagVIEw(Context context,AttributeSet attrs,int defStyleAttr) { super(context,defStyleAttr); mTextVIEw = new TextVIEw(context); mTextVIEw.setSingleline(true); } protected voID setTextSize(float size){ mTextVIEw.setTextSize(size); } protected voID setTextcolor(int color){ mTextVIEw.setTextcolor(color); } //给内部的TextVIEw添加文字 protected voID setTagText(String text){ mTextVIEw.setText(text); addTag(); } //添加进这个layout中 private voID addTag(){ LayoutParams layoutParams = new LayoutParams(VIEwGroup.LayoutParams.WRAP_CONTENT,VIEwGroup.LayoutParams.WRAP_CONTENT,Gravity.CENTER); int l = dp2px(8); int t = dp2px(8); int r = dp2px(8); int b = dp2px(8); layoutParams.setmargins(l,t,r,b); //addVIEw会引起所有VIEw的layout addVIEw(mTextVIEw,layoutParams); } private int dp2px(int value){ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,value,getResources().getdisplayMetrics()); }}

接下来我们看这个item,它是一个tag和一个fab的组合:

tag使用刚才我们自定义的TagVIEw,fab就用系统的floatingActionbutton,这里显然需要一个VIEwGroup来组合这两个子VIEw,可以使用linearLayout,这里我们就直接使用VIEwGroup。

public class TagFabLayout extends VIEwGroup

我们为这个VIEwGroup设置自定义属性,是为了给tag设置text:

 <declare-styleable name="FabTagLayout"> <attr name="tagText" format="string" /> </declare-styleable>

在构造器中获取自定义属性,初始化TagVIEw并添加到该VIEwGroup中:

 public TagFabLayout(Context context,defStyleAttr); getAttributes(context,attrs); settingTagVIEw(context); } private voID getAttributes(Context context,AttributeSet attributeSet){ TypedArray typedArray = context.obtainStyledAttributes(attributeSet,R.styleable.FabTagLayout); mTagText = typedArray.getString(R.styleable.FabTagLayout_tagText); typedArray.recycle(); } private voID settingTagVIEw(Context context){ mTagVIEw = new TagVIEw(context); mTagVIEw.setTagText(mTagText); addVIEw(mTagVIEw); }

在onMeasure对该VIEwGroup进行测量,这里我直接把宽高设置成wrap_content的了,match_parent和精确值感觉没有必要。TagVIEw和floatingActionbutton横向排列,中间和两边留一点空隙。

@OverrIDe protected voID onMeasure(int wIDthMeasureSpec,int heightmeasureSpec) { super.onMeasure(wIDthMeasureSpec,heightmeasureSpec); int wIDth = 0; int height = 0; int count = getChildCount(); for(int i=0; i<count; i++){  VIEw vIEw = getChildAt(i);  measureChild(vIEw,wIDthMeasureSpec,heightmeasureSpec);  wIDth += vIEw.getMeasureDWIDth();  height = Math.max(height,vIEw.getMeasuredHeight()); } wIDth += dp2px(8 + 8 + 8); height += dp2px(8 + 8); //直接将该VIEwGroup设定为wrap_content的 setMeasuredDimension(wIDth,height); }

在onLayout中横向布局,tag在左,fab在右。

@OverrIDe protected voID onLayout(boolean changed,int l,int t,int r,int b) { //为子VIEw布局 VIEw tagVIEw = getChildAt(0); VIEw fabVIEw = getChildAt(1); int tagWIDth = tagVIEw.getMeasureDWIDth(); int tagHeight = tagVIEw.getMeasuredHeight(); int fabWIDth = fabVIEw.getMeasureDWIDth(); int fabHeight = fabVIEw.getMeasuredHeight(); int tl = dp2px(8); int tt = (getMeasuredHeight() - tagHeight) / 2; int tr = tl + tagWIDth; int tb = tt + tagHeight; int fl = tr + dp2px(8); int ft = (getMeasuredHeight() - fabHeight) / 2; int fr = fl + fabWIDth; int fb = ft + fabHeight; fabVIEw.layout(fl,ft,fr,fb); tagVIEw.layout(tl,tt,tr,tb); bindEvents(tagVIEw,fabVIEw); }

还要为这两个子VIEw注册OnClickListener,这是点击事件传递的源头。

private voID bindEvents(VIEw tagVIEw,VIEw fabVIEw){ tagVIEw.setonClickListener(new OnClickListener() {  @OverrIDe  public voID onClick(VIEw v) {  if(mOnTagClickListener != null){   mOnTagClickListener.onTagClick();  }  } }); fabVIEw.setonClickListener(new OnClickListener() {  @OverrIDe  public voID onClick(VIEw v) {  if (mOnFabClickListener != null){   mOnFabClickListener.onFabClick();  }  } }); }

现在item的VIEwGroup有了,我们还需要一个蒙板,一个主fab,那么我们来看最终的VIEwGroup。

思路也很清楚,蒙板是match_parent的,主fab在右下角(当然我们可以自己设置,也可以对外提供接口来设置位置),三个item(也就是TagFabLayout)在主fab的上面。至于动画效果,在点击事件中触发。

public class MultifloatingActionbutton extends VIEwGroup

这里我们还需要自定义一些属性,比如蒙板的颜色、主Fab的颜色、主Fab的图案(当然,你把主Fab直接写在xml中就可以直接定义这些属性)、动画的duaration、动画的模式等。

 <attr name="animationMode"> <enum name="fade" value="0"/> <enum name="scale" value="1"/> <enum name="bounce" value="2"/> </attr> <attr name="position"> <enum name="left_bottom" value="0"/> <enum name="right_bottom" value="1"/> </attr> <declare-styleable name="MultifloatingActionbutton"> <attr name="backgroundcolor" format="color"/> <attr name="switchFabIcon" format="reference"/> <attr name="switchFabcolor" format="color"/> <attr name="animationDuration" format="integer"/> <attr name="animationMode"/> <attr name="position"/> </declare-styleable>

在构造器中我们同样是获取并初始化属性:

public MultifloatingActionbutton(Context context,defStyleAttr); //获取属性值 getAttributes(context,attrs); //添加一个背景VIEw和一个floatingActionbutton setBaseVIEws(context); }private voID getAttributes(Context context,AttributeSet attrs){ TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.MultifloatingActionbutton); mBackgroundcolor = typedArray.getcolor(  R.styleable.MultifloatingActionbutton_backgroundcolor,color.transparent); mFabIcon = typedArray.getDrawable(R.styleable.MultifloatingActionbutton_switchFabIcon); mFabcolor = typedArray.getcolorStateList(R.styleable.MultifloatingActionbutton_switchFabcolor); mAnimationDuration = typedArray.getInt(R.styleable.MultifloatingActionbutton_animationDuration,150); mAnimationMode = typedArray.getInt(R.styleable.MultifloatingActionbutton_animationMode,ANIM_SCALE); mposition = typedArray.getInt(R.styleable.MultifloatingActionbutton_position,POS_RIGHT_BottOM); typedArray.recycle(); }

接着我们初始化、添加蒙板和主fab。

private voID setBaseVIEws(Context context){ mBackgroundVIEw = new VIEw(context); mBackgroundVIEw.setBackgroundcolor(mBackgroundcolor); mBackgroundVIEw.setAlpha(0); addVIEw(mBackgroundVIEw); mfloatingActionbutton = new floatingActionbutton(context); mfloatingActionbutton.setBackgroundTintList(mFabcolor); mfloatingActionbutton.setimageDrawable(mFabIcon); addVIEw(mfloatingActionbutton); }

在onMeasure中,我们并不会对这个VIEwGroup进行wrap_content的支持,因为基本上都是match_parent的吧,也不会有精确值,而且这个VIEwGroup应该是在顶层的。我们看下onLayout方法,在这个方法中,我们对所有子VIEw进行布局。

@OverrIDe protected voID onLayout(boolean changed,int b) { if(changed){  //布局背景和主Fab  layoutfloatingActionbutton();  layoutBackgroundVIEw();  layoutItems(); } }

首先布局主Fab,它在右下角,然后添加点击事件,点击这个主Fab后,会涉及到旋转主Fab,改变蒙板透明度,打开或关闭items等 *** 作,这些等下再说。

private voID layoutfloatingActionbutton(){ int wIDth = mfloatingActionbutton.getMeasureDWIDth(); int height = mfloatingActionbutton.getMeasuredHeight(); int fl = 0; int ft = 0; int fr = 0; int fb = 0; switch (mposition){  case POS_left_BottOM:  case POS_RIGHT_BottOM:  fl = getMeasureDWIDth() - wIDth - dp2px(8);  ft = getMeasuredHeight() - height - dp2px(8);  fr = fl + wIDth;  fb = ft + height;  break; } mfloatingActionbutton.layout(fl,fb); bindfloatingEvent();}private voID bindfloatingEvent(){ mfloatingActionbutton.setonClickListener(new OnClickListener() {  @OverrIDe  public voID onClick(VIEw v) {  rotatefloatingbutton();  changeBackground();  changeStatus();  if (isMenuOpen) {   openMenu();  } else {   closeMenu();  }  } }); }

然后布局背景:

private voID layoutBackgroundVIEw(){ mBackgroundVIEw.layout(0,getMeasureDWIDth(),getMeasuredHeight()); }

接着布局items,并为items添加点击事件。每个item都是TagFabLayout,可以为它setonTagClickListener和setonFabClickListener,以便我们点击这两块区域的时候都要能响应,并且我们让这两个回调函数中做同样的事情:旋转主Fab、改变背景、关闭items(因为能点击一定是展开状态)。此时还要在这个VIEwGroup中设置一个接口OnFabItemClickListener,用于将点击的位置传递出去,例如Activity实现了这个接口,就可以在onTagClick和onFabClick方法中调用mOnFabItemClickListener.onFabItemClick()方法。说一下这里的布局,是累积向上的,注意坐标的计算。

private voID layoutItems(){ int count = getChildCount(); for(int i=2; i<count; i++) {  TagFabLayout child = (TagFabLayout) getChildAt(i);  child.setVisibility(INVISIBLE);  //获取自身测量宽高,这里说一下,由于TagFabLayout我们默认形成wrap_content,所以这里测量到的是wrap_content的最终大小  int wIDth = child.getMeasureDWIDth();  int height = child.getMeasuredHeight();  // 获取主Fab测量宽高  int fabHeight = mfloatingActionbutton.getMeasuredHeight();  int cl = 0;  int ct = 0;  switch (mposition) {  case POS_left_BottOM:  case POS_RIGHT_BottOM:   cl = getMeasureDWIDth() - wIDth - dp2px(8);   ct = getMeasuredHeight() - fabHeight - (i - 1) * height - dp2px(8);  }  child.layout(cl,ct,cl + wIDth,ct + height);  bindMenuEvents(child,i);  prepareAnim(child); }}private voID bindMenuEvents(final TagFabLayout child,final int pos){ child.setonTagClickListener(new TagFabLayout.OnTagClickListener() {  @OverrIDe  public voID onTagClick() {  rotatefloatingbutton();  changeBackground();  changeStatus();  closeMenu();  if(mOnFabItemClickListener != null){   mOnFabItemClickListener.onFabItemClick(child,pos);  }  } }); child.setonFabClickListener(new TagFabLayout.OnFabClickListener() {  @OverrIDe  public voID onFabClick() {  rotatefloatingbutton();  changeBackground();  changeStatus();  closeMenu();  if (mOnFabItemClickListener != null){   mOnFabItemClickListener.onFabItemClick(child,pos);  }  } });}

现在所有的布局和点击事件都已经绑定好了,我们来看下rotatefloatingbutton()、 changeBackground() 、 openMenu() 、closeMenu()这几个和属性动画相关的函数。

其实也很简单,rotatefloatingbutton()对mfloatingActionbutton的rotation这个属性进行改变,以菜单是否打开为判断条件。

private voID rotatefloatingbutton(){ ObjectAnimator animator = isMenuOpen ? ObjectAnimator.offloat(mfloatingActionbutton,"rotation",45F,0f) : ObjectAnimator.offloat(mfloatingActionbutton,0f,45f); animator.setDuration(150); animator.setInterpolator(new linearInterpolator()); animator.start(); }

changeBackground()改变mBackgroundVIEw的Alpha这个属性,也是以菜单是否打开为判断条件。

private voID changeBackground(){ ObjectAnimator animator = isMenuOpen ? ObjectAnimator.offloat(mBackgroundVIEw,"Alpha",0.9f,0f) : ObjectAnimator.offloat(mBackgroundVIEw,0.9f); animator.setDuration(150); animator.setInterpolator(new linearInterpolator()); animator.start(); }

openMenu() 中根据不同的模式来实现打开的效果,看一下scaletoShow(),这里同时对scaleX、scaleY、Alpha这3个属性进行动画,来达到放大显示的效果。

private voID openMenu(){ switch (mAnimationMode){  case ANIM_BOUNCE:  bouncetoShow();  break;  case ANIM_SCALE:  scaletoShow(); } }private voID scaletoShow(){ for(int i = 2; i<getChildCount(); i++){  VIEw vIEw = getChildAt(i);  vIEw.setVisibility(VISIBLE);  vIEw.setAlpha(0);  ObjectAnimator scaleX = ObjectAnimator.offloat(vIEw,"scaleX",1f);  ObjectAnimator scaleY = ObjectAnimator.offloat(vIEw,"scaleY",1f);  ObjectAnimator Alpha = ObjectAnimator.offloat(vIEw,1f);  AnimatorSet set = new AnimatorSet();  set.playTogether(scaleX,scaleY,Alpha);  set.setDuration(mAnimationDuration);  set.start(); }}

差不多达到我们要求的效果了,但是还有一个小地方需要注意一下,在menu展开的时候,如果我们点击menu以外的区域,即蒙板上的区域,此时VIEwGroup是不会拦截任何touch事件,如果在这个floatingActionbutton下面有可以被点击响应的VIEw,比如ListVIEw,就会在蒙板显示的情况下进行响应,正确的逻辑应该是关闭menu。

那么我们需要在onIntercepttouchEvent中处理事件的拦截,这里判断的方法是:如果menu是打开的,我们在DOWN事件中判断x,y是否落在了a或b区域,如下图

如果是的话,该VIEwGroup应该拦截这个事件,交由自身的ontouchEvent处理。

@OverrIDe public boolean onIntercepttouchEvent(MotionEvent ev) { boolean intercepted = false; int x = (int)ev.getX(); int y = (int)ev.getY(); if(isMenuOpen){  switch (ev.getAction()){  case MotionEvent.ACTION_DOWN:   if(judgeIftouchBackground(x,y)){   intercepted = true;   }   intercepted = false;   break;  case MotionEvent.ACTION_MOVE:   intercepted = false;   break;  case MotionEvent.ACTION_UP:   intercepted = false;   break;  } } return intercepted; } private boolean judgeIftouchBackground(int x,int y){  Rect a = new Rect();  Rect b = new Rect();  a.set(0,getWIDth(),getHeight() - getChildAt(getChildCount() - 1).gettop());  b.set(0,getChildAt(getChildCount() - 1).gettop(),getChildAt(getChildCount() - 1).getleft(),getHeight());  if(a.contains(x,y) || b.contains(x,y)){  return true;  }  return false; }

在ontouchEvent中做关闭menu等 *** 作。

 @OverrIDe public boolean ontouchEvent(MotionEvent event) { if(isMenuOpen){  closeMenu();  changeBackground();  rotatefloatingbutton();  changeStatus();  return true; } return super.ontouchEvent(event); }

再看一下,效果不错。

由于我做的小app中涉及到切换夜间模式,这个VIEwGroup的背景色应该随着主题改变,设置该VIEw的背景色为

app:backgroundcolor="?attr/myBackground"

重写VIEwGroup的 setBackgroundcolor方法,这里所谓的背景色其实就是蒙板的颜色。

public voID setBackgroundcolor(int color){ mBackgroundcolor = color; mBackgroundVIEw.setBackgroundcolor(color);}

基本功能到这里全部完成了,问题还有很多,比如没有提供根据不同的position进行布局、没有提供根据不同mode设置menu开闭的效果,但是后续我还会继续改进和完善^ ^。欢迎交流。如果大家需要源码,可以去我源码里的customvIEw里面自取。在这里

以上所述是小编给大家介绍的AndroID仿知乎悬浮功能按钮floatingActionbutton效果,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程小技巧网站的支持!

总结

以上是内存溢出为你收集整理的Android仿知乎悬浮功能按钮FloatingActionButton效果全部内容,希望文章能够帮你解决Android仿知乎悬浮功能按钮FloatingActionButton效果所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1146421.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-31
下一篇2022-05-31

发表评论

登录后才能评论

评论列表(0条)

    保存