前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >android SectorMenuView底部导航扇形菜单的实现代码

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

作者头像
砸漏
发布2020-11-02 12:29:03
2.7K1
发布2020-11-02 12:29:03
举报
文章被收录于专栏:恩蓝脚本

这次分析一个扇形菜单展开的自定义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. 将相关接口暴露给外界

使用方式

代码语言:javascript
复制
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()

源码实现

代码语言:javascript
复制
/**
* Email: frankchoochina@gmail.com
* Created by FrankChoo on 2017/10/9.
* Description: 底部扇形菜单, 通过Adapter添加Item
*       1. 调用openMenu打开菜单
*       2. 调用closeMenu关闭菜单
*/
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;
}
/**
* 供外界调用的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();
}
}

以上就是本文的全部内容,希望对大家的学习有所帮助。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档