自己定义九宫格手势解锁
項目中用到手勢解鎖,然而沒有在GitHub上找到想要的樣式= =,僅僅好自己來定義了。以下來看代碼~~
基本上非常多應用的手勢解鎖全都是九宮格的,內部內就是九個小圈圈而已。
那么我們就先來自己定義這個小圈圈吧~
圈圈的顏色選擇狀態有大致有三種狀態。所以我定義了一個枚舉來區分
package com.juzisang.com.library;/*** Created by 橘子桑 on 2016/3/27.*/ public enum LockState {SELECT_STATE,//選中ERRER_STATE, //錯誤DEFAULT_COLOR //默認 }圈圈分為邊框。內部填充色。還有內部圓。所以我定義了三個畫筆來區分。
package com.juzisang.com.library;import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View;/*** Created by 橘子桑 on 2016/3/27.*/ public class MarkerView extends View {//是否顯示內部的圈圈private boolean mInsideNodeShow;//寬度protected int mContentWidth;//寬度protected int mContentRadius;//選中狀態protected LockState mCurrentState = LockState.DEFAULT_COLOR;//畫邊框畫圓的的畫筆private Paint mNodeFramePaint;private Paint mNodeCirclePaint;private Paint mNodeFullPaint;//默認的顏色private int mDefaultColor = Color.parseColor("#757575");private int mDefailtFullColor = Color.parseColor("#64757575");private int mNodeDefaultColor = Color.parseColor("#757575");//選中的顏色private int mSelectColor = Color.parseColor("#7ECEF4");private int mFrameSelectFullColor = Color.parseColor("#647ECEF4");private int mNodeSelectColor = Color.parseColor("#7ECEF4");//錯誤時候的顏色private int mErrerColor = Color.parseColor("#EC6941");private int mErrerFullColor = Color.parseColor("#64EC6941");private int mErrerNodeColor = Color.parseColor("#EC6941");//邊框的寬度private int mFrameLineWidth;private int mNodeRadius;//每一個圈圈的內邊距private int mNodePadding;//觸摸有效的范圍private float mTouchRatio;//當前標記的位置private int mNum;public MarkerView(Context context, AttributeSet attrs) {super(context, attrs);initView(context, attrs, 0);}public MarkerView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView(context, attrs, defStyleAttr);}//以后外部布局傳來的參數public MarkerView(Context context, int mDefaultColor, int mDefailtFullColor, int mNodeDefaultColor,int mSelectColor, int mFrameSelectFullColor, int mNodeSelectColor,int mErrerColor, int mErrerFullColor, int mErrerNodeColor,int mFrameLineWidth, int mNodeRadius, int mNodePadding, boolean insideNodeShow) {super(context);this.mInsideNodeShow = insideNodeShow;this.mDefaultColor = mDefaultColor;this.mDefailtFullColor = mDefailtFullColor;this.mNodeDefaultColor = mNodeDefaultColor;this.mSelectColor = mSelectColor;this.mFrameSelectFullColor = mFrameSelectFullColor;this.mNodeSelectColor = mNodeSelectColor;this.mErrerColor = mErrerColor;this.mErrerFullColor = mErrerFullColor;this.mErrerNodeColor = mErrerNodeColor;this.mFrameLineWidth = mFrameLineWidth;this.mNodeRadius = mNodeRadius;this.mNodePadding = mNodePadding;//內邊距setPadding(mNodePadding, mNodePadding, mNodePadding, mNodePadding);//外部圓mNodeFramePaint = new Paint();mNodeFramePaint.setColor(mDefaultColor);mNodeFramePaint.setAntiAlias(true);mNodeFramePaint.setStrokeWidth(mFrameLineWidth);mNodeFramePaint.setStyle(Paint.Style.STROKE);//僅僅畫出邊框//內部填充色mNodeFullPaint = new Paint();mNodeFullPaint.setColor(mDefailtFullColor);mNodeFullPaint.setStyle(Paint.Style.FILL);mNodeFullPaint.setAntiAlias(true);//內部圓mNodeCirclePaint = new Paint();mNodeCirclePaint.setColor(mNodeDefaultColor);mNodeCirclePaint.setStyle(Paint.Style.FILL);//填充mNodeCirclePaint.setAntiAlias(true);}//取當前透明度的百分比public int getFullAlpha(int color, float ratio) {return Color.argb((int) (Color.alpha(color) * ratio), Color.red(color), Color.green(color), Color.blue(color));}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mContentWidth = getWidth();mContentRadius = mContentWidth / 2 - Math.abs(getPaddingLeft()) - mFrameLineWidth / 2;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);switch (mCurrentState) {case DEFAULT_COLOR: //默認mNodeFramePaint.setColor(mDefaultColor);mNodeFullPaint.setColor(mDefailtFullColor);mNodeCirclePaint.setColor(mNodeDefaultColor);//外圓canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius, mNodeFramePaint);//填充色canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius - mFrameLineWidth / 2, mNodeFullPaint);//中心圓if (mInsideNodeShow)canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mNodeRadius, mNodeCirclePaint);break;case ERRER_STATE://錯誤mNodeFramePaint.setColor(mErrerColor);mNodeFullPaint.setColor(mErrerFullColor);mNodeCirclePaint.setColor(mErrerNodeColor);//外圓canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius, mNodeFramePaint);//填充色canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius - mFrameLineWidth / 2, mNodeFullPaint);//中心圓canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mNodeRadius, mNodeCirclePaint);break;case SELECT_STATE://選中mNodeFramePaint.setColor(mSelectColor);mNodeFullPaint.setColor(mFrameSelectFullColor);mNodeCirclePaint.setColor(mNodeSelectColor);//外圓canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius, mNodeFramePaint);//填充色canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius - mFrameLineWidth / 2, mNodeFullPaint);//中心圓canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mNodeRadius, mNodeCirclePaint);break;}}//設置狀態,而且重繪public void setState(LockState CurrentState) {mCurrentState = CurrentState;invalidate();}//是否選中public boolean isHighLighted() {if (mCurrentState == LockState.SELECT_STATE || mCurrentState == LockState.ERRER_STATE) {return true;}return false;}//中心點Xpublic int getCenterX() {return (getLeft() + getRight()) / 2;}//中心點Ypublic int getCenterY() {return (getTop() + getBottom()) / 2;}//設置圈圈在手勢鎖其中的位置protected void setNum(int num) {mNum = num;}protected int getNum() {return mNum;} }以上就是一個簡單的圓了
那么,自己定義View當然會有自己定義屬性,所以有這么多T0T,不要問我為什么這么多屬性。任性= =(事實上我還想寫很多其它),自己定義屬性的方法
<!-- 線的顏色 --><attr name="lineColor" format="color" /><!-- 線的寬度 --><attr name="lineWidth" format="dimension" /><!--默認顏色 --><attr name="defaultColor" format="color" /><!--默認時的填充色--><attr name="defaultFullColor" format="color" /><!--默認內部圓顏色--><attr name="defaultNodeColor" format="color" /><!-- ======================================================= --><!-- 邊框選中時邊框的顏色 --><attr name="selectColor" format="color" /><!-- 邊框選中時內部的填充色 --><attr name="selectFrameFullColor" format="color" /><!--內部圓圈選中時的顏色--><attr name="selectNodeColor" format="color" /><!-- ======================================================= --><!-- 錯誤的顏色 --><attr name="errorColor" format="color" /><!--錯誤時內部的填充色--><attr name="errorFullColor" format="color" /><!-- 錯誤時的顏色 --><attr name="errorNodeColor" format="color" /><!-- ======================================================= --><!--邊框的的寬度--><attr name="frameLineWidth" format="dimension" /><!-- 內部圓圈的寬度 --><attr name="nodeRadius" format="dimension" /><!--內邊距--><attr name="nodePadding" format="dimension" /><!--觸摸有效的比例--><attr name="touchRatio" format="float" /><!-- 是否顯示內部的圓圈 --><attr name="insideNodeShow" format="boolean"/>LockView的代碼
package com.juzisang.com.library;import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ViewGroup;import java.util.ArrayList;/*** Created by 橘子桑 on 2016/3/27.*/ public class LockView extends ViewGroup {//畫連接線的畫筆private Paint mLinePaint;//能夠觸摸的區域百分比private float mTouchRatio;//線的顏色protected int mLineColor;//先的寬度protected float mLineWidth;//已經選中了的ViewArrayList<MarkerView> mNodeViews = new ArrayList<>();//存儲passwordprotected StringBuilder pawBuilder = new StringBuilder();//當前手指觸摸的x坐標protected float x;//當前手指觸摸的y坐標protected float y;//回調private onLockCallback mOnLockCallback;protected int mDefaultColor;protected int mSelectColor;protected int mErrerColor;//禁用手勢鎖private boolean mLockScreen;private boolean isTouch;//是否把連接線繪制在子View的上面private boolean mLineTop = false;//手指離開馬上重繪private boolean mFingerLeaveRedraw = true;public LockView(Context context) {this(context, null);}public LockView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public LockView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView(context, attrs, defStyleAttr);}protected void initView(Context context, AttributeSet attrs, int defStyleAttr) {TypedArray r = context.obtainStyledAttributes(attrs, R.styleable.MarkerView);boolean insideNodeShow = r.getBoolean(R.styleable.LockView_insideNodeShow, false);//默認的顏色mDefaultColor = r.getColor(R.styleable.LockView_defaultColor, context.getResources().getColor(android.R.color.holo_blue_dark));int mDefailtFullColor = r.getColor(R.styleable.LockView_defaultFullColor, getFullAlpha(mDefaultColor, 0.3F));int mNodeDefaultColor = (int) r.getColor(R.styleable.LockView_defaultNodeColor, mDefaultColor);//選中的顏色mSelectColor = (int) r.getColor(R.styleable.LockView_selectColor, context.getResources().getColor(android.R.color.holo_blue_light));int mFrameSelectFullColor = r.getColor(R.styleable.LockView_selectFrameFullColor, getFullAlpha(mSelectColor, 0.3F));int mNodeSelectColor = r.getColor(R.styleable.LockView_selectNodeColor, mSelectColor);//錯誤時候的顏色mErrerColor = r.getColor(R.styleable.LockView_errorColor, context.getResources().getColor(android.R.color.holo_red_light));int mErrerFullColor = r.getColor(R.styleable.LockView_errorFullColor, getFullAlpha(mErrerColor, 0.3F));int mErrerNodeColor = r.getColor(R.styleable.LockView_errorNodeColor, mErrerColor);//圓框變的寬度int mFrameLineWidth = (int) r.getDimension(R.styleable.LockView_frameLineWidth, DensityUtils.dip2px(context, 5));//內圓的直徑int mNodeRadius = (int) r.getDimension(R.styleable.LockView_nodeRadius, DensityUtils.dip2px(context, 5));//內邊距int mNodePadding = (int) r.getDimension(R.styleable.LockView_nodePadding, DensityUtils.dip2px(context, 10));//觸摸有效區域mTouchRatio = r.getFloat(R.styleable.LockView_touchRatio, mTouchRatio);mLineColor = r.getColor(R.styleable.LockView_lineColor, mDefaultColor);mLineWidth = r.getDimension(R.styleable.LockView_lineWidth, DensityUtils.dip2px(context, 5));r.recycle();//設置線的顏色mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);mLinePaint.setColor(mLineColor);mLinePaint.setStyle(Paint.Style.STROKE);mLinePaint.setStrokeWidth(mLineWidth);mLinePaint.setStrokeCap(Paint.Cap.ROUND);mLinePaint.setStrokeJoin(Paint.Join.ROUND);for (int i = 0; i < 9; i++) {MarkerView view = new MarkerView(context, mDefaultColor, mDefailtFullColor, mNodeDefaultColor, mSelectColor, mFrameSelectFullColor, mNodeSelectColor,mErrerColor, mErrerFullColor, mErrerNodeColor, mFrameLineWidth, mNodeRadius, mNodePadding, insideNodeShow);view.setNum(i + 1);ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);view.setLayoutParams(params);addView(view);}// 清除FLAG,否則 onDraw() 不會調用。原因是 ViewGroup 默認透明背景不須要調用 onDraw()setWillNotDraw(false);}public int getFullAlpha(int color, float ratio) {return Color.argb((int) (Color.alpha(color) * ratio), Color.red(color), Color.green(color), Color.blue(color));}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int size = Math.min(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); // 測量寬度setMeasuredDimension(size, size);for (int i = 0; i < getChildCount(); i++) {measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);}}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {if (changed) {float areaWidth = (r - l - getPaddingLeft() * 2) / 3;for (int n = 0; n < 9; n++) {MarkerView node = (MarkerView) getChildAt(n);// 獲取3*3宮格內坐標int row = n / 3;int col = n % 3;//加上內間距int left = (int) (getPaddingLeft() + col * areaWidth);int top = (int) (getPaddingTop() + row * areaWidth);int right = (int) (left + areaWidth);int bottom = (int) (top + areaWidth);node.layout(left, top, right, bottom);}}}/*** 設置連接線是否繪制在子View的上面* true 繪制在子View的上面* false 繪制在子View的以下** @param isLineTop 設置連接線是否繪制在子View的上面*/public void setLineTop(boolean isLineTop) {mLineTop = isLineTop;invalidate();}/*** 設置連接線是否繪制在子View的上面* true 繪制在子View的上面* false 繪制在子View的以下*/public boolean getLineTop() {return mLineTop;}@Overridepublic boolean onTouchEvent(MotionEvent event) {if (getLockScreen()) {invalidate();return false;}switch (event.getAction()) {case MotionEvent.ACTION_DOWN://恢復默認resetDefault();x = event.getX();y = event.getY();isTouch = true;break;case MotionEvent.ACTION_MOVE:x = event.getX(); // 這里要實時記錄手指的坐標y = event.getY();MarkerView nodeView = getNodeAt(x, y);//沒有選中if (nodeView != null && !nodeView.isHighLighted()) {nodeView.setState(LockState.SELECT_STATE);mNodeViews.add(nodeView);//進度if (mOnLockCallback != null) {pawBuilder.setLength(0);for (MarkerView markerView : mNodeViews) {pawBuilder.append(markerView.getNum());}mOnLockCallback.onProgress(pawBuilder.toString(), nodeView.getNum());}}if (mNodeViews.size() > 0) {invalidate();}break;case MotionEvent.ACTION_UP:LogUtils.i("手指抬起了");isTouch = false;pawBuilder.setLength(0);if (mNodeViews.size() <= 0) return true;pawBuilder.delete(0, pawBuilder.length());if (mOnLockCallback != null) {for (MarkerView markerView : mNodeViews) {pawBuilder.append(markerView.getNum());}mOnLockCallback.onFinish(pawBuilder.toString());}if (mFingerLeaveRedraw) {resetDefault();} else {invalidate();}break;}return true;}@Overrideprotected void onDraw(Canvas canvas) {//線畫在子view的以下if (!mLineTop) onDrawLock(canvas);}//畫子View的地方protected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);//放在這里的原因是。線會被子View擋到if (mLineTop) onDrawLock(canvas);}/*** 繪圖的方法*/private void onDrawLock(Canvas canvas) {//屏幕鎖住了,僅僅畫起點到終點的if (getLockScreen()) {onDrawNodeViewLock(canvas);return;}if (isTouch || mFingerLeaveRedraw) {//從第一個和最后一個的連接線onDrawNodeViewLock(canvas);//最后一個點,到手指之間的線if (mNodeViews.size() > 0) {MarkerView lastNode = mNodeViews.get(mNodeViews.size() - 1);canvas.drawLine(lastNode.getCenterX(), lastNode.getCenterY(), x, y, mLinePaint);}} else {//假設手指離開屏幕。而且設置了手指離開馬上重繪onDrawNodeViewLock(canvas);}}private void onDrawNodeViewLock(Canvas canvas) {//從第一個和最后一個的連接線for (int i = 1; i < mNodeViews.size(); i++) {MarkerView frontNode = mNodeViews.get(i - 1);MarkerView backNode = mNodeViews.get(i);canvas.drawLine(frontNode.getCenterX(), frontNode.getCenterY(), backNode.getCenterX(), backNode.getCenterY(), mLinePaint);}}/*** 獲取給定坐標點的Node,返回null表示當前手指在兩個Node之間*/private MarkerView getNodeAt(float x, float y) {for (int n = 0; n < getChildCount(); n++) {MarkerView node = (MarkerView) getChildAt(n);//計算觸摸區域以外的距離float ratioPadding = (node.getWidth() - (node.getWidth() * mTouchRatio)) / 2;if (!(x >= node.getLeft() + ratioPadding && x < node.getRight() - ratioPadding)) {continue;}if (!(y >= node.getTop() + ratioPadding && y < node.getBottom() - ratioPadding)) {continue;}return node;}return null;}/*** 設置連接線的顏色** @param color 顏色值*/public void setLineColor(int color) {mLinePaint.setColor(color);}/*** 手指離開馬上重繪*/public void setfingerLeaveRedraw(boolean mFingerLeaveRedraw) {this.mFingerLeaveRedraw = mFingerLeaveRedraw;}public boolean getfingerLeaveRedraw() {return this.mFingerLeaveRedraw;}/*** 重置狀態 為默認狀態*/public void resetDefault() {setState(LockState.DEFAULT_COLOR);mNodeViews.clear();}/*** 重置狀態錯誤狀態*/public void resetErrer() {setState(LockState.ERRER_STATE);}/*** 重置為選中狀態*/public void resetSelect() {setState(LockState.SELECT_STATE);}/*** 鎖屏,不同意觸摸*/public void LockScreen(boolean isScreen) {mLockScreen = isScreen;}public boolean getLockScreen() {return mLockScreen;}public void setState(LockState state) {switch (state) {case DEFAULT_COLOR:case SELECT_STATE:setLineColor(mSelectColor);break;case ERRER_STATE:setLineColor(mErrerColor);break;}int size = mNodeViews.size();for (int i = 0; i < size; i++) {mNodeViews.get(i).setState(state);}invalidate();}public void setLockCallback(onLockCallback lockCallback) {mOnLockCallback = lockCallback;}//回調public interface onLockCallback {void onProgress(String paw, int current);void onFinish(String paw);} }以上凝視都寫的非常清晰了。以下講一下遇到的一些問題。
1.畫出來的線被上面的圈圈覆蓋了。
通過百度。知道ViewGroup的onDraw是畫布局中的內容的,畫子View的的方法在這種方法的后面運行。所以ViewGroup的內容會被子View覆蓋。那么怎么才干把連接線畫在子View的上面呢,非常easy
僅僅要在畫子View的方法中運行就好了
以下是View的draw()方法
@CallSuperpublic void draw(Canvas canvas) {final int privateFlags = mPrivateFlags;final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;// Step 1, draw the background, if neededint saveCount;if (!dirtyOpaque) {drawBackground(canvas);}// skip step 2 & 5 if possible (common case)final int viewFlags = mViewFlags;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;if (!verticalEdges && !horizontalEdges) {// Step 3, draw the contentif (!dirtyOpaque) onDraw(canvas);// Step 4, draw the children//這里就是畫子View的方法了dispatchDraw(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// we're done...return;}2.怎么設置觸摸的區域?
/*** 獲取給定坐標點的Node,返回null表示當前手指在兩個Node之間*/private MarkerView getNodeAt(float x, float y) {for (int n = 0; n < getChildCount(); n++) {MarkerView node = (MarkerView) getChildAt(n);//計算觸摸區域以外的距離float ratioPadding = (node.getWidth() - (node.getWidth() * mTouchRatio)) / 2;if (!(x >= node.getLeft() + ratioPadding && x < node.getRight() - ratioPadding)) {continue;}if (!(y >= node.getTop() + ratioPadding && y < node.getBottom() - ratioPadding)) {continue;}return node;}return null;}看上面代碼。
依據圓圈的寬度減去可觸摸區域的長度除2。得到可觸摸區域距離邊框的距的距離。
光看代碼看著有點圓。畫個圖看一下吧
畫個圖是不是清晰非常多,僅僅要用getLeft+邊距。和getRight-邊距,就能得到可觸摸區域在x軸上的范圍了。Y軸同理。不懂的同學自己用筆畫一下吧~
幾乎相同就上面兩個問題了
下載地址
轉載于:https://www.cnblogs.com/yangykaifa/p/7338215.html
總結
以上是生活随笔為你收集整理的自己定义九宫格手势解锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ionic 实现仿苹果手机通讯录搜索功能
- 下一篇: HMM代码实现