您好,登錄后才能下訂單哦!
這篇“Android如何實現九宮格手勢密碼”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Android如何實現九宮格手勢密碼”文章吧。
先見圖
思路:首先是9個格子,接著是格子連線;那么我們的步驟就有了。
1.手勢監聽,進行連線
2.格子的狀態未連接(初始狀態)、已連接的(沒有結果前)、錯誤狀態(有結果后)。(先這三個,可擴展,比如按下狀態)
3.自定義viewgroup作為九宮格的容器,里面包含9個view(小格子)
為了擴展性,不自定義view,將三個狀態和有關屬性提取
1.提取屬性,代碼如下:
class NineChildInf { /** * 當前所在9宮格的位置 * 從1開始 */ var index = 0 /** * 是否被點亮 */ var isLight = false /** * 中心點所在父類容器內的坐標 */ var centerX = 0.toFloat() var centerY = 0.toFloat() fun setContent(index: Int, centerX: Float, centerY: Float) { this.index = index this.centerX = centerX this.centerY = centerY } constructor() fun updateCenterPoint(x: Float, y: Float) { this.centerX = x this.centerY = y } fun reset() { this.index = 0 this.centerX = 0f this.centerY = 0f this.isLight = false } override fun toString(): String { return "NineChildInf(index=$index, isLight=$isLight, centerX=$centerX, centerY=$centerY)" } }
2.三個狀態,代碼如下
/** * Created by XinHeng on 2019/02/27. * describe:9宮格子view必須實現此接口 */ abstract class NineChildParent<T : View>(var view: T) { protected open var context = view.context.applicationContext val NINE_CHILD_INF = NineChildInf() /** * 密碼錯誤時的顯示 */ abstract fun setErrorStatue() /** * 被選中時的顯示 */ abstract fun setLightStatue() /** * 默認顯示 */ abstract fun setDefaultStatue() }
既然是九宮格,那自然少不了這些屬性,水平間隔、垂直間隔、最小有效連接數、當前狀態、密碼是否設置完成等。還需要將開啟viewgroup的onDraw()方法。具體代碼如下:
class NineViewGroup @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ViewGroup(context, attrs, defStyleAttr) { /** * 水平間的間隔 */ private var paddingH = 60 /** * 垂直間的間隔 */ private var paddingV = 60 /** * 連線最小有效數字 */ var minEffectiveSize = 4 /** * 小格子的寬高 */ private var childSlide: Int = 30 private val ERROR_STATUE = 2 private val LINKING_STATUE = 1 private val DEFAULT_STATUE = 0 /** * 當前狀態 * 0->最初狀態 DEFAULT_STATUE * 1->正在連線中 LINKING_STATUE * 2->錯誤狀態 ERROR_STATUE */ private var nowStatue = DEFAULT_STATUE /** * 一次密碼設置完成標志 */ private var complete = false /** * 線條寬度 */ private var lineWidth = 5 private var lineColor = Color.parseColor("#33b5e5") private var errorLineColor = Color.RED private var childViews = ArrayList<NineChildParent<*>>(9) init { //使能調用onDraw()方法 setWillNotDraw(false) var array = context.obtainStyledAttributes(attrs, R.styleable.NineViewGroup, defStyleAttr, 0) (0..array.indexCount).forEach { var index = array.getIndex(it) when (index) { R.styleable.NineViewGroup_nine_child_size -> childSlide = array.getDimensionPixelSize(index, childSlide) R.styleable.NineViewGroup_nine_line_color -> lineColor = array.getColor(index, lineColor) R.styleable.NineViewGroup_nine_error_line_color -> errorLineColor = array.getColor(index, errorLineColor) R.styleable.NineViewGroup_nine_effective_size -> minEffectiveSize = array.getInt(index, minEffectiveSize) R.styleable.NineViewGroup_nine_padding_h -> paddingH = array.getDimensionPixelSize(index, paddingH) R.styleable.NineViewGroup_nine_padding_v -> paddingV = array.getDimensionPixelSize(index, paddingV) R.styleable.NineViewGroup_nine_line_width -> lineWidth = array.getDimensionPixelSize(index, lineWidth) } } array.recycle() } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { var width = childSlide * 3 + paddingLeft + paddingRight + paddingH * 2 var height = childSlide * 3 + paddingTop + paddingBottom + paddingV * 2 setMeasuredDimension(width, height) //又忘了計算子view的大小了。。。 measureChildren(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)) } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { var childView: View var top: Int = paddingTop var left: Int = paddingLeft var right: Int var bottom: Int if (childCount > 0) { (0 until childCount).forEach { childView = getChildAt(it) right = left + childView.measuredWidth bottom = top + childView.measuredHeight //Log.e("TAG", "onLayout: $left $top $right $bottom") var nineChildInf = (childViews[it]).NINE_CHILD_INF nineChildInf.setContent(it + 1, (left + right) / 2f, (top + bottom) / 2f) //Log.e("TAG", "onLayout: child=$nineChildInf") childView.layout(left, top, right, bottom) if ((it + 1) % 3 == 0) { left = paddingLeft top = bottom + paddingV } else { left = right + paddingH } } } } }
1.手勢監聽,重寫onTouchEvent()方法,必要需要時重寫onInterceptTouchEvent()方法進行攔截(跟情況而定,這里就不多說了)。簡單的三個手勢狀態按下、移動、抬起。在各個狀態下,記錄坐標,并且更新子view(小格子)的ui,還有線條。代碼片段如下:
override fun onTouchEvent(event: MotionEvent): Boolean { if (childCount == 0 || complete) { return super.onTouchEvent(event) } when (event.action) { MotionEvent.ACTION_DOWN -> { //記錄落點 lastX = event.x lastY = event.y downUpdateChild(lastX, lastY) } MotionEvent.ACTION_MOVE -> { lastX = event.x lastY = event.y moveUpdateChild(lastX, lastY) } MotionEvent.ACTION_UP -> { complete = true //統計 upUpdateChild() } } return true }
2.連線,在容器的onDraw()方法,進行畫線操作,代碼片段如下:
override fun onDraw(canvas: Canvas) { super.onDraw(canvas) if (!showLine) { return } paint.color = when (nowStatue) { ERROR_STATUE -> errorLineColor else -> lineColor } if (points.size > 1) { (1 until points.size).forEach { var pointXYStart = points[it - 1].NINE_CHILD_INF var pointXYEnd = points[it].NINE_CHILD_INF canvas.drawLine(pointXYStart.centerX, pointXYStart.centerY, pointXYEnd.centerX, pointXYEnd.centerY, paint) } } if (lastX > 0 && points.size > 0) { var pointXY = points[points.size - 1].NINE_CHILD_INF canvas.drawLine(pointXY.centerX, pointXY.centerY, lastX, lastY, paint) } }
但是還有一些細節:比如連線中需要判斷中間是否含有小格子、判斷觸點是否在小格子上、連接完成后的回調、錯誤狀態顯示、恢復初始狀態等。粘出部分代碼片段(這些只是能實現效果,還可以優化,交給大家了):
1.判斷觸點是否在小格子上
private fun childContains(x: Float, y: Float): Boolean { (0 until childCount).forEach { var childAt = getChildAt(it) //這一句,循環判斷,是否屬于其范圍 if (x >= childAt.left && x < childAt.right && y >= childAt.top && y < childAt.bottom) { return if (!childViews[it].NINE_CHILD_INF.isLight) { if (points.size > 0) { checkMiddleChild(points[points.size - 1], childViews[it])?.run { if (!NINE_CHILD_INF.isLight) { buffer.append(NINE_CHILD_INF.index) changeLightStatue(this) } } } buffer.append(it + 1) //TODO 改變子view的UI狀態 changeLightStatue(childViews[it]) true } else { false } } } return false }
2.判斷中間是否含有小格子
private fun checkMiddleChild(nineChildParent: NineChildParent<*>, nineChildParent1: NineChildParent<*>): NineChildParent<*>? { var index = nineChildParent.NINE_CHILD_INF.index var index1 = nineChildParent1.NINE_CHILD_INF.index var sum = index + index1 if (sum == 10) { return childViews[4] } else if (index % 2 != 0 && index1 % 2 != 0) { if ((sum == 4 || sum == 16) || (sum == 8 && (index == 1 || index1 == 1))||(sum == 12 && (index == 3 || index1 == 3))) return childViews[sum / 2 - 1] } return null }
/** * Created by XinHeng on 2019/01/29. * describe:九宮格的容器 */ class NineViewGroup @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ViewGroup(context, attrs, defStyleAttr) { /** * 水平間的間隔 */ private var paddingH = 60 /** * 垂直間的間隔 */ private var paddingV = 60 /** * 是否有第一個選中 */ private var firstSelect = true private val ERROR_STATUE = 2 private val LINKING_STATUE = 1 private val DEFAULT_STATUE = 0 /** * 是否顯示線條 */ var showLine = false /** * 連線最小有效數字 */ var minEffectiveSize = 4 /** * 當前狀態 * 0->最初狀態 DEFAULT_STATUE * 1->正在連線中 LINKING_STATUE * 2->錯誤狀態 ERROR_STATUE */ private var nowStatue = DEFAULT_STATUE /** * 一次密碼設置完成標志 */ private var complete = false /** * 線條寬度 */ private var lineWidth = 5 private var lastX: Float = 0f private var lastY: Float = 0f private var buffer = StringBuilder() private var points = ArrayList<NineChildParent<*>>(9) private var childViews = ArrayList<NineChildParent<*>>(9) /** * 小格子的寬高 */ private var childSlide: Int = 30 private var lineColor = Color.parseColor("#33b5e5") private var errorLineColor = Color.RED var onNineViewGroupListener: OnNineViewGroupListener? = null set(value) { field = value value?.let { setChildMode(it) } } private val paint = Paint().apply { isAntiAlias = true isDither = true } init { //使能調用onDraw()方法 setWillNotDraw(false) var array = context.obtainStyledAttributes(attrs, R.styleable.NineViewGroup, defStyleAttr, 0) (0..array.indexCount).forEach { var index = array.getIndex(it) when (index) { R.styleable.NineViewGroup_nine_child_size -> childSlide = array.getDimensionPixelSize(index, childSlide) R.styleable.NineViewGroup_nine_line_color -> lineColor = array.getColor(index, lineColor) R.styleable.NineViewGroup_nine_error_line_color -> errorLineColor = array.getColor(index, errorLineColor) R.styleable.NineViewGroup_nine_effective_size -> minEffectiveSize = array.getInt(index, minEffectiveSize) R.styleable.NineViewGroup_nine_padding_h -> paddingH = array.getDimensionPixelSize(index, paddingH) R.styleable.NineViewGroup_nine_padding_v -> paddingV = array.getDimensionPixelSize(index, paddingV) R.styleable.NineViewGroup_nine_show_line -> showLine = array.getBoolean(index, showLine) R.styleable.NineViewGroup_nine_line_width -> lineWidth = array.getDimensionPixelSize(index, lineWidth) } } array.recycle() paint.strokeWidth = lineWidth.toFloat() } private fun setChildMode(onNineViewGroupListener: OnNineViewGroupListener) { removeAllViews() childViews.clear() (0..8).forEach { var mode = onNineViewGroupListener.getChildMode() mode.NINE_CHILD_INF.index = it + 1 mode.setDefaultStatue() addView(mode.view, getLp()) childViews.add(mode) } } private fun getLp(): LayoutParams { return LayoutParams(childSlide, childSlide) } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { var width = childSlide * 3 + paddingLeft + paddingRight + paddingH * 2 var height = childSlide * 3 + paddingTop + paddingBottom + paddingV * 2 setMeasuredDimension(width, height) //又忘了計算子view的大小了。。。 measureChildren(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)) } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { var childView: View var top: Int = paddingTop var left: Int = paddingLeft var right: Int var bottom: Int if (childCount > 0) { (0 until childCount).forEach { childView = getChildAt(it) right = left + childView.measuredWidth bottom = top + childView.measuredHeight //Log.e("TAG", "onLayout: $left $top $right $bottom") var nineChildInf = (childViews[it]).NINE_CHILD_INF nineChildInf.setContent(it + 1, (left + right) / 2f, (top + bottom) / 2f) //Log.e("TAG", "onLayout: child=$nineChildInf") childView.layout(left, top, right, bottom) if ((it + 1) % 3 == 0) { left = paddingLeft top = bottom + paddingV } else { left = right + paddingH } } } } override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { return true } override fun onTouchEvent(event: MotionEvent): Boolean { if (childCount == 0 || complete) { return super.onTouchEvent(event) } when (event.action) { MotionEvent.ACTION_DOWN -> { //記錄落點 lastX = event.x lastY = event.y downUpdateChild(lastX, lastY) } MotionEvent.ACTION_MOVE -> { lastX = event.x lastY = event.y moveUpdateChild(lastX, lastY) } MotionEvent.ACTION_UP -> { complete = true //統計 upUpdateChild() } } return true } private fun downUpdateChild(x: Float, y: Float) { firstSelect = childContains(x, y) } private fun moveUpdateChild(x: Float, y: Float) { if (firstSelect) { moveUpdateLineAndChildView(x, y) } else { downUpdateChild(x, y) } } private fun moveUpdateLineAndChildView(x: Float, y: Float) { if (points.size != childCount) childContains(x, y) invalidate() } private fun upUpdateChild() { var effective = points.size >= minEffectiveSize onNineViewGroupListener?.complete(effective, buffer.toString()) } /** * 錯誤狀態展示 */ fun showErrorStatue() { nowStatue = ERROR_STATUE points.forEach { it.setErrorStatue() } invalidate() resetStatueDelayed(500) } /** * 恢復初始狀態 */ private fun resetStatue() { points.clear() firstSelect = false lastX = 0f lastY = 0f buffer.clear() nowStatue = DEFAULT_STATUE (0 until childCount).forEach { var nineChildParent = childViews[it] nineChildParent.setDefaultStatue() nineChildParent.NINE_CHILD_INF.isLight = false } invalidate() complete = false } fun resetStatueDelayed(time: Int) { postDelayed({ resetStatue() }, time.toLong()) } private fun childContains(x: Float, y: Float): Boolean { (0 until childCount).forEach { var childAt = getChildAt(it) if (x >= childAt.left && x < childAt.right && y >= childAt.top && y < childAt.bottom) { return if (!childViews[it].NINE_CHILD_INF.isLight) { if (points.size > 0) { checkMiddleChild(points[points.size - 1], childViews[it])?.run { if (!NINE_CHILD_INF.isLight) { buffer.append(NINE_CHILD_INF.index) changeLightStatue(this) } } } buffer.append(it + 1) //TODO 改變子view的UI狀態 changeLightStatue(childViews[it]) true } else { false } } } return false } private fun changeLightStatue(childParent: NineChildParent<*>) { childParent.NINE_CHILD_INF.isLight = true childParent.setLightStatue() points.add(childParent)//記錄 } private fun checkMiddleChild(nineChildParent: NineChildParent<*>, nineChildParent1: NineChildParent<*>): NineChildParent<*>? { var index = nineChildParent.NINE_CHILD_INF.index var index1 = nineChildParent1.NINE_CHILD_INF.index var sum = index + index1 if (sum == 10) { return childViews[4] } else if (index % 2 != 0 && index1 % 2 != 0) { if ((sum == 4 || sum == 16) || (sum == 8 && (index == 1 || index1 == 1))||(sum == 12 && (index == 3 || index1 == 3))) return childViews[sum / 2 - 1] } return null } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) if (!showLine) { return } paint.color = when (nowStatue) { ERROR_STATUE -> errorLineColor else -> lineColor } if (points.size > 1) { (1 until points.size).forEach { var pointXYStart = points[it - 1].NINE_CHILD_INF var pointXYEnd = points[it].NINE_CHILD_INF canvas.drawLine(pointXYStart.centerX, pointXYStart.centerY, pointXYEnd.centerX, pointXYEnd.centerY, paint) } } if (lastX > 0 && points.size > 0) { var pointXY = points[points.size - 1].NINE_CHILD_INF canvas.drawLine(pointXY.centerX, pointXY.centerY, lastX, lastY, paint) } } interface OnNineViewGroupListener { /** * 子view */ fun getChildMode(): NineChildParent<*> /** * 密碼設置結束 * @param effective 是否有效 * @param password 密碼 */ fun complete(effective: Boolean, password: String) } }
以上就是關于“Android如何實現九宮格手勢密碼”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。