美文网首页Android开发
(自定义控件)RaderView

(自定义控件)RaderView

作者: Kevin老师不迟到 | 来源:发表于2016-07-26 19:47 被阅读0次
    图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部分有了一定理解。个人认为在图形的形状部分,算是比较简单的,唯一可能麻烦的应该是长宽的计算和图像的绘制。

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

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

    相关文章

      网友评论

        本文标题:(自定义控件)RaderView

        本文链接:https://www.haomeiwen.com/subject/vgcgjttx.html