国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 系統(tǒng) > Android > 正文

Android View自定義鎖屏圖案

2019-12-12 00:49:13
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

前言

Android 自定義 View 技能是成為高級(jí)工程師所必備的,筆者覺(jué)得自定義 View 沒(méi)有什么捷徑可走,唯有經(jīng)常練習(xí)才能解決產(chǎn)品需求。筆者也好久沒(méi)有寫(xiě)自定義 View 了,趕緊寫(xiě)個(gè)控件找點(diǎn)感覺(jué)回來(lái)。

本文實(shí)現(xiàn)的是一個(gè) 鎖屏圖案的自定義控件。效果圖如下:

Github 地址:AndroidSample

LockView 介紹

自定義屬性:

引用方式:

(1) 在布局文件中引入

<com.xing.androidsample.view.LockView  android:id="@+id/lock_view"  app:rowCount="4"  app:normalColor=""  app:moveColor=""  app:errorColor=""  android:layout_width="match_parent"  android:layout_height="match_parent"  android:layout_margin="40dp" />

(2) 在代碼中設(shè)置正確的圖案,用于校驗(yàn)是否匹配成功,并在回調(diào)中獲取結(jié)果

List<Integer> intList = new ArrayList<>();  intList.add(3);  intList.add(7);  intList.add(4);  intList.add(2);  lockView.setStandard(intList);  lockView.setOnDrawCompleteListener(new LockView.OnDrawCompleteListener() {   @Override   public void onComplete(boolean isSuccess) {    Toast.makeText(CustomViewActivity.this, isSuccess ? "success" : "fail", Toast.LENGTH_SHORT).show();   }  });

實(shí)現(xiàn)思路

  1. 以默認(rèn)狀態(tài)繪制 rowCount * rowCount 個(gè)圓,外圓顏色需要在內(nèi)圓顏色上加上一定的透明度。
  2. 在 onTouchEvent() 方法中,判斷當(dāng)前觸摸點(diǎn)與各個(gè)圓的圓心距離是否小于圓的半徑,決定各個(gè)圓此時(shí)處于哪個(gè)狀態(tài)(normal,move,error),調(diào)用 invalidate() 重新繪制,更新顏色。
  3. ​將手指滑動(dòng)觸摸過(guò)的圓的坐標(biāo)添加到一個(gè) ArrayList 中,使用 Path 連接該集合中選中的圓,即可繪制出劃過(guò)的路徑線。

實(shí)現(xiàn)步驟

自定義屬性

在 res/values 目錄下新建 attrs.xml 文件:

<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="LockView">  <attr name="normalColor" format="color|reference" /> <!--默認(rèn)圓顏色-->  <attr name="moveColor" format="color|reference" />  <!--手指觸摸選中圓顏色-->   <attr name="errorColor" format="color|reference" />  <!--手指抬起錯(cuò)誤圓顏色-->   <attr name="rowCount" format="integer" />    <!--每行每列圓的個(gè)數(shù)-->  </declare-styleable></resources>

獲取自定義屬性

public LockView(Context context) {   this(context, null);  }  public LockView(Context context, @Nullable AttributeSet attrs) {   super(context, attrs);   readAttrs(context, attrs);   init();  } /** * 獲取自定義屬性 */  private void readAttrs(Context context, AttributeSet attrs) {   TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LockView);   normalColor = typedArray.getColor(R.styleable.LockView_normalColor, DEFAULT_NORMAL_COLOR);   moveColor = typedArray.getColor(R.styleable.LockView_moveColor, DEFAULT_MOVE_COLOR);   errorColor = typedArray.getColor(R.styleable.LockView_errorColor, DEFAULT_ERROR_COLOR);   rowCount = typedArray.getInteger(R.styleable.LockView_rowCount, DEFAULT_ROW_COUNT);   typedArray.recycle();  } /** * 初始化 */  private void init() {   stateSparseArray = new SparseIntArray(rowCount * rowCount);   points = new PointF[rowCount * rowCount];   innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);   innerCirclePaint.setStyle(Paint.Style.FILL);   outerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);   outerCirclePaint.setStyle(Paint.Style.FILL);   linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);   linePaint.setStyle(Paint.Style.STROKE);   linePaint.setStrokeCap(Paint.Cap.ROUND);   linePaint.setStrokeJoin(Paint.Join.ROUND);   linePaint.setStrokeWidth(30);   linePaint.setColor(moveColor);  }

計(jì)算圓的半徑

設(shè)定外圓半徑和相鄰兩圓之間間距相同,內(nèi)圓半徑是外圓半徑的一半,所以半徑計(jì)算方式為:

radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;

設(shè)置各圓坐標(biāo)

各圓坐標(biāo)使用一維數(shù)組保存,計(jì)算方式為:

// 各個(gè)圓設(shè)置坐標(biāo)點(diǎn)for (int i = 0; i < rowCount * rowCount; i++) {  points[i] = new PointF(0, 0);  points[i].set((i % rowCount * 3 + 1) * radius, (i / rowCount * 3 + 1) * radius);}

測(cè)量 View 寬高

根據(jù)測(cè)量模式設(shè)置控件的寬高,當(dāng)布局文件中設(shè)置的是 wrap_content ,默認(rèn)將控件寬高設(shè)置為 600dp

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  super.onMeasure(widthMeasureSpec, heightMeasureSpec);  int width = getSize(widthMeasureSpec);  int height = getSize(heightMeasureSpec);  setMeasuredDimension(width, height); } private int getSize(int measureSpec) {  int mode = MeasureSpec.getMode(measureSpec);  int size = MeasureSpec.getSize(measureSpec);  if (mode == MeasureSpec.EXACTLY) {   return size;  } else if (mode == MeasureSpec.AT_MOST) {   return Math.min(size, dp2Px(600));  }  return dp2Px(600); }

onTouchEvent() 觸摸事件

在手指滑動(dòng)過(guò)程中,根據(jù)當(dāng)前觸摸點(diǎn)坐標(biāo)是否落在圓的范圍內(nèi),更新該圓的狀態(tài),在重新繪制時(shí),繪制成新的顏色。手指抬起時(shí),將存放狀態(tài)的 list,選中圓的 list ,linePath 重置,并將結(jié)果回調(diào)出來(lái)。

 

private PointF touchPoint; @Override public boolean onTouchEvent(MotionEvent event) {  switch (event.getAction()) {   case MotionEvent.ACTION_DOWN:    reset();   case MotionEvent.ACTION_MOVE:    if (touchPoint == null) {     touchPoint = new PointF(event.getX(), event.getY());    } else {     touchPoint.set(event.getX(), event.getY());    }    for (int i = 0; i < rowCount * rowCount; i++) {     // 是否觸摸在圓的范圍內(nèi)     if (getDistance(touchPoint, points[i]) < radius) {      stateSparseArray.put(i, STATE_MOVE);      if (!selectedList.contains(points[i])) {       selectedList.add(points[i]);      }      break;     }    }    break;   case MotionEvent.ACTION_UP:    if (check()) { // 正確圖案     if (listener != null) {      listener.onComplete(true);     }     for (int i = 0; i < stateSparseArray.size(); i++) {      int index = stateSparseArray.keyAt(i);      stateSparseArray.put(index, STATE_MOVE);     }    } else {  // 錯(cuò)誤圖案     for (int i = 0; i < stateSparseArray.size(); i++) {      int index = stateSparseArray.keyAt(i);      stateSparseArray.put(index, STATE_ERROR);     }     linePaint.setColor(0xeeff0000);     if (listener != null) {      listener.onComplete(false);     }    }    touchPoint = null;    if (timer == null) {     timer = new Timer();    }    timer.schedule(new TimerTask() {     @Override     public void run() {      linePath.reset();      linePaint.setColor(0xee0000ff);      selectedList.clear();      stateSparseArray.clear();      postInvalidate();     }    }, 1000);    break;  }  invalidate();  return true; }

繪制各圓和各圓之間連接線段

@Override protected void onDraw(Canvas canvas) {  super.onDraw(canvas);  drawCircle(canvas);  drawLinePath(canvas); } private void drawCircle(Canvas canvas) {  // 依次從索引 0 到索引 8,根據(jù)不同狀態(tài)繪制圓點(diǎn)  for (int index = 0; index < rowCount * rowCount; index++) {   int state = stateSparseArray.get(index);   switch (state) {    case STATE_NORMAL:     innerCirclePaint.setColor(normalColor);     outerCirclePaint.setColor(normalColor & 0x66ffffff);     break;    case STATE_MOVE:     innerCirclePaint.setColor(moveColor);     outerCirclePaint.setColor(moveColor & 0x66ffffff);     break;    case STATE_ERROR:     innerCirclePaint.setColor(errorColor);     outerCirclePaint.setColor(errorColor & 0x66ffffff);     break;   }   canvas.drawCircle(points[index].x, points[index].y, radius, outerCirclePaint);   canvas.drawCircle(points[index].x, points[index].y, radius / 2f, innerCirclePaint);  } }

完整 View 代碼:

/** * Created by star.tao on 2018/5/30. * email: xing-java@foxmail.com * github: https://github.com/xing16 */public class LockView extends View { private static final int DEFAULT_NORMAL_COLOR = 0xee776666; private static final int DEFAULT_MOVE_COLOR = 0xee0000ff; private static final int DEFAULT_ERROR_COLOR = 0xeeff0000; private static final int DEFAULT_ROW_COUNT = 3; private static final int STATE_NORMAL = 0; private static final int STATE_MOVE = 1; private static final int STATE_ERROR = 2; private int normalColor; // 無(wú)滑動(dòng)默認(rèn)顏色 private int moveColor; // 滑動(dòng)選中顏色 private int errorColor; // 錯(cuò)誤顏色 private float radius; // 外圓半徑 private int rowCount; private PointF[] points; // 一維數(shù)組記錄所有圓點(diǎn)的坐標(biāo)點(diǎn) private Paint innerCirclePaint; // 內(nèi)圓畫(huà)筆 private Paint outerCirclePaint; // 外圓畫(huà)筆 private SparseIntArray stateSparseArray; private List<PointF> selectedList = new ArrayList<>(); private List<Integer> standardPointsIndexList = new ArrayList<>(); private Path linePath = new Path(); // 手指移動(dòng)的路徑 private Paint linePaint; private Timer timer; public LockView(Context context) {  this(context, null); } public LockView(Context context, @Nullable AttributeSet attrs) {  super(context, attrs);  readAttrs(context, attrs);  init(); } private void readAttrs(Context context, AttributeSet attrs) {  TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LockView);  normalColor = typedArray.getColor(R.styleable.LockView_normalColor, DEFAULT_NORMAL_COLOR);  moveColor = typedArray.getColor(R.styleable.LockView_moveColor, DEFAULT_MOVE_COLOR);  errorColor = typedArray.getColor(R.styleable.LockView_errorColor, DEFAULT_ERROR_COLOR);  rowCount = typedArray.getInteger(R.styleable.LockView_rowCount, DEFAULT_ROW_COUNT);  typedArray.recycle(); } private void init() {  stateSparseArray = new SparseIntArray(rowCount * rowCount);  points = new PointF[rowCount * rowCount];  innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  innerCirclePaint.setStyle(Paint.Style.FILL);  outerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  outerCirclePaint.setStyle(Paint.Style.FILL);  linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  linePaint.setStyle(Paint.Style.STROKE);  linePaint.setStrokeCap(Paint.Cap.ROUND);  linePaint.setStrokeJoin(Paint.Join.ROUND);  linePaint.setStrokeWidth(30);  linePaint.setColor(moveColor); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {  super.onSizeChanged(w, h, oldw, oldh);  // 外圓半徑 = 相鄰?fù)鈭A之間間距 = 2倍內(nèi)圓半徑  radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;  // 各個(gè)圓設(shè)置坐標(biāo)點(diǎn)  for (int i = 0; i < rowCount * rowCount; i++) {   points[i] = new PointF(0, 0);   points[i].set((i % rowCount * 3 + 1) * radius, (i / rowCount * 3 + 1) * radius);  } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  super.onMeasure(widthMeasureSpec, heightMeasureSpec);  int width = getSize(widthMeasureSpec);  int height = getSize(heightMeasureSpec);  setMeasuredDimension(width, height); } private int getSize(int measureSpec) {  int mode = MeasureSpec.getMode(measureSpec);  int size = MeasureSpec.getSize(measureSpec);  if (mode == MeasureSpec.EXACTLY) {   return size;  } else if (mode == MeasureSpec.AT_MOST) {   return Math.min(size, dp2Px(600));  }  return dp2Px(600); } @Override protected void onDraw(Canvas canvas) {  super.onDraw(canvas);  drawCircle(canvas);  drawLinePath(canvas); } private void drawCircle(Canvas canvas) {  // 依次從索引 0 到索引 8,根據(jù)不同狀態(tài)繪制圓點(diǎn)  for (int index = 0; index < rowCount * rowCount; index++) {   int state = stateSparseArray.get(index);   switch (state) {    case STATE_NORMAL:     innerCirclePaint.setColor(normalColor);     outerCirclePaint.setColor(normalColor & 0x66ffffff);     break;    case STATE_MOVE:     innerCirclePaint.setColor(moveColor);     outerCirclePaint.setColor(moveColor & 0x66ffffff);     break;    case STATE_ERROR:     innerCirclePaint.setColor(errorColor);     outerCirclePaint.setColor(errorColor & 0x66ffffff);     break;   }   canvas.drawCircle(points[index].x, points[index].y, radius, outerCirclePaint);   canvas.drawCircle(points[index].x, points[index].y, radius / 2f, innerCirclePaint);  } } /**  * 繪制選中點(diǎn)之間相連的路徑  *  * @param canvas  */ private void drawLinePath(Canvas canvas) {  // 重置linePath  linePath.reset();  // 選中點(diǎn)個(gè)數(shù)大于 0 時(shí),才繪制連接線段  if (selectedList.size() > 0) {   // 起點(diǎn)移動(dòng)到按下點(diǎn)位置   linePath.moveTo(selectedList.get(0).x, selectedList.get(0).y);   for (int i = 1; i < selectedList.size(); i++) {    linePath.lineTo(selectedList.get(i).x, selectedList.get(i).y);   }   // 手指抬起時(shí),touchPoint設(shè)置為null,使得已經(jīng)繪制游離的路徑,消失掉,   if (touchPoint != null) {    linePath.lineTo(touchPoint.x, touchPoint.y);   }   canvas.drawPath(linePath, linePaint);  } } private PointF touchPoint; @Override public boolean onTouchEvent(MotionEvent event) {  switch (event.getAction()) {   case MotionEvent.ACTION_DOWN:    reset();   case MotionEvent.ACTION_MOVE:    if (touchPoint == null) {     touchPoint = new PointF(event.getX(), event.getY());    } else {     touchPoint.set(event.getX(), event.getY());    }    for (int i = 0; i < rowCount * rowCount; i++) {     // 是否觸摸在圓的范圍內(nèi)     if (getDistance(touchPoint, points[i]) < radius) {      stateSparseArray.put(i, STATE_MOVE);      if (!selectedList.contains(points[i])) {       selectedList.add(points[i]);      }      break;     }    }    break;   case MotionEvent.ACTION_UP:    if (check()) { // 正確圖案     if (listener != null) {      listener.onComplete(true);     }     for (int i = 0; i < stateSparseArray.size(); i++) {      int index = stateSparseArray.keyAt(i);      stateSparseArray.put(index, STATE_MOVE);     }    } else {  // 錯(cuò)誤圖案     for (int i = 0; i < stateSparseArray.size(); i++) {      int index = stateSparseArray.keyAt(i);      stateSparseArray.put(index, STATE_ERROR);     }     linePaint.setColor(0xeeff0000);     if (listener != null) {      listener.onComplete(false);     }    }    touchPoint = null;    if (timer == null) {     timer = new Timer();    }    timer.schedule(new TimerTask() {     @Override     public void run() {      linePath.reset();      linePaint.setColor(0xee0000ff);      selectedList.clear();      stateSparseArray.clear();      postInvalidate();     }    }, 1000);    break;  }  invalidate();  return true; } /**  * 清除繪制圖案的條件,當(dāng)觸發(fā) invalidate() 時(shí)將清空?qǐng)D案  */ private void reset() {  touchPoint = null;  linePath.reset();  linePaint.setColor(0xee0000ff);  selectedList.clear();  stateSparseArray.clear(); } public void onStop() {  timer.cancel(); } private boolean check() {  if (selectedList.size() != standardPointsIndexList.size()) {   return false;  }  for (int i = 0; i < standardPointsIndexList.size(); i++) {   Integer index = standardPointsIndexList.get(i);   if (points[index] != selectedList.get(i)) {    return false;   }  }  return true; } public void setStandard(List<Integer> pointsList) {  if (pointsList == null) {   throw new IllegalArgumentException("standard points index can't null");  }  if (pointsList.size() > rowCount * rowCount) {   throw new IllegalArgumentException("standard points index list can't large to rowcount * columncount");  }  standardPointsIndexList = pointsList; } private OnDrawCompleteListener listener; public void setOnDrawCompleteListener(OnDrawCompleteListener listener) {  this.listener = listener; } public interface OnDrawCompleteListener {  void onComplete(boolean isSuccess); } private float getDistance(PointF centerPoint, PointF downPoint) {  return (float) Math.sqrt(Math.pow(centerPoint.x - downPoint.x, 2) + Math.pow(centerPoint.y - downPoint.y, 2)); } private int dp2Px(int dpValue) {  return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics()); }}

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 新民市| 灵丘县| 绵阳市| 西华县| 丰宁| 威海市| 浙江省| 开平市| 同仁县| 邵阳市| 辉南县| 若尔盖县| 繁昌县| 子洲县| 柳州市| 重庆市| 信丰县| 电白县| 夹江县| 吉隆县| 昌黎县| 辽阳市| 汨罗市| 龙海市| 顺昌县| 翁牛特旗| 武冈市| 太原市| 林周县| 广饶县| 临漳县| 巫溪县| 佛山市| 高碑店市| 大新县| 古蔺县| 北碚区| 普安县| 澄江县| 永定县| 和田市|