ArcMenu扇形菜单(底部)

本文介绍了如何参照鸿洋大神的教程实现一个位于底部的ArcMenu,详细阐述了中心菜单及子菜单的位置计算、点击事件及动画效果的设置过程,提供了全部源码供下载参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参照鸿洋大神的Android 自定义ViewGroup手把手教你实现ArcMenu
然后自己写了一个位于底部的ArcMenu,功能上和效果上都以实现!

主要思路:
1.计算出中心菜单的位置和其他子菜单的位置
2.设置中心菜单的点击事件(动画)
3.设置子菜单的点击监听和动画效果!

这里写图片描述

1)计算中心菜单的位置

if (i == 0) {// 中心菜单
    centerChildHeight = mHeight - child.getMeasuredHeight() / 2;
    // 中间view的中心坐标y
    centerChildWidth = mWidth / 2;
    // 中间view的中心坐标y

    child.layout((mWidth - childWidth) / 2, mHeight - childHeight, (mWidth + childWidth) / 2, mHeight);
    child.setOnClickListener(this);

中心菜单的中心位置的x坐标为,view的宽度的一半
中心菜单的中心位置的y坐标为,view的高度减去一半中心菜单自身高度的
所以中心菜单的左上右下坐标位置就可以算出来了!

2)计算子菜单的位置

child.setVisibility(View.GONE);
double temA = Math.PI / (childCount - 2) * (i - 1);
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
left = (int) (centerChildWidth + radius * Math.cos(temA) - childWidth / 2);
top = (int) (centerChildHeight - radius * Math.sin(temA) - childHeight / 2);
right = (int) (centerChildWidth + radius * Math.cos(temA) + childWidth / 2);
bottom = (int) (centerChildHeight - radius * Math.sin(temA) + childHeight / 2);
child.layout(left, top, right, bottom);

其他子菜单的位置的计算分两步:
1.根据中心坐标的中心点位置根据弧度/sin/cos分别计算出其他子菜单的中心点位置
2.根据计算所得的中心点坐标再根据子菜单自身的宽高计算出子菜单的左上右下坐标

位置计算好了基本上相当于ArcMenu完成了一大部分,其他的部分就是设置动画效果了

下面贴出来全部的代码

package com.example.arcmenu;

import android.content.Context;
import android.content.res.TypedArray;
import android.provider.AlarmClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.RelativeLayout;
import android.view.View.OnClickListener;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.OvershootInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;

public class ArcMenuGroup extends RelativeLayout implements OnClickListener {

    private static String TAG = "ArcMenuGroup";
    private int position = 1;// 标识菜单的位置 0:上 1:下(当前代码未使用,仅开发了下方的菜单)
    private int radius = dip2px(getContext(), 100);// 菜单的半径
    private int mHeight;
    private int mWidth;
    private int centerChildHeight;
    private int centerChildWidth;
    private View mMenu;
    private boolean mStatus = false;// true表示打开,false表示关闭
    private OnItemClicklistener mOnItemClickListener;
    private int childCount;
    private AnimationSet mItemAnimationSet;//菜单项被点击的动画集合

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

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

    public ArcMenuGroup(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArcMenu, defStyle, 0);
        int count = array.getIndexCount();
        for (int i = 0; i < count; i++) {
            int index = array.getIndex(i);
            switch (index) {
            case R.styleable.ArcMenu_position:
                position = array.getInteger(index, 1);
                break;
            case R.styleable.ArcMenu_radius:
                radius = (int) array.getDimension(index, dip2px(context, 100));
            default:
                break;
            }
        }

        array.recycle();

        /**
         * 完成菜单项被点击的动画的初始化
         */
        mItemAnimationSet = new AnimationSet(false);//false标识动画集中的动画各自使用自己的动画插入器

        //创建一个形变动画,从相对于自身中心点由1倍体积扩大到3倍体积
        ScaleAnimation mScaleAnimation = new ScaleAnimation(1.0f, 3.0f, 1.0f, 3.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        mScaleAnimation.setDuration(400);
        //创建一个透明度动画,透明度从不透明到完全透明
        AlphaAnimation mAlphaAnimation = new AlphaAnimation(1.0f, 0.0f);
        mAlphaAnimation.setDuration(400);
        mItemAnimationSet.addAnimation(mAlphaAnimation);
        mItemAnimationSet.addAnimation(mScaleAnimation);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mHeight = getMeasuredHeight();
        mWidth = getMeasuredWidth();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            childCount = getChildCount();
            if (childCount <= 2) {//菜单项不能少于两个
                return;
            }
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                int childHeight = child.getMeasuredHeight();
                int childWidth = child.getMeasuredWidth();
                if (i == 0) {// 中心菜单
                    centerChildHeight = mHeight - child.getMeasuredHeight() / 2;// 中间view的中心坐标y
                    centerChildWidth = mWidth / 2;// 中间view的中心坐标y

                    child.layout((mWidth - childWidth) / 2, mHeight - childHeight, (mWidth + childWidth) / 2, mHeight);
                    child.setOnClickListener(this);
                } else {//根据弧度计算出每个子菜单的位置
                    child.setVisibility(View.GONE);
                    double temA = Math.PI / (childCount - 2) * (i - 1);
                    int left = 0;
                    int top = 0;
                    int right = 0;
                    int bottom = 0;
                    left = (int) (centerChildWidth + radius * Math.cos(temA) - childWidth / 2);
                    top = (int) (centerChildHeight - radius * Math.sin(temA) - childHeight / 2);
                    right = (int) (centerChildWidth + radius * Math.cos(temA) + childWidth / 2);
                    bottom = (int) (centerChildHeight - radius * Math.sin(temA) + childHeight / 2);
                    child.layout(left, top, right, bottom);
                    Log.e(TAG, "i=" + i + " left = " + left + " top = " + top + " right = " + right + " bottom = " + bottom);
                }
            }
        }
    }

    @Override
    public void onClick(View v) {
        startCenterMenuAnimation();
        startItemMenuAnimation();
        changeStatus();
    }

    /**
     * 设置子菜单展开和收回的动画
     */
    private void startItemMenuAnimation() {
        final int childCount = getChildCount();
        TranslateAnimation animation = null;
        for (int i = 1; i < childCount; i++) {
            final int position = i;
            double temA = Math.PI / (childCount - 2) * (i - 1);
            final View child = getChildAt(i);
            if (mStatus) {
                animation = new TranslateAnimation(0, -(float) (radius * Math.cos(temA)), 0, (float) (radius * Math.sin(temA)));
                child.setVisibility(View.GONE);
                animation.setDuration(300);
            } else {

                animation = new TranslateAnimation(-(float) (radius * Math.cos(temA)), 0, (float) (radius * Math.sin(temA)), 0);
                animation.setInterpolator(new OvershootInterpolator(3f));
                child.setVisibility(View.VISIBLE);
                animation.setDuration(500);
            }
            child.startAnimation(animation);
            child.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    startItemClickAnimation(position);
                    changeStatus(false);
                    if (mOnItemClickListener != null) {
                        mOnItemClickListener.onClick(child, position);
                    }
                }
            });
        }
    }

    /**
     * 中间主菜单项的动画效果
     */
    private void startCenterMenuAnimation() {
        mMenu = getChildAt(0);
        RotateAnimation animation = new RotateAnimation(0f, 360F, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animation.setFillAfter(true);
        animation.setDuration(500);
        mMenu.startAnimation(animation);
    }

    /**
     * 改变菜单的状态
     */
    private void changeStatus() {
        if (mStatus) {
            mStatus = false;
        } else {
            mStatus = true;
        }
    }

    /**
     * 改变菜单的状态
     */
    private void changeStatus(boolean b) {
        mStatus = b;
    }

    /**
     * 设置菜单项被点击的动画
     */
    private void startItemClickAnimation(int positon) {
        for (int i = 1; i < childCount; i++) {
            View child = getChildAt(i);
            if (i == positon) {
                child.startAnimation(mItemAnimationSet);
            }
            child.setVisibility(View.GONE);
        }
    }


    /**
     *菜单项的监听 
     */
    public interface OnItemClicklistener {
        void onClick(View v, int position);
    }

    public OnItemClicklistener getOnItemClickListener() {
        return mOnItemClickListener;
    }

    public void setOnItemClickListener(OnItemClicklistener mOnItemClickListener) {
        this.mOnItemClickListener = mOnItemClickListener;
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

}

下载源码点击这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值