听朋友说【王者荣耀】挺火的让我也下载,没事的时候一块玩几局(这里不是打广告啊,毕竟我不是腾讯的员工,大写的尴尬。。。),当我查看个人资料的时候,看到有对战资料选项,对战的详细数据是以雷达网图的形式、圆形进度条形式等方式向用户展示。网上已经有大牛实现了类似的功能,那么,自己就更应该去尝试一下了。对于圆形进度方式相对较容易,我在前面的自定义View系列中简单讲解过这里就不再赘述,倒是像蜘蛛网状的图形吸引了我,网是由一层层的正n边形组成。然,,加之有一段时间没有写自定义View相关的东西了,所以就也来模仿着实现一下。顺便再温习下自定义View

老规矩,没图说个棒槌啊,来张效果图

分析

  1. 首先是对n边形的绘制,毫无疑问,我们想到了path,那么就需要确定x、y坐标。从而就需要先知道这个公式:弧度 = 度 * π / 180 (其中 π = Math.PI)以及 x = cosα * r(半径) , y = sinα * r
  2. 具体绘制的n边形的n等于几?由上面的效果图可以看出来,蜘蛛网的最外层n边形的顶点上对应着需要向用户展示的数据点,所以,根据数据点的个数确定n的值
  3. 需要绘制多少层n边形,这是做什么用的呢?显而易见,是每项技能对应的数据。假设每项技能的最大输出值为100,我们绘制了4层,那么每层就占了相应总数据的 1/4 即:25,那么在绘制中间的遮罩层的话就可以确定数据点了
  4. 中心点到n边形的各个顶点的连线,通过canvas的drawLine()方法
  5. 最后就剩下中间遮罩层了,当然也是通过path连接各个数据点,关于这个数据点的确定上面已经说过了,是通过每一个值占总数据的比例来确定数据点的位置

主要思路我们已经分析了,那么,接下来就开撸吧,当然了,可不是撸王者荣耀,而是 撸!!代!!码!!

还是原来的套路,首先是我们的自定义属性,绘制中需要将哪些属性开放出来给开发者灵活使用,就定义哪些属性

<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="MyRadarView"><!-- 雷达网的数量,即:多少层 --><attr name="radar_num" format="integer"/><!-- 雷达网 数据最大值 --><attr name="radar_max_value" format="dimension"/><!-- 雷达网中线的宽度 --><attr name="radar_line_width" format="dimension"/><!-- 雷达网中线的颜色 --><attr name="radar_line_color" format="color"/><!-- 文字的大小 --><attr name="text_size" format="dimension"/><!-- 文字的颜色 --><attr name="text_color" format="color"/><!-- 文字距离雷达网的距离 --><attr name="text_radar_distance" format="dimension"/></declare-styleable>
</resources>

接下来就是去获取开发者在XML文件中自己设置的自定义属性

public MyRadarView(Context context) {this(context, null);}public MyRadarView(Context context, AttributeSet attrs) {this(context, attrs, 0);
}public MyRadarView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyRadarView, defStyleAttr, 0);int count = typedArray.getIndexCount();for (int i = 0; i < count; i++){int attr = typedArray.getIndex(i);switch (attr){case R.styleable.MyRadarView_radar_num:radarNum = typedArray.getInt(attr, radarNum);break;case R.styleable.MyRadarView_radar_max_value:maxValue = typedArray.getDimension(attr, maxValue);break;case R.styleable.MyRadarView_radar_line_width:radarLineWidth = typedArray.getDimension(attr, radarLineWidth);break;case R.styleable.MyRadarView_radar_line_color:radarLineColor = typedArray.getColor(attr, radarLineColor);break;case R.styleable.MyRadarView_text_size:mTextSize = typedArray.getDimension(attr, mTextSize);break;case R.styleable.MyRadarView_text_color:mTextColor = typedArray.getColor(attr, mTextColor);break;case R.styleable.MyRadarView_text_radar_distance:mDistance = typedArray.getDimension(attr, mDistance);break;}}typedArray.recycle();//初始化画笔setPaint();//初始化pathmPath = new Path();//盛放文字的容器rectBounds = new Rect();
}

上面的代码大家肯定很熟悉了,这里还是简单的啰嗦一下,方便刚开始学自定义view的童鞋。当我们在xml文件中去手动设置一些属性配置后,那么我们通过int count = typedArray.getIndexCount();就可以得到在xml文件中手动配置多少项,就可以继续往下走,将获取到的属性值赋值给我们定义的变量。当我们没有手动去配置这些公开的属性的话,那么count为0就不会继续往下走,所以我们的变量就得不到赋值,只能使用默认值。这也就是为什么有些童鞋在写自定义view时总是出不来效果,因为我们需要设置一些默认值,当其他小伙伴使用你这个自定义控件时,不想手动配置属性值的情况。好吧,太啰嗦了。。。

然后就是测量 onMeasure(),这里为了简便就没重写onMeasure方法了,重写了onSizeChanged()方法,因为我们知道,此方法中得到的宽、高就是整个自定义view的最终宽、高。为什么这样说呢?因为有时候父控件会影响到我们子view的绘制,这时候就会调用onSizeChanged()方法,字面意思也很好理解,尺寸发生改变时执行。

@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);centerX = w / 2;centerY = h / 2;}

其中centerX、centerY 就是整个自定义view的中心点坐标

接下来,就到了我们的绘制了 onDraw()方法

按照文章开头的分析,首先,绘制正n边形,这时候那几个公式就派上用场了。注释的很清楚了,相信大家都能看懂

mRadarPaint.setStrokeWidth(dp2px(2));
float hudu = (float) (2 * Math.PI / pointCount);  // 弧度 = 度 * π / 180,有几个顶点就有几个角所以 (角度)angle * pointCount(总个数)=360°(一圈360度)
float distance = mRadius / radarNum;//每一层的间距
for(int i = 1;i <= radarNum;i++){//总共有几层网,中心点不用绘制float currentRadius = distance * i; //当前层所对应的间距(即:当前半径),由于每一层累加间距mPath.reset();for(int j = 1; j<=pointCount; j++){float x = (float) (Math.cos(hudu*j) * currentRadius + centerX);float y = (float) (Math.sin(hudu*j) * currentRadius + centerY);if(j == 1){mPath.moveTo(x, y);//起点坐标}else{mPath.lineTo(x, y);}}mPath.close();//绘制canvas.drawPath(mPath, mRadarPaint);//有多少层就画多少个
}

接着是绘制顶点到中心点的连线(注意:这里用的是mRadius,是每一层半径相加的总和,即总的半径)

mRadarPaint.setColor(Color.parseColor("#E2E2E2"));
mRadarPaint.setStrokeWidth(dp2px(2));
for(int i = 1; i<=pointCount;i++){float x = (float) (Math.cos(hudu*i) * mRadius + centerX);float y = (float) (Math.sin(hudu*i) * mRadius + centerY);//绘制连线canvas.drawLine(centerX, centerY, x, y, mRadarPaint);
}

然后是绘制n边形最外层上面的文字说明(这里需要注意一下,以为360°都分配的有文字,即文字遍布四个象限,不同象限的cosα, sinα正负值的变化影响文字的位置绘制。其次,Android中的象限与数学中的还不太一样,Android中第一象限(右下角)范围0-90度,推算得出0<弧度<π/2)

//画正n边形最外层上面的文字
for(int i = 1; i<=pointCount;i++){float y = (float) (Math.sin(hudu*i) * (mRadius + mDistance) + centerY);float x = (float) (Math.cos(hudu*i) * (mRadius + mDistance) + centerX);//绘制文字String text = mTextList.get(i-1);float textWidth = mTextPaint.measureText(text);Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();float textHeight = (fontMetrics.descent - fontMetrics.ascent)/2;//对于文字的绘制,在不同的象限则sinα 和 cosα 正负的影响  if(hudu*i >= 0 && hudu*i <= Math.PI / 2){//第一象限,右下角区域,可正常显示canvas.drawText(text, x, y + textHeight, mTextPaint);}else if(hudu*i > Math.PI / 2 && hudu*i <= Math.PI){//第二象限  左下角区域  需要进一步处理canvas.drawText(text, x - textWidth, y + textHeight, mTextPaint);}else if(hudu*i > Math.PI && hudu*i <= 3*Math.PI/2){//第三象限  左上角区域canvas.drawText(text, x - textWidth, y + textHeight/2, mTextPaint);}else if(hudu*i > 3*Math.PI/2 && hudu*i < 2*Math.PI){//第四项限  右上角区域  需要进一步处理canvas.drawText(text, x, y, mTextPaint);}else{//hudu*i = 2*Math.PI 的情况canvas.drawText(text, x, y + textHeight/2, mTextPaint);}
}

最后,根据传入的数据来绘制遮罩层

private void drawDatasOver(Canvas canvas, float hudu) {mPath.reset();//必须清空下,因为mPath被多处调用for (int k = 1; k <= mDatas.size(); k++){float value = mDatas.get(k - 1);//计算出每一个值占最大值的比例double percent = value / maxValue;float x = (float) (centerX + Math.cos(hudu*k)*mRadius*percent);//占总的半径的 mRadius*percentfloat y = (float) (centerY + Math.sin(hudu*k)*mRadius*percent);if(k == 1){mPath.moveTo(x, y);}else{mPath.lineTo(x, y);}}mPath.close();//画笔设置mRadarValuePaint.setStyle(Paint.Style.FILL);//设置透明度mRadarValuePaint.setAlpha(150);canvas.drawPath(mPath, mRadarValuePaint);}

需要特别说明一下,这里我们多次使用mPath来绘制不同的路径,所以每次绘制不同的模块时,要记得先清空下mPath.reset(),防止导致绘制错乱。你也可以根据不同的绘制模块创建不同的path

接下来,我们可以get/set一些方法,提供给外部调用、传递

//数据源
public List<Float> getmDatas() {return mDatas;}public void setmDatas(List<Float> mDatas) {this.mDatas = mDatas;
}/*** 设置最大值,类似进度条,当我们设置最大值为100时,表示进度加载完成*/
public float getMaxValue() {return maxValue;
}public void setMaxValue(float maxValue) {this.maxValue = maxValue;
}public List<String> getmTextList() {return mTextList;}public void setmTextList(List<String> mTextList) {this.mTextList = mTextList;//得到文字数据集合后,比较,获取文字宽度最长的文字(用作自定义view总宽度的取值)setTextDataSort();
}private void setTextDataSort() {//返回集合中最大长度的元素maxLengthText = Collections.max(mTextList, new Comparator<String>() {@Overridepublic int compare(String s, String t1) {return s.length() - t1.length();}});
}

最后就如何如使用了,分别看下activity_main布局文件和MainActivity

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.alone.radarview.MainActivity"><com.alone.radarview.MyRadarView
        android:id="@+id/radarView"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginLeft="60dp"android:layout_marginRight="60dp"app:radar_num="4"/>
</RelativeLayout>
public class MainActivity extends AppCompatActivity {private List<String> lists;//表示文字的集合private List<Float> mDatas;//数据private MyRadarView myRadarView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);myRadarView = (MyRadarView) findViewById(R.id.radarView);lists = new ArrayList<>();Collections.addAll(lists,"团战", "发育", "输出", "推进", "战绩(KDA)", "生存");myRadarView.setmTextList(lists);//设置数据  以及 最大值myRadarView.setMaxValue(100f);mDatas = new ArrayList<>();mDatas.add(30f);mDatas.add(70f);mDatas.add(20f);mDatas.add(50f);mDatas.add(80f);mDatas.add(100f);myRadarView.setmDatas(mDatas);}
}

主要的内容已经分析讲解完了,完整代码有时间再上传吧,其实这个demo还可以再炫酷点,比如,再加上一些滑动触发等事件以及动画,那么效果就更赞了,这里就留给你们去实现吧。使用过程中有什么问题或者有写的不正确的地方也欢迎提出。

点击下载源代码

仿王者荣耀对战资料选项中的【雷达网图展示详细数据】相关推荐

  1. Android 仿王者荣耀广告弹窗,android仿王者荣耀对战资料之能力图

    TX的王者荣耀最近是火的不得了啊,小编也是最近入坑.玩了几天之后,发现对战资料之能力图,蛮有意思(其实我想说,我们的产品妹子是个农药迷,非要把农药的能力图搬到我们的app中),于是就有了下面的自定义v ...

  2. 王者荣耀最低战力查询-王者战区修改

    王者荣耀最低战力查询-王者战区修改 应粉丝要求,今天我们来聊一聊,王者荣耀最低战力查询,以及王者战区修改的操作,让你快速获得荣耀战区称号. 当然,如果你有哪个实力的话,就完全不需要这个教程了​. 第一 ...

  3. 王者荣耀助手动态服务器维护中,王者荣耀助手动态怎么发不了 | 手游网游页游攻略大全...

    发布时间:2015-11-20 王者荣耀助手礼包怎么领取? 助手礼包领取地址是什么?下面来看看王者荣耀助手礼包怎么领取 助手礼包领取地址一览吧,希望能对大家有所帮助. 蚕豆网王者荣耀开黑群:43944 ...

  4. 王者荣耀s16服务器维护,王者荣耀S16战令更新了什么?S16战令系统更新一览

    王者荣耀随着S16赛季的更新上线,战令系统也做出了重置和调整,如新皮肤.新奖励规则等.那么,王者荣耀S16战令系统更新了什么?快来看看吧. (一) 精英版荣耀战令 精英版战令中的永久英雄与永久皮肤.头 ...

  5. 王者荣耀s15服务器维护,王者荣耀S15战令系统最新调整 经验等级重置 限定皮加入宝箱抽取...

    原标题:王者荣耀S15战令系统最新调整 经验等级重置 限定皮加入宝箱抽取 众所周知,S14已经逐渐接近了尾声,关于新赛季的筹划以及更新任务也在进行中,近日峡谷又传来了最新消息,同样是关于新赛季更新内容 ...

  6. python王者战斗_Python3 类与对象之王者荣耀对战小游戏

    王者荣耀对战小游戏 # 定义英雄: 亚瑟 class Arthur: hero_type = 'Tank' def __init__(self, attack_value=164, armor=98, ...

  7. 王者荣耀s17战令皮肤返场有哪些 返场皮肤名单详情

    王者荣耀s17战令皮肤马上就要返场了,这次返场皮肤有很多受欢迎的皮肤,那么,王者荣耀s17战令皮肤返场有哪些?下面来看看返场皮肤名单详情吧. 王者荣耀s17战令皮肤返场有哪些 返场皮肤名单详情 返场时 ...

  8. python面向对象实例王者荣耀_Python3 类与对象之王者荣耀对战小游戏

    王者荣耀对战小游戏 # 定义英雄: 亚瑟 class Arthur: hero_type = 'Tank' def __init__(self, attack_value=164, armor=98, ...

  9. 大白话总结类《王者荣耀》等MOBA游戏中的网络同步机制

    案例游戏: <英雄联盟> <王者荣耀>等PVP游戏 实际解决方案: 同步机制:不锁步的帧同步 网络:传输层的UDP配合应用层的可靠性检验 以下从问题出发讨论解决方案. 主要问题 ...

最新文章

  1. P5357 【模板】AC自动机(二次加强版)(AC自动机建fail树dfs求模式串出现次数)
  2. 用100元买100支笔c语言,用C编程!有100块钱,买100支笔,其中钢笔3元,圆珠笔2元,铅笔0.5元,问各买多少支?...
  3. Eclipse中Git的使用与Junit单元测试的编写
  4. 如何在MacOS上创建第一个iOS Flutter应用
  5. oracle 数据库bak文件怎么打开,Oracle数据库的参数文件备份与恢复
  6. 牛客网-华为-2020届校园招聘上机考试-软件类机考-1
  7. sample_venc解析
  8. 周记——20150817
  9. 利用lavarel框架实现Todos App
  10. 勒让德多项式的正交性和归一化
  11. 我的ubuntu论坛账号
  12. PCB需要清洗的技巧
  13. win10 开热点 【服务主机:网络服务】 疯狂跑流量的解决方案 亲测有效!!!!
  14. 大数据Java基础一
  15. 阿里巴巴二重身ABBC Coin虚涨逾100%
  16. HTML图片的路径问题
  17. 第15课:如何用RPA循环填写表单?(练兵场二)
  18. 笔记:GIT配置和命令
  19. “汉语”迟早要淘汰“英语”(精品转贴)
  20. wps怎么免费导出简历_求职简历怎么写 个人简历怎么写 简历怎么制作

热门文章

  1. 7d聚拉提的功效和原理,做完7d聚拉提三天后脸变大是怎么回事
  2. TogetherEC 6.3审计功能简单中文注解
  3. 晨风机器人成语接龙规则_搞了个微信机器人,求大家调戏!可以查天气,可以成语接龙……...
  4. layui弹出框confirm自定义样式
  5. 创业狼与打工狗-----是创业还是打工?
  6. 华为认证 HCIA-IoT V1.0 (物联网工程师)考试大纲
  7. vue中阻止事件冒泡
  8. platformer Microgame
  9. webuploader 文件上传
  10. 医院类软件开发公司有哪些北京