Android卫星弹射菜单ArcMenu实现

最近看到一个比较不错的菜单效果,大家都叫它卫星菜单。效果就是点击一个按钮在它的周围弹射出子菜单,效果如图:
这里写图片描述
如何封装一个这个这样的菜单?
1.创建一个类继承ViewGroup,先贴上ArcMenu类的所有代码,下面仔细讲解


package com.def.view;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;

import com.example.arcmenudemo.R;

public class ArcMenu extends ViewGroup implements OnClickListener {

    private static final String TAG = "quw.ArcMenu";
    private static final int POS_LEFT_TOP = 0;
    private static final int POS_LEFT_BUTTOM = 1;
    private static final int POS_RIGHT_TOP = 2;
    private static final int POS_RIGHT_BUTTOM = 3;

    private static final int STATUS_CLOSE = 0;
    private static final int STATUS_OPEN = 1;

    private int mPosition = POS_RIGHT_BUTTOM;
    private int mRadius = 0;
    private int mStatus = STATUS_CLOSE;
    /**
     * 启动菜单的主按钮
     */
    private View mCButton;
    private OnMenuItemClickListener mMenuItemClickListener;

    public void setOnMenuItemClickListener(
            OnMenuItemClickListener l) {
        this.mMenuItemClickListener = l;
    }

    public ArcMenu(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
        mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                100, getResources().getDisplayMetrics());

        TypedArray typedArray = context.getTheme().obtainStyledAttributes(
                attrs, R.styleable.ArcMenu, defStyle, 0);
        mPosition = typedArray.getInt(R.styleable.ArcMenu_position,
                POS_RIGHT_BUTTOM);
        mRadius = (int) typedArray.getDimension(R.styleable.ArcMenu_radius,
                mRadius);
        typedArray.recycle();
        Log.i(TAG, "mPosition = " + mPosition + ", mRadius = " + mRadius);
    }

    public ArcMenu(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        // TODO Auto-generated constructor stub
    }

    public ArcMenu(Context context) {
        this(context, null, 0);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
        if (changed) {
            layoutButton();
        }

    }

    private void layoutButton() {
        /**
         * 计算所有按钮的初始放置位置
         */
        int count = getChildCount();
        mCButton = getChildAt(count - 1);
        int l = 0;
        int t = 0;
        int width = mCButton.getMeasuredWidth();
        int height = mCButton.getMeasuredHeight();
        switch (mPosition) {
            case POS_LEFT_TOP:

                break;

            case POS_LEFT_BUTTOM:
                t = getMeasuredHeight() - height;
                break;

            case POS_RIGHT_TOP:
                l = getMeasuredWidth() - width;
                break;

            case POS_RIGHT_BUTTOM:
                l = getMeasuredWidth() - width;
                t = getMeasuredHeight() - height;
                break;

        }

        /**
         * 放置菜单按钮
         */
        mCButton.layout(l, t, l + width, t + height);
        mCButton.setClickable(true);
        mCButton.setFocusable(true);
        mCButton.setOnClickListener(this);

        /**
         * 放置子按钮
         */
        for (int i = 0; i < count - 1; i++) {
            View childView = getChildAt(i);
            childView.layout(l, t, l + width, t + height);
            childView.setVisibility(View.GONE);
            final int pos = i;
            childView.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    clickItemAnim(pos);
                    changeStatus();
                    if (mMenuItemClickListener != null) {
                        mMenuItemClickListener.onItemClick(v, pos);
                    }
                }
            });
        }
    }

    public interface OnMenuItemClickListener {
        void onItemClick(View view, int position);
    }

    @Override
    public void onClick(View v) {
        rotateCButton();
        toggleMenu();
    }

    private void toggleMenu() {
        int itemCount = getChildCount() - 1;
        for (int i = 0; i < itemCount; i++) {
            final View child = getChildAt(i);
            child.setVisibility(View.VISIBLE);
            child.setClickable(true);
            child.setFocusable(true);
            /**
             * 计算出子按钮位移距离
             */
            int x = (int) (mRadius * Math.sin(Math.PI / 2 / (itemCount - 1)
                    * i));
            int y = (int) (mRadius * Math.cos(Math.PI / 2 / (itemCount - 1)
                    * i));

            /**
             * 判断出位移方向
             */
            int xflag = 1;
            int yflag = 1;
            if (mPosition == POS_RIGHT_TOP || mPosition == POS_RIGHT_BUTTOM) {
                xflag = -1;
            }
            if (mPosition == POS_LEFT_BUTTOM || mPosition == POS_RIGHT_BUTTOM) {
                yflag = -1;
            }
            ObjectAnimator transXAnimator = null;
            ObjectAnimator transYAnimator = null;
            ObjectAnimator rotationAnimator = null;
            AnimatorSet animatorSet = new AnimatorSet();
            if (mStatus == STATUS_CLOSE) {// 设置弹出动画
                transXAnimator = ObjectAnimator.ofFloat(child, "translationX",
                        0,
                        x * xflag);
                transYAnimator = ObjectAnimator.ofFloat(child, "translationY",
                        0,
                        y * yflag);

            } else {// 设置收回的动画
                transXAnimator = ObjectAnimator.ofFloat(child, "translationX",
                        x
                                * xflag, 0);
                transYAnimator = ObjectAnimator.ofFloat(child, "translationY",
                        y
                                * yflag, 0);
            }
            rotationAnimator = ObjectAnimator.ofFloat(child, "rotation", 0,
                    720f);
            animatorSet.playTogether(transXAnimator, transYAnimator,
                    rotationAnimator);
            animatorSet.setDuration(300);

            animatorSet.setStartDelay(25 * i);
            animatorSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    // TODO Auto-generated method stub
                    if (mStatus == STATUS_CLOSE) {
                        child.setVisibility(View.GONE);
                        child.setClickable(false);
                        child.setFocusable(false);
                    }
                    super.onAnimationEnd(animation);
                }
            });
            animatorSet.start();

        }
        changeStatus();

    }

    private void clickItemAnim(int pos) {
        // TODO Auto-generated method stub
        int itemCount = getChildCount() - 1;
        for (int i = 0; i < itemCount; i++)
        {

            final View childView = getChildAt(i);
            AnimatorSet animatorSet = null;
            if (i == pos) {
                animatorSet = scaleBigAnim(childView);
            } else {
                animatorSet = scaleSmallAnim(childView);
            }
            animatorSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    // TODO Auto-generated method stub
                    /**
                     * 属性动画在执行之后会将View的各个属性确实更改,必须将他们还原
                     */
                    childView.setScaleX(1.0f);
                    childView.setScaleY(1.0f);
                    childView.setAlpha(1.0f);
                    childView.setX(0f);
                    childView.setTranslationX(0f);
                    childView.setTranslationY(0f);
                    if (mStatus == STATUS_CLOSE) {
                        childView.setVisibility(View.GONE);
                    }
                    super.onAnimationEnd(animation);
                }
            });
            animatorSet.start();
            childView.setClickable(false);
            childView.setFocusable(false);

        }

    }

    private AnimatorSet scaleBigAnim(final View v) {
        ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(v, "scaleX",
                2.0f, 1.0f);
        ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(v, "scaleY",
                2.0f, 1.0f);
        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha",
                1.0f,
                0f);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(scaleXAnimator, scaleYAnimator,
                alphaAnimator);
        animatorSet.setDuration(500);

        return animatorSet;
    }

    private AnimatorSet scaleSmallAnim(View v) {
        ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(v, "scaleX",
                1.0f, 0f);
        ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(v, "scaleY",
                1.0f, 0f);
        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha",
                1.0f,
                0f);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(scaleXAnimator, scaleYAnimator,
                alphaAnimator);
        animatorSet.setDuration(500);
        return animatorSet;
    }

    private void changeStatus() {
        // TODO Auto-generated method stub
        mStatus = (mStatus == STATUS_CLOSE ? STATUS_OPEN : STATUS_CLOSE);
    }

    private void rotateCButton() {
        ObjectAnimator anim = ObjectAnimator
                .ofFloat(mCButton, "rotation", 0, 360f);
        anim.setDuration(500);
        anim.start();
    }

}

2.自定义卫星菜单的位置、半径属性
valus文件夹下创建attr.xml,添加以下内容

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="position">
        <enum name="left_top" value="0"></enum>
        <enum name="left_bottom" value="1"></enum>
        <enum name="right_top" value="2"></enum>
        <enum name="right_bottom" value="3"></enum>
    </attr>
    <attr name="radius" format="dimension"></attr>

    <declare-styleable name="ArcMenu">
        <attr name="position"></attr>
        <attr name="radius"></attr>
    </declare-styleable>

</resources>

先定义了position、radius属性,然后利用declare-styleable标签申明“为ArcMenu添加positon、radius属性”,然后就在layout文件中进行使用

<?xml version="1.0" encoding="utf-8"?>
<com.def.view.ArcMenu 
xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:def="http://schemas.android.com/apk/res/com.example.arcmenudemo"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    def:position="right_bottom"
    def:radius="200dp" >

利用“xmlns:def”申明自定义属性的命名空间,利用“def:属性”对自定义属性进行调用。
在ArcMenu类中对属性值进行获取

 // 设置默认半径为100dp,根据需要自行修改
        mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                100, getResources().getDisplayMetrics());

        /**
         * 获取xml文件中设置的属性值
         */
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(
                attrs, R.styleable.ArcMenu, defStyle, 0);
        mPosition = typedArray.getInt(R.styleable.ArcMenu_position,
                POS_RIGHT_BUTTOM);
        mRadius = (int) typedArray.getDimension(R.styleable.ArcMenu_radius,
                mRadius);
        // 一定记得释放
        typedArray.recycle();

3.重写onMeasure方法

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            // 主要对child的大小进行计算
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

4.重写onLayout方法

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
        if (changed) {
            layoutButton();
        }

    }

    private void layoutButton() {
        /**
         * 计算所有按钮的初始放置位置
         */
        int count = getChildCount();
        mCButton = getChildAt(count - 1);
        int l = 0;
        int t = 0;
        int width = mCButton.getMeasuredWidth();
        int height = mCButton.getMeasuredHeight();
        switch (mPosition) {
            case POS_LEFT_TOP:

                break;

            case POS_LEFT_BUTTOM:
                t = getMeasuredHeight() - height;
                break;

            case POS_RIGHT_TOP:
                l = getMeasuredWidth() - width;
                break;

            case POS_RIGHT_BUTTOM:
                l = getMeasuredWidth() - width;
                t = getMeasuredHeight() - height;
                break;

        }

        /**
         * 放置菜单按钮
         */
        mCButton.layout(l, t, l + width, t + height);
        mCButton.setClickable(true);
        mCButton.setFocusable(true);
        mCButton.setOnClickListener(this);

        /**
         * 放置子按钮
         */
        for (int i = 0; i < count - 1; i++) {
            View childView = getChildAt(i);
            childView.layout(l, t, l + width, t + height);
            childView.setVisibility(View.GONE);
            final int pos = i;
            childView.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    clickItemAnim(pos);
                    changeStatus();
                    if (mMenuItemClickListener != null) {
                        mMenuItemClickListener.onItemClick(v, pos);
                    }
                }
            });
        }
    }

对按钮的初始放置位置进行计算,中心按钮注意要单独考虑
5.添加中心按钮的点击动画

private void rotateCButton() {
        ObjectAnimator anim = ObjectAnimator
                .ofFloat(mCButton, "rotation", 0, 360f);
        anim.setDuration(500);
        anim.start();
    }

中心按钮的旋转动画很简单,在其点击事件中进行调用
6.添加子按钮的弹出和回收动画

 private void toggleMenu() {
        int itemCount = getChildCount() - 1;
        for (int i = 0; i < itemCount; i++) {
            final View child = getChildAt(i);
            child.setVisibility(View.VISIBLE);
            child.setClickable(true);
            child.setFocusable(true);
            /**
             * 计算出子按钮位移距离
             */
            int x = (int) (mRadius * Math.sin(Math.PI / 2 / (itemCount - 1)
                    * i));
            int y = (int) (mRadius * Math.cos(Math.PI / 2 / (itemCount - 1)
                    * i));

            /**
             * 判断出位移方向
             */
            int xflag = 1;
            int yflag = 1;
            if (mPosition == POS_RIGHT_TOP || mPosition == POS_RIGHT_BUTTOM) {
                xflag = -1;
            }
            if (mPosition == POS_LEFT_BUTTOM || mPosition == POS_RIGHT_BUTTOM) {
                yflag = -1;
            }
            ObjectAnimator transXAnimator = null;
            ObjectAnimator transYAnimator = null;
            ObjectAnimator rotationAnimator = null;
            AnimatorSet animatorSet = new AnimatorSet();
            if (mStatus == STATUS_CLOSE) {// 设置弹出动画
                transXAnimator = ObjectAnimator.ofFloat(child, "translationX",
                        0,
                        x * xflag);
                transYAnimator = ObjectAnimator.ofFloat(child, "translationY",
                        0,
                        y * yflag);

            } else {// 设置收回的动画
                transXAnimator = ObjectAnimator.ofFloat(child, "translationX",
                        x
                                * xflag, 0);
                transYAnimator = ObjectAnimator.ofFloat(child, "translationY",
                        y
                                * yflag, 0);
            }
            rotationAnimator = ObjectAnimator.ofFloat(child, "rotation", 0,
                    720f);
            animatorSet.playTogether(transXAnimator, transYAnimator,
                    rotationAnimator);
            animatorSet.setDuration(300);

            animatorSet.setStartDelay(25 * i);
            animatorSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    // TODO Auto-generated method stub
                    if (mStatus == STATUS_CLOSE) {
                        child.setVisibility(View.GONE);
                        child.setClickable(false);
                        child.setFocusable(false);
                    }
                    super.onAnimationEnd(animation);
                }
            });
            animatorSet.start();

        }
        changeStatus();

    }

    private void changeStatus() {
        // TODO Auto-generated method stub
        mStatus = (mStatus == STATUS_CLOSE ? STATUS_OPEN : STATUS_CLOSE);
    }

7.添加子按钮点击动画

 private void clickItemAnim(int pos) {
        // TODO Auto-generated method stub
        int itemCount = getChildCount() - 1;
        for (int i = 0; i < itemCount; i++)
        {

            final View childView = getChildAt(i);
            AnimatorSet animatorSet = null;
            if (i == pos) {
                animatorSet = scaleBigAnim(childView);
            } else {
                animatorSet = scaleSmallAnim(childView);
            }
            animatorSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    // TODO Auto-generated method stub
                    /**
                     * 属性动画在执行之后会将View的各个属性确实更改,必须将他们还原
                     */
                    childView.setScaleX(1.0f);
                    childView.setScaleY(1.0f);
                    childView.setAlpha(1.0f);
                    childView.setX(0f);
                    childView.setTranslationX(0f);
                    childView.setTranslationY(0f);
                    if (mStatus == STATUS_CLOSE) {
                        childView.setVisibility(View.GONE);
                    }
                    super.onAnimationEnd(animation);
                }
            });
            animatorSet.start();
            childView.setClickable(false);
            childView.setFocusable(false);

        }

    }

    private AnimatorSet scaleBigAnim(final View v) {
        ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(v, "scaleX",
                2.0f, 1.0f);
        ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(v, "scaleY",
                2.0f, 1.0f);
        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha",
                1.0f,
                0f);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(scaleXAnimator, scaleYAnimator,
                alphaAnimator);
        animatorSet.setDuration(500);

        return animatorSet;
    }

    private AnimatorSet scaleSmallAnim(View v) {
        ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(v, "scaleX",
                1.0f, 0f);
        ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(v, "scaleY",
                1.0f, 0f);
        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha",
                1.0f,
                0f);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(scaleXAnimator, scaleYAnimator,
                alphaAnimator);
        animatorSet.setDuration(500);
        return animatorSet;
    }

子按钮点击效果的调用在onLayout方法中注册
8.添加子按钮的事件处理

 public interface OnMenuItemClickListener {
        void onItemClick(View view, int position);
    }

创建Item点击的外部接口,在layoutButton中对接口进行实际调用,外部类通过实现该接口进行对Item点击的监听

源码下载

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值