2015-09-05 148 views
22

我正在使用自定义视图创建FloatingActionMenuFloatingActionButtons。我已经修改了这个类,使它几乎完美地工作,当我最近试图添加一个阴影时,我遇到了一个问题,阴影被一个看不见的方块切割,因为类将它硬编码为一个正方形,我相信。FloatingActionButton Shadow by Square

见下面的图片:

FloatingActionButton

我现在用的就是这样我就可以在菜单多FloatingActionButtons(FAB)中的类。

这里是类:

package terranovaproductions.newcomicreader; 

import android.animation.Animator; 
import android.animation.AnimatorSet; 
import android.animation.ObjectAnimator; 
import android.animation.TimeInterpolator; 
import android.animation.ValueAnimator; 
import android.content.Context; 
import android.graphics.Color; 
import android.graphics.drawable.ColorDrawable; 
import android.graphics.drawable.Drawable; 
import android.os.Bundle; 
import android.os.Parcelable; 
import android.support.annotation.NonNull; 
import android.support.design.widget.FloatingActionButton; 
import android.util.AttributeSet; 
import android.util.Log; 
import android.view.GestureDetector; 
import android.view.Gravity; 
import android.view.MotionEvent; 
import android.view.View; 
import android.view.ViewGroup; 
import android.view.animation.AnticipateInterpolator; 
import android.view.animation.OvershootInterpolator; 
import android.widget.ImageView; 
import android.widget.TextView; 

import java.util.ArrayList; 

/** 
* Created by charry on 2015/6/11. https://gist.github.com/douo/dfde289778a9b3b6918f and modified by Tristan Wiley 
*/ 
public class FloatingActionMenu extends ViewGroup { 

    static final TimeInterpolator DEFAULT_OPEN_INTERPOLATOR = new OvershootInterpolator(); 
    static final TimeInterpolator DEFAULT_CLOSE_INTERPOLATOR = new AnticipateInterpolator(); 
    private static final long ANIMATION_DURATION = 300; 
    private static final int DEFAULT_CHILD_GRAVITY = Gravity.END | Gravity.BOTTOM; 
    Animator animator = new Animator() { 
     @Override 
     public long getStartDelay() { 
      return 0; 
     } 

     @Override 
     public void setStartDelay(long startDelay) { 

     } 

     @Override 
     public Animator setDuration(long duration) { 
      duration = 2; 
      return null; 
     } 

     @Override 
     public long getDuration() { 
      return 0; 
     } 

     @Override 
     public void setInterpolator(TimeInterpolator value) { 

     } 

     @Override 
     public boolean isRunning() { 
      return true; 
     } 
    }; 
    private FloatingActionButton mMenuButton; 
    private ArrayList<FloatingActionButton> mMenuItems; 
    private ArrayList<TextView> mMenuItemLabels; 
    private ArrayList<ItemAnimator> mMenuItemAnimators; 
    private int mItemMargin; 
    private AnimatorSet mOpenAnimatorSet = new AnimatorSet(); 
    private AnimatorSet mCloseAnimatorSet = new AnimatorSet(); 
    private ImageView mIcon; 
    private boolean mOpen; 
    private boolean animating; 
    private boolean mIsSetClosedOnTouchOutside = true; 
    private OnMenuItemClickListener onMenuItemClickListener; 
    private OnMenuToggleListener onMenuToggleListener; 
    GestureDetector mGestureDetector = new GestureDetector(getContext(), 
      new GestureDetector.SimpleOnGestureListener() { 

       @Override 
       public boolean onDown(MotionEvent e) { 
        return mIsSetClosedOnTouchOutside && isOpened(); 
       } 

       @Override 
       public boolean onSingleTapUp(MotionEvent e) { 
        close(); 
        return true; 
       } 
      }); 
    private OnClickListener mOnItemClickListener = new OnClickListener() { 
     @Override 
     public void onClick(View v) { 
      if (v instanceof FloatingActionButton) { 
       int i = mMenuItems.indexOf(v); 
       if (onMenuItemClickListener != null) { 
        onMenuItemClickListener.onMenuItemClick(FloatingActionMenu.this, i, (FloatingActionButton) v); 
       } 
      } else if (v instanceof TextView) { 
       int i = mMenuItemLabels.indexOf(v); 
       if (onMenuItemClickListener != null) { 
        onMenuItemClickListener.onMenuItemClick(FloatingActionMenu.this, i, mMenuItems.get(i)); 
       } 
      } 
      close(); 
     } 
    }; 


    public FloatingActionMenu(Context context) { 
     this(context, null, 0); 
    } 

    public FloatingActionMenu(Context context, AttributeSet attrs) { 
     this(context, attrs, 0); 
    } 

    public FloatingActionMenu(Context context, AttributeSet attrs, int defStyleAttr) { 
     super(context, attrs, defStyleAttr); 
     mMenuItems = new ArrayList<>(5); 
     mMenuItemAnimators = new ArrayList<>(5); 

     mMenuItemLabels = new ArrayList<>(5); 
     mIcon = new ImageView(context); 
    } 

    @Override 
    protected void onFinishInflate() { 
     bringChildToFront(mMenuButton); 
     bringChildToFront(mIcon); 
     super.onFinishInflate(); 
    } 

    @Override 
    public void addView(@NonNull View child, int index, LayoutParams params) { 
     super.addView(child, index, params); 
     if (getChildCount() > 1) { 
      if (child instanceof FloatingActionButton) { 
       addMenuItem((FloatingActionButton) child); 
      } 
     } else { 
      mMenuButton = (FloatingActionButton) child; 
      mIcon.setImageDrawable(mMenuButton.getDrawable()); 
      addView(mIcon); 
      mMenuButton.setImageDrawable(mMenuButton.getDrawable()); 
      createDefaultIconAnimation(); 
      mMenuButton.setOnClickListener(new OnClickListener() { 
       @Override 
       public void onClick(View v) { 
        toggle(); 
       } 
      }); 
     } 
    } 

    public void toggle() { 
     if (!mOpen) { 
      open(); 
     } else { 
      close(); 
     } 
    } 

    public void open() { 
     d("open"); 
     startOpenAnimator(); 
     mOpen = true; 
     if (onMenuToggleListener != null) { 
      onMenuToggleListener.onMenuToggle(true); 
     } 
    } 

    public void close() { 
     startCloseAnimator(); 
     mOpen = false; 
     if (onMenuToggleListener != null) { 
      onMenuToggleListener.onMenuToggle(true); 
     } 
    } 

    protected void startCloseAnimator() { 
     mCloseAnimatorSet.start(); 
     for (ItemAnimator anim : mMenuItemAnimators) { 
      anim.startCloseAnimator(); 
     } 
    } 

// Rect rect = new Rect(); 
// Paint paint = new Paint(); 
// 
// @Override 
// protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) { 
//  boolean b = super.drawChild(canvas, child, drawingTime); 
//  paint.setColor(0xFFFF0000); 
//  paint.setStyle(Paint.Style.STROKE); 
//  rect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); 
//  canvas.drawRect(rect, paint); 
//  return b; 
// } 

    protected void startOpenAnimator() { 
     mOpenAnimatorSet.start(); 
     for (ItemAnimator anim : mMenuItemAnimators) { 
      anim.startOpenAnimator(); 
     } 
    } 

    public void addMenuItem(FloatingActionButton item) { 
     mMenuItems.add(item); 
     mMenuItemAnimators.add(new ItemAnimator(item)); 

     TextView label = new TextView(getContext()); 


     label.setBackgroundResource(R.drawable.rounded_corners); 

     label.setTextColor(Color.WHITE); 
     label.setText(item.getContentDescription()); 

     Integer paddingSize = (int)label.getTextSize()/3; 

     float scale = getResources().getDisplayMetrics().density; 
     int pxtodp = (int) (6*scale + 0.5f); 

     label.setPadding(paddingSize, paddingSize, paddingSize + pxtodp, paddingSize); 

     addView(label); 
     mMenuItemLabels.add(label); 
     item.setTag(label); 
     item.setOnClickListener(mOnItemClickListener); 
     label.setOnClickListener(mOnItemClickListener); 
    } 

    @Override 
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
     int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
     int widthMode = MeasureSpec.getMode(widthMeasureSpec); 
     int width; 
     int heightSize = MeasureSpec.getSize(heightMeasureSpec); 
     int heightMode = MeasureSpec.getMode(heightMeasureSpec); 
     int height; 
     final int count = getChildCount(); 
     int maxChildWidth = 0; 
     for (int i = 0; i < count; i++) { 
      View child = getChildAt(i); 
      measureChild(child, widthMeasureSpec, heightMeasureSpec); 
     } 
     for (int i = 0; i < mMenuItems.size(); i++) { 
      FloatingActionButton fab = mMenuItems.get(i); 
      TextView label = mMenuItemLabels.get(i); 
      maxChildWidth = Math.max(maxChildWidth, label.getMeasuredWidth() + fab.getMeasuredWidth() + mItemMargin); 

     } 

     maxChildWidth = Math.max(mMenuButton.getMeasuredWidth(), maxChildWidth); 

     if (widthMode == MeasureSpec.EXACTLY) { 
      width = widthSize; 
     } else { 
      width = maxChildWidth + 30; 
     } 
     if (heightMode == MeasureSpec.EXACTLY) { 
      height = heightSize; 
     } else { 
      int heightSum = 0; 
      for (int i = 0; i < count; i++) { 
       View child = getChildAt(i); 
       heightSum += child.getMeasuredHeight(); 
      } 
      height = heightSum + 20; 
     } 

     setMeasuredDimension(resolveSize(width, widthMeasureSpec), 
       resolveSize(height, heightMeasureSpec)); 
    } 

    @Override 
    public boolean onTouchEvent(@NonNull MotionEvent event) { 
     if (mIsSetClosedOnTouchOutside) { 
      return mGestureDetector.onTouchEvent(event); 
     } else { 
      return super.onTouchEvent(event); 
     } 
    } 

    @Override 
    protected void onLayout(boolean changed, int l, int t, int r, int b) { 
     System.out.println("onLayout:" + changed); 
     if (changed) { 
      int right = r - getPaddingRight(); 
      int bottom = b - getPaddingBottom(); 
      int top = bottom - mMenuButton.getMeasuredHeight(); 

      mMenuButton.layout(right - mMenuButton.getMeasuredWidth(), top, right, bottom); 
      int dw = (mMenuButton.getMeasuredWidth() - mIcon.getMeasuredWidth())/2; 
      int dh = (mMenuButton.getMeasuredHeight() - mIcon.getMeasuredHeight())/2; 
      mIcon.layout(right - mIcon.getMeasuredWidth() - dw, bottom - mIcon.getMeasuredHeight() - dh, right - dw, bottom - dh); 
      for (int i = 0; i < mMenuItems.size(); i++) { 
       FloatingActionButton item = mMenuItems.get(i); 
       TextView label = mMenuItemLabels.get(i); 


       bottom = top -= mMenuItems.get(i).getPaddingBottom(); //Add 10px padding 

       top -= item.getMeasuredHeight(); 
       int width = item.getMeasuredWidth(); 
       int d = (mMenuButton.getMeasuredWidth() - width)/2; 
       item.layout(right - width - d, top, right - d, bottom); 
       d = (item.getMeasuredHeight() - label.getMeasuredHeight())/2; 

       label.layout(item.getLeft() - mItemMargin - label.getMeasuredWidth(), item.getTop() + d, item.getLeft() - mItemMargin, item.getTop() + d + label.getMeasuredHeight()); 
       label.setBackgroundResource(R.drawable.rounded_corners); 

       if (!animating) { 
        if (!mOpen) { 
         item.setTranslationY(mMenuButton.getTop() - item.getTop()); 
         item.setVisibility(GONE); 
         label.setVisibility(GONE); 
        } else { 
         item.setTranslationY(0); 
         item.setVisibility(VISIBLE); 
         label.setVisibility(VISIBLE); 
        } 
       } 
      } 
      if (!animating && getBackground() != null) { 
       if (!mOpen) { 
        getBackground().setAlpha(0); 
       } else { 
        getBackground().setAlpha(0xff); 
       } 
      } 
     } 
    } 

    private void createDefaultIconAnimation() { 
     Animator.AnimatorListener listener = new Animator.AnimatorListener() { 
      @Override 
      public void onAnimationStart(Animator animation) { 
       animating = true; 
      } 

      @Override 
      public void onAnimationEnd(Animator animation) { 
       animating = false; 
      } 

      @Override 
      public void onAnimationCancel(Animator animation) { 
       animating = false; 
      } 

      @Override 
      public void onAnimationRepeat(Animator animation) { 

      } 
     }; 
     ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(
       mIcon, 
       "rotation", 
       135f, 
       0f 
     ); 

     ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(
       mIcon, 
       "rotation", 
       0f, 
       135f 
     ); 

     if (getBackground() != null) { 


      ValueAnimator hideBackgroundAnimator = ObjectAnimator.ofInt(0xff, 0); 
      hideBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
       @Override 
       public void onAnimationUpdate(ValueAnimator animation) { 
        Integer alpha = (Integer) animation.getAnimatedValue(); 
        //System.out.println(alpha); 
        getBackground().setAlpha(alpha > 0xff ? 0xff : alpha); 
       } 
      }); 
      ValueAnimator showBackgroundAnimator = ObjectAnimator.ofInt(0, 0xff); 
      showBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
       @Override 
       public void onAnimationUpdate(ValueAnimator animation) { 

        Integer alpha = (Integer) animation.getAnimatedValue(); 
        //System.out.println(alpha); 
        getBackground().setAlpha(alpha > 0xff ? 0xff : alpha); 
       } 
      }); 

      mOpenAnimatorSet.playTogether(expandAnimator, showBackgroundAnimator); 
      mCloseAnimatorSet.playTogether(collapseAnimator, hideBackgroundAnimator); 
     } else { 
      mOpenAnimatorSet.playTogether(expandAnimator); 
      mCloseAnimatorSet.playTogether(collapseAnimator); 
     } 

     mOpenAnimatorSet.setInterpolator(DEFAULT_OPEN_INTERPOLATOR); 
     mCloseAnimatorSet.setInterpolator(DEFAULT_CLOSE_INTERPOLATOR); 

     mOpenAnimatorSet.setDuration(ANIMATION_DURATION); 
     mCloseAnimatorSet.setDuration(ANIMATION_DURATION); 

     mOpenAnimatorSet.addListener(listener); 
     mCloseAnimatorSet.addListener(listener); 
    } 

    public boolean isOpened() { 
     return mOpen; 
    } 

    @Override 
    public Parcelable onSaveInstanceState() { 
     d("onSaveInstanceState"); 
     Bundle bundle = new Bundle(); 
     bundle.putParcelable("instanceState", super.onSaveInstanceState()); 
     bundle.putBoolean("mOpen", mOpen); 
     // ... save everything 
     return bundle; 
    } 

    @Override 
    public void onRestoreInstanceState(Parcelable state) { 
     d("onRestoreInstanceState"); 
     if (state instanceof Bundle) { 
      Bundle bundle = (Bundle) state; 
      mOpen = bundle.getBoolean("mOpen"); 
      // ... load everything 
      state = bundle.getParcelable("instanceState"); 
     } 
     super.onRestoreInstanceState(state); 
    } 

    @Override 
    protected void onDetachedFromWindow() { 
     d("onDetachedFromWindow"); 
     //getBackground().setAlpha(bgAlpha);//reset default alpha 
     super.onDetachedFromWindow(); 
    } 

    @Override 
    public void setBackground(Drawable background) { 
     if (background instanceof ColorDrawable) { 
      // after activity finish and relaucher , background drawable state still remain? 
      int bgAlpha = Color.alpha(((ColorDrawable) background).getColor()); 
      d("bg:" + Integer.toHexString(bgAlpha)); 
      super.setBackground(background); 
     } else { 
      throw new IllegalArgumentException("floating only support color background"); 
     } 
    } 

    public OnMenuToggleListener getOnMenuToggleListener() { 
     return onMenuToggleListener; 
    } 

    public void setOnMenuToggleListener(OnMenuToggleListener onMenuToggleListener) { 
     this.onMenuToggleListener = onMenuToggleListener; 
    } 

    public OnMenuItemClickListener getOnMenuItemClickListener() { 
     return onMenuItemClickListener; 
    } 

    public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) { 
     this.onMenuItemClickListener = onMenuItemClickListener; 
    } 

    protected void d(String msg) { 
     Log.d("FAM", msg == null ? null : msg); 
    } 

    public interface OnMenuToggleListener { 
     void onMenuToggle(boolean opened); 
    } 


    public interface OnMenuItemClickListener { 
     void onMenuItemClick(FloatingActionMenu fam, int index, FloatingActionButton item); 
    } 

    private class ItemAnimator implements Animator.AnimatorListener { 
     private View mView; 
     private boolean playingOpenAnimator; 

     public ItemAnimator(View v) { 
      v.animate().setListener(this); 
      mView = v; 
     } 

     public void startOpenAnimator() { 
      mView.animate().cancel(); 
      playingOpenAnimator = true; 
      mView.animate().translationY(0).setInterpolator(DEFAULT_OPEN_INTERPOLATOR).start(); 
      mMenuButton.animate().rotation(135f).setInterpolator(DEFAULT_OPEN_INTERPOLATOR).start(); 
     } 

     public void startCloseAnimator() { 
      mView.animate().cancel(); 
      playingOpenAnimator = false; 
      mView.animate().translationY((mMenuButton.getTop() - mView.getTop())).setInterpolator(DEFAULT_CLOSE_INTERPOLATOR).start(); 
      mMenuButton.animate().rotation(0f).setInterpolator(DEFAULT_CLOSE_INTERPOLATOR).start(); 
     } 

     @Override 
     public void onAnimationStart(Animator animation) { 
      if (playingOpenAnimator) { 
       mView.setVisibility(VISIBLE); 
      } else { 
       ((TextView) mView.getTag()).setVisibility(GONE); 
      } 
     } 

     @Override 
     public void onAnimationEnd(Animator animation) { 
      if (!playingOpenAnimator) { 
       mView.setVisibility(GONE); 
      } else { 
       ((TextView) mView.getTag()).setVisibility(VISIBLE); 
      } 
     } 

     @Override 
     public void onAnimationCancel(Animator animation) { 

     } 

     @Override 
     public void onAnimationRepeat(Animator animation) { 
     } 
    } 
} 

我的布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:fab="http://schemas.android.com/apk/res-auto" 
    android:id="@+id/comicView" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:background="@color/background_main" 
    android:orientation="vertical"> 

    <terranovaproductions.newcomicreader.FloatingActionMenu 
     android:id="@+id/fab_menu" 
     android:layout_width="match_parent" 
     android:layout_height="match_parent" 
     android:padding="16dp" 
     > 

     <!--First button as menu button--> 
     <android.support.design.widget.FloatingActionButton 
      android:id="@+id/fab_main" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:src="@drawable/ic_add_white_24dp" 
      fab:fabSize="normal" 
      fab:backgroundTint="@color/material_orange" 
      fab:borderWidth="0dp" 
      fab:elevation="6dp"/> 

     <!-- Other button as menu items--> 
     <android.support.design.widget.FloatingActionButton 
      android:id="@+id/fab_random" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:contentDescription="@string/default_random" 
      android:paddingBottom="@dimen/menu_button_margin" 
      android:src="@drawable/ic_random" 
      fab:fabSize="mini" 
      fab:backgroundTint="@color/material_orange" /> 


     <android.support.design.widget.FloatingActionButton 
      android:id="@+id/fab_download" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:contentDescription="@string/download" 
      android:paddingBottom="@dimen/menu_button_margin" 
      android:src="@drawable/ic_download" 
      fab:fabSize="mini" 
      fab:backgroundTint="@color/material_orange"/> 

     <android.support.design.widget.FloatingActionButton 
      android:id="@+id/fab_browser" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:contentDescription="@string/default_browser" 
      android:paddingBottom="@dimen/menu_button_margin" 
      android:src="@drawable/ic_open_browser" 
      fab:fabSize="mini" 
      fab:backgroundTint="@color/material_orange"/> 

    </terranovaproductions.newcomicreader.FloatingActionMenu> 


</RelativeLayout> 

我知道有更好的方法做FloatingActionMenu,但我选择了这条路,因为我放了很多的工作。

我已经尝试删除填充,添加一个边距。我不确定如何使阴影扩展。我很确定在onLayout我需要改变一些东西。

如果需要,可以询问任何其他信息。

+0

你把你的东西拿出来了吗?或者它真的只是RL内的FAM?我相信你已经缩短了这个MCVE,但我会尽量发布实际的XML – AdamMc331

+0

我缩短了它,因为其他的东西是不相关的。只是两个TextViews和一个ImageView –

+0

它可能不是无关紧要的。为什么FAM match_parent的宽度和高度与wrap_content相反?另外,它如何在不使用layout_gravity的情况下在右下角显示? – AdamMc331

回答

31

您需要

android:clipChildren="false" 
android:clipToPadding="false" 

添加到父视图中的XML布局。

+0

我向CoordinatorLayout添加了包含fab和一切正常的内容。谢谢。 – javaddroid

+0

谢谢。你还必须考虑对父视图应用合适的填充,否则这将无法正常工作。 –

7

您可以尝试从FloatingActionMenu中移除16dp填充并为每个FloatingActionButton添加16dp的边距。

+0

marginBottom = 16dp为我工作 – behelit

1

只需在按钮上添加边距即可。像Orgazm pionerki建议的那样。我加了5dp,这没关系。