Android自定义视图二:如何绘制内容
这个系列是老外写的,干货!翻译出来一起学习。如有不妥,不吝赐教!
- Android自定义视图一:扩展现有的视图,添加新的XML属性
- Android自定义视图二:如何绘制内容
- Android自定义视图三:给自定义视图添加“流畅”的动画
- Android自定义视图四:定制onMeasure强制显示为方形
有的时候自持扩展一个标准的Android视图是不够的。你需要在视图上绘制你自己的内容才行。本文将会讲述如何使用Canvas类来绘制一个折线图,并会讲述如何处理尺寸和padding。
如果你还没有准备好的话,你可能需要阅读这个系列的前篇。
绘制第一个像素
如果你打算在自定义视图绘制自己的内容的话,最好的办法是继承基类View。View是UI绘制的最小单元,同时各种功能齐备。所以我们从继承View开始。
要画出第一个项目,只需要override方法onDraw()。在这个方法里我们可以获得一个canvas(画布),绘制就在这个canvas上进行。没有必要调用超类的onDraw()实现,因为其实并没有什么实现。在View中这个方法是空的。
class LineChartView : View {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attributeSet: AttributeSet) : super(ctx, attributeSet) {
}
constructor(ctx: Context, attributeSet: AttributeSet, defStyle: Int) : super(ctx, attributeSet, defStyle) {
}
override fun onDraw(canvas: Canvas) {
val p = Paint()
p.style = Paint.Style.STROKE
p.color = resources.getColor(android.R.color.holo_orange_dark) // 0xFF33B5E5.toInt()
p.strokeWidth = 4f
canvas.drawLine(0f, 0f, width.toFloat(), height.toFloat(), p)
}
}
布局:
<RelativeLayout>
<demo.customview.customviewdemo.Views.LineChartView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp" />
</RelativeLayout>
为了一个直观的第一映像,代码全部贴出来,布局贴主要部分。Constructor部分可以直接忽略不计。这个自定义视图会在屏幕上以(0,0)为起点,以这个视图的宽和高(width,height)为终点绘制一条桔色的线。Paint类用来控制如何绘制。paint对象可以实现很多酷炫的效果,不过这里只用来绘制一条线。
注意:绘制的坐标系是这个自定义视图。左上角为(0,0)点,也就是坐标原点。x轴向右为正值,y轴向下为正值。
添加padding
我们绘制出了第一条线,这是一个很好的开始。但是,有的时候需要在内容的展示上需要留白,也就是需要设置padding。如果在布局中给刚刚创建的视图设置padding,你会发现没有什么效果。
那是因为在绘制的时候我们并没有把padding值计算在内,而padding值是包含在视图的宽度和高度之内的。如果视图的宽度是100像素,两边的padding是10像素那么在一个宽度上可用的值是80像素。getWidth()方法返回的是整个视图的宽度。padding的值可以用方法getPaddingWidth()来获得。当视图设置了padding后,正确的绘制方法请看下面的代码:
override fun onDraw(canvas: Canvas) {
val p = Paint()
p.style = Paint.Style.STROKE
p.color = resources.getColor(android.R.color.holo_orange_dark)
p.strokeWidth = 4f
val left = paddingLeft
val top = paddingTop
val right = width - paddingRight
val bottom = height - paddingBottom
// canvas.drawLine(0f, 0f, width.toFloat(), height.toFloat(), p)
canvas.drawLine(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), p)
}
在布局中给这个视图的padding值设置为android:padding="20dp", 绘制结果如下所示:

虽然有些情况不一定需要自定义视图支持padding。但是个人建议还是在一开始就把padding考虑进来,即使你觉得不需要padding。给一个有很多子view的自定义视图添加padding支持非常棘手,但是如果从一开始就考虑padding的话事情会容易很多。
绘制折线图
一开始就说要绘制一个折线图,那么现在我们正式着手开始绘制。
第一步,我们需要绘制用的数据。
private var _points: List<Float>? = null
var points: List<Float>
get() = if (_points == null) listOf<Float>() else _points!!
set(value) {
_points = value
}
在Kotlin里给属性增加getter和setter要简单很多。private var _points: List<Float>? = null指定了一个back field,用来存放赋值进来的数据。在返回的时候,如果_points为空则返回一个空的数组,否则返回原数组。在java里需要手动写一个setter方法。
有了points属性,用户就可以给我们的自定义视图添加绘制需要的点数据。这里的points属性的值是二维平面的y值。在二维平面绘制总是需要两个坐标来定位绘制的点。这里假设x轴的坐标值都是视图的宽度平分得到的,实际上也确实是这样,那么我们就可以把给定的points的index作为x值来使用。
下面我们来研究一下如何把给定的点映射到视图的坐标系内。
fun getYPos(yValue: Float, maxValue: Float): Float {
var drawHeight = height - paddingTop - paddingBottom
var drawYValue = (yValue / maxValue) * drawHeight // 1
drawYValue = drawHeight - drawYValue // 2
drawYValue += paddingTop // 3
return drawYValue
}
- 把y值映射到视图的坐标系中。
- 反转,如上文所述:视图的坐标系和用户看到的坐标系的y轴方向是反的。
- 加上padding的offset值
现在我们已经可以绘制折线图了。我们可以通过在两点之间连线的方式来绘制折线图,但是还有一个更好的办法。使用Path。现在看来不会有太大的不同,不过随着后面代码的深入你会发现大有不同。现在的onDraw方法看起来是这样的:
override fun onDraw(canvas: Canvas?) {
var maxValue = getMax(this.points)
var path = Path()
path.moveTo(getXPos(0), getYPos(this.points[0], maxValue))
for (i: Int in 1..(points.count() - 1)) {
path.lineTo(getXPos(i), getYPos(points[i], maxValue))
}
var paint = Paint()
paint.style = Paint.Style.STROKE
paint.strokeWidth = 4f
paint.color = resources.getColor(android.R.color.holo_orange_dark)
canvas?.drawPath(path, paint)
}
前面我们介绍了getYPos()方法,上面的代码中还用到了一个类似的getXPos()方法。这个方法就是按照points数组的元素个数平分X轴,确定X轴的单元宽度有多少,并返回每一个index对应的X轴的值。
第二部分基本上和前面的绘制方法一样,只不过这里使用了drawPath()方法而不是drawLine()方法。
运行结果:

添加细节
首先要做的就是添加抗锯齿。开启这个功能之后,图像看起来更加顺滑。抗锯齿可以这样开启:
paint.isAntiAlias = true
另一个可以让图像看起来效果更好的功能是给线图添加阴影:
paint.setShadowLayer(4f, 2f, 2f, resources.getColor(android.R.color.darker_gray))
现在是用paint对象绘制的所有东西都会添加一个阴影。不过,在上面这行代码智商还需要添加一点佐料,否则的话上面的代码会不听使唤。在方法的最后一个参数中指定的颜色不会起作用。添加如下代码:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(LAYER_TYPE_SOFTWARE, paint)
}
setShadowLayer()这个方法需要硬件加速关闭,但是在HONEYCOMB和以上版本中默认这个功能是开启的。所以我们要让他在LAYER_TYPE_SOFTWARE条件下也能工作。
最后回到setShadowLayer()方法,第一个参数是模糊半径,值越大模糊的半径就越大,同时颜色就越模糊。就像一瓶墨水倒进多少水里一样,谁越多,颜色越浅,就是这个道理。第二、三个参数是指定了阴影向右移2像素,向下移2像素。最后一个参数指定颜色。
下面在背景添加横向的线:
private fun drawBackground(canvas: Canvas) {
var maxValue = getMax(points)
var range = getLineDistance(maxValue)
paint.style = Paint.Style.STROKE
paint.strokeWidth = 2f
paint.color = resources.getColor(android.R.color.background_light)
for (i: Int in 0..maxValue.toInt() - 1 step range) {
var yPos = getYPos(i.toFloat(), maxValue)
canvas.drawLine(0f, yPos, width.toFloat(), yPos, paint)
}
}
这样看起来就更像一个图表了。
从这里开始,我们的折线图可以添加更多的东西了。这样这个自定义的折线图就会更加的美观。在Canvas上,可以绘制线、路径,长方形和椭圆等。使用Path,我们可以修改填充方式、颜色、填充宽度等各种效果。官方文档中有关于Canvas和Paint的更多资料。
下一篇,我们要给折线图添加动画。
Android自定义视图二:如何绘制内容的更多相关文章
- Android自定义视图四:定制onMeasure强制显示为方形
这个系列是老外写的,干货!翻译出来一起学习.如有不妥,不吝赐教! Android自定义视图一:扩展现有的视图,添加新的XML属性 Android自定义视图二:如何绘制内容 Android自定义视图三: ...
- Android自定义视图三:给自定义视图添加“流畅”的动画
这个系列是老外写的,干货!翻译出来一起学习.如有不妥,不吝赐教! Android自定义视图一:扩展现有的视图,添加新的XML属性 Android自定义视图二:如何绘制内容 Android自定义视图三: ...
- Android自定义视图一:扩展现有的视图,添加新的XML属性
这个系列是老外写的,干货!翻译出来一起学习.如有不妥,不吝赐教! Android自定义视图一:扩展现有的视图,添加新的XML属性 Android自定义视图二:如何绘制内容 Android自定义视图三: ...
- 【转】ANDROID自定义视图——onLayout源码 流程 思路详解
转载(http://blog.csdn.net/a396901990) 简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量——onMeasure():决定View的大小 2.布局 ...
- 【转】ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解
原文地址:http://blog.csdn.net/a396901990/article/details/36475213 简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量—— ...
- ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解
简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量--onMeasure():决定View的大小 2.布局--onLayout():决定View在ViewGroup中的位置 3. ...
- Android自定义视图教程
Android自定义视图教程 Android的UI元素都是基于View(屏幕中单个元素)和ViewGroup(元素的集合),Android有许多自带的组件和布局,比如Button.TextView.R ...
- Android自定义视图
Android框架为我们提供了大量的视图类来帮助我们做好展示信息以及同用户进行交互的工作.然后有时候,我们的app或许需要一些在Android内建视图之外特殊的视图,那么此时我们就需要自定义视图.下面 ...
- android 学习随笔二十一(内容提供者 )
一.内容提供者* 应用的数据库是不允许其他应用访问的* 内容提供者的作用就是让别的应用访问到你的私有数据* 自定义内容提供者,继承ContentProvider类,重写增删改查方法,在方法中写增删改查 ...
随机推荐
- Couchbase学习和使用
Couchbase介绍 couchbase的关键有两点:延后写入和松散存储.延后写入,顾名思义,couchbase在对数据进行增删时会先体现在内存中,而不会立刻体现在硬盘上,从内存的修改到硬盘的修改这 ...
- JFinal Web开发学习(八)后台集成H-ui-admin前端框架
h-ui-admin是一个很不错的前端框架h-ui实现的一个后台管理系统的前端. 1.在WebRoot目录下新建admin目录 2.下载h-ui-admin(当前最新是2.5版本)并解压至admin文 ...
- C#中DllImport用法
http://blog.csdn.net/u011981242/article/details/52622923 http://www.jb51.net/article/46384.htm 读取身份证 ...
- asp.net core webapi 似乎未安装在 IIS 中承载 .NET Core 项目所需的 AspNetCoreModule。请尝试修复 Visual Studio 以纠正该问题。
安装 DotNetCore.1.1.0-WindowsHosting 后,提示如题错误. 解决办法: ASP.NET Core 应用程序运行,可以选择 IIS Express 也可以选择 自己运行 , ...
- JS高级:事件冒泡和事件捕获;
1.事件:浏览器客户端上客户触发的行为成为时事件:所有的事件都是天生自带的,不需要我们去绑定,只需要我们去触发 当用户触发一个事件时,浏览器的所有详细信息都存在一个叫做event的对象上,我们把它叫做 ...
- istio-jaeger-python调用链配置
虽然,istio ingress controller已经生成了jaeger 记录所需要的信息,但是多个分布式之间没法清晰记录相互之间的依赖关系.所以相关的项目还需要加入特殊配置. 如:python- ...
- Maven 系列 一 :Maven 快速入门及简单使用
开发环境 MyEclipse 2014 JDK 1.8 Maven 3.2.1 1.什么是Maven? Maven是一个项目管理工具,主要用于项目构建,依赖管理,项目信息管理. 2.下载及安装 下载最 ...
- 文档根元素 "mapper" 必须匹配 DOCTYPE 根 "configuration"
该问题是因为xml的头部写错了,一个是configuration,一个是mapper,不能直接复制. 参考链接:http://blog.csdn.net/testcs_dn/article/detai ...
- 20172306《java程序设计与数据结构》第六周学习总结
20172306<Java程序设计>第六周学习总结 教材学习内容总结 第八章关键学习了数组的相关内容.我觉得主要分一下几点: 1.索引是从0开始,要区分好索引值和个数值.0的索引处是第一个 ...
- merge_关系
create(olive:Person{chau:"Bill White",name:"Olive Stone",bornin:"New York&q ...