前言
趁着这几天比较清闲就了解了一下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. 自定义属性
首先我来晒出效果图供大家参考,如下图:
我们需要自定义的属性有:文字大小、文字颜色、蜘蛛网颜色、蜘蛛网边框宽度、属性区域背景颜色。
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个六边形后再将每个顶点与原点相连接,效果如下图:
至此整个蜘蛛网的背景图就绘制完毕
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) }
}
}
效果如下图
文字中心的绿点即绘制文字的坐标,将顶点坐标*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直接用,不要太爽......来看一下最终效果图
嘿,你还别说,还挺像那回事~~~ Demo已托管至Github,需要的朋友可以去下载。。
总结
这是我发布的第一篇关于Kotlin的文章,所以就选了一个比较简单的自定义View作为案例,由于逻辑过于简单导致Kotlin中Lambda表达式以及一些高阶函数都用到,所以Kotlin 的简洁体现的也不是很充分。最后我想声明一点,今天是我学习Kotlin的第四天,没错就是第四天我已经可以常规使用Kotlin了,所以Kotlin并不难,希望大家有时间一定要好好的学一遍,它能很大程度的提高你的开发效率。