androidSectorMenuView底部導(dǎo)航扇形菜單的實現(xiàn)代碼

這次分析一個扇形菜單展開的自定義View, 也是我實習(xí)期間做的一個印象比較深刻的自定義View, 前后切換了很多種實現(xiàn)思路, 先看看效果展示

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、重慶小程序開發(fā)、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了洪江管理區(qū)免費建站歡迎大家使用!

效果展示

android SectorMenuView底部導(dǎo)航扇形菜單的實現(xiàn)代碼

效果分析

  1. 點擊圓形的FloatActionBar, 自身旋轉(zhuǎn)一定的角度
  2. 菜單像波紋一樣擴散開來
  3. 顯示我們添加的item

實現(xiàn)分析

使用adapter適配器去設(shè)置View, 用戶可自定義性強, 不過每次使用需要去設(shè)置Adapter, 較為繁瑣

直接調(diào)用ItemView, 將ImageView和TextView寫死, 用戶操作簡單, 但是缺乏可定制性(利他)

本次功能實現(xiàn)采用了方案 2

實現(xiàn)步驟

  1. 與氣泡拖拽類似, 新開啟一個Window進行自定義View的繪制
  2. 初始化時調(diào)用setWillNotDraw(false)方法, 強行啟動ViewGroup的繪制
  3. onMeasure中將寬高寫死
  4. 繪制背景
    1. 錨點為View的底部中心點
    2. 半徑為屏幕寬度一半的平方和的開方(注意這里不是屏幕的一半)
  5. 添加itemView, 在onLayout中去確定其位置
  6. 添加動畫效果
  7. 將相關(guān)接口暴露給外界

使用方式

BottomSectorMenuView.Converter(mFab)
        .setToggleDuration(500, 800)
        .setAnchorRotationAngle(135f)
        .addMenuItem(R.drawable.icon_camera, "拍照") { Toast.makeText(this@MainActivity, "拍照", Toast.LENGTH_SHORT).show() }
        .addMenuItem(R.drawable.icon_photo, "圖片") { Toast.makeText(this@MainActivity, "圖片", Toast.LENGTH_SHORT).show() }
        .addMenuItem(R.drawable.icon_text, "文字") { Toast.makeText(this@MainActivity, "文字", Toast.LENGTH_SHORT).show() }
        .addMenuItem(R.drawable.icon_video, "視頻") { Toast.makeText(this@MainActivity, "視頻", Toast.LENGTH_SHORT).show() }
        .addMenuItem(R.drawable.icon_camera_shooting, "攝像") { Toast.makeText(this@MainActivity, "攝像", Toast.LENGTH_SHORT).show() }
        .apply()

源碼實現(xiàn)

/**
 * Email: frankchoochina@gmail.com
 * Created by FrankChoo on 2017/10/9.
 * Description: 底部扇形菜單, 通過Adapter添加Item
 *       1. 調(diào)用openMenu打開菜單
 *       2. 調(diào)用closeMenu關(guān)閉菜單
 */
public class SectorMenuView extends FrameLayout {
  // 每個ItemView之間的角度差
  private double mAngle;
  // 圓心坐標
  private Point mCenterPoint;
  // ItemView到圓心的半徑
  private float mMaxItemRadius;
  private float mCurItemRadius;
  // 背景圓的半徑
  private float mMaxBkgRadius;
  private float mCurBkgRadius;
  private Paint mPaint;

  private SectorMenuAdapter mAdapter;
  private OnMenuOpenedListener mMenuOpenedListener;
  private OnMenuClosedListener mMenuClosedListener;

  public SectorMenuView(Context context) {
    this(context, null);
  }

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

  public SectorMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
  }

  private void init() {
    // 初始化畫筆
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setDither(true);
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setColor(Color.WHITE);
    // 設(shè)置背景圓繪制的半徑
    int displayWidth = getResources().getDisplayMetrics().widthPixels;
    mMaxBkgRadius = (int) Math.sqrt(Math.pow(displayWidth/2, 2.0) + Math.pow(displayWidth/2, 2.0));
    // 開啟ViewGroup的繪制
    setWillNotDraw(false);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    // 這里直接將寬高寫死, 不支持Margin
    int width = getResources().getDisplayMetrics().widthPixels;
    int height = (int) Math.sqrt(Math.pow(width / 2, 2.0) + Math.pow(width / 2, 2.0));
    setMeasuredDimension(width, height);
    // 計算半徑
    int realWidth = width - getPaddingRight() - getPaddingLeft();
    int realHeight = height - getPaddingTop() - getPaddingBottom();
    mMaxItemRadius = realWidth / 2;
    // 計算圓心
    int centerX = getPaddingLeft() + realWidth / 2;
    int centerY = getPaddingTop() + realHeight;
    mCenterPoint = new Point(centerX, centerY);
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    for (int i = 0; i < getChildCount(); i++) {
      View child = getChildAt(i);
      double curAngle = Math.PI - mAngle * (i + 1);
      int childCenterX = (int) (mCenterPoint.x + mCurItemRadius * Math.cos(curAngle));
      int childCenterY = (int) (mCenterPoint.y - mCurItemRadius * Math.sin(curAngle));
      child.layout(
          childCenterX - child.getMeasuredWidth() / 2,
          childCenterY - child.getMeasuredHeight() / 2,
          childCenterX + child.getMeasuredWidth() / 2,
          childCenterY + child.getMeasuredHeight() / 2
      );
      // 這里動態(tài)的去設(shè)置子View的透明度
      child.setAlpha(mCurItemRadius / mMaxItemRadius);
    }
  }

  @Override
  protected void onDraw(Canvas canvas) {
    canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mCurBkgRadius, mPaint);
    super.onDraw(canvas);
  }

  public void setAdapter(SectorMenuAdapter adapter) {
    mAdapter = adapter;
    for (int i = 0; i < mAdapter.getCount(); i++) {
      View child = mAdapter.getView(i, null, this);
      addView(child);
    }
    mAngle = Math.PI / (mAdapter.getCount() + 1);
  }

  public void setBackgroudColor(@ColorInt int color) {
    mPaint.setColor(color);
  }

  public void setBackgroundResource(@ColorRes int colorResId) {
    mPaint.setColor(ContextCompat.getColor(getContext(), colorResId));
  }

  /**
   * 打開菜單
   */
  public void openMenu() {
    if (mMaxItemRadius == 0) {
      mMaxItemRadius = getResources().getDisplayMetrics().widthPixels / 2
          - getPaddingRight() - getPaddingLeft();
    }
    // 背景動畫
    ValueAnimator bkgAnim = ValueAnimator.ofFloat(0f, mMaxBkgRadius).setDuration(300);
    bkgAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        mCurBkgRadius = (float) animation.getAnimatedValue();
        invalidate();
      }
    });
    // item的位置動畫
    ValueAnimator itemTranslationAnim = ValueAnimator.ofFloat(0f, mMaxItemRadius).setDuration(300);
    itemTranslationAnim.setInterpolator(new OvershootInterpolator(2f));
    itemTranslationAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        mCurItemRadius = (float) animation.getAnimatedValue();
        requestLayout();
      }
    });
    // 動畫集合
    final AnimatorSet set = new AnimatorSet();
    set.playSequentially(bkgAnim, itemTranslationAnim);
    set.addListener(new AnimatorListenerAdapter() {
      @Override
      public void onAnimationStart(Animator animation) {
        setAlpha(1f);
        setVisibility(View.VISIBLE);
      }
      @Override
      public void onAnimationEnd(Animator animation) {
        if (mMenuOpenedListener != null) {
          mMenuOpenedListener.opened();
        }
      }
    });
    set.start();
  }

  /**
   * 關(guān)閉菜單
   */
  public void closeMenu() {
    // Item動畫
    ValueAnimator itemViewAnim = ValueAnimator.ofFloat(mMaxItemRadius, 0f).setDuration(300);
    itemViewAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        mCurItemRadius = (float) animation.getAnimatedValue();
        requestLayout();
      }
    });
    itemViewAnim.setInterpolator(new AnticipateInterpolator(2f));

    // 背景動畫
    ValueAnimator backgroundAnim = ValueAnimator.ofFloat(mMaxBkgRadius, 0f).setDuration(300);
    backgroundAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        mCurBkgRadius = (float) animation.getAnimatedValue();
        invalidate();
      }
    });
    // 這里設(shè)置了該View整體透明度的變化, 防止消失的背景不在錨點處, 顯示效果突兀
    ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f).setDuration(250);

    // 動畫集合
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.play(itemViewAnim).before(backgroundAnim);
    animatorSet.play(backgroundAnim).with(alphaAnim);
    animatorSet.addListener(new AnimatorListenerAdapter() {
      @Override
      public void onAnimationEnd(Animator animation) {
        if (mMenuClosedListener != null) {
          mMenuClosedListener.closed();
        }
        setVisibility(View.INVISIBLE);
      }
    });
    animatorSet.start();
  }

  public void setOnMenuOpenedListener(OnMenuOpenedListener listener) {
    mMenuOpenedListener = listener;
  }

  public void setOnMenuClosedListener(OnMenuClosedListener listener) {
    mMenuClosedListener = listener;
  }


  /**
   * 供外界調(diào)用的Adapter
   */
  public abstract static class SectorMenuAdapter extends BaseAdapter {

    @Override
    public long getItemId(int position) {
      return 0;
    }

    @Override
    public Object getItem(int position) {
      return null;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      return createView(position, parent);
    }

    protected abstract View createView(int position, ViewGroup parent);

    @Override
    public abstract int getCount();
  }

  public interface OnMenuOpenedListener {
    void opened();
  }

  public interface OnMenuClosedListener {
    void closed();
  }
}

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。

分享名稱:androidSectorMenuView底部導(dǎo)航扇形菜單的實現(xiàn)代碼
網(wǎng)頁URL:http://muchs.cn/article22/pgdejc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、搜索引擎優(yōu)化做網(wǎng)站、域名注冊軟件開發(fā)、響應(yīng)式網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站建設(shè)