├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── attrs.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── colors.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── layout
│ │ │ │ └── activity_preview.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── demo
│ │ │ └── facerecognition
│ │ │ ├── util
│ │ │ └── ScreenUtils.java
│ │ │ ├── PreviewActivity.java
│ │ │ └── view
│ │ │ ├── FaceView2.java
│ │ │ └── FaceView.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── demo
│ │ │ └── facerecognition
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── demo
│ │ └── facerecognition
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── markdown-navigator
│ └── profiles_settings.xml
├── vcs.xml
├── encodings.xml
├── runConfigurations.xml
├── gradle.xml
├── jarRepositories.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── codeStyles
│ └── Project.xml
└── misc.xml
├── README.md
├── .gitignore
├── gradle.properties
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/androidneter/FaceRecognitionUI/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.idea/markdown-navigator/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
16 | */
17 | public class ScreenUtils {
18 | /**
19 | * 获状态栏高度
20 | */
21 | public static int getStatusBarHeight(Context context) {
22 | int statusBarHeight = 0;
23 | try {
24 | Class> clazz = Class.forName("com.android.internal.R$dimen");
25 | Object obj = clazz.newInstance();
26 | Field field = clazz.getField("status_bar_height");
27 | int temp = Integer.parseInt(field.get(obj).toString());
28 | statusBarHeight = context.getResources().getDimensionPixelSize(temp);
29 | } catch (Exception e) {
30 | e.printStackTrace();
31 | }
32 | return statusBarHeight;
33 | }
34 |
35 | /**
36 | * 隐藏状态栏
37 | *
也就是设置全屏,一定要在setContentView之前调用,否则报错
38 | *此方法Activity可以继承AppCompatActivity
39 | *启动的时候状态栏会显示一下再隐藏,比如QQ的欢迎界面
40 | *在配置文件中Activity加属性android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
41 | *如加了以上配置Activity不能继承AppCompatActivity,会报错
42 | * 43 | * @param activity activity 44 | */ 45 | public static void hideStatusBar(Activity activity) { 46 | activity.requestWindowFeature(Window.FEATURE_NO_TITLE); 47 | activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 48 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 49 | } 50 | 51 | /** 52 | * 透明状态栏 setContentView之前调用有效 53 | * >21 54 | * 55 | * @param activity 56 | */ 57 | public static void setSystemBarTransparent(Activity activity) { 58 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 59 | activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 60 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 61 | activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 62 | } 63 | //如果布局没有置顶 导致空白问题 64 | /* else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 65 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 66 | }*/ 67 | } 68 | 69 | 70 | public static int getSHeight(Context context) { 71 | DisplayMetrics metrics = new DisplayMetrics(); 72 | ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) 73 | .getDefaultDisplay() 74 | .getMetrics(metrics); 75 | 76 | return metrics.heightPixels; 77 | 78 | } 79 | 80 | public static int getSWidth(Context context) { 81 | DisplayMetrics metrics = new DisplayMetrics(); 82 | ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) 83 | .getDefaultDisplay() 84 | .getMetrics(metrics); 85 | 86 | return metrics.widthPixels; 87 | 88 | } 89 | 90 | /** 91 | * 根据手机的分辨率从dp 到 px(像素) 92 | */ 93 | public static int dp2px(Context context, float dpValue) { 94 | final float scale = context.getResources().getDisplayMetrics().density; 95 | return (int) (dpValue * scale + 0.5f); 96 | } 97 | 98 | /** 99 | * 根据手机的分辨率从px 到 dp 100 | */ 101 | public static int px2dp(Context context, float pxValue) { 102 | final float scale = context.getResources().getDisplayMetrics().density; 103 | return (int) (pxValue / scale + 0.5f); 104 | } 105 | 106 | /** 107 | * 将sp值转换为px值,保证文字大小不变 108 | */ 109 | public static int sp2px(Context context, float spValue) { 110 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 111 | return (int) (spValue * fontScale + 0.5f); 112 | } 113 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/java/com/demo/facerecognition/PreviewActivity.java: -------------------------------------------------------------------------------- 1 | package com.demo.facerecognition; 2 | 3 | import android.Manifest; 4 | import android.content.pm.PackageManager; 5 | import android.graphics.PixelFormat; 6 | import android.hardware.Camera; 7 | import android.os.Bundle; 8 | import android.support.constraint.ConstraintLayout; 9 | import android.support.v4.app.ActivityCompat; 10 | import android.support.v4.content.ContextCompat; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.util.Log; 13 | import android.view.SurfaceHolder; 14 | import android.view.SurfaceView; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.widget.Button; 18 | import android.widget.RelativeLayout; 19 | 20 | import com.demo.facerecognition.view.FaceView; 21 | 22 | import java.io.IOException; 23 | 24 | /** 25 | * 人脸识别预览视频由相机预览显示 26 | * 这个可以根据SDK决定 27 | *
28 | * 预览界面
29 | */
30 | public class PreviewActivity extends AppCompatActivity {
31 | private final String TAG = "PreviewActivity";
32 | private Camera camera;
33 | private boolean isPreview = false;
34 | final String[] PERMISSION = new String[]{
35 | //获取照相机权限
36 | Manifest.permission.CAMERA,
37 | };
38 | private FaceView faceView;
39 |
40 | /**
41 | * 设置Android6.0的权限申请
42 | */
43 | private void setPermissions() {
44 | if (ContextCompat.checkSelfPermission(PreviewActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
45 | //Android 6.0申请权限
46 | ActivityCompat.requestPermissions(this, PERMISSION, 1);
47 | } else {
48 | Log.i(TAG, "权限申请ok");
49 | }
50 | }
51 |
52 | @Override
53 | protected void onCreate(Bundle savedInstanceState) {
54 | super.onCreate(savedInstanceState);
55 | setContentView(R.layout.activity_preview);
56 | //初始化布局
57 | ConstraintLayout constraintLayout = findViewById(R.id.cl_root);
58 | faceView = findViewById(R.id.fv_title);
59 | Button changeBtn = findViewById(R.id.changge);
60 | //申请手机的权限
61 | setPermissions();
62 |
63 | changeBtn.setOnClickListener(new View.OnClickListener() {
64 | @Override
65 | public void onClick(View v) {
66 | int i = (int) (1 + Math.random() * 4);
67 | switch (i) {
68 | case 1:
69 | faceView.resetPositionStart();
70 | faceView.updateTipsInfo("没有检测人脸");
71 | break;
72 | case 2:
73 | faceView.backAnimator();
74 | faceView.updateTipsInfo("请露正脸");
75 | break;
76 | case 3:
77 | faceView.pauseAnimator();
78 | faceView.updateTipsInfo(" 眨眨眼");
79 | break;
80 | case 4:
81 | faceView.startAnimator();
82 | faceView.updateTipsInfo("离近一点");
83 | break;
84 | default:
85 | break;
86 | }
87 | }
88 | });
89 |
90 | //添加布局
91 | SurfaceView mSurfaceView = new SurfaceView(this);
92 | RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
93 | ViewGroup.LayoutParams.MATCH_PARENT);
94 | params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
95 | mSurfaceView.setLayoutParams(params);
96 | constraintLayout.addView(mSurfaceView, 0);
97 | //得到getHolder实例
98 | SurfaceHolder mSurfaceHolder = mSurfaceView.getHolder();
99 | mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT);
100 | // 添加 Surface 的 callback 接口
101 | mSurfaceHolder.addCallback(mSurfaceCallback);
102 | }
103 |
104 | private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
105 | @Override
106 | public void surfaceCreated(SurfaceHolder surfaceHolder) {
107 | try {
108 | //打开硬件摄像头,这里导包得时候一定要注意是android.hardware.Camera
109 | // Camera,open() 默认返回的后置摄像头信息
110 | //设置角度,此处 CameraId 我默认 为 1 (前置)
111 | if (Camera.getNumberOfCameras() > 1) {
112 | camera = Camera.open(1);
113 | } else {
114 | camera = Camera.open(0);
115 | }
116 | //设置相机角度
117 | camera.setDisplayOrientation(90);
118 | //通过SurfaceView显示取景画面
119 | camera.setPreviewDisplay(surfaceHolder);
120 | //开始预览
121 | camera.startPreview();
122 | //设置是否预览参数为真
123 | isPreview = true;
124 | } catch (IOException e) {
125 | e.printStackTrace();
126 | }
127 | }
128 |
129 | @Override
130 | public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
131 | }
132 |
133 | @Override
134 | public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
135 | if (camera != null) {
136 | if (isPreview) {//正在预览
137 | try {
138 | camera.stopPreview();
139 | camera.release();
140 | } catch (Exception e) {
141 | e.printStackTrace();
142 | }
143 | }
144 | }
145 | }
146 | };
147 |
148 | @Override
149 | protected void onDestroy() {
150 | //释放资源
151 | faceView.destroyView();
152 | if (camera != null) {
153 | if (isPreview) {//正在预览
154 | try {
155 | camera.release();
156 | camera = null;
157 | } catch (Exception e) {
158 | e.printStackTrace();
159 | }
160 | }
161 | }
162 | super.onDestroy();
163 | }
164 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
78 |
91 |
96 |
108 |
24 | * 文件名: FaceView 25 | * 作 者: zj 26 | * 时 间: 2019/1/2 18:27 27 | * 描 述:人脸识别界面UI 28 | *29 | */ 30 | public class FaceView2 extends View implements Runnable { 31 | private final String TAG = "FaceView"; 32 | /** 33 | * 是否可以开始绘制了 34 | */ 35 | private boolean mStart = false; 36 | /** 37 | * 是否结束 38 | */ 39 | private volatile boolean mFinsh = false; 40 | /** 41 | * 默认中间圆的半径从0开始 42 | */ 43 | private float currentRadius = 0; 44 | /** 45 | * 控件的宽度(默认) 46 | */ 47 | private int mViewWidth = 400; 48 | /** 49 | * 控件高度 50 | */ 51 | private int mViewHeight = 400; 52 | /** 53 | * 中心圆屏幕边距 54 | */ 55 | private int margin; 56 | /** 57 | * 圆圈画笔 58 | */ 59 | private Paint mPaint; 60 | /** 61 | * 提示文本 62 | */ 63 | private String mTipText; 64 | /** 65 | * 提示文本颜色 66 | */ 67 | private int mTipTextColor; 68 | /** 69 | * 提示文本颜色 70 | */ 71 | private int mTipTextSize; 72 | /** 73 | * 内圆半径 74 | */ 75 | private int mRadius; 76 | /** 77 | * 背景弧宽度 78 | */ 79 | private float mBgArcWidth; 80 | 81 | /** 82 | * 圆心点坐标 83 | */ 84 | private Point mCenterPoint = new Point(); 85 | /** 86 | * 圆弧边界 87 | */ 88 | private RectF mBgRectF = new RectF(); 89 | 90 | /** 91 | * 开始角度 92 | */ 93 | private int mStartAngle = 105; 94 | 95 | /** 96 | * 结束角度 97 | */ 98 | private int mEndAngle = 330; 99 | 100 | /** 101 | * 设置默认转动角度0 102 | */ 103 | float currentAngle = 0; 104 | 105 | /** 106 | * 圆弧背景画笔 107 | */ 108 | private Paint mBgArcPaint; 109 | /** 110 | * 提示语画笔 111 | */ 112 | private Paint mTextPaint; 113 | 114 | /** 115 | * 圆弧画笔 116 | */ 117 | private Paint mArcPaint; 118 | /** 119 | * 渐变器 120 | */ 121 | private SweepGradient mSweepGradient; 122 | 123 | /** 124 | * 是否开始 125 | */ 126 | private boolean isRunning = true; 127 | 128 | /** 129 | * 是否后退 130 | */ 131 | private boolean isBack = false; 132 | /** 133 | * 绘制速度 134 | */ 135 | private int speed = 5; 136 | 137 | 138 | public FaceView2(Context context) { 139 | this(context, null); 140 | } 141 | 142 | public FaceView2(Context context, AttributeSet attrs) { 143 | this(context, attrs, 0); 144 | } 145 | 146 | public FaceView2(Context context, AttributeSet attrs, int defStyleAttr) { 147 | super(context, attrs, defStyleAttr); 148 | //获取xml里面的属性值 149 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FaceView); 150 | mTipText = array.getString(R.styleable.FaceView_tip_text); 151 | mTipTextColor = array.getColor(R.styleable.FaceView_tip_text_color, Color.WHITE); 152 | mTipTextSize = array.getDimensionPixelSize(R.styleable.FaceView_tip_text_size, ScreenUtils.sp2px(context, 12)); 153 | array.recycle(); 154 | 155 | Log.d(TAG, "FaceView构造"); 156 | initPaint(context); 157 | } 158 | 159 | /** 160 | * 初始化控件View 161 | */ 162 | private void initPaint(Context context) { 163 | //获取界面焦点 164 | setFocusable(true); 165 | //保持屏幕长亮 166 | setKeepScreenOn(true); 167 | 168 | //初始化值 169 | margin = ScreenUtils.dp2px(context, 60); 170 | mBgArcWidth = ScreenUtils.dp2px(context, 5); 171 | 172 | //初始化画笔 173 | mPaint = new Paint(); 174 | mPaint.setAntiAlias(true); 175 | mPaint.setColor(getResources().getColor(R.color.colorAccent)); 176 | mPaint.setStyle(Paint.Style.FILL); 177 | 178 | //绘制文字画笔 179 | mTextPaint = new Paint(); 180 | mTextPaint.setStyle(Paint.Style.FILL); 181 | mTextPaint.setStrokeWidth(8); 182 | mTextPaint.setColor(mTipTextColor); 183 | mTextPaint.setTextSize(mTipTextSize); 184 | mTextPaint.setTextAlign(Paint.Align.CENTER); 185 | 186 | // 圆弧背景 187 | mBgArcPaint = new Paint(); 188 | mBgArcPaint.setAntiAlias(true); 189 | mBgArcPaint.setColor(getResources().getColor(R.color.circleBg)); 190 | mBgArcPaint.setStyle(Paint.Style.STROKE); 191 | mBgArcPaint.setStrokeWidth(mBgArcWidth); 192 | mBgArcPaint.setStrokeCap(Paint.Cap.ROUND); 193 | 194 | // 圆弧 195 | mArcPaint = new Paint(); 196 | mArcPaint.setAntiAlias(true); 197 | mArcPaint.setStyle(Paint.Style.STROKE); 198 | mArcPaint.setStrokeWidth(mBgArcWidth); 199 | mArcPaint.setStrokeCap(Paint.Cap.ROUND); 200 | 201 | //开启线程检测 202 | new Thread(this).start(); 203 | } 204 | 205 | @SuppressLint("DrawAllocation") 206 | @Override 207 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 208 | //测量view的宽度 209 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 210 | if (widthMode == MeasureSpec.EXACTLY) { 211 | mViewWidth = MeasureSpec.getSize(widthMeasureSpec); 212 | } 213 | 214 | //测量view的高度 215 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 216 | if (heightMode == MeasureSpec.EXACTLY) { 217 | mViewHeight = MeasureSpec.getSize(heightMeasureSpec); 218 | } 219 | 220 | setMeasuredDimension(mViewWidth, mViewHeight); 221 | Log.d(TAG, "onMeasure mViewWidth : " + mViewWidth + " mViewHeight : " + mViewHeight); 222 | 223 | //获取圆的相关参数 224 | mCenterPoint.x = mViewWidth / 2; 225 | mCenterPoint.y = mViewHeight / 2; 226 | 227 | //外环圆的半径 228 | mRadius = mCenterPoint.x - margin; 229 | 230 | //绘制背景圆弧的边界 231 | mBgRectF.left = mCenterPoint.x - mRadius - mBgArcWidth / 2; 232 | mBgRectF.top = mCenterPoint.y - mRadius - mBgArcWidth / 2; 233 | mBgRectF.right = mCenterPoint.x + mRadius + mBgArcWidth / 2; 234 | mBgRectF.bottom = mCenterPoint.y + mRadius + mBgArcWidth / 2; 235 | 236 | //进度条颜色 -mStartAngle/2将位置到原处 237 | mSweepGradient = new SweepGradient(mCenterPoint.x - mStartAngle / 2, 238 | mCenterPoint.y - mStartAngle / 2, getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark)); 239 | } 240 | 241 | @Override 242 | protected void onVisibilityChanged(View changedView, int visibility) { 243 | super.onVisibilityChanged(changedView, visibility); 244 | mStart = (visibility == VISIBLE); 245 | } 246 | 247 | @Override 248 | public void run() { 249 | //循环绘制画面内容 250 | while (!mFinsh) { 251 | if (mStart) { 252 | try { 253 | changeValue(); 254 | Thread.sleep(50); 255 | } catch (InterruptedException e) { 256 | e.printStackTrace(); 257 | } 258 | } 259 | } 260 | } 261 | 262 | /** 263 | * 动态检测改变值 264 | */ 265 | private void changeValue() { 266 | // 内圆形,放大效果 267 | currentRadius += 20; 268 | if (currentRadius > mRadius) 269 | currentRadius = mRadius; 270 | 271 | //外部圈的动画效果 272 | if (isRunning) { 273 | if (isBack) { 274 | currentAngle -= speed; 275 | if (currentAngle <= 0) 276 | currentAngle = 0; 277 | } else { 278 | currentAngle += speed; 279 | if (currentAngle >= mEndAngle) 280 | currentAngle = mEndAngle; 281 | } 282 | } 283 | //重绘view 284 | invalidate(); 285 | } 286 | 287 | @Override 288 | protected void onDraw(Canvas canvas) { 289 | super.onDraw(canvas); 290 | //绘制画布内容 291 | drawContent(canvas); 292 | } 293 | 294 | /** 295 | * 跟新提示信息 296 | * 297 | * @param title 298 | */ 299 | public void updateTipsInfo(String title) { 300 | mTipText = title; 301 | } 302 | 303 | private void drawContent(Canvas canvas) { 304 | //防止save()和restore()方法代码之后对Canvas执行的操作,继续对后续的绘制会产生影响 305 | canvas.save(); 306 | //先画提示语 307 | drawHintText(canvas); 308 | //绘制正方形的框内类似人脸识别 309 | // drawFaceRectTest(canvas); 310 | //绘制人脸识别部分 311 | drawFaceCircle(canvas); 312 | //画外边进度条 313 | drawRoundProgress(canvas); 314 | canvas.restore(); 315 | } 316 | 317 | private void drawFaceCircle(Canvas canvas) { 318 | //设置画板样式 319 | Path path = new Path(); 320 | //以(400,200)为圆心,半径为100绘制圆 指创建顺时针方向的矩形路径 321 | path.addCircle(mCenterPoint.x, mCenterPoint.y, currentRadius, Path.Direction.CW); 322 | // 是A形状中不同于B的部分显示出来 323 | canvas.clipPath(path, Region.Op.DIFFERENCE); 324 | // 半透明背景效果 325 | canvas.clipRect(0, 0, mViewWidth, mViewHeight); 326 | //绘制背景颜色 327 | canvas.drawColor(getResources().getColor(R.color.viewBgWhite)); 328 | } 329 | 330 | 331 | /** 332 | * 绘制人脸识别界面进度条 333 | * 334 | * @param canvas canvas 335 | */ 336 | private void drawRoundProgress(Canvas canvas) { 337 | // 逆时针旋转105度 338 | canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y); 339 | // 设置圆环背景 340 | canvas.drawArc(mBgRectF, 0, mEndAngle, false, mBgArcPaint); 341 | // 设置渐变颜色 342 | mArcPaint.setShader(mSweepGradient); 343 | canvas.drawArc(mBgRectF, 0, currentAngle, false, mArcPaint); 344 | } 345 | 346 | /** 347 | * 从头位置开始动画 348 | */ 349 | public void resetPositionStart() { 350 | currentAngle = 0; 351 | isBack = false; 352 | } 353 | 354 | /** 355 | * 动画直接完成 356 | */ 357 | public void finnishAnimator() { 358 | currentAngle = mEndAngle; 359 | isBack = false; 360 | } 361 | 362 | /** 363 | * 停止动画 364 | */ 365 | public void pauseAnimator() { 366 | isRunning = false; 367 | } 368 | 369 | /** 370 | * 开始动画 371 | */ 372 | public void startAnimator() { 373 | isRunning = true; 374 | } 375 | 376 | 377 | /** 378 | * 动画回退 379 | */ 380 | public void backAnimator() { 381 | isRunning = true; 382 | isBack = true; 383 | } 384 | 385 | /** 386 | * 动画前进 387 | */ 388 | public void forwardAnimator() { 389 | isRunning = true; 390 | isBack = false; 391 | } 392 | 393 | /** 394 | * 销毁视图,释放资源 395 | */ 396 | public void destroyView() { 397 | //停止运行 398 | mFinsh = true; 399 | mStart = false; 400 | isRunning = false; 401 | isBack = false; 402 | } 403 | 404 | /** 405 | * 绘制人脸识别提示 406 | * 407 | * @param canvas canvas 408 | */ 409 | private void drawHintText(Canvas canvas) { 410 | //圆视图宽度 (屏幕减去两边距离) 411 | int cameraWidth = mViewWidth - 2 * margin; 412 | //x轴起点(文字背景起点) 413 | int x = margin; 414 | //宽度(提示框背景宽度) 415 | int width = cameraWidth; 416 | //y轴起点 417 | int y = (int) (mCenterPoint.y - mRadius); 418 | //提示框背景高度 419 | int height = cameraWidth / 4; 420 | Rect rect = new Rect(x, y, x + width, y + height); 421 | canvas.drawRect(rect, mPaint); 422 | 423 | //计算baseline 424 | Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); 425 | float distance = (fontMetrics.bottom - fontMetrics.top) / 4; 426 | float baseline = rect.centerY() + distance; 427 | canvas.drawText(mTipText, rect.centerX(), baseline, mTextPaint); 428 | } 429 | 430 | /** 431 | * 绘制人脸识别矩形区域 432 | * 433 | * @param canvas canvas 434 | */ 435 | private void drawFaceRectTest(Canvas canvas) { 436 | int cameraWidth = mViewWidth - 2 * margin; 437 | int x = margin + cameraWidth / 6; 438 | int width = cameraWidth * 2 / 3; 439 | int y = mCenterPoint.x + (width / 2); 440 | int height = width; 441 | Rect rect = new Rect(x, y, x + width, y + height); 442 | mPaint.setColor(Color.GREEN); 443 | mPaint.setStyle(Paint.Style.STROKE); 444 | canvas.drawRect(rect, mPaint); 445 | } 446 | } -------------------------------------------------------------------------------- /app/src/main/java/com/demo/facerecognition/view/FaceView.java: -------------------------------------------------------------------------------- 1 | package com.demo.facerecognition.view; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.PixelFormat; 11 | import android.graphics.Point; 12 | import android.graphics.PorterDuff; 13 | import android.graphics.Rect; 14 | import android.graphics.RectF; 15 | import android.graphics.Region; 16 | import android.graphics.SweepGradient; 17 | import android.util.AttributeSet; 18 | import android.util.Log; 19 | import android.view.SurfaceHolder; 20 | import android.view.SurfaceView; 21 | 22 | import com.demo.facerecognition.R; 23 | import com.demo.facerecognition.util.ScreenUtils; 24 | 25 | /** 26 | *
27 | * 文件名: FaceView 28 | * 作 者: zj 29 | * 时 间: 2019/1/2 18:27 30 | * 描 述:人脸识别界面UI,这里可以接视频流和UI,也可以单纯作为UI展示 31 | *32 | */ 33 | public class FaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { 34 | private final String TAG = "FaceView"; 35 | private SurfaceHolder mSurfaceHolder; 36 | /** 37 | * 是否可以开始绘制了 38 | */ 39 | private boolean mStart = false; 40 | /** 41 | * 是否结束 42 | */ 43 | private volatile boolean mFinsh = false; 44 | /** 45 | * 默认中间圆的半径从0开始 46 | */ 47 | private float currentRadius = 0; 48 | /** 49 | * 控件的宽度(默认) 50 | */ 51 | private int mViewWidth = 400; 52 | /** 53 | * 控件高度 54 | */ 55 | private int mViewHeight = 400; 56 | /** 57 | * 中心圆屏幕边距 58 | */ 59 | private int margin; 60 | /** 61 | * 提示文本画笔 62 | */ 63 | private Paint mTextBgPaint; 64 | /** 65 | * 提示文本 66 | */ 67 | private String mTipText; 68 | /** 69 | * 提示文本颜色 70 | */ 71 | private int mTipTextColor; 72 | /** 73 | * 提示文本颜色 74 | */ 75 | private int mTipTextSize; 76 | /** 77 | * 内圆半径 78 | */ 79 | private int mRadius; 80 | /** 81 | * 背景弧宽度 82 | */ 83 | private float mBgArcWidth; 84 | 85 | /** 86 | * 圆心点坐标 87 | */ 88 | private Point mCenterPoint = new Point(); 89 | /** 90 | * 圆弧边界 91 | */ 92 | private RectF mBgRectF = new RectF(); 93 | 94 | /** 95 | * 开始角度 96 | */ 97 | private int mStartAngle = 105; 98 | 99 | /** 100 | * 结束角度 101 | */ 102 | private int mEndAngle = 330; 103 | 104 | /** 105 | * 圆弧背景画笔 106 | */ 107 | private Paint mBgArcPaint; 108 | /** 109 | * 提示语画笔 110 | */ 111 | private Paint mTextPaint; 112 | 113 | /** 114 | * 圆弧画笔 115 | */ 116 | private Paint mArcPaint; 117 | /** 118 | * 渐变器 119 | */ 120 | private SweepGradient mSweepGradient; 121 | 122 | /** 123 | * 是否开始 124 | */ 125 | private boolean isRunning = true; 126 | 127 | /** 128 | * 是否后退 129 | */ 130 | private boolean isBack = false; 131 | /** 132 | * 绘制速度 133 | */ 134 | private int speed = 5; 135 | 136 | /** 137 | * 设置默认转动角度0 138 | */ 139 | float currentAngle = 0; 140 | 141 | /** 142 | * 声明 143 | */ 144 | Canvas canvas = null; 145 | 146 | public FaceView(Context context) { 147 | this(context, null); 148 | } 149 | 150 | public FaceView(Context context, AttributeSet attrs) { 151 | this(context, attrs, 0); 152 | } 153 | 154 | public FaceView(Context context, AttributeSet attrs, int defStyleAttr) { 155 | super(context, attrs, defStyleAttr); 156 | //获取xml里面的属性值 157 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FaceView); 158 | mTipText = array.getString(R.styleable.FaceView_tip_text); 159 | mTipTextColor = array.getColor(R.styleable.FaceView_tip_text_color, Color.WHITE); 160 | mTipTextSize = array.getDimensionPixelSize(R.styleable.FaceView_tip_text_size, ScreenUtils.sp2px(context, 12)); 161 | array.recycle(); 162 | Log.d(TAG, "FaceView构造"); 163 | initHolder(context); 164 | } 165 | 166 | /** 167 | * 初始化控件View 168 | */ 169 | private void initHolder(Context context) { 170 | //获得SurfaceHolder对象 171 | mSurfaceHolder = getHolder(); 172 | //设置透明背景 173 | mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT); 174 | //添加回调 175 | mSurfaceHolder.addCallback(this); 176 | //显示顶层 177 | setZOrderOnTop(true); 178 | //防止遮住控件 179 | setZOrderMediaOverlay(true); 180 | //屏蔽界面焦点 181 | setFocusable(true); 182 | //保持屏幕长亮 183 | setKeepScreenOn(true); 184 | 185 | //初始化值 186 | margin = ScreenUtils.dp2px(context, 60); 187 | mBgArcWidth = ScreenUtils.dp2px(context, 5); 188 | 189 | //绘制文字画笔 190 | mTextPaint = new Paint(); 191 | mTextPaint.setStyle(Paint.Style.FILL); 192 | mTextPaint.setStrokeWidth(8); 193 | mTextPaint.setColor(mTipTextColor); 194 | mTextPaint.setTextSize(mTipTextSize); 195 | mTextPaint.setTextAlign(Paint.Align.CENTER); 196 | 197 | //初始化文字背景画笔 198 | mTextBgPaint = new Paint(); 199 | mTextBgPaint.setAntiAlias(true); 200 | mTextBgPaint.setColor(getResources().getColor(R.color.colorAccent)); 201 | mTextBgPaint.setStyle(Paint.Style.FILL); 202 | 203 | // 圆弧背景 204 | mBgArcPaint = new Paint(); 205 | mBgArcPaint.setAntiAlias(true); 206 | mBgArcPaint.setColor(getResources().getColor(R.color.circleBg)); 207 | mBgArcPaint.setStyle(Paint.Style.STROKE); 208 | mBgArcPaint.setStrokeWidth(mBgArcWidth); 209 | mBgArcPaint.setStrokeCap(Paint.Cap.ROUND); 210 | 211 | // 圆弧 212 | mArcPaint = new Paint(); 213 | mArcPaint.setAntiAlias(true); 214 | mArcPaint.setStyle(Paint.Style.STROKE); 215 | mArcPaint.setStrokeWidth(mBgArcWidth); 216 | mArcPaint.setStrokeCap(Paint.Cap.ROUND); 217 | 218 | //开启线程检测 219 | new Thread(this).start(); 220 | } 221 | 222 | @Override 223 | public void surfaceCreated(SurfaceHolder surfaceHolder) { 224 | mStart = true; 225 | Log.d(TAG, "surfaceCreated()"); 226 | } 227 | 228 | 229 | @Override 230 | public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { 231 | Log.d(TAG, "surfaceChanged()"); 232 | } 233 | 234 | @Override 235 | public void surfaceDestroyed(SurfaceHolder surfaceHolder) { 236 | mStart = false; 237 | Log.d(TAG, "surfaceDestroyed()"); 238 | } 239 | 240 | @SuppressLint("DrawAllocation") 241 | @Override 242 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 243 | //测量view的宽度 244 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 245 | if (widthMode == MeasureSpec.EXACTLY) { 246 | mViewWidth = MeasureSpec.getSize(widthMeasureSpec); 247 | } 248 | 249 | //测量view的高度 250 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 251 | if (heightMode == MeasureSpec.EXACTLY) { 252 | mViewHeight = MeasureSpec.getSize(heightMeasureSpec); 253 | } 254 | 255 | setMeasuredDimension(mViewWidth, mViewHeight); 256 | Log.d(TAG, "onMeasure mViewWidth : " + mViewWidth + " mViewHeight : " + mViewHeight); 257 | 258 | //获取圆的相关参数 259 | mCenterPoint.x = mViewWidth / 2; 260 | mCenterPoint.y = mViewHeight / 2; 261 | 262 | //外环圆的半径 263 | mRadius = mCenterPoint.x - margin; 264 | 265 | //绘制背景圆弧的边界 266 | mBgRectF.left = mCenterPoint.x - mRadius - mBgArcWidth / 2; 267 | mBgRectF.top = mCenterPoint.y - mRadius - mBgArcWidth / 2; 268 | mBgRectF.right = mCenterPoint.x + mRadius + mBgArcWidth / 2; 269 | mBgRectF.bottom = mCenterPoint.y + mRadius + mBgArcWidth / 2; 270 | 271 | //进度条颜色 -mStartAngle将位置便宜到原处 272 | mSweepGradient = new SweepGradient(mCenterPoint.x - mStartAngle, mCenterPoint.y - mStartAngle, getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark)); 273 | } 274 | 275 | @Override 276 | public void run() { 277 | //循环绘制画面内容 278 | while (!mFinsh) { 279 | if (mStart) { 280 | drawView(); 281 | } 282 | } 283 | } 284 | 285 | 286 | private void drawView() { 287 | try { 288 | //获得canvas对象 289 | canvas = mSurfaceHolder.lockCanvas(); 290 | if (canvas != null) { 291 | //清除画布上面里面的内容 292 | canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); 293 | //绘制画布内容 294 | drawContent(canvas); 295 | } 296 | } catch (Exception e) { 297 | e.printStackTrace(); 298 | } finally { 299 | if (canvas != null) { 300 | //释放canvas锁,并且显示视图 301 | mSurfaceHolder.unlockCanvasAndPost(canvas); 302 | } 303 | } 304 | } 305 | 306 | /** 307 | * 跟新提示信息 308 | * 309 | * @param title title 310 | */ 311 | public void updateTipsInfo(String title) { 312 | mTipText = title; 313 | } 314 | 315 | private void drawContent(Canvas canvas) { 316 | //防止save()和restore()方法代码之后对Canvas执行的操作,继续对后续的绘制会产生影响 317 | canvas.save(); 318 | //先画提示语 319 | drawHintText(canvas); 320 | //绘制正方形的框内类似人脸识别 321 | // drawFaceRectTest(canvas); 322 | //绘制人脸识别部分 323 | drawFaceCircle(canvas); 324 | //画外边进度条 325 | drawRoundProgress(canvas); 326 | canvas.restore(); 327 | } 328 | 329 | private void drawFaceCircle(Canvas canvas) { 330 | // 圆形,放大效果 331 | currentRadius += 20; 332 | if (currentRadius > mRadius) 333 | currentRadius = mRadius; 334 | 335 | //设置画板样式 336 | Path path = new Path(); 337 | //以(400,200)为圆心,半径为100绘制圆 指创建顺时针方向的矩形路径 338 | path.addCircle(mCenterPoint.x, mCenterPoint.y, currentRadius, Path.Direction.CW); 339 | //是A形状中不同于B的部分显示出来 340 | canvas.clipPath(path, Region.Op.DIFFERENCE); 341 | // 半透明背景效果 342 | canvas.clipRect(0, 0, mViewWidth, mViewHeight); 343 | //绘制背景颜色 344 | canvas.drawColor(getResources().getColor(R.color.viewBgWhite)); 345 | } 346 | 347 | /** 348 | * 绘制人脸识别界面进度条 349 | * 350 | * @param canvas canvas 351 | */ 352 | private void drawRoundProgress(Canvas canvas) { 353 | // 逆时针旋转105度 354 | canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y); 355 | // 设置圆环背景 356 | canvas.drawArc(mBgRectF, 0, mEndAngle, false, mBgArcPaint); 357 | //判断是否正在运行 358 | if (isRunning) { 359 | if (isBack) { 360 | currentAngle -= speed; 361 | if (currentAngle <= 0) 362 | currentAngle = 0; 363 | } else { 364 | currentAngle += speed; 365 | if (currentAngle >= mEndAngle) 366 | currentAngle = mEndAngle; 367 | } 368 | } 369 | // 设置渐变颜色 370 | mArcPaint.setShader(mSweepGradient); 371 | canvas.drawArc(mBgRectF, 0, currentAngle, false, mArcPaint); 372 | } 373 | 374 | /** 375 | * 从头位置开始动画 376 | */ 377 | public void resetPositionStart() { 378 | currentAngle = 0; 379 | isBack = false; 380 | } 381 | 382 | /** 383 | * 动画直接完成 384 | */ 385 | public void finnishAnimator() { 386 | currentAngle = mEndAngle; 387 | isBack = false; 388 | } 389 | 390 | /** 391 | * 停止动画 392 | */ 393 | public void pauseAnimator() { 394 | isRunning = false; 395 | } 396 | 397 | /** 398 | * 开始动画 399 | */ 400 | public void startAnimator() { 401 | isRunning = true; 402 | } 403 | 404 | /** 405 | * 动画回退 406 | */ 407 | public void backAnimator() { 408 | isRunning = true; 409 | isBack = true; 410 | } 411 | 412 | /** 413 | * 动画前进 414 | */ 415 | public void forwardAnimator() { 416 | isRunning = true; 417 | isBack = false; 418 | } 419 | 420 | /** 421 | * 销毁视图,释放资源 422 | */ 423 | public void destroyView() { 424 | //停止运行 425 | mFinsh = true; 426 | mStart = false; 427 | isRunning = false; 428 | isBack = false; 429 | } 430 | 431 | 432 | /** 433 | * 绘制人脸识别提示 434 | * 435 | * @param canvas canvas 436 | */ 437 | private void drawHintText(Canvas canvas) { 438 | //圆视图宽度 (屏幕减去两边距离) 439 | int cameraWidth = mViewWidth - 2 * margin; 440 | //x轴起点(文字背景起点) 441 | int x = margin; 442 | //宽度(提示框背景宽度) 443 | int width = cameraWidth; 444 | //y轴起点 445 | int y = (int) (mCenterPoint.y - mRadius); 446 | //提示框背景高度 447 | int height = cameraWidth / 4; 448 | Rect rect = new Rect(x, y, x + width, y + height); 449 | canvas.drawRect(rect, mTextBgPaint); 450 | 451 | //计算baseline 452 | Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); 453 | float distance = (fontMetrics.bottom - fontMetrics.top) / 4; 454 | float baseline = rect.centerY() + distance; 455 | canvas.drawText(mTipText, rect.centerX(), baseline, mTextPaint); 456 | } 457 | 458 | /** 459 | * 绘制人脸识别矩形区域 460 | * 461 | * @param canvas canvas 462 | */ 463 | private void drawFaceRectTest(Canvas canvas) { 464 | int cameraWidth = mViewWidth - 2 * margin; 465 | int x = margin + cameraWidth / 6; 466 | int width = cameraWidth * 2 / 3; 467 | int y = mCenterPoint.x + (width / 2); 468 | int height = width; 469 | Rect rect = new Rect(x, y, x + width, y + height); 470 | mTextBgPaint.setColor(Color.GREEN); 471 | mTextBgPaint.setStyle(Paint.Style.STROKE); 472 | canvas.drawRect(rect, mTextBgPaint); 473 | } 474 | } --------------------------------------------------------------------------------