这个系列是老外写的,干货!翻译出来一起学习。如有不妥,不吝赐教!

  1. Android自定义视图一:扩展现有的视图,添加新的XML属性
  2. Android自定义视图二:如何绘制内容
  3. Android自定义视图三:给自定义视图添加“流畅”的动画
  4. Android自定义视图四:定制onMeasure强制显示为方形

有的时候自持扩展一个标准的Android视图是不够的。你需要在视图上绘制你自己的内容才行。本文将会讲述如何使用Canvas类来绘制一个折线图,并会讲述如何处理尺寸和padding。

如果你还没有准备好的话,你可能需要阅读这个系列的前篇。

绘制第一个像素

如果你打算在自定义视图绘制自己的内容的话,最好的办法是继承基类ViewView是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)    }}

布局:

    

为了一个直观的第一映像,代码全部贴出来,布局贴主要部分。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", 绘制结果如下所示:

Android自定义视图二:如何绘制内容_第1张图片

虽然有些情况不一定需要自定义视图支持padding。但是个人建议还是在一开始就把padding考虑进来,即使你觉得不需要padding。给一个有很多子view的自定义视图添加padding支持非常棘手,但是如果从一开始就考虑padding的话事情会容易很多。

绘制折线图

一开始就说要绘制一个折线图,那么现在我们正式着手开始绘制。

第一步,我们需要绘制用的数据。

private var _points: List? = nullvar points: List    get() = if (_points == null) listOf() else _points!!    set(value) {        _points = value    }

在Kotlin里给属性增加getter和setter要简单很多。private var _points: List? = 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}
  1. 把y值映射到视图的坐标系中。
  2. 反转,如上文所述:视图的坐标系和用户看到的坐标系的y轴方向是反的。
  3. 加上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()方法。

运行结果:


Android自定义视图二:如何绘制内容_第2张图片

添加细节

首先要做的就是添加抗锯齿。开启这个功能之后,图像看起来更加顺滑。抗锯齿可以这样开启:

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的更多资料。

下一篇,我们要给折线图添加动画。

更多相关文章

  1. 用fastboot大刷Android ~换个方法刷android手机
  2. [Android]ScrollView和ListView套用冲突的解决方法
  3. Android页面去掉标题全屏的方法-第一次用安卓试试看
  4. android 中使文本(TextView 、button等可参考)在屏幕正中心显示的
  5. android屏幕自适应4方法案例整合
  6. 打开SDK Manager检查Android SDK下载和更新失败的解决方法
  7. android studio 3.6.0 绑定视图新特性的方法
  8. androidSDK无法更新的解决方法之一
  9. android全屏去掉title栏的多种实现方法

随机推荐

  1. PHP设计模式(创建型)
  2. php无乱码切割中文字符
  3. 树状数据结构存储方式(CUD 篇)
  4. 超详细分析php docker的原理及作用
  5. 基于 PHP-Casbin 的 ABAC 权限控制
  6. 在 PHP 中格式化并高亮 SQL 语句
  7. QueryPHP V1-beta.5 改进 ORM 设计体验
  8. 定位分析内存泄漏的原因和后果
  9. 树状数据结构存储方式(查询篇)
  10. php重定向后跳转不了