自定义 view 练习(2):卫星式菜单

根据慕课网 hyman 老师的视频,实现自定义的卫星式菜单 View。
这里是源码地址,欢迎查看。

自定义属性

首先,我们需要为自定义控件设置属性。
新建一个 xml 文件,在里面自定义属性。这里设置了 position 位置,和 radius 半径。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="position">
        <enum name="left_top" value="0" />
        <enum name="left_bottom" value="1" />
        <enum name="right_top" value="2" />
        <enum name="right_bottom" value="3" />
    </attr>

    <attr name="radius" format="dimension" />

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

随后在布局中引用自定义控件时,注意不要忘了引用:

    xmlns:srcmenu="http://schemas.android.com/apk/res-auto"

设置自定义属性

接下来,新建一个 .class 文件继承自 ViewGroup,接着自定义点击事件的回调接口,重写 onLayout() 与 onMeasure(),根据自定义属性值绘制自定义控件的位置:

   private static final int POS_LEFT_TOP = 0;
    private static final int POS_LEFT_BOTTOM = 1;
    private static final int POS_RIGHT_TOP = 2;
    private static final int POS_RIGHT_BOTTOM = 3;
    private Position mPosition = Position.RIGHT_BOTTOM;//菜单位置
    private int mRadius;//菜单半径
    private Status mStatus = Status.CLOSE;//菜单开闭状态
    private View mCButton;//菜单主按钮

    private OnMenuItemClickListener mOnMenuItemClickListener;//菜单点击事件的成员变量


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


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

    public SrcMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100,
                getResources().getDisplayMetrics());//设置菜单半径默认值

        //获取自定义属性值
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SrcMenu,
                defStyleAttr, 0);
        //获取位置值
        int pos = array.getInt(R.styleable.SrcMenu_position, POS_RIGHT_BOTTOM);
        switch (pos) {
            case POS_LEFT_TOP:
                mPosition = Position.LEFT_TOP;
                break;
            case POS_LEFT_BOTTOM:
                mPosition = Position.LEFT_BOTTOM;
                break;
            case POS_RIGHT_TOP:
                mPosition = Position.RIGHT_TOP;
                break;
            case POS_RIGHT_BOTTOM:
                mPosition = Position.RIGHT_BOTTOM;
                break;
        }
        //获取菜单半径值
        mRadius = (int) array.getDimension(R.styleable.SrcMenu_radius,
                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100,
                        getResources().getDisplayMetrics()));
        array.recycle();
    }

    /**
     * 设置菜单点击事件
     */
    public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) {
        mOnMenuItemClickListener = onMenuItemClickListener;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            //测量 child
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        if (b) {
            layoutCButton();
            int count = getChildCount();

            for (int j = 0; j < count - 1; j++) {
                View child = getChildAt(j + 1);

                //开始时设置子菜单不可见
                child.setVisibility(GONE);

                //默认按钮位于左上时
                int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * j));
                int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * j));
                int cWidth = child.getMeasuredWidth();
                int cHeight = child.getMeasuredHeight();
                //按钮位于左下、右下时
                if (mPosition == Position.LEFT_BOTTOM || mPosition == Position.RIGHT_BOTTOM) {
                    ct = getMeasuredHeight() - cHeight - ct;
                }
                //按钮位于右上、右下时
                if (mPosition == Position.RIGHT_TOP || mPosition == Position.RIGHT_BOTTOM) {
                    cl = getMeasuredWidth() - cWidth - cl;
                }

                child.layout(cl, ct, cl + cWidth, ct + cHeight);
            }
        }
    }

    /**
     * 定位主菜单按钮
     */
    private void layoutCButton() {
        mCButton = getChildAt(0);
        mCButton.setOnClickListener(this);

        int l = 0;
        int t = 0;
        int width = mCButton.getMeasuredWidth();
        int height = mCButton.getMeasuredHeight();

        //设置按钮显示的位置
        switch (mPosition) {
            case LEFT_BOTTOM:
                l = 0;
                t = getMeasuredHeight() - height;
                break;
            case LEFT_TOP:
                l = 0;
                t = 0;
                break;
            case RIGHT_TOP:
                l = getMeasuredWidth() - width;
                t = 0;
                break;
            case RIGHT_BOTTOM:
                l = getMeasuredWidth() - width;
                t = getMeasuredHeight() - height;
                break;
        }
        mCButton.layout(l, t, l + width, t + height);
    }
/**
     * 菜单状态枚举类
     */
    public enum Status {
        OPEN, CLOSE
    }

    /**
     * 菜单位置枚举类
     */
    public enum Position {
        LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM
    }

    /**
     * 菜单按钮点击事件回调接口
     */
    public interface OnMenuItemClickListener {
        void onClick(View view, int position);
    }

实现动画效果

接下来我们 需要设置点击主菜单时弹出子菜单的动画,和点击子菜单项时消失的动画。


    private Status mStatus = Status.CLOSE;//菜单开闭状态

    @Override
    public void onClick(View view) {
        rotateCButton(view, 0f, 360f, 300);
        toggleMenu();
    }

    /**
     * 切换菜单
     */
    private void toggleMenu() {
        int count = getChildCount();
        for (int i = 0; i < count - 1; i++) {
            final View childView = getChildAt(i + 1);
            childView.setVisibility(VISIBLE);

            int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i));
            int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i));

            //根据菜单所处位置的不同,设置不同的参数值
            int xFlag = 1;
            int yFlag = 1;
            if (mPosition == Position.LEFT_TOP || mPosition == Position.LEFT_BOTTOM) {
                xFlag = -1;
            }
            if (mPosition == Position.LEFT_TOP || mPosition == Position.RIGHT_TOP) {
                yFlag = -1;
            }

            //平移动画
            AnimationSet animationSet = new AnimationSet(true);
            Animation animation;

            if (mStatus == Status.CLOSE) {
                //打开按钮的动画
                animation = new TranslateAnimation(xFlag * cl, 0, yFlag * ct, 0);
                childView.setFocusable(true);
                childView.setClickable(true);
            } else {
                //关闭按钮的动画
                animation = new TranslateAnimation(0, xFlag * cl, 0, yFlag * ct);
                childView.setFocusable(false);
                childView.setClickable(false);
            }
            animation.setDuration(300);
            animation.setFillAfter(true);
            animation.setStartOffset((i * 100) / count);
            //监听动画状态
            animation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    if (mStatus == Status.CLOSE) {
                        childView.setVisibility(GONE);
                    }
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });

            //旋转动画
            RotateAnimation rotateAnimation = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF,
                    0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            rotateAnimation.setFillAfter(true);
            rotateAnimation.setDuration(300);

            //添加动画
            animationSet.addAnimation(rotateAnimation);
            animationSet.addAnimation(animation);
            childView.startAnimation(animationSet);

            //为子菜单项添加点击事件
            final int pos = i + 1;
            childView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (mOnMenuItemClickListener != null) {
                        mOnMenuItemClickListener.onClick(childView, pos);
                        menuItemAnim(pos - 1);
                        changeStatus();
                    }
                }
            });
        }
        changeStatus();
    }

    /**
     * 点击子菜单项的动画
     */
    private void menuItemAnim(int pos) {
        for (int i = 0; i < getChildCount() - 1; i++) {
            View childView = getChildAt(i + 1);
            if (i == pos) {
                childView.startAnimation(scaleBigAnim(300));
            } else {
                childView.startAnimation(scaleSmallAnim(300));
            }

            //设置子菜单隐藏
            childView.setClickable(false);
            childView.setFocusable(false);
        }
    }

    /**
     * 使菜单项变小并消失的动画
     */
    private Animation scaleSmallAnim(int duration) {
        AnimationSet animationSet = new AnimationSet(true);
        ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 0.0f, 1.0f, 0.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
        animationSet.addAnimation(scaleAnimation);
        animationSet.addAnimation(alphaAnimation);
        animationSet.setDuration(duration);
        animationSet.setFillAfter(true);
        return animationSet;
    }

    /**
     * 使菜单项变大,透明度降低的动画
     */
    private Animation scaleBigAnim(int duration) {
        AnimationSet animationSet = new AnimationSet(true);
        ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
        animationSet.addAnimation(scaleAnimation);
        animationSet.addAnimation(alphaAnimation);
        animationSet.setDuration(duration);
        animationSet.setFillAfter(true);
        return animationSet;
    }

    /**
     * 改变菜单状态值
     */
    private void changeStatus() {
        mStatus = (mStatus == Status.CLOSE ? Status.OPEN : Status.CLOSE);
    }

    /**
     * 按钮旋转动画
     */
    private void rotateCButton(View view, float start, float end, int duration) {
        RotateAnimation animation = new RotateAnimation(start, end, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
        animation.setDuration(duration);
        animation.setFillAfter(true);
        view.startAnimation(animation);
    }

引用自定义控件

这样我们的卫星式菜单就差不多完成了,接下来就是实际使用了。
在布局中引用控件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:srcmenu="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.shmj.mouzhai.srcmenudemo.MainActivity">

    <com.shmj.mouzhai.srcmenudemo.view.SrcMenu
        android:id="@+id/src_menu"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        srcmenu:position="left_bottom"
        srcmenu:radius="150dp">

        <RelativeLayout
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="@mipmap/composer_button">

            <ImageView
                android:id="@+id/btn_plus"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_centerInParent="true"
                android:src="@mipmap/composer_icn_plus" />
        </RelativeLayout>

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:src="@mipmap/composer_camera"
            android:tag="Camera" />

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:src="@mipmap/composer_music"
            android:tag="Music" />

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:src="@mipmap/composer_place"
            android:tag="Place" />

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:src="@mipmap/composer_sleep"
            android:tag="Sleep" />

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:src="@mipmap/composer_thought"
            android:tag="Thought" />

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:src="@mipmap/composer_with"
            android:tag="With" />
    </com.shmj.mouzhai.srcmenudemo.view.SrcMenu>
</RelativeLayout>

在 MainActivity 中,可以调用之前我们自定义的回调接口来实现具体的点击逻辑。这里弹出 Toast 作为示例。

package com.shmj.mouzhai.srcmenudemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;

import com.shmj.mouzhai.srcmenudemo.view.SrcMenu;

public class MainActivity extends AppCompatActivity {

    private SrcMenu mSrcMenu;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSrcMenu = (SrcMenu) findViewById(R.id.src_menu);
        mSrcMenu.setOnMenuItemClickListener(new SrcMenu.OnMenuItemClickListener() {
            @Override
            public void onClick(View view, int position) {
                Toast.makeText(MainActivity.this, position + ":" + view.getTag(), Toast.LENGTH_SHORT)
                        .show();
            }
        });
    }
}

最后运行代码,大功告成!
示例图

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值