欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android仿知乎懸浮功能按鈕FloatingActionButton效果

 更新時間:2017年04月11日 11:18:24   作者:chengkun_123  
前段時間在看屬性動畫,恰巧這個按鈕的效果可以用屬性動畫實現(xiàn),下面通過本文給大家分享adroid仿知乎懸浮功能按鈕FloatingActionButton效果,需要的朋友參考下吧

前段時間在看屬性動畫,恰巧這個按鈕的效果可以用屬性動畫實現(xiàn),所以就來實踐實踐。效果基本出來了,大家可以自己去完善。

首先看一下效果圖:

p2.gif

我們看到點擊FloatingActionButton后會展開一些item,然后會有一個蒙板效果,這都是這個View的功能。那么這整個View肯定是個ViewGroup,我們一部分一部分來看。

p3.jpg

首先是這個最小的Tag:

p5.jpg

這個Tag帶文字,可以是一個TextView,但為了美觀,我們使用CardView,CardView是一個FrameLayout,我們要讓它具有顯示文字的功能,就繼承CardView自定義一個ViewGroup。

public class TagView extends CardView

內(nèi)部維護一個TextView,在其構(gòu)造函數(shù)中我們實例化一個TextView用來顯示文字,并在外部調(diào)用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, attrs, defStyleAttr);
 mTextView = new TextView(context);
 mTextView.setSingleLine(true);
 }
 protected void setTextSize(float size){
 mTextView.setTextSize(size);
 }
 protected void setTextColor(int color){
 mTextView.setTextColor(color);
 }
 //給內(nèi)部的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的組合:

p6.jpg

tag使用剛才我們自定義的TagView,fab就用系統(tǒng)的FloatingActionButton,這里顯然需要一個ViewGroup來組合這兩個子View,可以使用LinearLayout,這里我們就直接使用ViewGroup。

public class TagFabLayout extends ViewGroup

我們?yōu)檫@個ViewGroup設(shè)置自定義屬性,是為了給tag設(shè)置text:

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

在構(gòu)造器中獲取自定義屬性,初始化TagView并添加到該ViewGroup中:

 public TagFabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, 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進行測量,這里我直接把寬高設(shè)置成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設(shè)定為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注冊O(shè)nClickListener,這是點擊事件傳遞的源頭。

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();
  }
  }
 });
 }

現(xiàn)在item的ViewGroup有了,我們還需要一個蒙板,一個主fab,那么我們來看最終的ViewGroup。

p7.jpg

思路也很清楚,蒙板是match_parent的,主fab在右下角(當(dāng)然我們可以自己設(shè)置,也可以對外提供接口來設(shè)置位置),三個item(也就是TagFabLayout)在主fab的上面。至于動畫效果,在點擊事件中觸發(fā)。

public class MultiFloatingActionButton extends ViewGroup

這里我們還需要自定義一些屬性,比如蒙板的顏色、主Fab的顏色、主Fab的圖案(當(dāng)然,你把主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>

在構(gòu)造器中我們同樣是獲取并初始化屬性:

public MultiFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, 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應(yīng)該是在頂層的。我們看下onLayout方法,在這個方法中,我們對所有子View進行布局。

@Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 if(changed){
  //布局背景和主Fab
  layoutFloatingActionButton();
  layoutBackgroundView();
  layoutItems();
 }
 }

首先布局主Fab,它在右下角,然后添加點擊事件,點擊這個主Fab后,會涉及到旋轉(zhuǎn)主Fab,改變蒙板透明度,打開或關(guān)閉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, ft, fr, 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, 0
  , getMeasuredWidth(), getMeasuredHeight());
 }

接著布局items,并為items添加點擊事件。每個item都是TagFabLayout,可以為它setOnTagClickListener和setOnFabClickListener,以便我們點擊這兩塊區(qū)域的時候都要能響應(yīng),并且我們讓這兩個回調(diào)函數(shù)中做同樣的事情:旋轉(zhuǎn)主Fab、改變背景、關(guān)閉items(因為能點擊一定是展開狀態(tài))。此時還要在這個ViewGroup中設(shè)置一個接口OnFabItemClickListener,用于將點擊的位置傳遞出去,例如Activity實現(xiàn)了這個接口,就可以在onTagClick和onFabClick方法中調(diào)用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);
  }
  }
 });
}

現(xiàn)在所有的布局和點擊事件都已經(jīng)綁定好了,我們來看下rotateFloatingButton()、 changeBackground() 、 openMenu() 、closeMenu()這幾個和屬性動畫相關(guān)的函數(shù)。

其實也很簡單,rotateFloatingButton()對mFloatingActionButton的rotation這個屬性進行改變,以菜單是否打開為判斷條件。

private void rotateFloatingButton(){
 ObjectAnimator animator = isMenuOpen ? ObjectAnimator.ofFloat(mFloatingActionButton
  , "rotation", 45F, 0f) : ObjectAnimator.ofFloat(mFloatingActionButton, "rotation", 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, "alpha", 0f, 0.9f);
 animator.setDuration(150);
 animator.setInterpolator(new LinearInterpolator());
 animator.start();
 }

openMenu() 中根據(jù)不同的模式來實現(xiàn)打開的效果,看一下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", 0f, 1f);
  ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 0f, 1f);
  ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
  AnimatorSet set = new AnimatorSet();
  set.playTogether(scaleX, scaleY, alpha);
  set.setDuration(mAnimationDuration);
  set.start();
 }
}

差不多達到我們要求的效果了,但是還有一個小地方需要注意一下,在menu展開的時候,如果我們點擊menu以外的區(qū)域,即蒙板上的區(qū)域,此時ViewGroup是不會攔截任何Touch事件,如果在這個FloatingActionButton下面有可以被點擊響應(yīng)的View,比如ListView,就會在蒙板顯示的情況下進行響應(yīng),正確的邏輯應(yīng)該是關(guān)閉menu。

p8.gif

那么我們需要在onInterceptTouchEvent中處理事件的攔截,這里判斷的方法是:如果menu是打開的,我們在DOWN事件中判斷x,y是否落在了a或b區(qū)域,如下圖

p4.png

如果是的話,該ViewGroup應(yīng)該攔截這個事件,交由自身的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, 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中做關(guān)閉menu等操作。

 @Override
 public boolean onTouchEvent(MotionEvent event) {
 if(isMenuOpen){
  closeMenu();
  changeBackground();
  rotateFloatingButton();
  changeStatus();
  return true;
 }
 return super.onTouchEvent(event);
 }

再看一下,效果不錯。

由于我做的小app中涉及到切換夜間模式,這個ViewGroup的背景色應(yīng)該隨著主題改變,設(shè)置該View的背景色為

app:backgroundColor="?attr/myBackground"

重寫ViewGroup的 setBackgroundColor方法,這里所謂的背景色其實就是蒙板的顏色。

public void setBackgroundColor(int color){
 mBackgroundColor = color;
 mBackgroundView.setBackgroundColor(color);
}

基本功能到這里全部完成了,問題還有很多,比如沒有提供根據(jù)不同的position進行布局、沒有提供根據(jù)不同mode設(shè)置menu開閉的效果,但是后續(xù)我還會繼續(xù)改進和完善^ ^。歡迎交流。如果大家需要源碼,可以去我源碼里的customview里面自取。在這里

以上所述是小編給大家介紹的Android仿知乎懸浮功能按鈕FloatingActionButton效果,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!

相關(guān)文章

最新評論