有那么点感觉的FloatView menu

最开始看到FloatView就想,为啥使用float.。原来Float 有浮动,漂浮的意思- -。

一、FloatView的功能

首先效果图奉献上:


功能特点:
1. 可以设置menu的弹出方向
2. 可以代码控制添加,删除子类botton
3. 可根据developer的需求,更改样式

涉及到的内容:
1. 自定义控件的流程
2. 子类view的排列运算
3. 样式的设计
4. 动画的实现

二、主要代码详解

1. FloatingActionsMenu

思路:
FloatingActionsMenu 继承viewgroup, 因为他需要包含一些botton用来展示。
重写方法:
3个构造方法:对样式进行初始化
onMeasure : 计算自身view的大小
onLayout   :  对其内部的view的排列

animation :动画的添加
buttonCount::统计内部view的数量

(1) 样式设计

这边设计的样式,主要用来对 第一个FloatingActionButton样式的设计文字的方向图标展开的方向
    <declare-styleable name="FloatingActionsMenu">
        <attr name="fab_addButtonColorPressed" format="color"/>
        <attr name="fab_addButtonColorNormal" format="color"/>
        <attr name="fab_addButtonSize" format="enum">            // 调节第一个botton的大小
            <enum name="normal" value="0"/>
            <enum name="mini" value="1"/>
            <enum name="large" value="2"/>
        </attr>
        <attr name="fab_addButtonPlusIconColor" format="color"/>
        <attr name="fab_addButtonStrokeVisible" format="boolean"/>
        <attr name="fab_labelStyle" format="reference"/>
        <attr name="fab_labelsPosition" format="enum">          //文字描述的方向
            <enum name="left" value="0"/>
            <enum name="right" value="1"/>
        </attr>
        <attr name="fab_expandDirection" format="enum">         //展示图标的方向
            <enum name="up" value="0"/>
            <enum name="down" value="1"/>
            <enum name="left" value="2"/>
            <enum name="right" value="3"/>
        </attr>
    </declare-styleable>


(2) 初始化

通过TypeArray 将定义的styleable 中的参数提取出来,如果xml文件中没有设值,则取默认值
注意:需将attr进行释放。
  //初始化属性值
  private void init(Context context, AttributeSet attributeSet) {
    mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing) - getResources().getDimension(R.dimen.fab_shadow_radius) - getResources().getDimension(R.dimen.fab_shadow_offset));
    mLabelsMargin = getResources().getDimensionPixelSize(R.dimen.fab_labels_margin);
    mLabelsVerticalOffset = getResources().getDimensionPixelSize(R.dimen.fab_shadow_offset);

    mTouchDelegateGroup = new TouchDelegateGroup(this);
    setTouchDelegate(mTouchDelegateGroup);

    TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0);
    mAddButtonPlusColor = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonPlusIconColor, getColor(android.R.color.white));
    mAddButtonColorNormal = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorNormal, getColor(R.color.float_menu_default_color));
    mAddButtonColorPressed = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorPressed, getColor(R.color.float_menu_default_pressed_color));
    mAddButtonSize = attr.getInt(R.styleable.FloatingActionsMenu_fab_addButtonSize, FloatingActionButton.SIZE_NORMAL);
    mAddButtonStrokeVisible = attr.getBoolean(R.styleable.FloatingActionsMenu_fab_addButtonStrokeVisible, true);
    mExpandDirection = attr.getInt(R.styleable.FloatingActionsMenu_fab_expandDirection, EXPAND_UP);
    mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionsMenu_fab_labelStyle, 0);
    mLabelsPosition = attr.getInt(R.styleable.FloatingActionsMenu_fab_labelsPosition, LABELS_ON_LEFT_SIDE);
    attr.recycle();

//    if (mLabelsStyle != 0 && expandsHorizontally()) {
//      throw new IllegalStateException("Action labels in horizontal expand orientation is not supported.");
//    }

    //创建一个空ImageView,并且给他一个点击事件,用力展示,隐藏child
    createAddButton(context);
  }

初始化过程中,会创建第一个ImageBotton,用来对Menu进行一个展示和关闭功能。
通过addView ,可将该botton添加到view中,并且通过mButtonsCount统计子类view的数量。
  //创建一个空ImageView,并且给他一个点击事件,用力展示,隐藏child
  private void createAddButton(Context context) {
    mAddButton = new FloatingActionButton(context) {
      @Override
      void updateBackground() {
        mColorNormal = mAddButtonColorNormal;
        mColorPressed = mAddButtonColorPressed;
        super.updateBackground();
      }
    };

    mAddButton.setId(R.id.fab_expand_menu_button);   //设置id
    Drawable d = getResources().getDrawable(R.drawable.ic_arrow_white);   //设置icon资源
    mAddButton.setIconDrawable(d);
    mAddButton.setSize(mAddButtonSize);   //设置button的大小
    mAddButton.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        toggle();              //打开,关闭菜单
      }
    });

    addView(mAddButton, super.generateDefaultLayoutParams());
    mButtonsCount++;
  }


(3)onMeasure

思路:通过getChildAt(i) 获得内部vew的宽度以及高度(不断叠加),从而计算出正确的合适高度。
  //计算自身view的大小
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    measureChildren(widthMeasureSpec, heightMeasureSpec);

    int width = 0;
    int height = 0;

    mMaxButtonWidth = 0;
    mMaxButtonHeight = 0;
    int maxLabelWidth = 0;

    for (int i = 0; i < mButtonsCount; i++) {
      View child = getChildAt(i);

      if (child.getVisibility() == GONE) {    //如果内部view有隐藏的,直接return,不参加计算
        continue;
      }

      switch (mExpandDirection) {
      case EXPAND_UP:
      case EXPAND_DOWN:
        mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth());   //计算宽度
        height += child.getMeasuredHeight();        //计算高度
        break;
      case EXPAND_LEFT:
      case EXPAND_RIGHT:
        width += child.getMeasuredWidth();
        mMaxButtonHeight = Math.max(mMaxButtonHeight, child.getMeasuredHeight());
        break;
      }

      if (!expandsHorizontally()) {
        TextView label = (TextView) child.getTag(R.id.fab_label);
        if (label != null) {
          maxLabelWidth = Math.max(maxLabelWidth, label.getMeasuredWidth());
        }
      }
    }

    if (!expandsHorizontally()) {
      width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0);
    } else {
      height = mMaxButtonHeight;
    }

    switch (mExpandDirection) {    //将间隔添加上去
    case EXPAND_UP:
    case EXPAND_DOWN:
      height += mButtonSpacing * (mButtonsCount - 1);
      height = adjustForOvershoot(height);
      break;
    case EXPAND_LEFT:
    case EXPAND_RIGHT:
      width += mButtonSpacing * (mButtonsCount - 1);
      width = adjustForOvershoot(width);
      break;
    }

    setMeasuredDimension(width, height);   //设置view的大小
  }

(4) onLayout

这边挑一个图标的位置
case EXPAND_RIGHT:
      boolean expandLeft = mExpandDirection == EXPAND_LEFT;

      int addButtonX = expandLeft ? r - l - mAddButton.getMeasuredWidth() : 0;
      // Ensure mAddButton is centered on the line where the buttons should be
      int addButtonTop = b - t - mMaxButtonHeight + (mMaxButtonHeight - mAddButton.getMeasuredHeight()) / 2; //第一个button的位置
      mAddButton.layout(addButtonX, addButtonTop, addButtonX + mAddButton.getMeasuredWidth(), addButtonTop + mAddButton.getMeasuredHeight());

      int nextX = expandLeft ?
          addButtonX - mButtonSpacing :
          addButtonX + mAddButton.getMeasuredWidth() + mButtonSpacing;

      //根据getChildAt 来确定child位置
      for (int i = mButtonsCount - 1; i >= 0; i--) {
        final View child = getChildAt(i);

        if (child == mAddButton || child.getVisibility() == GONE) continue;

        int childX = expandLeft ? nextX - child.getMeasuredWidth() : nextX;
        int childY = addButtonTop + (mAddButton.getMeasuredHeight() - child.getMeasuredHeight()) / 2;
        child.layout(childX, childY, childX + child.getMeasuredWidth(), childY + child.getMeasuredHeight());//通过第一个button的位置以及自身的大小确定位置

        float collapsedTranslation = addButtonX - childX;
        float expandedTranslation = 0f;

        child.setTranslationX(mExpanded ? expandedTranslation : collapsedTranslation);
        child.setAlpha(mExpanded ? 1f : 0f);

        LayoutParams params = (LayoutParams) child.getLayoutParams();
        params.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation);
        params.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation);
        params.setAnimationsTarget(child);

        View label = (View) child.getTag(R.id.fab_label);
        if(label != null) {
          label.setVisibility(GONE);
        }

        nextX = expandLeft ?
            childX - mButtonSpacing :
            childX + child.getMeasuredWidth() + mButtonSpacing;
      }

      break;
    }


(5) 展开与闭合的操作

  private void collapse(boolean immediately) {
    if (mExpanded) {
      mExpanded = false;
      mTouchDelegateGroup.setEnabled(false);
      mCollapseAnimation.setDuration(immediately ? 0 : ANIMATION_DURATION);
      mCollapseAnimation.start();   //animation的改动
      mExpandAnimation.cancel();       
      Drawable d = getResources().getDrawable(R.drawable.ic_arrow_white);
      mAddButton.setIconDrawable(d);      //改变第一个button的样式
      setAddButtonColor(getResources().getColor(R.color.float_menu_default_color), getResources().getColor(R.color.float_menu_default_pressed_color));
      mAddButton.updateBackground();
      if (mListener != null) {
        mListener.onMenuCollapsed();
      }
    }
  }

  public void toggle() {
    if (mExpanded) {
      collapse();
    } else {
      expand();
    }
  }


  //展示   并且改变第一个view的样式
  public void expand() {
    if (!mExpanded) {
      mExpanded = true;
      mTouchDelegateGroup.setEnabled(true);
      mCollapseAnimation.cancel();
      mExpandAnimation.start();
      Drawable d = getResources().getDrawable(R.drawable.ic_close_black);
      mAddButton.setIconDrawable(d);    //改变第一个button的样式
      setAddButtonColor(getResources().getColor(R.color.white), getResources().getColor(R.color.float_menu_exposed_pressed_color));
      mAddButton.updateBackground();
      if (mListener != null) {
        mListener.onMenuExpanded();
      }
    }
  }


(6) 其他设置

1. 提供了addButton 以及 removeButton方法
2. onSaveInstanceState  保存了当前状态
3. setAddButtonColor  设置第一个button的颜色
4. 使用了TouchDelegate 来扩大点击区域



三、源码下载

https://github.com/stormxz/FloatViewT   


猜你喜欢

转载自blog.csdn.net/weixin_39158738/article/details/77684924