(自定义控件)RaderView

图1:预期效果图

先上一张效果图,该图为我制作用于展示游戏中,玩家的各项能力数值的一张雷达图。

对于自定义控件你得需要有以下知识储备:(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部分的绘制请各位进行一个简单的思考。

四.动画效果

属性动画部分的有关问题请参考郭林写的

Android属性动画完全解析

动画效果则是用属性动画实现的一个非常简单的匀速扩散的效果。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部分有了一定理解。个人认为在图形的形状部分,算是比较简单的,唯一可能麻烦的应该是长宽的计算和图像的绘制。

写完这个控件后,我认为我只是知道了皮毛。

学无止境,共勉。如果有什么地方有问题、或者可以改进,希望可以共同探讨。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容