Android自定义view实现圆形与半圆形菜单
前不久看到鸿洋大大的圆形菜单,就想开始模仿,因为实在是太酷了,然后自己根据别人(zw哥)给我讲的一些思路、一些分析,就开始改造自己的圆形菜单了。
文章结构:1.功能介绍以及展示;2.部分代码讲解;3.大致可以实现的ui效果展示讲解。4.源码附送。
一、功能介绍以及展示
第一个展示是本控件的原样。但是我们可以使用很多技巧去达到我们的商业ui效果嘛。
这里给出的是本博客作品demo的展示图以及第三点的联动展示,可见是一圆型菜单,相较于鸿洋大大的那个圆形菜单多了一些需求:
1.到时候展示只需要半圆的转盘。
2.在规定的角度不能让他们自动旋转(涉及延伸的一些数学计算,一会重点讲解)。
3.要绑定fragment。
4.一个缓冲角度,即我们将要固定几个位置,而不是任意位置。我们要设计一个可能的角度去自动帮他选择。
二、代码讲解:
结合实际使用的方式来讲解。分为:1.调用方式;2.此控件onmeasure方法;3.onlayout方法的作用;4.此控件事件机制dispatchtouchevent的使用;5.数学计算—一个缓冲角度。
(1)调用方式 :(代码为展示区下方的效果代码)
//采用的是联动,使用fragment管理器fragmenttransaction去实现fragment管理 package com.fuzhucheng.circlemenu; import android.os.bundle; import android.support.v4.app.fragmenttransaction; import android.support.v7.app.appcompatactivity; import android.view.keyevent; import android.view.view; import android.widget.toast; public class mainactivity extends appcompatactivity { private upcirclemenulayout mycirclemenulayout; //四个fragment页面 private homepagefragment homepagefragment; private settingfragment settingfragment; private historyfragment historyfragment; private fourthfragment fourthfragment; private fifthfragment fifthfragment; private string[] mitemtexts = new string[]{"安全中心 ", "特色服务", "投资理财", "转账汇款", "我的账户", "安全中心", "特色服务", "投资理财", "转账汇款", "我的账户"}; private int[] mitemimgs = new int[]{r.drawable.home_mbank_1_normal, r.drawable.home_mbank_2_normal, r.drawable.home_mbank_3_normal, r.drawable.home_mbank_4_normal, r.drawable.home_mbank_5_normal, r.drawable.home_mbank_1_normal, r.drawable.home_mbank_2_normal, r.drawable.home_mbank_3_normal, r.drawable.home_mbank_4_normal, r.drawable.home_mbank_5_normal}; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); //第一次初始化首页默认显示第一个fragment initfragment1(); mycirclemenulayout = (upcirclemenulayout) findviewbyid(r.id.id_mymenulayout); mycirclemenulayout.setmenuitemiconsandtexts(mitemimgs);//一句设置图片 mycirclemenulayout.setonmenuitemclicklistener(new upcirclemenulayout.onmenuitemclicklistener() { @override public void itemclick(int pos) { toast.maketext(mainactivity.this, mitemtexts[pos], toast.length_short).show(); switch (pos) { case 0: initfragment1(); settitle("安全中心"); break; case 1: initfragment2(); settitle("特色服务"); break; case 2: initfragment3(); settitle("投资理财"); break; case 3: initfragment4(); settitle("转账汇款"); break; case 4: initfragment5(); settitle("我的账户"); break; case 5: initfragment1(); settitle("安全中心"); break; case 6: initfragment2(); settitle("特色服务"); break; case 7: initfragment3(); settitle("投资理财"); break; case 8: initfragment4(); settitle("转账汇款"); break; case 9: initfragment5(); settitle("我的账户"); break; } } @override public void itemcenterclick(view view) { toast.maketext(mainactivity.this, "you can do something just like ccb ", toast.length_short).show(); } }); } //显示第一个fragment private void initfragment1(){ //开启事务,fragment的控制是由事务来实现的 homepagefragment = new homepagefragment(); fragmenttransaction transaction = getsupportfragmentmanager().begintransaction(); transaction.replace(r.id.fragment_tv,homepagefragment); transaction.addtobackstack(null); transaction.commit(); } //显示第二个fragment private void initfragment2(){ //开启事务,fragment的控制是由事务来实现的 settingfragment = new settingfragment(); fragmenttransaction transaction = getsupportfragmentmanager().begintransaction(); transaction.replace(r.id.fragment_tv,settingfragment); transaction.addtobackstack(null); transaction.commit(); } private void initfragment3(){ //开启事务,fragment的控制是由事务来实现的 historyfragment = new historyfragment(); fragmenttransaction transaction = getsupportfragmentmanager().begintransaction(); transaction.replace(r.id.fragment_tv,historyfragment); transaction.addtobackstack(null); transaction.commit(); } private void initfragment4(){ //开启事务,fragment的控制是由事务来实现的 fourthfragment = new fourthfragment(); fragmenttransaction transaction = getsupportfragmentmanager().begintransaction(); transaction.replace(r.id.fragment_tv,fourthfragment); transaction.addtobackstack(null); transaction.commit(); } private void initfragment5(){ //开启事务,fragment的控制是由事务来实现的 fifthfragment = new fifthfragment(); fragmenttransaction transaction = getsupportfragmentmanager().begintransaction(); transaction.replace(r.id.fragment_tv,fifthfragment); transaction.addtobackstack(null); transaction.commit(); } public boolean onkeydown(int keycode, keyevent event) { if (keycode == keyevent.keycode_back && event.getrepeatcount() == 0) { finish(); return true; } return super.onkeydown(keycode, event); } }
(2)此控件onmeasure方法讲解:重点讲解迭代测量
/** * 设置布局的宽高,并策略menu item宽高 */ @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { int reswidth = 0; int resheight = 0; double startangle = mstartangle; double angle = 360 / 10; //我们传入了10个孩子 /** * 根据传入的参数,分别获取测量模式和测量值 */ int width = measurespec.getsize(widthmeasurespec); int widthmode = measurespec.getmode(widthmeasurespec); int height = measurespec.getsize(heightmeasurespec); int heightmode = measurespec.getmode(heightmeasurespec); /** * 如果宽或者高的测量模式非精确值 */ if (widthmode != measurespec.exactly || heightmode != measurespec.exactly) { // 主要设置为背景图的高度 reswidth = getdefaultwidth(); resheight = (int) (reswidth * default_banner_heigth / default_banner_width); } else { // 如果都设置为精确值,则直接取小值; reswidth = resheight = math.min(width, height); } setmeasureddimension(reswidth, resheight); // 获得直径 mradius = math.max(getmeasuredwidth(), getmeasuredheight()); // menu item数量 final int count = getchildcount(); // menu item尺寸 int childsize; // menu item测量模式 int childmode = measurespec.exactly; // 迭代测量:根据孩子的数量进行遍历,为每一个孩子测量大小,设置监听回调。 for (int i = 0; i < count; i++) { final view child = getchildat(i); startangle = startangle % 360; if (startangle > 269 && startangle < 271 && istouchup) { monmenuitemclicklistener.itemclick(i); //设置监听回调。 mcurrentposition = i; //本次使用mcurrentposition,只是把他作为一个temp变量,可以有更多的使用,比如动态设置每个孩子相隔的角度 childsize = densityutil.dip2px(getcontext(), radio_top_child_dimension);//设置大小 } else { childsize = densityutil.dip2px(getcontext(), radio_default_child_dimension);//设置大小 } if (child.getvisibility() == gone) { continue; } // 计算menu item的尺寸;以及和设置好的模式,去对item进行测量 int makemeasurespec = -1; makemeasurespec = measurespec.makemeasurespec(childsize, childmode); child.measure(makemeasurespec, makemeasurespec); startangle += angle; } //item容器内边距 mpadding = densityutil.dip2px(getcontext(), radio_margin_layout); }
onmeasure深入:view在屏幕上显示出来要先经过measure(计算)和layout(布局)。这方法作用就是计算出自定义view的宽度和高度。这个计算的过程参照父布局给出的大小,以及自己特点算出结果 。当然,还有相关的尺寸测量模式。此处奉上一篇好博文:onmeasure理解。此外,我还在这方法里作为监听回调的设置!!而为控件设置图片可以直接使用我们下面设计的方法:setmenuitemiconsandtexts一句收工。
(3)onlayout方法的讲解:(此处的圆的数学计算布置图标围绕圆位置可见鸿洋大大的推荐,讲得很清楚,当然我下面也会略微讲解下)
/** * 设置menu item的位置 */ @override protected void onlayout(boolean changed, int l, int t, int r, int b) { int layoutradius = mradius; // laying out the child views final int childcount = getchildcount(); int left, top; // menu item 的尺寸 int cwidth; // 根据menu item的个数,计算角度 float angledelay = 360 / 10; // 遍历去设置menuitem的位置 for (int i = 0; i < childcount; i++) { final view child = getchildat(i); //根据孩子遍历,设置中间顶部那个的大小以及其他图片大小。 if (mstartangle > 269 && mstartangle < 271 && istouchup) { cwidth = densityutil.dip2px(getcontext(), radio_top_child_dimension); child.setselected(true); } else { cwidth = densityutil.dip2px(getcontext(), radio_default_child_dimension); child.setselected(false); } if (child.getvisibility() == gone) { continue; } //大于360就取余归于小于360度 mstartangle = mstartangle % 360; float tmp = 0; //计算图片布置的中心点的圆半径。就是tmp tmp = layoutradius / 2f - cwidth / 2 - mpadding; // tmp cosa 即menu item中心点的横坐标。计算的是item的位置,是计算位置!!! left = layoutradius / 2 + (int) math.round(tmp * math.cos(math.toradians(mstartangle)) - 1 / 2f * cwidth) + densityutil .dip2px(getcontext(), 1); // tmp sina 即menu item的纵坐标 top = layoutradius / 2 + (int) math.round(tmp * math.sin(math.toradians(mstartangle)) - 1 / 2f * cwidth) + densityutil .dip2px(getcontext(), 8); //接着当然是布置孩子的位置啦,就是根据小圆的来布置的 child.layout(left, top, left + cwidth, top + cwidth); // 叠加尺寸 mstartangle += angledelay; } }
给出鸿洋大大的计算小圆的思路图:
(4)此控件事件机制dispatchtouchevent的使用:
//dispatchtouchevent是处理触摸事件分发,事件(多数情况)是从activity的dispatchtouchevent开始的。执行super.dispatchtouchevent(ev),事件向下分发。 //ontouchevent是view中提供的方法,viewgroup也有这个方法,view中不提供onintercepttouchevent。view中默认返回true,表示消费了这个事件。 @override public boolean dispatchtouchevent(motionevent event) { float x = event.getx(); float y = event.gety(); getparent().requestdisallowintercepttouchevent(true); switch (event.getaction()) { case motionevent.action_down: //直接就是获取x,y值了,还有一个downtime(附送) mlastx = x; mlasty = y; mdowntime = system.currenttimemillis(); mtmpangle = 0; break; case motionevent.action_move: istouchup = false; //注意istouchup 这个标记量!!! /** * 获得开始的角度 */ float start = getangle(mlastx, mlasty); /** * 获得当前的角度 */ float end = getangle(x, y); // 如果是一、四象限,则直接end-start,角度值都是正值 if (getquadrant(x, y) == 1 || getquadrant(x, y) == 4) { mstartangle += end - start; mtmpangle += end - start;//按下到抬起时旋转的角度 } else // 二、三象限,色角度值是负值 { mstartangle += start - end; mtmpangle += start - end; } // 重新布局 if (mtmpangle != 0) { requestlayout(); } mlastx = x; mlasty = y; break; case motionevent.action_up: //当手指up啦,就是关键啦,一个缓冲角度,即我们将要固定几个位置,而不是任意位置。我们要设计一个可能的角度去自动帮他选择。 backorpre(); break; } return super.dispatchtouchevent(event); }
motionevent事件机制:(此控件我只用了三个)主要的事件类型有:action_down: 表示用户开始触摸。action_move: 表示用户在移动(手指或者其他)。action_up:表示用户抬起了手指。
(5)数学计算—一个缓冲角度。
private void backorpre() { //缓冲的角度。即我们将要固定几个位置,而不是任意位置。我们要设计一个可能的角度去自动帮他选择。 istouchup = true; float angledelay = 360 / 10; //这个是每个图形相隔的角度 //我们本来的上半圆的图片角度应该是:18,54,90,126,162。所以我们这里是:先让当前角度把初始的18度减去再取余每个图形相隔角度。得到的是什么呢?就是一个图片本来应该在的那堆角度。所以如果是就直接return了。 if ((mstartangle-18)%angledelay==0){ return; } float angle = (float)((mstartangle-18)%36); //angle就是那个不是18度开始布局,然后是36度的整数的多出来的部分角度 //以下就是我们做的缓冲角度处理啦,如果多出来的部分角度大于图片相隔角度的一半就往前进一个,如果小于则往后退一个。 if (angledelay/2 > angle){ mstartangle -= angle; }else if (angledelay/2<angle){ mstartangle = mstartangle - angle + angledelay; //mstartangle就是当前角度啦,取余36度就是多出来的角度,拿这个多出来的角度去数据处理。 } //然后重新布局onlayout requestlayout(); }
至于其他小的方法详情,可见源代码,有详细解释。
源码传送门:github地址:android-自定义view之圆形与“半圆形”菜单 喜欢的可以star或fork啦,谢谢!
好了,android-自定义view之圆形与“半圆形”菜单讲完了。本博客是经过仔细研究鸿洋大大的圆形菜单博客的,并在这里做出进一步拓展以及写出自己的理解。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
Android自定义view实现圆形与半圆形菜单
-
Android自定义VIew实现卫星菜单效果浅析
-
Android编程基于自定义View实现绚丽的圆形进度条功能示例
-
Android编程基于自定义View实现绚丽的圆形进度条功能示例
-
Android自定义控件:图形报表的实现(折线图、曲线图、动态曲线图)(View与SurfaceView分别实现图表控件)
-
Android自定义view实现圆形waveview
-
Android自定义View 仿QQ侧滑菜单的实现代码
-
Android自定义View 仿QQ侧滑菜单的实现代码
-
Android自定义View实现环形进度条的思路与实例
-
Android自定义View展开菜单功能的实现