软件编程
位置:首页>> 软件编程>> Android编程>> Android View自定义锁屏图案

Android View自定义锁屏图案

作者:星火燎原2016  发布时间:2021-11-21 14:48:20 

标签:Android,锁屏

前言

Android 自定义 View 技能是成为高级工程师所必备的,笔者觉得自定义 View 没有什么捷径可走,唯有经常练习才能解决产品需求。笔者也好久没有写自定义 View 了,赶紧写个控件找点感觉回来。

本文实现的是一个 锁屏图案的自定义控件。效果图如下:

Github 地址:AndroidSample

Android  View自定义锁屏图案

LockView 介绍

自定义属性:

Android  View自定义锁屏图案

引用方式:

(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) 在代码中设置正确的图案,用于校验是否匹配成功,并在回调中获取结果


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();
  }
 });

实现思路

  1. 以默认状态绘制 rowCount * rowCount 个圆,外圆颜色需要在内圆颜色上加上一定的透明度。

  2. 在 onTouchEvent() 方法中,判断当前触摸点与各个圆的圆心距离是否小于圆的半径,决定各个圆此时处于哪个状态(normal,move,error),调用 invalidate() 重新绘制,更新颜色。

  3. 将手指滑动触摸过的圆的坐标添加到一个 ArrayList 中,使用 Path 连接该集合中选中的圆,即可绘制出划过的路径线。

实现步骤

自定义属性

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


<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LockView">
 <attr name="normalColor" format="color|reference" /> <!--默认圆颜色-->
 <attr name="moveColor" format="color|reference" />  <!--手指触摸选中圆颜色-->
 <attr name="errorColor" format="color|reference" />  <!--手指抬起错误圆颜色-->
 <attr name="rowCount" format="integer" />    <!--每行每列圆的个数-->
</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);
 }

计算圆的半径

设定外圆半径和相邻两圆之间间距相同,内圆半径是外圆半径的一半,所以半径计算方式为:


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

设置各圆坐标

各圆坐标使用一维数组保存,计算方式为:


// 各个圆设置坐标点
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);
}

测量 View 宽高

根据测量模式设置控件的宽高,当布局文件中设置的是 wrap_content ,默认将控件宽高设置为 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() 触摸事件

在手指滑动过程中,根据当前触摸点坐标是否落在圆的范围内,更新该圆的状态,在重新绘制时,绘制成新的颜色。手指抬起时,将存放状态的 list,选中圆的 list ,linePath 重置,并将结果回调出来。

 


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++) {
    // 是否触摸在圆的范围内
    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 {  // 错误图案
    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,根据不同状态绘制圆点
 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; // 无滑动默认颜色
private int moveColor; // 滑动选中颜色
private int errorColor; // 错误颜色

private float radius; // 外圆半径

private int rowCount;

private PointF[] points; // 一维数组记录所有圆点的坐标点

private Paint innerCirclePaint; // 内圆画笔

private Paint outerCirclePaint; // 外圆画笔

private SparseIntArray stateSparseArray;

private List<PointF> selectedList = new ArrayList<>();

private List<Integer> standardPointsIndexList = new ArrayList<>();

private Path linePath = new Path(); // 手指移动的路径

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);
 // 外圆半径 = 相邻外圆之间间距 = 2倍内圆半径
 radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;
 // 各个圆设置坐标点
 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,根据不同状态绘制圆点
 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);
 }
}

/**
 * 绘制选中点之间相连的路径
 *
 * @param canvas
 */
private void drawLinePath(Canvas canvas) {
 // 重置linePath
 linePath.reset();
 // 选中点个数大于 0 时,才绘制连接线段
 if (selectedList.size() > 0) {
  // 起点移动到按下点位置
  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);
  }
  // 手指抬起时,touchPoint设置为null,使得已经绘制游离的路径,消失掉,
  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++) {
    // 是否触摸在圆的范围内
    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 {  // 错误图案
    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;
}

/**
 * 清除绘制图案的条件,当触发 invalidate() 时将清空图案
 */
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());
}

}

来源:https://blog.csdn.net/xingxtao/article/details/80545120

0
投稿

猜你喜欢

  • 背景传说里玉皇大帝派龙王马上降雨到共光一带,龙王接到玉皇大帝命令,立马从海上调水,跑去共光施云布雨,但粗心又着急的龙王不小心把海里的鲸鱼随着
  • 一、特性无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对
  • 帧率(Frame rate)是用于测量显示帧数的量度。所谓的测量单位为每秒显示帧数(Frames per Second,简称:FPS)或“赫
  • 开发项目的时候,表很多,是不可能一点点的自己去写xml ,dao文件的,这里就需要用到代码的自动生成工具了。第一步:导入jar包,当然,这之
  • @Value获取yml和properties配置参数Yml:#定时任务配置    application:  
  • Android 的APP 需要集成一个蓝牙扫码器, 特别的是,需要扫码的地方是没有输入框的(EditText),不能通过直觉上理解的通过对E
  • 说起观察者模式,估计在园子里能搜出一堆来。所以写这篇博客的目的有两点:1.观察者模式是写松耦合代码的必备模式,重要性不言而喻,抛开代码层面,
  • 开发中经常需要将某个文件向另一个应用程序传递,如图片上传到另一个应用程序、文件在不同存储路径之间的复制粘贴等都需要共享文件,可以这样理解接收
  • 一、返回BufferedImage由于spring mvc不支持返回BufferedImage ,所以增加图片转换器@Configurati
  • Android 定时器实现图片的变换在Android中,要让每秒进行一次ui更新,就需要利用到定时器和handler,message的结合,
  • Android设备的内存有限,对于大图片,必须进行压缩后再进行显示,否则会出现内存溢出:OOM;处理策略:1.使用缩略图(Thumbnail
  • 0 写在前面相信用过相机的同学都知道虚化特效,这是一种使焦点聚集在拍摄主题上,让背景变得朦胧的效果,例如本文最后实现的背景虚化效果相机虚化特
  • 这篇文章说明了如何通过Maven配置Spring依赖项。最新的Spring版本可以在Maven Central上找到。Maven中的Spri
  • 本文实例讲述了C#提取网页中超链接link和text部分的方法。分享给大家供大家参考,具体如下:string s = "..&qu
  • 重新指定分配默认值的参数时,可以显式地为指定参数名称赋值,隐式指定的时候,是根据方法参数的顺序,靠c#编译器的推断。 代码示例: void
  • 一、前言微信接口调用验证最终需要用到的三个参数noncestr、timestamp、signature:接下来将会给出获取这三个参数的详细代
  • 本文实例讲述了Java开发之spring security实现基于MongoDB的认证功能。分享给大家供大家参考,具体如下:spring s
  • 一、LINQ的体系结构语言集成查询 (LINQ) (C#) | Microsoft 官方文档LINQ总共包括五个部分: 程序集命名
  • 本文实例为大家分享了Android Webview使用小结,供大家参考,具体内容如下#采用重载URL的方式实现Java与Js交互在Andro
  • 讲解之前需要说明的是旋转屏幕:在系统的自动旋转屏幕开启的情况下,我们旋转屏幕手动设置屏幕:我们自己去调用Activity的 setReque
手机版 软件编程 asp之家 www.aspxhome.com