当前位置: 代码网 > it编程>App开发>Android > Android自定义实现转盘菜单

Android自定义实现转盘菜单

2024年05月18日 Android 我要评论
一、前言旋转菜单是一种占用空间较大,实用性稍弱的ui,一方面由于展示空间的问题,其展示的数据有限,但另一方面真由于这个原因,对用户而言趣味性和操作性反而更有好。二、绘制原理绘制原理很简单,通过细微的观

一、前言

旋转菜单是一种占用空间较大,实用性稍弱的ui,一方面由于展示空间的问题,其展示的数据有限,但另一方面真由于这个原因,对用户而言趣味性和操作性反而更有好。

二、绘制原理

绘制原理很简单,通过细微的观察,我们发现文字是不需要旋转的,也就是每个菜单是不需要自旋转,只需要旋转其位置坐标即可,实际上其难点并不是绘制,而是在于触摸事件的处理方式。

本篇菜单特性:

  • 动态设置菜单
  • 计算旋转方向和旋转角度
  • 支持点击

难点1:

旋转方向判断,旋转时记录起始点,计算出旋转方向。

首先,我们要理解,touch事件也存在抽象的坐标体系,和view左上角重合,因此我们需要转换坐标

float cx = event.getx() - getwidth() / 2f;
float cy = event.gety() - getheight() / 2f;

旋转角度的计算

这种计算是为了计算出与原始落点位置的夹角,这里的方法是计算使用math.asin反正切函数,然后结合坐标系进行判断

float linewidth = (float) math.sqrt(math.pow(cx, 2) + math.pow(cy, 2));
float degreeradian = (float) math.asin(cy / linewidth);
float dr = 0;
if (cy > 0) {
         //一二象限
 if (cx > 0) {
    dr = degreeradian;
 } else {
   dr = (float) ((math.pi - degreeradian));
 }

} else {
    //三四象限
    if (cx > 0) {
         dr = (float) (math.pi * 2 - math.abs(degreeradian));
    } else {
        dr = (float) ((math.pi + math.abs(degreeradian)));
    }
}

由于对math的了解我们知道,math.asin不能反映真实的夹角,因此需要做上面的补充。但是后来我们发现,math.atan2函数的存在,直接可以求出斜率夹角,而且不会丢失象限关系,一下子就省了好几行代码。

dr = (float) math.atan2(cy, cx);

难点2:实时更新

为了旋转,我们可能忘记记录最新位置,这个可能导致圆反向旋转,因此要实时记录位置

estartx = cx;
estarty = cy;

难点3:由于拦截了up事件,因此需要对up事件进行专门处理

if (system.currenttimemillis() - startdowntime > 500) {    
  break;
}
float upx = event.getx() - getwidth() / 2f;
float upy = event.gety() - getheight() / 2f;
handleclicktap(upx, upy);

全部代码:

public class oribitview extends view {

    private final string tag = "oribitview";
    private displaymetrics displaymetrics;
    private float moutlineraduis;
    private float minlineradius;
    private textpaint mpaint;
    private float linewidth = 5f;
    private float textsize = 12f;
    private int itemcount = 5;

    private int mtouchslop = 0;
    private float rotatedegreeradian = 0;

    private onitemclicklistener onitemclicklistener;

    private float estartx = 0f;
    private float estarty = 0f;
    private boolean ismovetouch = false;
    private float startdegreeradian = 0l; //记录用于落点角度,用于参考
    private long startdowntime = 0l;

    rect bounds = new rect();
    private final list<oribititempoint> moribititempoints = new arraylist<>();

    public oribitview(context context) {
        this(context, null);
    }

    public oribitview(context context, @nullable attributeset attrs) {
        this(context, attrs, 0);
    }

    public oribitview(context context, @nullable attributeset attrs, int defstyleattr) {
        super(context, attrs, defstyleattr);
        displaymetrics = context.getresources().getdisplaymetrics();
        initpaint();
        mtouchslop = viewconfiguration.get(context).getscaledtouchslop();
        setlayertype(layer_type_software,null);
    }

    private void initpaint() {
        // 实例化画笔并打开抗锯齿
        mpaint = new textpaint(paint.anti_alias_flag);
        mpaint.setantialias(true);
        mpaint.settextsize(dptopx(textsize));

    }

    private float dptopx(float dp) {
        return typedvalue.applydimension(typedvalue.complex_unit_dip, dp, getresources().getdisplaymetrics());
    }


    @override
    protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
        super.onmeasure(widthmeasurespec, heightmeasurespec);

        int widthmode = measurespec.getmode(widthmeasurespec);
        int widthsize = measurespec.getsize(widthmeasurespec);

        if (widthmode != measurespec.exactly) {
            widthsize = displaymetrics.widthpixels / 2;
        }

        int heightmode = measurespec.getmode(heightmeasurespec);
        int heightsize = measurespec.getsize(heightmeasurespec);

        if (heightmode != measurespec.exactly) {
            heightsize = displaymetrics.widthpixels / 2;
        }
        widthsize = heightsize = math.min(widthsize, heightsize);

        setmeasureddimension(widthsize, heightsize);
    }


    @override
    protected void onsizechanged(int w, int h, int oldw, int oldh) {
        super.onsizechanged(w, h, oldw, oldh);

        moutlineraduis = w / 2.0f - dptopx(linewidth);
        minlineradius = moutlineraduis * 3 / 5.0f - dptopx(linewidth);

    }

    @override
    protected void ondraw(canvas canvas) {
        super.ondraw(canvas);

        int width = getwidth();
        int height = getwidth();

        mpaint.setstyle(paint.style.stroke);
        mpaint.setstrokewidth(dptopx(linewidth / 4));
        mpaint.setcolor(color.gray);
        int id = canvas.save();

        float centerradius = (moutlineraduis + minlineradius) / 2;
        float itemradius = (moutlineraduis - minlineradius) / 2;

        canvas.translate(width / 2f, height / 2f);

      //  canvas.drawcircle(0, 0, moutlineraduis, mpaint); //画外框
      //  canvas.drawcircle(0, 0, minlineradius, mpaint); //画内框

        float strokewidth = mpaint.getstrokewidth();
        mpaint.setstrokewidth(itemradius * 2 - dptopx(linewidth / 2));
        mpaint.setcolor(color.dkgray);
        mpaint.setshadowlayer(10,0,10,color.dkgray);
        canvas.drawcircle(0, 0, centerradius, mpaint);
        mpaint.setstrokewidth(strokewidth);

        float degree = (float) (2 * math.asin(itemradius / centerradius));
        //计算出从原点过item的切线夹角,求出每个圆所占夹角大小

        float spacedegree = (float) ((math.pi * 2 - degree * itemcount) / itemcount);


        for (int i = 0; i < moribititempoints.size(); i++) {

            oribititempoint itempoint = moribititempoints.get(i);

            float x = (float) (centerradius * math.cos(rotatedegreeradian + i * (spacedegree + degree)));
            float y = (float) (centerradius * math.sin(rotatedegreeradian + i * (spacedegree + degree)));

            itempoint.x = x;
            itempoint.y = y;


            oribititem oribititem = itempoint.getoribititem();

            mpaint.setstyle(paint.style.fill);
            mpaint.setcolor(oribititem.backgroundcolor);


            //减去线宽
            float strokeoffset = dptopx(linewidth / 2);

            canvas.drawcircle(x, y, itemradius - strokeoffset, mpaint);
            mpaint.setcolor(oribititem.textcolor);
            string text = string.valueof(oribititem.text);
            mpaint.gettextbounds(text, 0, text.length(), bounds);
            float textbaseline = gettextpaintbaseline(mpaint) - y - bounds.height() + strokeoffset;
            canvas.drawtext(text, x - bounds.width() / 2f, -textbaseline, mpaint);

        }
        canvas.restoretocount(id);

    }


    @override
    public boolean ontouchevent(motionevent event) {

        switch (event.getaction()) {
            case motionevent.action_down:
                estartx = event.getx() - getwidth() / 2f;
                //这里转为原点为画布中心的点,便于计算角度
                estarty = event.gety() - getheight() / 2f;

                //求出落点与坐标系x轴方向的夹角(
                float locationradian = (float) math.asin(estarty / (float) math.sqrt(math.pow(estartx, 2) + math.pow(estarty, 2)));

//                //根据正弦值计算起点在那个象限
//                if (estarty > 0) {
//                    //一二象限
//                    if (estartx < 0) {
//                        startdegreeradian = (float) (math.pi - locationradian);
//                    } else {
//                        startdegreeradian = locationradian;
//                    }
//                } else {
//                    //三四象限
//                    if (estartx > 0) {
//                        startdegreeradian = (float) (math.pi * 2 - math.abs(locationradian));
//                    } else {
//                        startdegreeradian = (float) (math.pi + math.abs(locationradian));
//                    }
//                }
                startdegreeradian = locationradian;
                startdowntime = system.currenttimemillis();
                getparent().requestdisallowintercepttouchevent(true);
                super.ontouchevent(event);
                return true;
            case motionevent.action_move:
                //坐标转换
                float cx = event.getx() - getwidth() / 2f;
                float cy = event.gety() - getheight() / 2f;

                float dx = cx - estartx;
                float dy = cy - estarty;
                float slideslop = (float) math.sqrt(math.pow(dx, 2) + math.pow(dy, 2));

                if (slideslop > mtouchslop) {
                    ismovetouch = true;
                } else {
                    ismovetouch = false;

                }
                if (ismovetouch) {

                    float linewidth = (float) math.sqrt(math.pow(cx, 2) + math.pow(cy, 2));
                    float degreeradian = (float) math.asin(cy / linewidth);

                    float dr = 0;
//
//                    if (cy > 0) {
//                        //一二象限
//                        if (cx > 0) {
//                            dr = degreeradian;
//                        } else {
//                            dr = (float) ((math.pi - degreeradian));
//                        }
//
//                    } else {
//                        //三四象限
//                        if (cx > 0) {
//                            dr = (float) (math.pi * 2 - math.abs(degreeradian));
//                        } else {
//                            dr = (float) ((math.pi + math.abs(degreeradian)));
//                        }
//                    }

                    dr = (float) math.atan2(cy, cx);
                    rotatedegreeradian += (dr - startdegreeradian);
                    startdegreeradian = dr;

                    estartx = cx;
                    estarty = cy;

                    postinvalidate();
                }

                break;
            case motionevent.action_up:
            case motionevent.action_cancel:
            case motionevent.action_outside:
                getparent().requestdisallowintercepttouchevent(false);
                if (ismovetouch) {
                    ismovetouch = false;
                    break;
                }

                if (system.currenttimemillis() - startdowntime > 500) {
                    break;
                }
                float upx = event.getx() - getwidth() / 2f;
                float upy = event.gety() - getheight() / 2f;
                handleclicktap(upx, upy);

                break;
        }

        return super.ontouchevent(event);
    }

    private void handleclicktap(float upx, float upy) {
        if (itemcount == 0 || moribititempoints == null) return;

        oribititempoint clickitempoint = null;
        float itemradius = (moutlineraduis - minlineradius) / 2;

        for (oribititempoint itempoint : moribititempoints) {
            if (float.isnan(itempoint.x) || float.isnan(itempoint.y)) {
                continue;
            }
            float dx = (itempoint.x - upx);
            float dy = (itempoint.y - upy);
            float clickslop = (float) math.sqrt(math.pow(dx, 2) + math.pow(dy, 2));
            if (clickslop >= itemradius) {
                continue;
            }
            clickitempoint = itempoint;
            break;
        }

        if (clickitempoint == null) return;

        if (this.moribititempoints != null) {
            this.onitemclicklistener.onitemclick(this, clickitempoint.oribititem);
        }

    }

    public int getitemcount() {
        return itemcount;
    }

    public static float gettextpaintbaseline(paint p) {
        paint.fontmetrics fontmetrics = p.getfontmetrics();
        return (fontmetrics.descent - fontmetrics.ascent) / 2 - fontmetrics.descent;
    }

    public void showitems(list<oribititem> oribititems) {

        moribititempoints.clear();
        if (oribititems != null) {
            for (oribititem item : oribititems) {
                oribititempoint point = new oribititempoint();
                point.x = float.nan;
                point.y = float.nan;
                point.oribititem = item;
                moribititempoints.add(point);
            }
        }
        this.itemcount = moribititempoints.size();
        postinvalidate();
    }


    public void setonitemclicklistener(onitemclicklistener onitemclicklistener) {
        this.onitemclicklistener = onitemclicklistener;
    }

    public static class oribititem {
        public string text;
        public int textcolor;
        public int backgroundcolor;

    }

    static class oribititempoint<t extends oribititem> extends pointf {

        private t oribititem;

        public void setoribititem(t oribititem) {
            this.oribititem = oribititem;
        }

        public t getoribititem() {
            return oribititem;
        }
    }

    public interface onitemclicklistener {

        public void onitemclick(view contentview, oribititem item);
    }
}

用法:

 oribitview oribitview = findviewbyid(r.id.oribitview);

        oribitview.setonitemclicklistener(new oribitview.onitemclicklistener() {
            @override
            public void onitemclick(view contentview, oribitview.oribititem item) {
                toast.maketext(contentview.getcontext(),item.text,toast.length_short).show();
            }
        });

        list<oribitview.oribititem> oribititems = new arraylist<>();
        string[] chs = new string[]{"鲜花", "牛奶", "橘子", "生活", "新闻", "热点"};
        int[] colors = new int[]{argb(random.nextfloat(), random.nextfloat(), random.nextfloat()),
                argb(random.nextfloat(), random.nextfloat(), random.nextfloat()),
                argb(random.nextfloat(), random.nextfloat(), random.nextfloat()),
                argb(random.nextfloat(), random.nextfloat(), random.nextfloat()),
                argb(random.nextfloat(), random.nextfloat(), random.nextfloat()),
                argb(random.nextfloat(), random.nextfloat(), random.nextfloat())
        };
        for (int i = 0; i < chs.length; i++) {
            oribitview.oribititem item = new oribitview.oribititem();
            item.text = chs[i];
            item.textcolor = color.white;
            item.backgroundcolor = colors[i];

            oribititems.add(item);
        }

        oribitview.showitems(oribititems);

三、总结

本篇难点主要是事件处理,当然可能有人会问,使用layout添加岂不是更方便,答案是肯定的,但是本篇主要重点介绍canvas 绘制,后续有layout的布局,当然这里其实区别并不大,不同点是一个需要onlayout的调用,另一个是ondraw的调用,做好坐标轴转换即可,难度并不大。

以上就是android自定义实现转盘菜单的详细内容,更多关于android转盘菜单的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com