###1.概述


自定义View效果越写越难,但是将这些效果一步一步分解后,其实挺简单的,早期自己项目中用到九宫格解锁,我都是从网上下的,因为心里一开始觉得自己写应该会很困难,后来发现自己闲下来写写原来这么简单。这期的自定义View的效果我们用Kotlin来写

###2.实现


2.1. 绘制出相对于这个View的居中的九个圆,刚开始当然是默认的 2.2. 当触摸屏幕的时候判断是否点击在这九个圆上 2.3. 在屏幕上滑动的时候,绘制两个点之间的线条和箭头,以及选中状态的点 2.4. 提供几个方法供用户调用,监听回调密码是否太短,合法等等

2.1. 绘制出相对于这个View的居中的九个圆

class LockPatternView : View {// 二维数组初始化,int[3][3]private var mPoints: Array<Array<Point?>> = Array(3) { Array<Point?>(3, { null }) }// 是否初始化private var mIsInit = falseprivate var mWidth: Int = 0private var mHeight: Int = 0// 外圆的半径private var mDotRadius: Int = 0// 画笔private var mLinePaint: Paint? = nullprivate var mPressedPaint: Paint? = nullprivate var mErrorPaint: Paint? = nullprivate var mNormalPaint: Paint? = nullprivate var mArrowPaint: Paint? = null// 颜色private val mOuterPressedColor = 0xff8cbad8.toInt()private val mInnerPressedColor = 0xff0596f6.toInt()private val mOuterNormalColor = 0xffd9d9d9.toInt()private val mInnerNormalColor = 0xff929292.toInt()private val mOuterErrorColor = 0xff901032.toInt()private val mInnerErrorColor = 0xffea0945.toInt()constructor(context: Context) : super(context)constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)override fun onDraw(canvas: Canvas) {if (!mIsInit) {initPoints()}drawToCanvas(canvas)}private fun drawToCanvas(canvas: Canvas) {for (i in mPoints.indices) {for (j in 0..mPoints[i].size - 1) {val point = mPoints[i][j]if (point != null) {// 循环绘制默认圆mNormalPaint!!.color = mOuterNormalColorcanvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(), mDotRadius.toFloat(), mNormalPaint!!)mNormalPaint!!.color = mInnerNormalColorcanvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(), (mDotRadius / 6).toFloat(), mNormalPaint!!}}}drawLineToCanvas(canvas)}/*** 初始化点*/private fun initPoints() {mWidth = widthmHeight = heightvar offsetX = 0var offsetY = 0if (mWidth > mHeight) {offsetX = (mWidth - mHeight) / 2mWidth = mHeight} else {offsetY = (mHeight - mWidth) / 2mHeight = mWidth}mDotRadius = mWidth / 12val padding = mDotRadius / 2val sideSize = (mWidth - 2 * padding) / 3offsetX += paddingoffsetY += paddingfor (i in mPoints.indices) {for (j in mPoints.indices) {// 循环初始化九个点mPoints[i][j] = Point(offsetX + sideSize * (i * 2 + 1) / 2,offsetY + sideSize * (j * 2 + 1) / 2, i * mPoints.size + j)}}initPaint()mIsInit = true}private fun initPaint() {// 线的画笔mLinePaint = Paint()mLinePaint!!.color = mInnerPressedColormLinePaint!!.style = Paint.Style.STROKEmLinePaint!!.isAntiAlias = truemLinePaint!!.strokeWidth = (mDotRadius / 9).toFloat()// 按下的画笔mPressedPaint = Paint()mPressedPaint!!.style = Paint.Style.STROKEmPressedPaint!!.isAntiAlias = truemPressedPaint!!.strokeWidth = (mDotRadius / 6).toFloat()// 错误的画笔mErrorPaint = Paint()mErrorPaint!!.style = Paint.Style.STROKEmErrorPaint!!.isAntiAlias = truemErrorPaint!!.strokeWidth = (mDotRadius / 6).toFloat()// 默认的画笔mNormalPaint = Paint()mNormalPaint!!.style = Paint.Style.STROKEmNormalPaint!!.isAntiAlias = truemNormalPaint!!.strokeWidth = (mDotRadius / 9).toFloat()// 箭头的画笔mArrowPaint = Paint()mArrowPaint!!.color = mInnerPressedColormArrowPaint!!.style = Paint.Style.FILLmArrowPaint!!.isAntiAlias = true}
复制代码

2.2. 当触摸屏幕的时候判断是否点击在这九个圆上

override fun onTouchEvent(event: MotionEvent): Boolean {mMovingX = event.xmMovingY = event.ywhen (event.action) {MotionEvent.ACTION_DOWN -> {val firstPoint = pointif (firstPoint != null) {// 已经开始选点了mSelectPoints.add(firstPoint)// 点设置为已经选中firstPoint.setStatusPressed()// 开始绘制mSelectBegin = true}}}invalidate()return true}/*** 获取按下的点* @return 当前按下的点*/private val point: Point?get() {for (i in mPoints.indices) {for (j in 0..mPoints[i].size - 1) {val point = mPoints[i][j]if (point != null) {if (MathUtil.checkInRound(point.centerX.toFloat(), point.centerY.toFloat(), mDotRadius.toFloat(), mMovingX, mMovingY)) {return point}}}}return null}
复制代码

2.3. 在屏幕上滑动的时候,绘制两个点之间的线条和箭头,以及选中状态的点

override fun onTouchEvent(event: MotionEvent): Boolean {mMovingX = event.xmMovingY = event.ywhen (event.action) {MotionEvent.ACTION_MOVE -> if (mSelectBegin) {val selectPoint = pointif (selectPoint != null) {selectPoint.setStatusPressed()if (!mSelectPoints.contains(selectPoint)) {// 把选中的点添加到集合mSelectPoints.add(selectPoint)}}}MotionEvent.ACTION_UP -> if (mSelectBegin) {if (mSelectPoints.size == 1) {// 清空选择clearSelectPoints()} else if (mSelectPoints.size <= 4) {// 太短显示错误showSelectError()} else {// 成功回调if (mListener != null) {lockCallBack()}}mSelectBegin = false}}invalidate()return true}/*** 画线* @param canvas*/private fun drawLineToCanvas(canvas: Canvas) {if (mSelectPoints.size >= 1) {if (mIsErrorStatus) {mLinePaint!!.color = mInnerErrorColormArrowPaint!!.color = mInnerErrorColor} else {mLinePaint!!.color = mInnerPressedColormArrowPaint!!.color = mInnerPressedColor}var lastPoint = mSelectPoints[0]for (i in 1..mSelectPoints.size - 1) {val point = mSelectPoints[i]// 不断的画线drawLine(lastPoint, point, canvas, mLinePaint!!)drawArrow(canvas, mArrowPaint!!, lastPoint, point, (mDotRadius / 4).toFloat(), 38)lastPoint = point}val isInnerPoint = MathUtil.checkInRound(lastPoint.centerX.toFloat(), lastPoint.centerY.toFloat(), mDotRadius.toFloat(), mMovingX, mMovingY)if (mSelectBegin && !isInnerPoint) {drawLine(lastPoint, Point(mMovingX.toInt(), mMovingY.toInt(), -1), canvas, mLinePaint!!)}}}/*** 画线*/private fun drawLine(start: Point, end: Point, canvas: Canvas, paint: Paint) {val d = MathUtil.distance(start.centerX.toDouble(), start.centerY.toDouble(), end.centerX.toDouble(), end.centerY.toDouble())val rx = (((end.centerX - start.centerX) * mDotRadius).toDouble() / 5.0 / d).toFloat()val ry = (((end.centerY - start.centerY) * mDotRadius).toDouble() / 5.0 / d).toFloat()canvas.drawLine(start.centerX + rx, start.centerY + ry, end.centerX - rx, end.centerY - ry, paint)}/*** 画箭头*/private fun drawArrow(canvas: Canvas, paint: Paint, start: Point, end: Point, arrowHeight: Float, angle: Int) {val d = MathUtil.distance(start.centerX.toDouble(), start.centerY.toDouble(), end.centerX.toDouble(), end.centerY.toDouble())val sin_B = ((end.centerX - start.centerX) / d).toFloat()val cos_B = ((end.centerY - start.centerY) / d).toFloat()val tan_A = Math.tan(Math.toRadians(angle.toDouble())).toFloat()val h = (d - arrowHeight.toDouble() - mDotRadius * 1.1).toFloat()val l = arrowHeight * tan_Aval a = l * sin_Bval b = l * cos_Bval x0 = h * sin_Bval y0 = h * cos_Bval x1 = start.centerX + (h + arrowHeight) * sin_Bval y1 = start.centerY + (h + arrowHeight) * cos_Bval x2 = start.centerX + x0 - bval y2 = start.centerY.toFloat() + y0 + aval x3 = start.centerX.toFloat() + x0 + bval y3 = start.centerY + y0 - aval path = Path()path.moveTo(x1, y1)path.lineTo(x2, y2)path.lineTo(x3, y3)path.close()canvas.drawPath(path, paint)}
复制代码

2.4. 提供几个方法供用户调用,监听回调密码是否太短,合法等等

    /*** 回调*/private fun lockCallBack() {var password = ""for (selectPoint in mSelectPoints) {password += selectPoint.index}mListener!!.lock(password)}/*** 显示错误*/fun showSelectError() {for (selectPoint in mSelectPoints) {selectPoint.setStatusError()mIsErrorStatus = true}postDelayed({clearSelectPoints()mIsErrorStatus = falseinvalidate()}, 1000)}/*** 清空所有的点*/private fun clearSelectPoints() {for (selectPoint in mSelectPoints) {selectPoint.setStatusNormal()}mSelectPoints.clear()}/*** 清空所有的点*/fun clearSelect() {for (selectPoint in mSelectPoints) {selectPoint.setStatusNormal()}mSelectPoints.clear()invalidate()}private var mListener: LockPatternListener? = nullfun setLockPatternListener(listener: LockPatternListener) {this.mListener = listener}interface LockPatternListener {fun lock(password: String)}
复制代码

如果觉得还是有点困难,可以看看前几篇自定义View的文章,至于箭头的绘制涉及到 sin、cos、tan ,我会在介绍贝塞尔曲线消息拖拽的时候讲一次数学课,当然我们在项目中有可能会直接使用资源图片。

所有分享大纲:Android进阶之旅 - 自定义View篇

视频讲解地址:http://pan.baidu.com/s/1pK8eQPt

Touch事件分发 九宫格解锁相关推荐

  1. ViewGroup的Touch事件分发(源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,View的touch事件分发相对比较简单,可参考 View的Touch事件分发(一.初步了解) View的Touch事 ...

  2. View的Touch事件分发(二.源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 主要分析View的dispatchTouchEvent()方法和onTou ...

  3. View的Touch事件分发(一.初步了解)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 一次完整的Touch事件序列为: ACTION_DOWN -> AC ...

  4. 源码阅读分析 View的Touch事件分发

    其实 Android 事件分发机制在早几年一直都困扰着我,那时候处理事件分发的自定义 View 脑子都是一片白,老感觉处理不好.后来自己看了 android 源码,也阅读了很多大牛的文章才算彻底明白, ...

  5. Android:30分钟弄明白Touch事件分发机制

    Touch事件分发中只有两个主角:ViewGroup和View.Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理. View在 ...

  6. android控件的touch事件_Android touch 事件分发时序

    点击上方"蓝字"关注我们 1,touch 事件是如何从驱动层传递给 Framework 层的 InputManagerService: 2,WMS 是如何通过 ViewRooImp ...

  7. android touch事件无反应,android的touch事件分发响应机制

    想要弄明白android的touch事件分发响应机制需要先充分理解一下几个知识点: View和ViewGroup touch事件的构成 ViewGroup如何对事件分发和拦截 View和ViewGro ...

  8. Android Touch事件分发(源码分析)

    Android一文让你轻松搞定Touch事件分发 源码分析 下面,咱们一起通过源码,全面解析事件分发机制,即按顺序讲解: Activity事件分发机制 ViewGroup事件分发机制 View事件分发 ...

  9. Android Touch事件分发—拦截—处理

    Android Touch事件分发(dispatchTouchEvent)-拦截(onInterceptTouchEvent)-处理(onTouchEvent) 转自:http://www.cnblo ...

最新文章

  1. SpringMVC_实现简单的增删改查
  2. python画-python如何画出漂亮的地图?
  3. VTK:IO之HDRReader
  4. Linux带给了我什么?
  5. java手写实现BST
  6. 给 kibana 增加一个退出logout按钮
  7. 如何设置 Excel 文件打印时刚好是一页的宽度?让打印范围刚好是一页纸
  8. JAVA创建对象有哪几种方式
  9. 2021-07-25梦笔记
  10. php循环语句时间戳转换,php怎么实现时间戳转换为时间
  11. 光学动作捕捉系统原理
  12. local_listener参数(1)---elaine
  13. ourplay插件_ourplay64位辅助包
  14. 如何让使用小博通蓝牙BK3432的鲁哇客智能挪车号码牌,一节钮扣电池工作一年
  15. 计算机声音音乐小星星,幼儿园小班音乐课件:《小星星》
  16. 赴日IT派遣,如何避免入坑
  17. 【经验总结】小白挖洞十天经验分享
  18. 《面试技巧-招聘篇》课程笔记
  19. 小程序使用vant中的步骤条 vant-steps
  20. 计算机网络——交换机的生成树协议STP

热门文章

  1. vuetify,一直使用一直爽
  2. python爬虫入门学习
  3. 算法工程师-笔(面)试题汇总
  4. 华为OSPF多区域简单配置
  5. SVM算法原理以及实现
  6. C# 使用Emit动态注入代码,实现监控属性的目的
  7. 2015美团网面试经验分享
  8. 个人职业生涯规划书范文
  9. Python爬虫之scrapy高级(全站爬取,分布式,增量爬虫)
  10. Connect city