android 自定义view: 矩形图表
Posted 史大拿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android 自定义view: 矩形图表相关的知识,希望对你有一定的参考价值。
本系列自定义View全部采用kt
系统mac
android studio: 4.1.3
kotlin version1.5.0
gradle: gradle-6.5-bin.zip
本篇效果:
tips: 本篇是在上一篇的基础上来绘制的,背景表格,和左侧文字都是上一篇的东西, 如果不清楚可以先学习上一篇!
绘制网格和文字
这是上一篇的代码,我把它封装成了一下,如果需要请下载看看吧 !
class E2RectChartBlogView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : BaseChartView(context, attrs, defStyleAttr)
init
// 初始化数据
originList = arrayListOf(
148, 120, 21, 60, 10,
70, 80, 100, 222, 74,
)
override fun onDraw(canvas: Canvas)
canvas.scale(0.8f, 0.8f, width / 2f, width / 2f)
// 绘制网格
super.drawGrid(canvas)
// 绘制文字
super.drawLeftText(canvas)
绘制矩形
首先绘制矩形,先得确定要绘制到什么地方:
如图:
我们需要将矩形绘制到每一格的正中心位置,
假设我们需要绘制矩形高度为 90
我们知道
-
每一格的宽度 eachWidth
-
需要绘制的矩形宽度(rectWidth) = eachWidth * 0.6
-
每一格的矩形高度 = View#height / 数组最大值
-
对应的高度 = 每一个的矩形高度 * 90
那么最终矩形的坐标为
- left = (eachWidth / 2f ) - (eachWidth * 0.6) / 2f
- right = left + rectWidth
- top = 每一个的矩形高度 * 高度对应的值
- bottom = height
来看看代码:
// 每一格矩形的宽
private val eachWidth: Float
get()
return width / verticalCount.toFloat()
override fun onDraw(canvas: Canvas)
// 每一格的矩形宽度
val eachWidth = eachWidth
// 矩形的宽度
val rectWidth = eachWidth * 0.6f
// 计算出每一格的高度
val eachGrid = height.toFloat() / originMax
// 绘制矩形
drawRect(canvas, eachWidth, rectWidth, eachGrid)
/*
* 作者:史大拿
* 创建时间: 9/26/22 3:28 PM
* @param eachWidth: 每一格的矩形宽度
* @param rectWidth: 每一个矩形的宽
* @param eachGridHeight: 每一格矩形的高度
*/
private fun drawRect(
canvas: Canvas,
eachWidth: Float,
rectWidth: Float,
eachGridHeight: Float
)
var tempLeft = eachWidth / 2f - rectWidth / 2f
originList.forEach
val left = tempLeft
val top = height - eachGridHeight * it
val right = left + rectWidth
val bottom = height.toFloat()
canvas.drawRoundRect(left, top, right, bottom, 0f, 0f, paint)
tempLeft += eachWidth
可以看出,绘制成功了,但是绘制出表格外面了,这种情况是不允许的,
和上一篇的套路一样,只保留屏幕内的数据(裁剪一下)
override fun onDraw(canvas: Canvas)
canvas.withSave
// 裁剪
canvas.clipRect(0, 0, width, height)
// 绘制矩形
drawRect(canvas, eachWidth, rectWidth, eachGrid)
手势监听
手势监听还是用到了 GestureDetectorCompat
如果手势监听使用有点模糊 建议看这一篇 android 图解 PhotoView,从‘百草园’到‘三味书屋’!
首先先让他滚动起来
private var offsetX = 0f
private var downX = 0f
private var originX = 0f
private inner class E2RectGesture : GestureDetector.OnGestureListener
override fun onDown(event: MotionEvent): Boolean
// 记录按下位置
downX = event.x
// 记录上一次偏移量
originX = offsetX
return true
override fun onScroll(
e1: MotionEvent,// down按下的位置
event: MotionEvent, // 当前滑动的事件
distanceX: Float, // event - e1 的X距离
distanceY: Float // event - e2 的Y距离
): Boolean
when (event.action)
MotionEvent.ACTION_MOVE ->
// 当前偏移位置 = 当前位置 - 按压位置 + 原始偏移量
offsetX = event.x - downX + originX
// 修复X轴距离 保证offsetX 不会滑出屏幕
repairOffSetX()
invalidate()
return false
....
private fun repairOffSetX()
// 限制滑动距离
if (offsetX > 0)
offsetX = 0f
if (offsetX <= -(originList.size * eachWidth - width))
offsetX = -(originList.size * eachWidth - width)
使用GestureDetectorCompat
// 手势监听
private val gestureDetectorCompat = GestureDetectorCompat(context, E2RectBlogGesture())
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean
gestureDetectorCompat.onTouchEvent(event)
return true
记得设置offsetX 用来滑动
/*
* 作者:史大拿
* 创建时间: 9/26/22 3:28 PM
* @param eachWidth: 每一格的矩形宽度
* @param rectWidth: 每一个矩形的宽
* @param eachGridHeight: 每一格矩形的高度
*/
private fun drawRect(
canvas: Canvas,
eachWidth: Float,
rectWidth: Float,
eachGridHeight: Float
)
var tempLeft = eachWidth / 2f - rectWidth / 2f
originList.forEach
val left = tempLeft + offsetX // TODO 设置偏移量
val top = height - eachGridHeight * it
val right = left + rectWidth
val bottom = height.toFloat()
/// 绘制矩形
....
设置fling事件
因为只可以左右滑动,所以只需要关心x轴即可
fling事件还是用到了OverScroller
private inner class E2RectGesture : GestureDetector.OnGestureListener
...
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean
val startX = offsetX.toInt() // 只需要关心x轴即可
val startY = 0
val vX = velocityX.toInt()
val vY = 0
val minX = -(originList.size * eachWidth - width).toInt()
val maxX = 0
val minY = 0
val maxY = 0
overScroll.fling(
startX, startY,
vX, vY,
minX, maxX,
minY, maxY,
0, 0
)
postOnAnimation(this@E2RectChartView)
return true
override fun run()
// 计算偏移量 返回是否完成
val isOver = overScroll.computeScrollOffset()
if (isOver)
offsetX = overScroll.currX.toFloat()
repairOffSetX() // 修复X轴距离
invalidate()
postOnAnimation(this) //
这里代码也比较简单,就不过多赘述了.
设置动画
动画其实就是在2秒内一直的变换高度即可
变换进度从0到1变换
只需要高度 * 变换进度即可
先来定义一个动画
private var currentFraction = 0f
private val admissionAnimation by lazy
val animator = ObjectAnimator.ofFloat(0f, 1f)
animator.duration = 2000 // 动画时间
animator.addUpdateListener
currentFraction = it.animatedValue as Float
invalidate()
animator
currentFraction 会在两秒内,从0 变换到1
现在只需要将currentFraction设置给矩形的高度即可
private fun drawRect(
canvas: Canvas,
eachWidth: Float,
rectWidth: Float,
eachGridHeight: Float
)
var tempLeft = eachWidth / 2f - rectWidth / 2f
originList.forEach
val left = tempLeft + offsetX // 偏移量
val top = height - eachGridHeight * it * currentFraction // 将当前进度设置给top
val right = left + rectWidth
val bottom = height.toFloat()
canvas.drawRoundRect(left, top, right, bottom, 0f, 0f, paint)
tempLeft += eachWidth
添加文字
文字的x,y位置和矩形的top位置是一样的
override fun onDraw(canvas: Canvas)
canvas.withSave
// 裁剪
canvas.clipRect(0, 0, width, height)
// 绘制矩形
drawRect(canvas, eachWidth, rectWidth, eachGrid)
// 绘制文字
drawText(canvas, eachWidth, eachGrid)
/*
* 作者:史大拿
* 创建时间: 9/26/22 4:04 PM
* @param eachWidth: 每一格的矩形宽度
* @param eachGridHeight: 每一格矩形的高度
*/
private fun drawText(canvas: Canvas, eachWidth: Float, eachGridHeight: Float)
var tempX = eachWidth / 2f
originList.forEachIndexed _, value ->
val text = "$value"
// 文字的宽
val textWidth = paint.measureText(text)
val x = tempX - textWidth / 2f + offsetX
val y = (height - eachGridHeight * value) - paint.fontMetrics.bottom
// 如果在范围内才绘制
if (x >= 0 && x <= width)
canvas.drawText(text, x, y, paint)
tempX += eachWidth
可以看出,文字绘制成功了,但是我们想要的是和矩形一样一直变换
那么肯定和currentFraction有关系
同样的套路,y轴 * currentFraction即可
private fun drawText(canvas: Canvas, eachWidth: Float, eachGridHeight: Float)
var tempX = eachWidth / 2f
originList.forEachIndexed _, value ->
val text = String.format("%.0f", value * currentFraction)
// 文字的宽
val textWidth = paint.measureText(text)
val x = tempX - textWidth / 2f + offsetX
val y = (height - eachGridHeight * value * currentFraction) - paint.fontMetrics.bottom
// 如果在范围内才绘制
if (x >= 0 && x <= width)
canvas.drawText(text, x, y, paint)
tempX += eachWidth
原创不易,您的点赞就是对我最大的帮助!
以上是关于android 自定义view: 矩形图表的主要内容,如果未能解决你的问题,请参考以下文章