用Kotlin自定义一个"蜘蛛网图"

前言

趁着这几天比较清闲就了解了一下Kotlin,Google在2017年就已经推出Kotlin作为Android开发官方语言,它相比于java最大的优势就是简洁。其实在最初我是抗拒去学习Kotlin的,可能是忠于对java的一种情怀吧,但在初步了解了Kotlin之后我立即决定去学习这门语言,因为Kotlin也是基于JVM的一种语言,只是对java比较啰嗦的部位进行了一些改良。既然Kotlin的优势这么明显那为什么在国内的推广如此缓慢呢?我总结一下大概有两点,第一点:java用了这么多年突然又让我去学一门新语言,那之前的java不就是白学了嘛。第二点:Kotlin毕竟是一种语言,学习成本应该会很大。我负责任的告诉大家完全没必要有这种困惑,首先Kotlin是基于JVM的,目前很多特性只能用java代码实现的,二者是不冲突的,想成为一名优秀的Android工程师还是要在Java上费功夫的,我更愿意把Kotlin描述成一种工具。其次如果有Java基础学习Kotlin是非常快的,专心致志一个星期足矣。本篇文章我会基于Kotlin实现一个自定义网状图,也算是为国内Kotlin推广做一份微小贡献吧。

1. 自定义属性

首先我来晒出效果图供大家参考,如下图:


net.PNG

我们需要自定义的属性有:文字大小、文字颜色、蜘蛛网颜色、蜘蛛网边框宽度、属性区域背景颜色。

XML中定义

首先创建一个attrs文件,然后再在里面自定义这几个属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="net">
        <attr name="netColor" format="color" />
        <attr name="pathColor" format="color" />
        <attr name="textColor" format="color" />
        <attr name="netWidth" format="dimension" />
        <attr name="textSize" format="dimension" />
    </declare-styleable>
</resources>
View中获取
 var type = context.theme.obtainStyledAttributes(attrs,R.styleable.net,defStyleAttr,0)
        mNetColor = type.getColor(R.styleable.net_netColor,Color.GREEN)
        mPathColor = type.getColor(R.styleable.net_pathColor,Color.BLACK)
        mTextColor = type.getColor(R.styleable.net_textColor,Color.BLACK)
        mNetWidth = type.getDimension(R.styleable.net_netWidth,6f)
        mTextSize = type.getDimension(R.styleable.net_textSize,30f)
        type.recycle()

获取到属性设置到对应的画笔中即可

2. 宽高测量

因为对宽高没有特殊需求,所以直接通过View的resolveSize()方法进行测量即可,代码如下:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        mWidth = resolveSize(600,widthMeasureSpec)
        mHeight = resolveSize(400,heightMeasureSpec)
        setMeasuredDimension(mWidth,mHeight)
        radius = mWidth/3
        ...
        ...
    }

测量完宽高后设置六边形半径radius (顶点到原点的距离)

3. 图像绘制

绘图大概有三部分组成:蜘蛛网底图、属性区域、文字描述,本我们首先来看底图的绘制

3.1 底图绘制

我们看到底图是由五个六边形组成,首先我来跟大家捋一下绘制六边形的思路

  • 通过三角函数计算出6个点坐标
  • 通过path将6个点相连
  • 通过canvas绘制path
计算坐标点

因为是完全对称六边形,所以每个顶点与原点相连后形成的六条边与临边的夹角都是360/60=60度,所以确定了第一个点坐标其余五个点都可以通过三角函数计算得出,来看代码实现:

   private var mArrX = FloatArray(6)//记录外圈6个点x轴坐标
   private var mArrY = FloatArray(6)//记录外圈6个点y轴坐标
  ..
  ..
  //获取到6个点
  for (i in 0..5){
      mArrX[i] = (radius*Math.cos((Math.PI/3)*i).toFloat())
      mArrY[i] = (radius*Math.sin((Math.PI/3)*i)).toFloat()
  }

其中radius原点到一个顶点的距离,计算出坐标值后分别存入对应的数组中

绘制path

将六个顶点通过path进行连接然后绘制,实例中存在5个大小不同的六边形,所以要循环绘制五个逐渐缩小的六边形,代码如下

//绘制底图
    private fun drawBack(canvas: Canvas?){
        var path = Path()
        //循环绘制5个
        for (i in 1..5) {
            //绘制六边形
            for (j in 0..5) {
                if (j == 0) {
                    path.moveTo(mArrX[j]*i*0.2.toFloat(), mArrY[j]*i*0.2.toFloat())
                } else {
                    path.lineTo(mArrX[j]*i*0.2.toFloat(), mArrY[j]*i*0.2.toFloat())
                }
            }
            //闭合path
            path.close()
        }
        //当canvas不为null的时候执行let中代码
        canvas?.let { it.drawPath(path,mPaint) }
        //绘制直线
        for(i in 0..5){
            canvas?.let {it.drawLine(0f,0f,mArrX[i],mArrY[i],mPaint) }
        }
    }

绘制完5个六边形后再将每个顶点与原点相连接,效果如下图:


back.PNG

至此整个蜘蛛网的背景图就绘制完毕

3.2 绘制文字

我们要在六个顶点旁边绘制描述文字,在View中文字的绘制是在坐标的右上方进行的,所以绘制文字的坐标要在顶点坐标的延长处,然后将文字稍作平移即可,来看代码

//绘制文字
    private fun drawText(canvas: Canvas?){
        for (i in 0..5){
            //mList为bean类集合
            var content = mList[i].key
            val textBound = Rect()
            mPaint.getTextBounds(content, 0, content.length, textBound)
            canvas?.let{it.drawCircle(mArrX[i],mArrY[i],10f,mPathPaint)}
            //将文字中心平移至坐标处
            canvas?.let{ it.drawText(content,mArrX[i]*1.2f- textBound.width()-14
                    ,mArrY[i]*1.2f+ textBound.height() / 2+7,mTextPaint) }
        }
    }

效果如下图


point.PNG

文字中心的绿点即绘制文字的坐标,将顶点坐标*1.2即可。

3.3 属性区域绘制

属性区域和六边形绘制基本相同,首先根据提供的属性值计算出坐标位置,然后通过path将六个点进行连接,最后将画笔设置为Paint.Style.FILL即可填充整个区域,下面来看代码:

 //绘制属区域
    private fun drawValue(canvas: Canvas?){
        var path = Path()
        for (j in 0..5) {
            var value = mList[j].value
            if (j == 0) {
                path.moveTo(mArrX[j]*value*percentage, mArrY[j]*value*percentage)
            } else {
                path.lineTo(mArrX[j]*value*percentage, mArrY[j]*value*percentage)
            }
        }
        path.close()
        canvas?.let{it.drawPath(path,mPathPaint)}
    }

到这一步就可以绘制出文章开头亮出那个效果图,基本可以使用,但整个过程都是静态的,看着着实让人难受,所以我决定给它加个动画,往下看。。。

4. 施加动画

需求:将属性区域由小到大进行展示

我们可以通过自定义属性动画来实现这一需求,定义一个属性percentage并为其提供一个setter方法,用来记录属性动画进行进度,一定要在setter方法中执行postInvalidate()进行视图更新,否则动画无法进行。

 private var percentage:Float = 0f//用于属性动画
 fun setPercentage(percentage:Float){
        this.percentage = percentage
        postInvalidate()
   }

然后定义一个属性动画并与percentage关联,将属性值设置为0-1用时1秒

fun start(list:ArrayList<Bean>){
        this.mList = list
        var animator = ObjectAnimator.ofFloat(this,"percentage",0f,1f)
        animator.setDuration(1000)
                .start()
 }
Activity中代码
 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        var list = ArrayList<Bean>()
        list.add(Bean("投篮",0.6f))
        list.add(Bean("突破",0.9f))
        list.add(Bean("篮板",0.8f))
        list.add(Bean("助攻",0.9f))
        list.add(Bean("抢断",0.8f))
        list.add(Bean("盖帽",0.6f))
        netView.start(list)
    }

不用findViewById了,拿着xml中id直接用,不要太爽......来看一下最终效果图

anim.gif

嘿,你还别说,还挺像那回事~~~ Demo已托管至Github,需要的朋友可以去下载。。

总结

这是我发布的第一篇关于Kotlin的文章,所以就选了一个比较简单的自定义View作为案例,由于逻辑过于简单导致Kotlin中Lambda表达式以及一些高阶函数都用到,所以Kotlin 的简洁体现的也不是很充分。最后我想声明一点,今天是我学习Kotlin的第四天,没错就是第四天我已经可以常规使用Kotlin了,所以Kotlin并不难,希望大家有时间一定要好好的学一遍,它能很大程度的提高你的开发效率。

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

推荐阅读更多精彩内容