Android -- 自定义一个弧形弹出按钮

作者: 雷l阵l雨 | 来源:发表于2017-10-02 16:54 被阅读272次

效果图

啥都不说先看图:

效果图.gif

如果还能入的了您的法眼,那就继续往下看,听我给你慢慢道来。

具体实现

1.布局

图中有五个圆形控件,当然可以是任意奇数个,每个圆形控件这里使用的是TextView添加的圆形背景。布局代码如下:

<com.example.arcmenu.view.ArcMenu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/id_arcmenu"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:radius="360dp"
    app:angle="45" >

    <TextView
        android:id="@+id/id_button"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="@drawable/yuan"
        android:gravity="center"
        android:text="A"
        android:textSize="16dp"
        android:textColor="#FFFFFF" />

    <TextView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="@drawable/yuan"
        android:gravity="center"
        android:text="B"
        android:textSize="16dp"
        android:textColor="#FFFFFF" />

    <TextView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="@drawable/yuan"
        android:gravity="center"
        android:text="+"
        android:textSize="16dp"
        android:textColor="#FFFFFF" />

    <TextView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="@drawable/yuan"
        android:gravity="center"
        android:text="C"
        android:textSize="16dp"
        android:textColor="#FFFFFF" />

    <TextView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="@drawable/yuan"
        android:gravity="center"
        android:text="D"
        android:textSize="16dp"
        android:textColor="#FFFFFF" />

</com.example.arcmenu.view.ArcMenu>

2.自定义属性

自定义了一个叫ArcMenu 的控件,并且添加了两个自定义属性radius(圆弧半径)和angle(圆弧的角度)。在attrs.xml添加如下代码:

<declare-styleable name="ArcMenu">
        <attr name="radius" format="dimension" />
        <attr name="angle" format="float" />
</declare-styleable>

这样就可以在ArcMenu中通过代码获取radius和angle的值。radius默认360dp,angle默认45° 。

// 获取自定义属性的值
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.ArcMenu, defStyleAttr, 0);
radius = (int) a.getDimension(R.styleable.ArcMenu_radius, TypedValue
                .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 360,
                        getResources().getDisplayMetrics()));
angle = (float) (a.getFloat(R.styleable.ArcMenu_angle, 45) * Math.PI/180);

3.坐标计算

坐标.PNG

radius即为图中的r,angle即为图中的角1,我们用width表示View的宽度,count表示子View的个数,角2即为angle / (count-1)。
圆c的圆心坐标:
x轴坐标:width / 2 + radius * Math.sin( angle / (count - 1) )
y轴坐标:radius * Math.cos( angle / (count - 1) )
每个圆的圆心坐标:
x轴坐标:width / 2 + radius * Math.sin( angle / (count - 1) * (i-count/2) )
y轴坐标:radius * Math.cos( angle / (count - 1) * (i-count/2) )
其中,i为第几个圆,从0开始。
因此,在onLayout方法中设置子View的位置。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed){
            int count = getChildCount();
            //设置中间的子View为主按钮
            mMainButton = getChildAt(count/2);
            mMainButton.setOnClickListener(this);
            for (int i = 0; i < count; i++){
                View child = getChildAt(i);
                //开始时隐藏非主按钮
                if(!child.equals(mMainButton)){
                    child.setVisibility(View.GONE);
                }
                //子View的左上角坐标(cl,ct)
                int cl = (int) (mRadius * Math.sin(mAngle / (count - 1) * (i-count/2)))
                        + getMeasuredWidth()/2 - child.getMeasuredWidth()/2;
                int ct = (int) (mRadius * Math.cos(mAngle / (count - 1) * (i-count/2))) ;
                //测量的子View的宽,高
                int cWidth = child.getMeasuredWidth();
                int cHeight = child.getMeasuredHeight();
                //设置子view的位置
                child.layout(cl, ct, cl + cWidth, ct + cHeight);
            }
        }

4.添加动画

初始时只显示最中间的子View(称为主View),隐藏其他子View;点击主View展开其他子View,此时,每个子View移动的角度endAngle为:
endAngle = angle / (count-1) * (i-count/2)
这里使用ValueAnimator做动画,首先定义一个估值器AngleEvaluator:

public class AngleEvaluator implements TypeEvaluator {
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        float start = (Float) startValue;
        float end = (Float) endValue;
        float angle = start + fraction * (end-start);  
        return angle;  
    }  
}

设置动画
anim = ValueAnimator.ofObject(new AngleEvaluator(), 0, endAngle);
再次点击主View收缩其他子View时
anim = ValueAnimator.ofObject(new AngleEvaluator(), endAngle, 0);
代码如下:

    /**
     * 按钮开关,控制按钮的显示与隐藏
     * @param duration 执行动画时间
     */
    private void toggleMenu(int duration) {
        // 为menuItem添加旋转动画
        int count = getChildCount();
        for (int i = 0; i < count; i++){
            if(i!=count/2){
                final View childView = getChildAt(i);
                childView.setVisibility(View.VISIBLE);
                float startAngle = 0;
                float endAngle = (float) (mAngle / (count - 1) * (i-count/2));
                ValueAnimator anim = null;
                // 若当前状态为关闭,则打开按钮 to open
                if (mCurrentStatus == CLOSE){
                    anim = ValueAnimator.ofObject(new AngleEvaluator(), startAngle, endAngle);
                } else {// 若当前状态为打开,则关闭按钮 to close
                    anim = ValueAnimator.ofObject(new AngleEvaluator(), endAngle, startAngle);
                    anim.addListener(new Animator.AnimatorListener() {
                        @Override
                        public void onAnimationStart(Animator animation) {

                        }

                        @Override
                        public void onAnimationRepeat(Animator animation) {

                        }

                        @Override
                        public void onAnimationEnd(Animator animation) {
                            childView.setVisibility(View.GONE);
                        }

                        @Override
                        public void onAnimationCancel(Animator animation) {

                        }
                    });
                }
                //监听动画传回的结果,重新设置子View位置
                anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float currentAngle = (Float) animation.getAnimatedValue();
                        int cl = (int) (mRadius * Math.sin(currentAngle)) + getMeasuredWidth()/2 - childView.getMeasuredWidth()/2;
                        int ct = (int) (mRadius * Math.cos(currentAngle)) ;

                        int cWidth = childView.getMeasuredWidth();
                        int cHeight = childView.getMeasuredHeight();
                        childView.layout(cl, ct, cl + cWidth, ct + cHeight);
                    }
                });
                anim.setInterpolator(new AnticipateOvershootInterpolator());
                anim.setDuration(duration);
                anim.start();
            }
        }
        // 切换菜单状态
        changeStatus();
    }

    private void changeStatus() {
        mCurrentStatus = (mCurrentStatus == CLOSE ? OPEN : CLOSE);
    }

到此已经全部结束了,有哪些做的不对的地方,希望大家多多指点。
源码

猿自话.jpg

相关文章

网友评论

    本文标题:Android -- 自定义一个弧形弹出按钮

    本文链接:https://www.haomeiwen.com/subject/ayftyxtx.html