有那么点感觉的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   


Android 悬浮窗菜单,可在launcher或app中使用。示例代码:@Override public void onCreate() {     super.onCreate();     mFloatMenu = new FloatMenu.Builder(this)             .floatLoader(R.drawable.yw_anim_background)             .floatLogo(R.drawable.yw_image_float_logo)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_account, Const.MENU_ITEMS[0], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_favour, Const.MENU_ITEMS[1], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_fb, Const.MENU_ITEMS[2], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_msg, Const.MENU_ITEMS[3], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_close, Const.MENU_ITEMS[4], android.R.color.black, this)             .menuBackground(R.drawable.yw_menu_bg)             .onMenuActionListner(this)             .build();     mFloatMenu.show(); } public void showFloat() {     if (mFloatMenu != null)         mFloatMenu.show(); } public void hideFloat() {     if (mFloatMenu != null) {         mFloatMenu.hide();     } } public void destroyFloat() {     hideFloat();     if (mFloatMenu != null) {         mFloatMenu.destroy();     }     mFloatMenu = null; }  private void showRed() {     if (!hasNewMsg) {         mFloatMenu.changeLogo(R.drawable.yw_image_float_logo, R.drawable.yw_menu_msg, 3);     } else {         mFloatMenu.changeLogo(R.drawable.yw_image_float_logo_red, R.drawable.yw_menu_msg_red, 3);     } }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值