Android手勢(shì)密碼view學(xué)習(xí)筆記(一)
剛接觸Android的時(shí)候看到別人寫的手勢(shì)密碼view,然后當(dāng)時(shí)就在想,我什么時(shí)候才能寫出如此高端的東西?? 沒關(guān)系,不要怕哈,說出這樣話的人不是你技術(shù)不咋地而是你不愿意花時(shí)間去研究它,其實(shí)也沒有那么難哦(世上無難事,只怕有心人?。旅嫖覀兙鸵徊揭徊綄?shí)現(xiàn)一個(gè)手勢(shì)密碼view。
想必都看過手勢(shì)密碼view,但我們還是看看我們今天要實(shí)現(xiàn)的效果吧:

上面是一個(gè)手勢(shì)view的提示view,下面是一個(gè)手勢(shì)view。
用法:
<com.leo.library.view.GestureContentView android:id="@+id/id_gesture_pwd" android:layout_gravity="center_horizontal" android:layout_marginTop="10dp" android:layout_width="match_parent" android:layout_height="match_parent" app:column="3" app:row="3" app:padding="50dp" app:normalDrawable="@drawable/gesture_node_normal" app:selectedDrawable="@drawable/gesture_node_pressed" app:erroDrawable="@drawable/gesture_node_wrong" app:normalStrokeColor="#000" app:erroStrokeColor="#ff0000" app:strokeWidth="4dp" />
app打頭的是自定義的一些屬性,
attrs.xml:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="IndicatorView"> <!--默認(rèn)狀態(tài)的drawable--> <attr name="normalDrawable" format="reference" /> <!--被選中狀態(tài)的drawable--> <attr name="selectedDrawable" format="reference" /> <!--錯(cuò)誤狀態(tài)的drawabe--> <attr name="erroDrawable" format="reference" /> <!--列數(shù)--> <attr name="column" format="integer" /> <!--行數(shù)--> <attr name="row" format="integer" /> <!--padding值,padding值越大點(diǎn)越小--> <attr name="padding" format="dimension" /> <!--默認(rèn)連接線顏色--> <attr name="normalStrokeColor" format="color" /> <!--錯(cuò)誤連接線顏色--> <attr name="erroStrokeColor" format="color" /> <!--連接線size--> <attr name="strokeWidth" format="dimension" /> </declare-styleable> </resources>
MainActivity.java:
public class MainActivity extends AppCompatActivity implements IGesturePwdCallBack {
private GestureContentView mGestureView;
private IndicatorView indicatorView;
private TextView tvIndicator;
private int count=0;
private String pwd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGestureView= (GestureContentView) findViewById(R.id.id_gesture_pwd);
indicatorView= (IndicatorView) findViewById(R.id.id_indicator_view);
tvIndicator= (TextView) findViewById(R.id.id_indicator);
mGestureView.setGesturePwdCallBack(this);
}
@Override
public void callBack(List<Integer> pwds) {
StringBuffer sbPwd=new StringBuffer();
for (Integer pwd:pwds) {
sbPwd.append(pwd);
}
tvIndicator.setText(sbPwd.toString());
if(pwds!=null&&pwds.size()>0){
indicatorView.setPwds(pwds);
}
if(count++==0){
pwd=sbPwd.toString();
Toast.makeText(this,"請(qǐng)?jiān)俅卫L制手勢(shì)密碼",Toast.LENGTH_SHORT).show();
mGestureView.changePwdState(PointState.POINT_STATE_NORMAL,0);
} else{
count=0;
if(pwd.equals(sbPwd.toString())){
Toast.makeText(this,"密碼設(shè)置成功",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this,"兩次密碼不一致,請(qǐng)重新繪制",Toast.LENGTH_SHORT).show();
indicatorView.startAnimation(AnimationUtils.loadAnimation(this,R.anim.anim_shake));
count=0;
mGestureView.changePwdState(PointState.POINT_STATE_ERRO,0);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mGestureView.changePwdState(PointState.POINT_STATE_NORMAL,0);
}
},1000);
}
}
}
}
看不懂也沒關(guān)系啊,我們先明確下我們要完成的目標(biāo),然后一步一步實(shí)現(xiàn):
先實(shí)現(xiàn)下我們的指示器view,因?yàn)閷?shí)現(xiàn)了指示器view也就相當(dāng)于實(shí)現(xiàn)了一半的手勢(shì)密碼view了:

實(shí)現(xiàn)思路:
1、我們需要知道指示器有多少行、多少列、默認(rèn)顯示什么、選中后顯示什么?
2、然后根據(jù)傳入的密碼把對(duì)應(yīng)的點(diǎn)顯示成選中狀態(tài),沒有選中的點(diǎn)為默認(rèn)狀態(tài)。
好了,知道我們的思路,首先自定義一個(gè)view叫IndicatorView繼承view,然后重寫三個(gè)構(gòu)造方法:
public class IndicatorView extends View {
public IndicatorView(Context context) {
this(context, null);
}
public IndicatorView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
定義自定義屬性(在res/values下創(chuàng)建attrs.xml文件):
1、我們需要傳入的默認(rèn)顯示圖片:
<!--默認(rèn)狀態(tài)的drawable--> <attr name="normalDrawable" format="reference" />
2、我們需要拿到傳入的選中時(shí)圖片:
<!--被選中狀態(tài)的drawable--> <attr name="selectedDrawable" format="reference" />
其它的一些屬性:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="IndicatorView"> <!--默認(rèn)狀態(tài)的drawable--> <attr name="normalDrawable" format="reference" /> <!--被選中狀態(tài)的drawable--> <attr name="selectedDrawable" format="reference" /> <!--列數(shù)--> <attr name="column" format="integer" /> <!--行數(shù)--> <attr name="row" format="integer" /> </declare-styleable> </resources>
定義完屬性后,此時(shí)我們xml中就可以引用自定義view了:
<com.leo.library.view.IndicatorView android:id="@+id/id_indicator_view" android:layout_marginTop="20dp" android:layout_width="85dp" android:layout_height="85dp" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" app:column="3" app:normalDrawable="@drawable/shape_white_indicator" app:padding="8dp" app:row="3" app:selectedDrawable="@drawable/shape_orange_indicator" />
注意:
中間的drawable文件可以在github項(xiàng)目中找到,鏈接我會(huì)在文章最后給出。
有了自定義屬性,然后我們?cè)趲齻€(gè)參數(shù)的構(gòu)造方法中獲取我們?cè)诓季治募魅氲淖远x屬性:
private static final int NUMBER_ROW = 3; private static final int NUMBER_COLUMN = 3; private int DEFAULT_PADDING = dp2px(10); private final int DEFAULT_SIZE = dp2px(40); private Bitmap mNormalBitmap; private Bitmap mSelectedBitmap; private int mRow = NUMBER_ROW; private int mColumn = NUMBER_COLUMN;
public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.IndicatorView, defStyleAttr, 0);
mNormalBitmap = drawableToBitmap(a.getDrawable(R.styleable.IndicatorView_normalDrawable));
mSelectedBitmap = drawableToBitmap(a.getDrawable(R.styleable.IndicatorView_selectedDrawable));
if (a.hasValue(R.styleable.IndicatorView_row)) {
mRow = a.getInt(R.styleable.IndicatorView_row, NUMBER_ROW);
}
if (a.hasValue(R.styleable.IndicatorView_column)) {
mColumn = a.getInt(R.styleable.IndicatorView_row, NUMBER_COLUMN);
}
if (a.hasValue(R.styleable.IndicatorView_padding)) {
DEFAULT_PADDING = a.getDimensionPixelSize(R.styleable.IndicatorView_padding, DEFAULT_PADDING);
}
}
好了,現(xiàn)在我們已經(jīng)拿到了我們想要的東西了,接下來我們需要知道我的view要多大,相比小伙伴都知道接下來要干什么了吧?對(duì)~! 我們需要重寫下onMeasure方法,然后指定我們view的大?。?/p>
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
}
那么我們?cè)撘砸粋€(gè)什么樣的規(guī)則指定我們的view的大小呢?
1、當(dāng)用戶自己指定了view的大小的話,我們就用用戶傳入的size,然后根據(jù)傳入的寬、高計(jì)算出我們的點(diǎn)的大小。
<com.leo.library.view.IndicatorView android:id="@+id/id_indicator_view" android:layout_marginTop="20dp" android:layout_width="85dp" android:layout_height="85dp"
2、如果用戶沒有指定view的大小,寬高都設(shè)置為wrap_content的話,我們需要根據(jù)用戶傳入的選中圖片跟沒選中圖片的大小計(jì)算view的大小:
android:layout_width="wrap_content" android:layout_height="wrap_content"
好了,既然知道咋測(cè)量我們的view后,我們接下來就實(shí)現(xiàn)出來:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
float width = MeasureSpec.getSize(widthMeasureSpec);
float height = MeasureSpec.getSize(heightMeasureSpec);
float result=Math.min(width,height);
height = getHeightValue(result, heightMode);
width = getWidthValue(result, widthMode);
}
private float getHeightValue(float height, int heightMode) {
//當(dāng)size為確定的大小的話
//每個(gè)點(diǎn)的高度等于(控件的高度-(行數(shù)+1)*padding值)/行數(shù)
if (heightMode == MeasureSpec.EXACTLY) {
mCellHeight = (height - (mRow + 1) * DEFAULT_PADDING) / mRow;
} else {
//高度不確定的話,我們就取選中的圖片跟未選中圖片中的高度的最小值
mCellHeight = Math.min(mNormalBitmap.getHeight(), mSelectedBitmap.getHeight());
//此時(shí)控件的高度=點(diǎn)的高度*行數(shù)+(行數(shù)+1)*默認(rèn)padding值
height = mCellHeight * mRow + (mRow + 1) * DEFAULT_PADDING;
}
return height;
}
寬度計(jì)算方式也是一樣的話,只是行數(shù)換成了列數(shù):
private float getWidthValue(float width, int widthMode) {
if (widthMode == MeasureSpec.EXACTLY) {
mCellWidth = (width - (mColumn + 1) * DEFAULT_PADDING) / mColumn;
} else {
mCellWidth = Math.min(mNormalBitmap.getWidth(), mSelectedBitmap.getWidth());
width = mCellWidth * mColumn + (mColumn + 1) * DEFAULT_PADDING;
}
return width;
}
好了,現(xiàn)在是知道了點(diǎn)的高度跟寬度,然后控件的寬高自然也就知道了,但是如果我們傳入的選中的圖片跟未選擇的圖片大小不一樣咋辦呢?沒關(guān)系,接下來我們重新修改下圖片的size:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
.....
height = getHeightValue(result, heightMode);
width = getWidthValue(result, widthMode);
setMeasuredDimension((int) width, (int) height);
//重新修改圖片的size
resizeBitmap(mCellWidth, mCellHeight);
}
private void resizeBitmap(float width, float height) {
if (width > 0 && height > 0) {
if (mNormalBitmap.getWidth() != width || mNormalBitmap.getHeight() !=height) {
if (mNormalBitmap.getWidth() > 0 && mNormalBitmap.getHeight() > 0) {
mNormalBitmap = Bitmap.createScaledBitmap(mNormalBitmap, (int) width, (int) height, false);
}
}
if (mSelectedBitmap.getWidth()!=width || mSelectedBitmap.getHeight() !=height) {
if (mSelectedBitmap.getWidth() > 0 && mSelectedBitmap.getHeight() > 0) {
mSelectedBitmap = Bitmap.createScaledBitmap(mSelectedBitmap, (int) width, (int) height, false);
}
}
}
}
好了,圖片也拿到了,控件的寬高跟點(diǎn)的寬高都知道,所以接下來我們?cè)撨M(jìn)入我們的核心代碼了(重寫onDraw方法,畫出我們的點(diǎn)):
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//遍歷行數(shù)
for (int i = 0; i < mRow; i++) {
//遍歷列數(shù)
for (int j = 0; j < mColumn; j++) {
float left = (j + 1) * DEFAULT_PADDING + j * mCellWidth;
float top = (i + 1) * DEFAULT_PADDING + i * mCellHeight;
//每個(gè)點(diǎn)代表的密碼值=點(diǎn)對(duì)應(yīng)的行數(shù)值*列數(shù)+對(duì)應(yīng)的列數(shù)
//比如3*3的表格,然后第二排的第一個(gè)=1*3+0=3
int num=i * mColumn + j;
//此點(diǎn)是不是在傳入的密碼集合中?
if (pwds!=null&&pwds.contains(num)) {
//這個(gè)點(diǎn)在傳入的密碼集合中的話就畫一個(gè)選中的bitmap
canvas.drawBitmap(mSelectedBitmap, left, top, null);
} else {
canvas.drawBitmap(mNormalBitmap, left, top, null);
}
}
}
}
嗯嗯?。∪缓笪覀儽┞兑粋€(gè)方法,讓外界傳入需要現(xiàn)實(shí)的密碼集合:
public void setPwds(List<Integer> pwds) {
if(pwds!=null)this.pwds=pwds;
if (Looper.myLooper() == Looper.getMainLooper()) {
invalidate();
} else {
postInvalidate();
}
}
好啦~~ 我們的指示器view就做完啦~~~ 是不是很簡(jiǎn)單呢? 指示器view做完后,再想想手勢(shì)密碼view,是不是就只是差根據(jù)手勢(shì)改變,然后畫出我們的line呢?
現(xiàn)附上項(xiàng)目的github鏈接:
https://github.com/913453448/GestureContentView/
下一節(jié)我們將一起實(shí)現(xiàn)一下手勢(shì)密碼view。
Android手勢(shì)密碼view筆記(二)
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android手勢(shì)密碼的實(shí)現(xiàn)
- Android自定義控件實(shí)現(xiàn)手勢(shì)密碼
- Android自定義UI手勢(shì)密碼終結(jié)版
- Android仿支付寶手勢(shì)密碼解鎖功能
- Android手勢(shì)密碼實(shí)現(xiàn)實(shí)例代碼
- Android九宮格手勢(shì)密碼代碼設(shè)計(jì)
- Android自定義UI手勢(shì)密碼改進(jìn)版源碼下載
- Android自定義UI手勢(shì)密碼簡(jiǎn)單版
- Android手勢(shì)密碼view學(xué)習(xí)筆記(二)
- Android自定義View手勢(shì)密碼
相關(guān)文章
Android4.0開發(fā)之Keyguard解鎖屏機(jī)制詳解
這篇文章主要介紹了Android4.0開發(fā)之Keyguard解鎖屏機(jī)制,結(jié)合實(shí)例形式詳細(xì)分析了Android開發(fā)中Keyguard解鎖屏模塊的原理、使用方法與相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2017-12-12
詳細(xì)講解Android中使用LoaderManager加載數(shù)據(jù)的方法
這篇文章主要介紹了Android中使用LoaderManager加載數(shù)據(jù)的方法,講到了LoaderManager的異步加載與聲明周期的管理等相關(guān)用法,需要的朋友可以參考下2016-04-04
詳解Android開發(fā)技巧之PagerAdapter實(shí)現(xiàn)類的封裝
這篇文章主要介紹了詳解Android開發(fā)技巧之PagerAdapter實(shí)現(xiàn)類的封裝,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11
Android嚴(yán)苛模式StrictMode使用詳解
StrictMode類是Android 2.3 (API 9)引入的一個(gè)工具類,可以用來幫助開發(fā)者發(fā)現(xiàn)代碼中的一些不規(guī)范的問題,以達(dá)到提升應(yīng)用響應(yīng)能力的目的2018-01-01
Android 自定義View實(shí)現(xiàn)抽屜效果
這篇文章主要介紹了Android 自定義View實(shí)現(xiàn)抽屜效果的相關(guān)資料,需要的朋友可以參考下2017-05-05
android dialog根據(jù)彈窗等級(jí)排序顯示的示例代碼
這篇文章主要介紹了android dialog根據(jù)彈窗等級(jí)排序顯示,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
Android獲取手機(jī)SIM卡運(yùn)營(yíng)商信息的方法
這篇文章主要介紹了Android獲取手機(jī)SIM卡運(yùn)營(yíng)商信息的方法,可獲得手機(jī)的型號(hào)、運(yùn)營(yíng)商信息及系統(tǒng)版本等,需要的朋友可以參考下2014-09-09

