栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 移动开发 > Android

android SectorMenuView底部导航扇形菜单的实现代码

Android 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

android SectorMenuView底部导航扇形菜单的实现代码

这次分析一个扇形菜单展开的自定义View, 也是我实习期间做的一个印象比较深刻的自定义View, 前后切换了很多种实现思路, 先看看效果展示

效果展示

效果分析

  1. 点击圆形的FloatActionBar, 自身旋转一定的角度
  2. 菜单像波纹一样扩散开来
  3. 显示我们添加的item

实现分析

使用adapter适配器去设置View, 用户可自定义性强, 不过每次使用需要去设置Adapter, 较为繁琐

直接调用ItemView, 将ImageView和TextView写死, 用户操作简单, 但是缺乏可定制性(利他)

本次功能实现采用了方案 2

实现步骤

  1. 与气泡拖拽类似, 新开启一个Window进行自定义View的绘制
  2. 初始化时调用setWillNotDraw(false)方法, 强行启动ViewGroup的绘制
  3. onMeasure中将宽高写死
  4. 绘制背景
    1. 锚点为View的底部中心点
    2. 半径为屏幕宽度一半的平方和的开方(注意这里不是屏幕的一半)
  5. 添加itemView, 在onLayout中去确定其位置
  6. 添加动画效果
  7. 将相关接口暴露给外界

使用方式

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()

源码实现


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);
    // 设置背景圆绘制的半径
    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
      );
      // 这里动态的去设置子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();
  }

  
  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();
      }
    });
    // 这里设置了该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;
  }


  
  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();
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/156444.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号