前言:本篇文章主要用于介紹實現(xiàn)圖案解鎖所學到的知識點
環(huán)境:Android studio 4.0 開發(fā)語言:Kotlin
效果展示:
主要知識點:
- 界面布局,包括添加各種圖片和約束
- 使用Sharedpreference存儲數(shù)據(jù)(進行簡單封裝薪韩,提供必要的功能定庵,并且使用單例模式)
- 實現(xiàn)觸摸滑動過程中圖案的展示
- 實現(xiàn)圖案密碼的設置和顏色,這里主要通過設tag值來實現(xiàn)
界面UI的實現(xiàn)關(guān)鍵的是合理選擇容器,這里我們優(yōu)先使用約束布局钦无,代碼量少,且易于實現(xiàn)我們想要的效果
這里主要分為3部分盖袭,中間的虛線是我們設置的guideline,這里設置了倆條失暂,分為3個部分,主要目的是便于實現(xiàn)控件的大小和約束鳄虱,第一部分是圓角圖片弟塞,不管是自定義還是實現(xiàn)第三方庫都可以實現(xiàn)圓角的效果,第二部分是中間的顯示文本醇蝴,第三部分就是圖案解鎖部分宣肚,如果我們在第三部分設置一個ConstraintLayout的約束布局,那么我們想要實現(xiàn)的效果就可以用拖拽的方式添加到容器中悠栓,再設置合理的約束霉涨,便能很快實現(xiàn)想要的效果
那么如何使用Sharedpreference存儲數(shù)據(jù)呢?
具體步驟:
- 1.獲取SharedPreference的對象惭适,通過getSharedPreference(String,int)方法笙瑟。第一個參數(shù)用于指定該存儲文件的名稱,不用加后綴癞志,第二個參數(shù)指定文件的操作模式往枷。一般用MODE_PRIVATE 私有方式存儲,其他應用無法訪問。
- 2.設置參數(shù)错洁,必須通過一個SharedPreference.Editor對象秉宿。存儲鍵值對。只能存放Boolean屯碴,F(xiàn)loat描睦,Int,Long导而,String 五種類型忱叭。editor.putXxx("key","value")。
- 3.通過editor.commit()提交數(shù)據(jù)今艺。也可以通過clean(),remove()清除韵丑。
- 4.數(shù)據(jù)存儲在Android系統(tǒng)的 /data/data/"app package name"/shared_prefs 目錄下的一個.xml文件。
這里我們還進行了簡單的封裝虚缎,加上單例模式撵彻,更好的實現(xiàn)我們想要功能的訪問
import android.annotation.SuppressLint
import android.content.Context
class SharedPreferenceUtil private constructor() {
private val key = "PASSWORD"
/**
* 單例模式
* */
companion object {
private var mContext: Context? = null
private var instance: SharedPreferenceUtil? = null
fun getInstance(context: Context): SharedPreferenceUtil {
mContext = context
if (instance == null) {
instance = SharedPreferenceUtil()
}
return instance!!
}
}
/**
* 保存圖案密碼
* */
@SuppressLint("CommitPrefEdits")
fun savePassword(pwd: String) {
//獲取SharedPreferences對象
mContext?.getSharedPreferences("config", Context.MODE_PRIVATE).also {
val edit = it?.edit()
//寫入數(shù)據(jù)
edit?.putString(key, pwd)
edit?.apply()
}
}
/**
* 獲取保存的數(shù)據(jù)(這里就是圖案密碼)
* */
fun getPassword(): String? {
mContext?.getSharedPreferences("config", Context.MODE_PRIVATE).also {
return it?.getString(key,null)
}
}
}
接下來就是實現(xiàn)圖案解鎖功能了,那么如何實現(xiàn)呢?
這里主要是通過將觸摸點坐標轉(zhuǎn)換為相對于根容器实牡,也就是上面我們提到的約束布局容器的坐標千康,然后判斷觸摸點是否在某一個圓內(nèi),是的話就顯示選中狀態(tài)的效果(默認不可見)
獲取狀態(tài)欄高度铲掐,使用懶加載,只加載一次
/**
* 獲取狀態(tài)欄高度值桩,使用懶加載摆霉,只加載一次
* */
private val barHeight:Int by lazy{
//獲取屏幕尺寸
val display = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(display)
//獲取空白區(qū)域尺寸
val rect = Rect()
window.findViewById<ViewGroup>(Window.ID_ANDROID_CONTENT).getDrawingRect(rect)
display.heightPixels - rect.height()
}
將觸摸點的坐標轉(zhuǎn)化為相對容器的坐標
/**
* 將觸摸點的坐標轉(zhuǎn)化為相對容器的坐標
* */
private fun convertPointAsContainer(event: MotionEvent):Point{
return Point().apply {
x = (event.x.minus(mContainer.x)).toInt()
y = (event.y - barHeight - mContainer.y).toInt()
}
}
獲取當前觸摸點所在的圓
/**
* 獲取當前觸摸點所在的圓
* */
private fun getCurrPointOfCircle(point: Point):ImageView?{
for (dot in dots){
//遍歷判斷是否包含當前觸摸點
getRect(dot).also {
if (it.contains(point.x,point.y)){
return dot
}
}
}
return null
}
/**
* 獲取當前控件對應的Rect
* */
private fun getRect(v:ImageView) = Rect(v.left,v.top,v.right,v.bottom)
點亮圓
/**
* 點亮圓
* */
private fun setSmallCircleVisible(v: ImageView) {
v.visibility = View.VISIBLE
selectedViews.add(v)
password.append(v.tag)
lastSelectView = v
scaleAnimation(v, true)
}
點亮線
//記錄點亮的上一個圓
private var lastSelectView: ImageView? = null
這里首先記錄上一個觸摸點所在圓,然后根據(jù)當前觸摸點所在圓的tag值和上一個圓的tag拼接找到對應的線的tag值奔坟,再根據(jù)tag值找到線携栋,然后點亮這條線
/**
* 點亮線
* */
private fun setLineVisible(v: ImageView) {
//連線
val lTag = (lastSelectView?.tag as String).toInt()
val cTag = (v.tag as String).toInt()
val lineTag = if (lTag > cTag) cTag * 10 + lTag else lTag * 10 + cTag
//判斷是否有這條線,如果有咳秉,點亮
if (tags.contains(lineTag)) {
//先點亮這個點
if (v.visibility != View.VISIBLE) {
setSmallCircleVisible(v)
}
//再點亮連線
mContainer.findViewWithTag<ImageView>(lineTag.toString()).apply {
visibility = View.VISIBLE
selectedViews.add(this)
}
}
}
然后點亮圓和線在一個方法里使用
/**
* 點亮點和線
* */
private fun setCircleAndLineVisible(v: ImageView?) {
if (v != null && v.visibility == View.INVISIBLE) {
//判斷是否是第一個點亮的點
if (lastSelectView == null) {
setSmallCircleVisible(v)
} else {
setLineVisible(v)
}
} else {
if (lastSelectView != null && v != null) {
if (isLineExit(lastSelectView!!, v)) {
setLineVisible(v)
}
}
}
}
這里的isLineExit方法婉支,傳入兩個參數(shù),分別是上一個圓和當前圓澜建,目的是為了使兩個已經(jīng)點亮的圓之間的線點亮
/**
* 判斷是否存在點亮的兩個小圓之間的連線未點亮
* */
private fun isLineExit(last: ImageView, curr: ImageView): Boolean {
if (last.visibility == View.VISIBLE && curr.visibility == View.VISIBLE) {
return true
}
return false
}
接下來是密碼設置和保存向挖,主要使用Sharedpreference來記錄用戶設置的密碼
/**
* 判斷兩次密碼是否一致
* */
private fun isPasswordSetSucceed(first: String, second: String) {
if (first == second) {
mTextView.text = "設置密碼成功"
//保存密碼
SharedPreferenceUtil.getInstance(this).savePassword(first)
orgPassWord = SharedPreferenceUtil.getInstance(this).getPassword()
} else {
mTextView.text = "兩次密碼不一致,請重新設置密碼"
firstPassWord = null
}
}
最后如果想在觸摸小圓上加一點動畫炕舵,可以在觸摸時放大或縮小圓點以增強用戶使用感
/**
* 縮放動畫
* */
private fun scaleAnimation(v: ImageView, flag: Boolean) {
AnimationUtils.loadAnimation(this, R.anim.scale_anim_set).apply {
duration = 200
fillAfter = true
if (flag) {
v.startAnimation(this)
} else {
v.clearAnimation()
}
}
}
這里的flag參數(shù)的目的是何之,便于控制動畫的開啟和取消,當我們觸摸時咽筋,flag傳入?yún)?shù)為true,當我們松手時溶推,應該取消動畫,這時flag傳入?yún)?shù)便為false
開啟動畫
scaleAnimation(v, true)
取消動畫(松手時使用)
scaleAnimation(v, false)