简介
今天为大家介绍的是图案解锁功能,采用自定义View的方式,小伙伴们可以直接拿过去用,献上效果图:
效果图
实现
1、创建一个SlideUnlockView继承于View,用于管理密码图案,响应滑动事件并返回密码
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
}
//圆内部遮盖的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()
}
//具体绘制内容
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
//计算半径和间距
//判断你用户设置当前View的尺寸 确保在正方形区域绘制
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
}
//设置九个点组成的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->{
//判断点是否在某个点的矩形区域内
containsPoint(x,y).also {
if (it != null){
//在某个圆点
selectItem(it)
selectedDots.add(it)
linePath.moveTo(it.cx,it.cy)
}else{
//不在某个圆点
}
}
}
MotionEvent.ACTION_MOVE->{
//判断点是否在某个点的矩形区域内
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
}
//重设
private fun reset(){
//将颜色改为正常颜色
for (item in selectedDots){
item.isSelected = false
}
invalidate()
//清空
selectedDots.clear()
lastSelectDot = null
//线条重设
linePath.reset()
//设置endPoint为空
endPoint.set(0,0)
//清空密码
Log.v("yyy",password.toString())
password.delete(0,password.length)
}
//选中某个点
private fun selectItem(item: DotView){
//设为被选中
item.isSelected = true
//刷新
invalidate()
//保存点亮点
selectedDots.add(item)
//记录点
lastSelectDot = item
//设置endPoint为空
endPoint.set(0,0)
//记录当前密码
password.append(item.tag)
}
//查找某个矩形区域是否包含某个触摸点
private fun containsPoint(x: Float?,y: Float?):DotView?{
for (item in dots){
if (item.rect.contains(x!!.toInt(),y!!.toInt())){
return item
}
}
return null
}
//回调密码
fun passwordBlock(pwd:(String) -> Unit){
pwd(password.toString())
}
//接口 回调密码
interface OnPasswordChangedListenner{
fun passwordChanged(pwd:String)
}
2、单独创建一个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()
//选中点内圆半径
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、创建好视图,就可在xml中使用并布局,再此创建一个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="请设置密码"
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回调来的密码,使用SharedPreference保存在应用中
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
//确认密码 设置密码时
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 = "请设置密码"
}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() {
//头像旋转
mHeader.animate()
.rotationBy(360f)
.setDuration(1000)
.start()
//保存密码
if (orgPassword == null) {
//设置密码
if (firstSurePassword == null) {
firstSurePassword = password.toString()
mAlert.text = "请确认密码"
} else {
//判断两次密码是否一致
if (firstSurePassword.equals(password.toString())) {
mAlert.text = "密码设置成功"
SharedPreferenceUtil.getInstance(this).savePassword(firstSurePassword!!)
} else {
mAlert.setTextColor(Color.RED)
mAlert.text = "两次密码不一致,请重新设置"
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对象 -> 写数据
val edit = sharedPreferences?.edit()
//写入数据
edit?.putString(KEY,pwd)
//提交
edit?.apply()
}
fun getPassword(): String?{
//获取preference对象
val sharedPreferences = mContext?.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
return sharedPreferences?.getString(KEY,null)
}
}
结语
Github地址:https://github.com/InuyashaY/DrawLogin.git
每天进步一点点!!!