Android自定义View基础篇
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义View基础篇相关的知识,希望对你有一定的参考价值。
参考技术A 在我们自定义View,尤其是制作一些复杂炫酷的效果的时候,实际上是将一些简单的东西通过数学上精密的计算组合到一起形成的效果。这其中可能会涉及到画布的相关操作(旋转),以及一些正余弦函数的计算等,这些内容就会用到一些角度、弧度相关的知识。圆一周对应的角度为360度(角度),对应的弧度为2π弧度。
故得等价关系:360(角度) = 2π(弧度) ==> 180(角度) = π(弧度)
几种创建或使用颜色的方式
android自定义属性可分为以下几步:
2.自定义View中获取属性
3.在布局中使用
4.属性值的类型归纳
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基础篇的主要内容,如果未能解决你的问题,请参考以下文章