Android自定义视图二:如何绘制内容

简介:

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

  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)
    }
}

布局:

<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
}
  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()方法。

运行结果:

添加细节

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

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,我们可以修改填充方式、颜色、填充宽度等各种效果。官方文档中有关于CanvasPaint的更多资料。

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
















本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/p/5460023.html,如需转载请自行联系原作者


相关文章
|
3月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
67 1
|
3月前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
4月前
|
Android开发 开发者
安卓应用开发中的自定义视图
【9月更文挑战第37天】在安卓开发的海洋中,自定义视图犹如一座座小岛,等待着勇敢的探索者去发现其独特之处。本文将带领你踏上这段旅程,从浅滩走向深海,逐步揭开自定义视图的神秘面纱。
58 3
|
4月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
157 0
|
2月前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
81 19
|
2月前
|
XML 搜索推荐 前端开发
安卓开发中的自定义视图:打造个性化UI组件
在安卓应用开发中,自定义视图是一种强大的工具,它允许开发者创造独一无二的用户界面元素,从而提升应用的外观和用户体验。本文将通过一个简单的自定义视图示例,引导你了解如何在安卓项目中实现自定义组件,并探讨其背后的技术原理。我们将从基础的View类讲起,逐步深入到绘图、事件处理以及性能优化等方面。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
3月前
|
搜索推荐 前端开发 Android开发
安卓开发中的自定义视图——打造个性化用户界面
在安卓应用开发的广阔天地里,自定义视图是实现个性化界面设计的重要手段。通过深入理解安卓绘图基础、触摸事件处理和布局机制,开发者可以突破标准控件的限制,创造出独一无二的用户体验。本文将引导你探索自定义视图的核心概念,提供实用的代码示例,并分享如何有效地解决开发过程中可能遇到的挑战。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往创新和个性化的大门。
|
3月前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
3月前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
71 5
|
4月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。

热门文章

最新文章

  • 1
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 2
    Android历史版本与APK文件结构
  • 3
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
  • 4
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 5
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
  • 6
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 7
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 8
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 9
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
  • 10
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 1
    即时通讯安全篇(一):正确地理解和使用Android端加密算法
    21
  • 2
    escrcpy:【技术党必看】Android开发,Escrcpy 让你无线投屏新体验!图形界面掌控 Android,30-120fps 超流畅!🔥
    37
  • 3
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
    107
  • 4
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
    37
  • 5
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    54
  • 6
    Android历史版本与APK文件结构
    145
  • 7
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    46
  • 8
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    39
  • 9
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    66
  • 10
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    46