簡介
今天為大家介紹的是圖案解鎖功能,采用自定義View的方式,小伙伴們可以直接拿過去用,獻上效果圖:
實現(xiàn)
1、創(chuàng)建一個SlideUnlockView繼承于View龄捡,用于管理密碼圖案卓嫂,響應(yīng)滑動事件并返回密碼
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import java.lang.StringBuilder
class SlideUnlockView: View{
constructor(context: Context?):super(context){}
constructor(context: Context?,attrs: AttributeSet?): super(context,attrs){}
constructor(context: Context?,attrs: AttributeSet?,style: Int): super(context,attrs,style){}
//圓的半徑
private var radius = 0f
//間距
private var padding = 0f
//保存所有九個點的對象
private val dots = mutableListOf<DotView>()
//保存被選中的點
private val selectedDots = mutableListOf<DotView>()
//上一次被點亮的點
private var lastSelectDot:DotView? = null
//記錄移動線條
private var endPoint = Point(0,0)
//記錄劃線的路徑
private val linePath = Path()
//線條畫筆
private val linePaint = Paint().apply {
color = Color.DKGRAY
strokeWidth = 5f
style = Paint.Style.STROKE
}
//圓內(nèi)部遮蓋的Paint
private val innerCirclePaint = Paint().apply{
color = Color.WHITE
}
//記錄密碼
private val password = StringBuilder()
public var oPasswordListenner:OnPasswordChangedListenner? = null
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
//初始化
init()
}
//具體繪制內(nèi)容
override fun onDraw(canvas: Canvas?) {
//畫線
canvas?.drawPath(linePath,linePaint)
//繪制移動的線
if (!endPoint.equals(0,0)){
canvas?.drawLine(lastSelectDot!!.cx,lastSelectDot!!.cy,endPoint.x.toFloat(),endPoint.y.toFloat(),linePaint)
}
//繪制九個點
drawNineDots(canvas)
}
//繪制九個點
private fun drawNineDots(canvas: Canvas?){
for (dot in dots){
canvas?.drawCircle(dot.cx,dot.cy,dot.radius,dot.paint)
canvas?.drawCircle(dot.cx,dot.cy,radius-2,innerCirclePaint)
if (dot.isSelected){
canvas?.drawCircle(dot.cx,dot.cy,dot.innerCircleRadius,dot.paint)
}
}
}
//初始化
private fun init(){
//第一個點的中心坐標
var cx = 0f
var cy = 0f
//計算半徑和間距
//判斷你用戶設(shè)置當前View的尺寸 確保在正方形區(qū)域繪制
if (measuredWidth >= measuredHeight){
//半徑
radius = measuredHeight/5/2f
//間距
padding = (measuredHeight-3*radius*2) / 4
//中心點
cx = (measuredWidth - measuredWidth)/2f + padding
cy = padding + radius
}else{
radius = measuredWidth/5/2f
padding = (measuredWidth - 3*radius*2) / 4
cx = padding + radius
cy = (measuredHeight - measuredWidth)/2f + padding +radius
}
//設(shè)置九個點組成的Path
for (row in 0..2){
for (colum in 0..2){
DotView(cx+colum*(2*radius+padding),
cy+row*(2*radius+padding),
radius,row*3+colum+1).also { dots.add(it) }
}
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
//獲取觸摸點坐標
val x = event?.x
val y = event?.y
when(event?.action){
MotionEvent.ACTION_DOWN->{
//判斷點是否在某個點的矩形區(qū)域內(nèi)
containsPoint(x,y).also {
if (it != null){
//在某個圓點
selectItem(it)
selectedDots.add(it)
linePath.moveTo(it.cx,it.cy)
}else{
//不在某個圓點
}
}
}
MotionEvent.ACTION_MOVE->{
//判斷點是否在某個點的矩形區(qū)域內(nèi)
containsPoint(x,y).also {
if (it != null){
if (!it.isSelected){
//沒有被點亮
//是不是第一個點
if (lastSelectDot == null){
//第一個點
linePath.moveTo(it.cx,it.cy)
}else{
//從上一個點畫線
linePath.lineTo(it.cx,it.cy)
}
//點亮這個點
selectItem(it)
selectedDots.add(it)
}
}else{
//觸摸點在外部
if (lastSelectDot != null){
endPoint.set(x!!.toInt(),y!!.toInt())
invalidate()
}
}
}
}
MotionEvent.ACTION_UP->{
oPasswordListenner?.passwordChanged(password.toString())
reset()
}
}
return true
}
//重設(shè)
private fun reset(){
//將顏色改為正常顏色
for (item in selectedDots){
item.isSelected = false
}
invalidate()
//清空
selectedDots.clear()
lastSelectDot = null
//線條重設(shè)
linePath.reset()
//設(shè)置endPoint為空
endPoint.set(0,0)
//清空密碼
Log.v("yyy",password.toString())
password.delete(0,password.length)
}
//選中某個點
private fun selectItem(item: DotView){
//設(shè)為被選中
item.isSelected = true
//刷新
invalidate()
//保存點亮點
selectedDots.add(item)
//記錄點
lastSelectDot = item
//設(shè)置endPoint為空
endPoint.set(0,0)
//記錄當前密碼
password.append(item.tag)
}
//查找某個矩形區(qū)域是否包含某個觸摸點
private fun containsPoint(x: Float?,y: Float?):DotView?{
for (item in dots){
if (item.rect.contains(x!!.toInt(),y!!.toInt())){
return item
}
}
return null
}
//回調(diào)密碼
fun passwordBlock(pwd:(String) -> Unit){
pwd(password.toString())
}
//接口 回調(diào)密碼
interface OnPasswordChangedListenner{
fun passwordChanged(pwd:String)
}
2、單獨創(chuàng)建一個DotView聘殖,用于管理顯示的九個圓點晨雳,包含圓的坐標、半徑奸腺、畫筆顏色等信息餐禁,在ondraw中繪制時使用自己管理的屬性
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
/**
* 管理沒贏過圓點的具體樣式
* 中心 x,y
* 半徑 radius
* 畫筆 paint
* */
class DotView(val cx:Float, val cy:Float, val radius:Float,val tag: Int) {
val paint = Paint().apply {
color = Color.BLACK
strokeWidth = 2f
style = Paint.Style.FILL
}
//點的矩形范圍
val rect = Rect()
//選中點內(nèi)圓半徑
var innerCircleRadius = 0f
//記錄是否被選中
var isSelected = false
set(value) {
field = value
if (value){
paint.color = Color.rgb(0,199,255)
}else{
paint.color = Color.BLACK
}
}
//初始化代碼塊
init {
rect.left = (cx - radius).toInt()
rect.top = (cy - radius).toInt()
rect.right = (cx + radius).toInt()
rect.bottom = (cy + radius).toInt()
innerCircleRadius = radius / 3.5f
}
fun setColor(color: Int){
paint.color = color
}
}
3突照、創(chuàng)建好視圖帮非,就可在xml中使用并布局,再此創(chuàng)建一個SlideUnlockActivity讹蘑,并在xml中配置如下
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SlideLoginActivity">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.15" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.22" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.29" />
<View
android:id="@+id/view"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#333"
app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/mHeader"
android:layout_width="96dp"
android:layout_height="96dp"
app:civ_border_width="3dp"
app:civ_border_color="#fff"
android:src="@drawable/tt"
app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintEnd_toEndOf="@+id/view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/view" />
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="浪到飛起的王者"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="@+id/guideline3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline2" />
<TextView
android:id="@+id/mAlert"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="請設(shè)置密碼"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline3" />
<yzl.swu.drawlogin.SlideUnlockView
android:id="@+id/mSlideUnlock"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
app:layout_constraintDimensionRatio="w,1.05:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline4"></yzl.swu.drawlogin.SlideUnlockView>
</androidx.constraintlayout.widget.ConstraintLayout>
5末盔、在Activity代碼中增加動畫,接收SlideUnlockView回調(diào)來的密碼座慰,使用SharedPreference保存在應(yīng)用中
import android.animation.ObjectAnimator
import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_slide_login.*
class SlideLoginActivity : AppCompatActivity() {
//記錄繪制的密碼
private var password:String? = null
//記錄初始密碼
private var orgPassword:String? = null
//確認密碼 設(shè)置密碼時
private var firstSurePassword:String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_slide_login)
//獲取初始密碼
orgPassword = SharedPreferenceUtil.getInstance(this).getPassword()
if (orgPassword == null){
mAlert.text = "請設(shè)置密碼"
}else{
mAlert.text = "請繪制密碼"
}
mSlideUnlock.oPasswordListenner = (object :SlideUnlockView.OnPasswordChangedListenner{
override fun passwordChanged(pwd: String) {
Log.v("yyy","pwd is:$pwd")
password = pwd
passwordOperation()
}
})
}
//判斷密碼操作
private fun passwordOperation() {
//頭像旋轉(zhuǎn)
mHeader.animate()
.rotationBy(360f)
.setDuration(1000)
.start()
//保存密碼
if (orgPassword == null) {
//設(shè)置密碼
if (firstSurePassword == null) {
firstSurePassword = password.toString()
mAlert.text = "請確認密碼"
} else {
//判斷兩次密碼是否一致
if (firstSurePassword.equals(password.toString())) {
mAlert.text = "密碼設(shè)置成功"
SharedPreferenceUtil.getInstance(this).savePassword(firstSurePassword!!)
} else {
mAlert.setTextColor(Color.RED)
mAlert.text = "兩次密碼不一致陨舱,請重新設(shè)置"
firstSurePassword = null
loginAnim()
}
}
} else {
//確認密碼
if (orgPassword.equals(password.toString())) {
mAlert.text = "密碼正確"
} else {
mAlert.setTextColor(Color.RED)
mAlert.text = "密碼錯誤,請重新繪制"
loginAnim()
}
}
//清空
password = null
Handler().postDelayed({
mAlert.setTextColor(Color.BLACK)
}, 1000)
}
//密碼確認動畫
private fun loginAnim(){
ObjectAnimator.ofFloat(mAlert,"translationX",5f,-5f,0f).apply {
duration=200
start()
}
}
}
6版仔、上例中用到SharedPreference游盲,封裝成一個工具類误墓,不容易出錯
import android.content.Context
class SharedPreferenceUtil private constructor(){
private val FILE_NAME = "password"
private val KEY = "passwordKey"
companion object{
private var instance: SharedPreferenceUtil? = null
private var mContext: Context? = null
fun getInstance(context: Context): SharedPreferenceUtil{
mContext = context
if (instance == null){
synchronized(this){
instance = SharedPreferenceUtil()
}
}
return instance!!
}
}
fun savePassword(pwd: String){
//獲取preference對象
val sharedPreferences = mContext?.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
//獲取edit對象 -> 寫數(shù)據(jù)
val edit = sharedPreferences?.edit()
//寫入數(shù)據(jù)
edit?.putString(KEY,pwd)
//提交
edit?.apply()
}
fun getPassword(): String?{
//獲取preference對象
val sharedPreferences = mContext?.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
return sharedPreferences?.getString(KEY,null)
}
}
結(jié)語
Github地址:https://github.com/InuyashaY/DrawLogin.git
每天進步一點點!R娑小谜慌!