(自定义控件)RaderView
先上一张效果图,该图为我制作用于展示游戏中,玩家的各项能力数值的一张雷达图。
对于自定义控件你得需要有以下知识储备:(RaderView涉及到的只是点较为基础)
1.自定义属性
2.onMeasure/onDraw
鸿洋大神有一篇对自定义View介绍的的比较详细的文章,附链接。
http://blog.csdn.net/lmj623565791/article/details/24252901/
本文将结合上文的基础知识,进行实践性地实现一个满足特殊需求的自定义View。本文将从正常流程来编写RaderView,包括碰到的许多问题及思考。我将尽力为您展现一个比较完整的自定义View的实现过程。
一.自定义属性
结合我们预期的效果图,这算是一个比较简单的自定义View,图中只有两个属性是我们需要关注的。
1.图中的文字大小
2.由中心点到某一个点的线段的长度
综合以上两点,在value资源文件下添加attrs.xml文件,并添加如下属性。
自定义属性的格式为:名称name,格式format(string,color,demension,integer,enum,reference,float,boolean,fraction,flag)
<resources>
<attr name="diameter" format="dimension"></attr>
<attr name="textsize" format="dimension"></attr>
<declare-styleable name="radar_view">
<attr name="diameter"/>
<attr name="textsize"/>
</declare-styleable>
</resources>
以上则是对属性的定义部分。在RaderView的构造方法中获取我们已经定义好的属性:
privateint diameter;
privateint textsize;
public RadarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
……
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.radar_view, defStyleAttr, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.radar_view_diameter:
diameter = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
case R.styleable.radar_view_textsize:
textsize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
}
}……
a.recycle();
}
需要注意的是,TypedValue.applyDimension方法用于将sp转换px,属性a记得recycle,上述代码记得写到构造器中。
二.控件的长宽测量
顾名思义,这里要实现复写onMeasure方法。先上一部分代码(这里为了节省版面,只上宽度的测量过程)
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
if (getWidth() / 2 > centerX) {
centerX = getWidth() / 2;
} else {
centerX = diameter + getPaddingLeft();
}
} else {
float mWidth = diameter;
width = desired * 2;
centerX = width / 2;
}
width=width+4*textsize;
if (width / 2 < diameter) {
scaleX = width / 2 * 1.0f / diameter;
}
if (scaleX < scaleY) {
scaleY = scaleX;
} else {
scaleX = scaleY;
}
//由于左边text导致中心右移
centerX = centerX + 2 * textsize;
setMeasuredDimension(width, height);
那么上面的代码即为比较完整的,测量宽度的代码。下面将提出几个我在实现过程中遇到的问题,以及几个变量代表的含义。
MeasureSpec.EXACTLY:当宽度设置为精确值或者MATCH_PARENT
MeasureSpec.AT_MOST:当宽度设置为WRAP_CONTENT
按照我的构思以及预期的图,我直接将width的值设置为widthSize+padding(左右),但是当绘制时,无法将左边和右边的字(生存、伤害)展示完全,所以应该加上4*textsize的宽度,最终的结果才是这个控件整体的宽度。这里还有一个地方要注意的是,我在这里 加上 4*textsize的做法是十分不建议的,考虑到拓展性,我们应该可以自己设置左右的字体,那么当X轴上添加的字体不为4的时候,就会出现问题。因此,在这应该添加记录字体个数的变量,例如mTextCount,最终宽度加上mTextCount * textSize就好啦~
diamater:雷达图中心到某个定点的边长
centerX:记录中心点,用于从中心绘制雷达图的background,很好理解。
scaleX:缩放的比例。在宽度的测量中,经常会遇到长和宽设置的比较不搭配(高大于宽,或索性直接设置为MATCH_PARENT),这个时候就需要进行缩放。可以看出,当widthMode为MeasureSpec.EXACTLY时,说明我们为这个控件设置了具体的宽度或者为MATCH_PARENT(控件宽度并不是diameter)。那么当diameter(半径)>width/2时,就需要进行缩放。
当程序进行到这里的时候,我发现当我设置width和height相同的时候,并且都需要进行0.5倍缩放时,我的图是这样的:
图2为什么会出现这种情况呢?答案是Y轴上需要*根号3/2(这是为什么呢??嘿嘿嘿)
其实这种情况不止要出现在需要缩放的情况下,只是设计进行到缩放这一步,会出现这个问题,原因就是纵轴上的高度,并不是我们设置的diameter,因为diameter被掰弯了。。。因此这个缩放比例很有必要。
如果将图片展示完全,那么就需要取scaleX和scaleY中比较小得值。
至此,onMeasure中的方法介绍完毕。
三:绘制
前面介绍的诸多变量都是为了给绘制打基础。即onDraw函数的实现
看到预期效果图中有三部分需要绘制
1.背景
2.透明的雷达填充部分
3.文字
首先背景的绘制,主要是骨架部分的绘制,这部分可以根据自己的需求绘制。没有难点,唯一比较复杂的就是计算添加交点的比例,和绘制的过程。
Hexagon记录了6个点的坐标,通过draw6point方法将其按照相邻顺序连线。
private void initBackground(Canvas canvas) {
canvas.scale(scaleX, scaleY);
for (int i = 1; i <= 4; i++) {
dia = diameter / 4 * i;
sqrt3diameter = (int) (Math.sqrt(3) * dia / 2);
halfDiameter = dia / 2;
Hexagon hexagon = new Hexagon(centerX + halfDiameter, centerY + sqrt3diameter, centerX + dia, centerY, centerX + halfDiameter, centerY - sqrt3diameter,
centerX - halfDiameter, centerY - sqrt3diameter, centerX - dia, centerY, centerX - halfDiameter, centerY + sqrt3diameter);
draw6point(canvas, hexagon);
}
}
这里我的绘制方法是从中点到外圈逐圈绘制。
半透明的雷达填充部分方法如上,只是hexagon类中存放的是能力值终点的值。
文字更简单啦,调用canvas的drawText方法就可以了。
2,3部分的绘制请各位进行一个简单的思考。
四.动画效果
属性动画部分的有关问题请参考郭林写的
动画效果则是用属性动画实现的一个非常简单的匀速扩散的效果。Evaluator如下
public class RadarEvaluator implements TypeEvaluator<Hexagon> {
public Hexagon evaluate(float fraction, Hexagon startValue, Hexagon endValue) {
int x1 = (int) (startValue.getX1() + fraction * (endValue.getX1() - startValue.getX1()));
int y1 = (int) (startValue.getY1() + fraction * (endValue.getY1() - startValue.getY1()));
……
return new Hexagon(x1, y1, x2, y2, x3, y3, x4, y4, x5, y5, x6, y6);
}
}
五.总结
我做完这个控件后,对自定义View中onMeasure和onDraw部分有了一定理解。个人认为在图形的形状部分,算是比较简单的,唯一可能麻烦的应该是长宽的计算和图像的绘制。
写完这个控件后,我认为我只是知道了皮毛。
学无止境,共勉。如果有什么地方有问题、或者可以改进,希望可以共同探讨。