android自定义实现转盘菜单-kb88凯时官网登录

来自:网络
时间:2024-06-09
阅读:

一、前言

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

android自定义实现转盘菜单

二、绘制原理

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

本篇菜单特性:

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

难点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 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 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 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 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转盘菜单的资料请关注其它相关文章!

返回顶部
顶部
网站地图