Android手勢密碼view學(xué)習(xí)筆記(二)
我們還是接著我們上一篇博客中的內(nèi)容往下講哈,上一節(jié) Android手勢密碼view筆記(一)我們已經(jīng)實(shí)現(xiàn)了我們的IndicatorView指示器view了:
下面我們來實(shí)現(xiàn)下我們的手勢密碼view:
實(shí)現(xiàn)思路:
1、我們照樣需要拿到用戶需要顯示的一些屬性(行、列、選中的圖片、未選中的圖片、錯誤顯示的圖片、連接線的寬度跟顏色......)。
2、我們需要根據(jù)手勢的變換然后需要判斷當(dāng)前手指位置是不是在某個點(diǎn)中,在的話就把該點(diǎn)設(shè)置為選中狀態(tài),然后每移動到兩個點(diǎn)(也就是一個線段)就記錄該兩個點(diǎn)。
3、最后把記錄的所有點(diǎn)(所有線段)畫在canvas上,并記錄每個點(diǎn)對應(yīng)的num值(也就是我們設(shè)置的密碼)。
4、當(dāng)手指抬起的時候,執(zhí)行回調(diào)方法,把封裝的密碼集合傳給調(diào)用著。
好啦~ 既然右了思路,我們就來實(shí)現(xiàn)下:
首先是定義一個attrs.xml文件(為了方便,我就直接在上一篇博客中實(shí)現(xiàn)的indicatorview的attr里面繼續(xù)往下定義了):
<?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" /> <!--錯誤狀態(tài)的drawabe--> <attr name="erroDrawable" format="reference" /> <!--padding值,padding值越大點(diǎn)越小--> <attr name="padding" format="dimension" /> <!--默認(rèn)連接線顏色--> <attr name="normalStrokeColor" format="color" /> <!--錯誤連接線顏色--> <attr name="erroStrokeColor" format="color" /> <!--連接線size--> <attr name="strokeWidth" format="dimension" /> </declare-styleable> </resources>
然后就是第一個叫GestureContentView的view去繼承viewgroup,并重新三個構(gòu)造方法:
public class GestureContentView extends ViewGroup { public void setGesturePwdCallBack(IGesturePwdCallBack gesturePwdCallBack) { this.gesturePwdCallBack = gesturePwdCallBack; } public GestureContentView(Context context) { this(context, null); } public GestureContentView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public GestureContentView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } }
然后我們需要在帶三個參數(shù)的構(gòu)造方法中獲取我們傳入的自定義屬性:
public GestureContentView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setWillNotDraw(false); obtainStyledAttr(context, attrs, defStyleAttr); initViews(); }
你會發(fā)現(xiàn)我這里調(diào)用下setWillNotDraw(false);方法,顧名思義,設(shè)置為false后onDraw方法才會被調(diào)用,當(dāng)然你也可以重寫dispatchDraw方法(具體原因我就不扯了哈,自己網(wǎng)上查或者看view的源碼)。
private void obtainStyledAttr(Context context, AttributeSet attrs, int defStyleAttr) { final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.IndicatorView, defStyleAttr, 0); mNormalDrawable = a.getDrawable(R.styleable.IndicatorView_normalDrawable); mSelectedDrawable = a.getDrawable(R.styleable.IndicatorView_selectedDrawable); mErroDrawable = a.getDrawable(R.styleable.IndicatorView_erroDrawable); checkDrawable(); 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); } strokeColor=a.getColor(R.styleable.IndicatorView_normalStrokeColor,DEFAULT_STROKE_COLOR); erroStrokeColor=a.getColor(R.styleable.IndicatorView_erroStrokeColor,ERRO_STROKE_COLOR); strokeWidth=a.getDimensionPixelSize(R.styleable.IndicatorView_strokeWidth,DEFAULT_STROKE_W); }
然后獲取到了我們需要的東西后,我們需要知道每個點(diǎ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); setMeasuredDimension((int) width, (int) height); }
private float getHeightValue(float height, int heightMode) { if (heightMode == MeasureSpec.EXACTLY) { mCellHeight = (height - (mColumn + 1) * DEFAULT_PADDING) / mColumn; } else { mCellHeight = Math.min(mNormalDrawable.getIntrinsicHeight(), mSelectedDrawable.getIntrinsicHeight()); height = mCellHeight * mColumn + (mColumn + 1) * DEFAULT_PADDING; } return height; } private float getWidthValue(float width, int widthMode) { if (widthMode == MeasureSpec.EXACTLY) { mCellWidth = (width - (mRow + 1) * DEFAULT_PADDING) / mRow; } else { mCellWidth = Math.min(mNormalDrawable.getIntrinsicWidth(), mSelectedDrawable.getIntrinsicWidth()); width = mCellWidth * mRow + (mRow + 1) * DEFAULT_PADDING; } return width; }
好了,view的大小跟點(diǎn)的大小我們都知道了,然后我們需要根據(jù)我們傳入的行數(shù)跟列數(shù)添加我們的點(diǎn)了:
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (!isInitialed && getChildCount() == 0) { isInitialed = true; points = new ArrayList<>(); addChildViews(); } }
首先在onSizeChanged方法中去添加我們的點(diǎn)(也就是子view)因?yàn)閛nSizeChanged會被調(diào)很多次,然后避免重復(fù)添加子view,我們做了一個判斷,第一次并且child=0的時候再去添加子view:
private void addChildViews() { for (int i = 0; i < mRow; i++) { for (int j = 0; j < mColumn; j++) { GesturePoint point = new GesturePoint(); ImageView image = new ImageView(getContext()); point.setImageView(image); int left = (int) ((j + 1) * DEFAULT_PADDING + j * mCellWidth); int top = (int) ((i + 1) * DEFAULT_PADDING + i * mCellHeight); int right = (int) (left + mCellWidth); int bottom = (int) (top + mCellHeight); point.setLeftX(left); point.setRightX(right); point.setTopY(top); point.setBottomY(bottom); point.setCenterX((int) (left + mCellWidth / 2)); point.setCenterY((int) (top + mCellHeight / 2)); point.setNormalDrawable(mNormalDrawable); point.setErroDrawable(mErroDrawable); point.setSelectedDrawable(mSelectedDrawable); point.setState(PointState.POINT_STATE_NORMAL); point.setNum(Integer.parseInt(String.valueOf(mRow * i + j))); point.setPointX(i); point.setPointY(j); this.addView(image, (int) mCellWidth, (int) mCellHeight); points.add(point); } } }
添加的個數(shù)=行數(shù)*列數(shù),然后把創(chuàng)建的image添加進(jìn)當(dāng)前viewgroup中:
for (int i = 0; i < mRow; i++) { for (int j = 0; j < mColumn; j++) { GesturePoint point = new GesturePoint(); ImageView image = new ImageView(getContext()); ...... this.addView(image, (int) mCellWidth, (int) mCellHeight); } }
并且把所有的點(diǎn)信息存放在了一個叫points.add(point);的集合中,集合中存放的是GesturePoint對象:
public class GesturePoint { //點(diǎn)的左邊距值 private int leftX; //點(diǎn)的top值 private int topY; //點(diǎn)的右邊距值 private int rightX; private int bottomY; //點(diǎn)的中間值x軸 private int centerX; private int centerY; //點(diǎn)對應(yīng)的行值 private int pointX; //點(diǎn)對應(yīng)的列值 private int pointY; //點(diǎn)對應(yīng)的imageview private ImageView imageView; //當(dāng)前點(diǎn)的狀態(tài):選中、未選中、錯誤 private PointState state; //當(dāng)前點(diǎn)對應(yīng)的密碼數(shù)值 private int num; //未選中點(diǎn)的drawale private Drawable normalDrawable; private Drawable erroDrawable; private Drawable selectedDrawable; }
既然我們已經(jīng)添加了很多個點(diǎn),然后我們要做的就是根據(jù)行列排列我們點(diǎn)的位置了(重寫onLayout方法擺放子view(點(diǎn))的位置):
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (points != null && points.size() > 0) { for (GesturePoint point : points) { point.layout(); } } }
public void layout() { if (this.imageView != null) { this.imageView.layout(leftX, topY, rightX, bottomY); } }
然后我們添加下我們的view并運(yùn)行代碼:
<FrameLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:layout_marginTop="10dp" > <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" /> </FrameLayout>
效果圖:
寫到這里,離我們的目標(biāo)越來越近了,接下來要做的就是重寫onTouchEvent方法,然后通過手指滑動位置做出相應(yīng)的改變了:
1、我們需要根據(jù)手指的位置查看是否在某個點(diǎn)內(nèi),判斷該點(diǎn)是不是被選中,沒選中就改變其狀態(tài)為選中狀態(tài)。
2、手指每連接兩個點(diǎn)的時候,我們需要判斷兩個點(diǎn)中間是否有點(diǎn),比如:我們手指連接了(0,0) 跟(2,2)這兩個點(diǎn),那么我們需要判斷中間是否有點(diǎn)(1、1)存在。然后需要把線段(0,0)~(1、1)和線段(1、1)~(2、2)保存在集合中,然后下一次再畫線段的時候起點(diǎn)就為(2、2)點(diǎn)了。
3、沒選中一個點(diǎn)我們就把該點(diǎn)對應(yīng)的num值(密碼)存入集合中。
4、當(dāng)執(zhí)行ACTION_UP事件(也就是手指抬起的時候),回調(diào)方法,把存儲的集合數(shù)據(jù)傳給調(diào)用者。
@Override public boolean onTouchEvent(MotionEvent event) { //是否允許用戶繪制 if (!isDrawEnable) return super.onTouchEvent(event); //畫筆顏色設(shè)置為繪制顏色 linePaint.setColor(strokeColor); int action = event.getAction(); //當(dāng)手指按下的時候 if (MotionEvent.ACTION_DOWN == action) { //清除畫板 changeState(PointState.POINT_STATE_NORMAL); preX = (int) event.getX(); preY = (int) event.getY(); //根據(jù)當(dāng)前手指位置找出對應(yīng)的點(diǎn) currPoint = getPointByPosition(preX, preY); //如果當(dāng)前手指在某個點(diǎn)中的時候,把該點(diǎn)標(biāo)記為選中狀態(tài) if (currPoint != null) { currPoint.setState(PointState.POINT_STATE_SELECTED); //把當(dāng)前選中的點(diǎn)添加進(jìn)集合中 pwds.add(currPoint.getNum()); } //當(dāng)手指移動的時候 } else if (MotionEvent.ACTION_MOVE == action) { //,清空畫板,然后畫出前面存儲的線段 clearScreenAndDrawLine(); //獲取當(dāng)前移動的位置是否在某個點(diǎn)中 GesturePoint point = getPointByPosition((int) event.getX(), (int) event.getY()); //沒有在點(diǎn)的范圍內(nèi)的話并且currpoint也為空的時候(在畫板外移動手指)直接返回 if (point == null && currPoint == null) { return super.onTouchEvent(event); } else { //當(dāng)按下時候的點(diǎn)為空,然后手指移動到了某一點(diǎn)的時候,把該點(diǎn)賦給currpoint if (currPoint == null) { currPoint = point; //修改該點(diǎn)的狀態(tài) currPoint.setState(PointState.POINT_STATE_SELECTED); //添加該點(diǎn)的值 pwds.add(currPoint.getNum()); } } //當(dāng)移動的不在點(diǎn)范圍內(nèi)、一直在同一個點(diǎn)中移動、選中了某個點(diǎn)后再次選中的時候不讓選中(也就是不讓出現(xiàn)重復(fù)密碼) if (point == null || currPoint.getNum() == point.getNum() || point.getState() == PointState.POINT_STATE_SELECTED) { lineCanvas.drawLine(currPoint.getCenterX(), currPoint.getCenterY(), event.getX(), event.getY(), linePaint); } else { //修改該點(diǎn)的狀態(tài)為選中 point.setState(PointState.POINT_STATE_SELECTED); //連接currpoint跟當(dāng)前point lineCanvas.drawLine(currPoint.getCenterX(), currPoint.getCenterY(), point.getCenterX(), point.getCenterY(), linePaint); //判斷兩個點(diǎn)中是否存在點(diǎn) List<Pair<GesturePoint, GesturePoint>> betweenPoints = getBetweenPoints(currPoint, point); //如果存在點(diǎn)的話,把中間點(diǎn)對應(yīng)的線段存入集合總 if (betweenPoints != null && betweenPoints.size() > 0) { pointPairs.addAll(betweenPoints); currPoint = point; pwds.add(point.getNum()); } else { pointPairs.add(new Pair(currPoint, point)); pwds.add(point.getNum()); currPoint = point; } } invalidate(); } else if (MotionEvent.ACTION_UP == action) { //手指抬起的時候回調(diào) if (gesturePwdCallBack != null) { List<Integer> datas=new ArrayList<>(pwds.size()); datas.addAll(pwds); gesturePwdCallBack.callBack(datas); } } return true; }
重點(diǎn)解釋下getBetweenPoints方法(聲明一下哈:本人數(shù)學(xué)比較差,有好方法的童鞋虐過哈。記得評論告訴我一下,拜謝啦~~ 自己覺得自己的方法比較蠢,嘻嘻~?。。?/p>
private List<Pair<GesturePoint, GesturePoint>> getBetweenPoints(GesturePoint currPoint, GesturePoint point) { //定義一個集合裝傳入的點(diǎn) List<GesturePoint> points1 = new ArrayList<>(); points1.add(currPoint); points1.add(point); //排序兩個點(diǎn) Collections.sort(points1, new Comparator<GesturePoint>() { @Override public int compare(GesturePoint o1, GesturePoint o2) { return o1.getNum() - o2.getNum(); } }); GesturePoint maxPoint = points1.get(1); GesturePoint minPoint = points1.get(0); points1.clear(); /** * 根據(jù)等差數(shù)列公式an=a1+(n-1)*d,我們知道an跟a1,n=(an的列或者行值-a1的列或者行值+1), * 算出d,如果d為整數(shù)那么為等差數(shù)列,如果an的列或者行值-a1的列或者行值>1的話,就證明存在 * 中間值。 * 1、算出的d是否為整數(shù) * 2、an的行值-a1的行值>1或者an的列值-a1的列值>1 * * 兩個條件成立的話就證明有中間點(diǎn) */ if (((maxPoint.getNum() - minPoint.getNum()) % Math.max(maxPoint.getPointX(), maxPoint.getPointY()) == 0) && ((maxPoint.getPointX() - minPoint.getPointX()) > 1 || maxPoint.getPointY() - minPoint.getPointY() > 1 )) { //算出等差d int duration = (maxPoint.getNum() - minPoint.getNum()) / Math.max(maxPoint.getPointX(), maxPoint.getPointY()); //算出中間有多少個點(diǎn) int count = maxPoint.getPointX() - minPoint.getPointX() - 1; count = Math.max(count, maxPoint.getPointY() - minPoint.getPointY() - 1); //利用等差數(shù)列公式算出中間點(diǎn)(an=a1+(n-1)*d) for (int i = 0; i < count; i++) { int num = minPoint.getNum() + (i + 1) * duration; for (GesturePoint p : this.points) { //在此判斷算出的中間點(diǎn)是否存在并且沒有被選中 if (p.getNum() == num && p.getState() != PointState.POINT_STATE_SELECTED) { //把選中的點(diǎn)添加進(jìn)集合 pwds.add(p.getNum()); //修改該點(diǎn)的狀態(tài)為選中狀態(tài) p.setState(PointState.POINT_STATE_SELECTED); points1.add(p); } } } } //利用算出的中間點(diǎn)來算出中間線段 List<Pair<GesturePoint, GesturePoint>> pairs = new ArrayList<>(); for (int i = 0; i < points1.size(); i++) { GesturePoint p = points1.get(i); if (i == 0) { pairs.add(new Pair(minPoint, p)); } else if (pairs.size() > 0) { pairs.add(new Pair(pairs.get(0).second, p)); } if (i == points1.size() - 1) { pairs.add(new Pair(p, maxPoint)); } } //返回中間線段 return pairs; }
好啦??!代碼就解析到這里了哈,看不懂的童鞋自己去拖代碼然后跑跑就知道了。
整個做下來除了某幾個地方有點(diǎn)難度外,其它的地方也都是很簡單的東西呢?以前看起來很高大上的東西是不是現(xiàn)在覺得soeasy了呢?? 哈哈~~~
最后給出項(xiàng)目github鏈接:
https://github.com/913453448/GestureContentView
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android中AutoCompleteTextView與MultiAutoCompleteTextView的用法
這篇文章主要介紹了Android中AutoCompleteTextView與MultiAutoCompleteTextView的用法,需要的朋友可以參考下2014-07-07Android?ViewPager2?+?Fragment?聯(lián)動效果的實(shí)現(xiàn)思路
這篇文章主要介紹了Android?ViewPager2?+?Fragment?聯(lián)動,本篇主要介紹一下 ViewPager2 + Fragment聯(lián)動效果的實(shí)現(xiàn)思路,需要的朋友可以參考下2022-12-12Android開發(fā)ThreadPoolExecutor與自定義線程池詳解
這篇文章主要為大家介紹了Android開發(fā)ThreadPoolExecutor與自定義線程池詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Flutter 實(shí)現(xiàn)進(jìn)度條效果
在一些上傳頁面炫酷的進(jìn)度條效果都是怎么實(shí)現(xiàn)的,今天小編通過本文給大家分享Flutter 一行代碼快速實(shí)現(xiàn)你的進(jìn)度條效果,感興趣的朋友一起看看吧2020-05-05Android巧用ViewPager實(shí)現(xiàn)左右循環(huán)滑動圖片
這篇文章主要為大家詳細(xì)介紹了Android巧用ViewPager實(shí)現(xiàn)左右循環(huán)滑動圖片的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-05-05Android高仿QQ6.0側(cè)滑刪除實(shí)例代碼
先給大家分享一下,側(cè)滑刪除,布局也就是前面一個item,然后有兩個隱藏的按鈕(TextView也可以),然后我們可以向左側(cè)滑動,然后顯示出來,然后對delete(刪除鍵)實(shí)現(xiàn)監(jiān)聽,就可以了哈。好了那就來看看代碼怎么實(shí)現(xiàn)的吧2016-02-02Android編程實(shí)現(xiàn)應(yīng)用獲取包名、版本號、權(quán)限等信息的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)應(yīng)用獲取包名、版本號、權(quán)限等信息的方法,涉及Android針對應(yīng)用相關(guān)信息的獲取操作實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-02-02