Android手勢密碼view學習筆記(二)
我們還是接著我們上一篇博客中的內(nèi)容往下講哈,上一節(jié) Android手勢密碼view筆記(一)我們已經(jīng)實現(xiàn)了我們的IndicatorView指示器view了:
下面我們來實現(xiàn)下我們的手勢密碼view:
實現(xiàn)思路:
1、我們照樣需要拿到用戶需要顯示的一些屬性(行、列、選中的圖片、未選中的圖片、錯誤顯示的圖片、連接線的寬度跟顏色......)。
2、我們需要根據(jù)手勢的變換然后需要判斷當前手指位置是不是在某個點中,在的話就把該點設(shè)置為選中狀態(tài),然后每移動到兩個點(也就是一個線段)就記錄該兩個點。
3、最后把記錄的所有點(所有線段)畫在canvas上,并記錄每個點對應(yīng)的num值(也就是我們設(shè)置的密碼)。
4、當手指抬起的時候,執(zhí)行回調(diào)方法,把封裝的密碼集合傳給調(diào)用著。
好啦~ 既然右了思路,我們就來實現(xiàn)下:
首先是定義一個attrs.xml文件(為了方便,我就直接在上一篇博客中實現(xiàn)的indicatorview的attr里面繼續(xù)往下定義了):
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="IndicatorView"> <!--默認狀態(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值越大點越小--> <attr name="padding" format="dimension" /> <!--默認連接線顏色--> <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)用,當然你也可以重寫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); }
然后獲取到了我們需要的東西后,我們需要知道每個點的大小跟自己的大小了(還是一樣的套路,不懂的看上一篇博客哈):
@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的大小跟點的大小我們都知道了,然后我們需要根據(jù)我們傳入的行數(shù)跟列數(shù)添加我們的點了:
@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方法中去添加我們的點(也就是子view)因為onSizeChanged會被調(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添加進當前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); } }
并且把所有的點信息存放在了一個叫points.add(point);的集合中,集合中存放的是GesturePoint對象:
public class GesturePoint { //點的左邊距值 private int leftX; //點的top值 private int topY; //點的右邊距值 private int rightX; private int bottomY; //點的中間值x軸 private int centerX; private int centerY; //點對應(yīng)的行值 private int pointX; //點對應(yīng)的列值 private int pointY; //點對應(yīng)的imageview private ImageView imageView; //當前點的狀態(tài):選中、未選中、錯誤 private PointState state; //當前點對應(yīng)的密碼數(shù)值 private int num; //未選中點的drawale private Drawable normalDrawable; private Drawable erroDrawable; private Drawable selectedDrawable; }
既然我們已經(jīng)添加了很多個點,然后我們要做的就是根據(jù)行列排列我們點的位置了(重寫onLayout方法擺放子view(點)的位置):
@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并運行代碼:
<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>
效果圖:
寫到這里,離我們的目標越來越近了,接下來要做的就是重寫onTouchEvent方法,然后通過手指滑動位置做出相應(yīng)的改變了:
1、我們需要根據(jù)手指的位置查看是否在某個點內(nèi),判斷該點是不是被選中,沒選中就改變其狀態(tài)為選中狀態(tài)。
2、手指每連接兩個點的時候,我們需要判斷兩個點中間是否有點,比如:我們手指連接了(0,0) 跟(2,2)這兩個點,那么我們需要判斷中間是否有點(1、1)存在。然后需要把線段(0,0)~(1、1)和線段(1、1)~(2、2)保存在集合中,然后下一次再畫線段的時候起點就為(2、2)點了。
3、沒選中一個點我們就把該點對應(yīng)的num值(密碼)存入集合中。
4、當執(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(); //當手指按下的時候 if (MotionEvent.ACTION_DOWN == action) { //清除畫板 changeState(PointState.POINT_STATE_NORMAL); preX = (int) event.getX(); preY = (int) event.getY(); //根據(jù)當前手指位置找出對應(yīng)的點 currPoint = getPointByPosition(preX, preY); //如果當前手指在某個點中的時候,把該點標記為選中狀態(tài) if (currPoint != null) { currPoint.setState(PointState.POINT_STATE_SELECTED); //把當前選中的點添加進集合中 pwds.add(currPoint.getNum()); } //當手指移動的時候 } else if (MotionEvent.ACTION_MOVE == action) { //,清空畫板,然后畫出前面存儲的線段 clearScreenAndDrawLine(); //獲取當前移動的位置是否在某個點中 GesturePoint point = getPointByPosition((int) event.getX(), (int) event.getY()); //沒有在點的范圍內(nèi)的話并且currpoint也為空的時候(在畫板外移動手指)直接返回 if (point == null && currPoint == null) { return super.onTouchEvent(event); } else { //當按下時候的點為空,然后手指移動到了某一點的時候,把該點賦給currpoint if (currPoint == null) { currPoint = point; //修改該點的狀態(tài) currPoint.setState(PointState.POINT_STATE_SELECTED); //添加該點的值 pwds.add(currPoint.getNum()); } } //當移動的不在點范圍內(nèi)、一直在同一個點中移動、選中了某個點后再次選中的時候不讓選中(也就是不讓出現(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 { //修改該點的狀態(tài)為選中 point.setState(PointState.POINT_STATE_SELECTED); //連接currpoint跟當前point lineCanvas.drawLine(currPoint.getCenterX(), currPoint.getCenterY(), point.getCenterX(), point.getCenterY(), linePaint); //判斷兩個點中是否存在點 List<Pair<GesturePoint, GesturePoint>> betweenPoints = getBetweenPoints(currPoint, point); //如果存在點的話,把中間點對應(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; }
重點解釋下getBetweenPoints方法(聲明一下哈:本人數(shù)學比較差,有好方法的童鞋虐過哈。記得評論告訴我一下,拜謝啦~~ 自己覺得自己的方法比較蠢,嘻嘻~?。。?/p>
private List<Pair<GesturePoint, GesturePoint>> getBetweenPoints(GesturePoint currPoint, GesturePoint point) { //定義一個集合裝傳入的點 List<GesturePoint> points1 = new ArrayList<>(); points1.add(currPoint); points1.add(point); //排序兩個點 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 * * 兩個條件成立的話就證明有中間點 */ 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()); //算出中間有多少個點 int count = maxPoint.getPointX() - minPoint.getPointX() - 1; count = Math.max(count, maxPoint.getPointY() - minPoint.getPointY() - 1); //利用等差數(shù)列公式算出中間點(an=a1+(n-1)*d) for (int i = 0; i < count; i++) { int num = minPoint.getNum() + (i + 1) * duration; for (GesturePoint p : this.points) { //在此判斷算出的中間點是否存在并且沒有被選中 if (p.getNum() == num && p.getState() != PointState.POINT_STATE_SELECTED) { //把選中的點添加進集合 pwds.add(p.getNum()); //修改該點的狀態(tài)為選中狀態(tài) p.setState(PointState.POINT_STATE_SELECTED); points1.add(p); } } } } //利用算出的中間點來算出中間線段 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; }
好啦??!代碼就解析到這里了哈,看不懂的童鞋自己去拖代碼然后跑跑就知道了。
整個做下來除了某幾個地方有點難度外,其它的地方也都是很簡單的東西呢?以前看起來很高大上的東西是不是現(xiàn)在覺得soeasy了呢?? 哈哈~~~
最后給出項目github鏈接:
https://github.com/913453448/GestureContentView
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android中AutoCompleteTextView與MultiAutoCompleteTextView的用法
這篇文章主要介紹了Android中AutoCompleteTextView與MultiAutoCompleteTextView的用法,需要的朋友可以參考下2014-07-07Android?ViewPager2?+?Fragment?聯(lián)動效果的實現(xiàn)思路
這篇文章主要介紹了Android?ViewPager2?+?Fragment?聯(lián)動,本篇主要介紹一下 ViewPager2 + Fragment聯(lián)動效果的實現(xiàn)思路,需要的朋友可以參考下2022-12-12Android開發(fā)ThreadPoolExecutor與自定義線程池詳解
這篇文章主要為大家介紹了Android開發(fā)ThreadPoolExecutor與自定義線程池詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11Android巧用ViewPager實現(xiàn)左右循環(huán)滑動圖片
這篇文章主要為大家詳細介紹了Android巧用ViewPager實現(xiàn)左右循環(huán)滑動圖片的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-05-05Android編程實現(xiàn)應(yīng)用獲取包名、版本號、權(quán)限等信息的方法
這篇文章主要介紹了Android編程實現(xiàn)應(yīng)用獲取包名、版本號、權(quán)限等信息的方法,涉及Android針對應(yīng)用相關(guān)信息的獲取操作實現(xiàn)技巧,需要的朋友可以參考下2018-02-02