有那麼點感覺的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   


發佈了37 篇原創文章 · 獲贊 10 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章