├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ ├── arcsoft_face.jar │ └── arcsoft_image_util.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xiaoyou │ │ └── face │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── xiaoyou │ │ │ └── face │ │ │ ├── Application.java │ │ │ ├── activity │ │ │ ├── BaseActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── RegisterAndRecognizeActivity.java │ │ │ └── SignDetailActivity.java │ │ │ ├── adapter │ │ │ └── FunctionAdapter.java │ │ │ ├── common │ │ │ └── Constants.java │ │ │ ├── engine │ │ │ └── GlideEngine.java │ │ │ ├── faceserver │ │ │ ├── CompareResult.java │ │ │ └── FaceServer.java │ │ │ ├── fragment │ │ │ ├── IndexFragment.java │ │ │ ├── MeFragment.java │ │ │ └── ToolFragment.java │ │ │ ├── model │ │ │ ├── Channel.java │ │ │ ├── DrawInfo.java │ │ │ ├── FacePreviewInfo.java │ │ │ ├── FaceRegisterInfo.java │ │ │ └── Login.java │ │ │ ├── service │ │ │ ├── DateFormatUtils.java │ │ │ ├── DateHistoryTO.java │ │ │ ├── History.java │ │ │ ├── Is_Sign.java │ │ │ ├── RegisterInfo.java │ │ │ ├── SQLiteHelper.java │ │ │ ├── Service.java │ │ │ ├── StudentInfo.java │ │ │ └── StudentInfoTO.java │ │ │ ├── utils │ │ │ ├── ConfigUtil.java │ │ │ ├── DrawHelper.java │ │ │ ├── PieChartManager.java │ │ │ ├── ToastUtils.java │ │ │ ├── Tools.java │ │ │ ├── TrackUtil.java │ │ │ ├── TypeChange.java │ │ │ ├── camera │ │ │ │ ├── CameraHelper.java │ │ │ │ ├── CameraListener.java │ │ │ │ └── DualCameraHelper.java │ │ │ └── face │ │ │ │ ├── FaceHelper.java │ │ │ │ ├── FaceListener.java │ │ │ │ ├── LivenessType.java │ │ │ │ ├── RecognizeColor.java │ │ │ │ ├── RequestFeatureStatus.java │ │ │ │ └── RequestLivenessStatus.java │ │ │ ├── view │ │ │ ├── ColorfulMonthView.java │ │ │ └── ColorfulWeekView.java │ │ │ └── widget │ │ │ ├── FaceRectView.java │ │ │ └── FaceSearchResultAdapter.java │ ├── jniLibs │ │ ├── arm64-v8a │ │ │ ├── libarcsoft_face.so │ │ │ ├── libarcsoft_face_engine.so │ │ │ └── libarcsoft_image_util.so │ │ └── armeabi-v7a │ │ │ ├── libarcsoft_face.so │ │ │ ├── libarcsoft_face_engine.so │ │ │ └── libarcsoft_image_util.so │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_add_reaction.xml │ │ ├── ic_arrow_back.xml │ │ ├── ic_calendar.xml │ │ ├── ic_camera.xml │ │ ├── ic_close.xml │ │ ├── ic_credit.xml │ │ ├── ic_face.xml │ │ ├── ic_faces_white.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_photo_camera.xml │ │ ├── ic_pie_chart.xml │ │ ├── ic_search.xml │ │ ├── ic_search_white.xml │ │ ├── ic_switch_camera.xml │ │ └── ic_tool_box.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_register_and_recognize.xml │ │ ├── activity_sign_detail.xml │ │ ├── dialog_input.xml │ │ ├── fragment_index.xml │ │ ├── fragment_me.xml │ │ ├── fragment_tool.xml │ │ ├── include_close_dialog.xml │ │ ├── item_grid.xml │ │ └── recycler_item_search_result.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── face.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ ├── login.png │ │ ├── search.png │ │ └── statistics.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ └── file_paths_public.xml │ └── test │ └── java │ └── com │ └── xiaoyou │ └── face │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image ├── 1.jpg ├── 2.jpg ├── 2.png ├── 3.jpg ├── 3.png ├── 4.jpg └── 4.png ├── lombok.config └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 注意事项 2 | 1.首次启动程序会提示引擎激活失败,我们需要关闭应用重新启动,出现下面这个才能使用 3 | 4 | 5 | 2.人脸识别需要注册登记,配置路径在:`\app\src\main\java\com\xiaoyou\face\common` 6 | 7 | 修改下面这两个地方 8 | 9 | ```java 10 | public static final String APP_ID = "xxxxxx"; 11 | public static final String SDK_KEY = "xxxxx"; 12 | ``` 13 | 14 | 注册网址:[点击访问](https://www.arcsoft.com.cn/technology/face.html) 15 | 16 | 3.默认的SDK好像只有一年的有效期,所以需要进行替换 17 | 18 | 第一个就是 `libs` 目录下的两个jar文件。 19 | 然后就是 `app/src/main/jniLibs`目录下的so文件(包括face和image_util的) 20 | 21 | 22 | 23 | ## 功能介绍 24 | 25 | ### 签到日历功能 26 | 27 | 28 | 29 | ### 人脸录入 30 | 31 | 32 | 33 | ### 签到功能 34 | 35 | 36 | 37 | ### 签到详情 38 | 39 | 40 | 41 | ### 签到数据查询 42 | 43 | 44 | 45 | ### 图表展示 46 | 47 | 48 | 49 | ## 开发人员 50 | 51 | 1.唥雨凊:负责人脸识别和数据库部分 52 | 53 | 2.小游:负责整个APP的界面开发和整合 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | 6 | android { 7 | compileSdkVersion 29 8 | buildToolsVersion "29.0.3" 9 | 10 | defaultConfig { 11 | applicationId "com.xiaoyou.face" 12 | minSdkVersion 24 13 | targetSdkVersion 29 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | 19 | // 使用lombok插件 20 | javaCompileOptions { 21 | annotationProcessorOptions { 22 | includeCompileClasspath true 23 | } 24 | } 25 | } 26 | 27 | buildTypes { 28 | release { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | // 开启视图绑定功能 38 | viewBinding { 39 | enabled = true 40 | } 41 | 42 | } 43 | 44 | dependencies { 45 | // 使用本地库 46 | implementation fileTree(dir: 'libs', include: ['*.jar']) 47 | // 人脸识别 48 | implementation 'io.reactivex.rxjava2:rxjava:2.2.6' 49 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' 50 | 51 | // 使用XUL库 https://gitee.com/xuexiangjys/XUI 52 | implementation 'com.github.xuexiangjys:XUI:1.1.5' 53 | 54 | // 黄油刀 https://github.com/JakeWharton/butterknife (这个库已经不推荐使用了) 55 | // implementation 'com.jakewharton:butterknife:10.2.3' 56 | // annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3' 57 | 58 | // 底部导航栏 59 | implementation 'com.ashokvarma.android:bottom-navigation-bar:2.2.0' 60 | // 多彩日历组件 61 | implementation 'com.haibin:calendarview:3.7.0' 62 | // 拍照库 63 | implementation 'com.github.HuanTanSheng:EasyPhotos:3.0.6' 64 | // 相机,不需要了 65 | // implementation 'com.wonderkiln:camerakit:0.13.1' 66 | 67 | // 图片加载引擎 68 | implementation 'com.github.bumptech.glide:glide:4.11.0' 69 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 70 | annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' 71 | 72 | // 图表库 https://github.com/PhilJay/MPAndroidChart 73 | implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' 74 | 75 | //WebView 76 | implementation 'com.github.xuexiangjys.AgentWeb:agentweb-core:1.0.0' 77 | implementation 'com.github.xuexiangjys.AgentWeb:agentweb-download:1.0.0'//选填 78 | implementation 'com.github.xuexiangjys.AgentWeb:agentweb-filechooser:1.0.0'//选填 79 | 80 | // Lombok插件 81 | //1、lombok依赖 82 | implementation 'javax.annotation:javax.annotation-api:1.2' 83 | //依赖lombok的API,内部虽然已经声明了注解解释器,但在AndroidStudio上有Bug(导致编译报错找不到生成的方法),需要自己主动再声明一次 84 | implementation 'org.projectlombok:lombok:1.16.6' 85 | //主动声明注解解释器 86 | annotationProcessor 'org.projectlombok:lombok:1.16.6' 87 | 88 | // gson 一个json 解析库 https://github.com/google/gson 89 | implementation 'com.google.code.gson:gson:2.8.6' 90 | 91 | // 表格显示库 https://github.com/huangyanbin/smartTable 92 | implementation 'com.github.huangyanbin:SmartTable:2.2.0' 93 | 94 | 95 | 96 | // 其他安卓自带依赖 97 | implementation 'androidx.appcompat:appcompat:1.2.0' 98 | implementation 'com.google.android.material:material:1.2.1' 99 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 100 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 101 | testImplementation 'junit:junit:4.13.1' 102 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 103 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 104 | } -------------------------------------------------------------------------------- /app/libs/arcsoft_face.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyou-project/android-course-design/caddcbae0cd19af083382942f34f1f1d2754fcbd/app/libs/arcsoft_face.jar -------------------------------------------------------------------------------- /app/libs/arcsoft_image_util.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyou-project/android-course-design/caddcbae0cd19af083382942f34f1f1d2754fcbd/app/libs/arcsoft_image_util.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/xiaoyou/face/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import androidx.test.platform.app.InstrumentationRegistry; 7 | import androidx.test.ext.junit.runners.AndroidJUnit4; 8 | 9 | import com.xiaoyou.face.service.Is_Sign; 10 | import com.xiaoyou.face.service.RegisterInfo; 11 | import com.xiaoyou.face.service.SQLiteHelper; 12 | import com.xiaoyou.face.service.Service; 13 | import com.xiaoyou.face.service.StudentInfo; 14 | 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | 18 | import java.time.LocalDate; 19 | import java.time.LocalDateTime; 20 | 21 | import static org.junit.Assert.*; 22 | 23 | /** 24 | * Instrumented test, which will execute on an Android device. 25 | * 26 | * @see Testing documentation 27 | */ 28 | @RunWith(AndroidJUnit4.class) 29 | public class ExampleInstrumentedTest { 30 | @Test 31 | public void useAppContext() { 32 | // Context of the app under test. 33 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 34 | assertEquals("com.xiaoyou.face", appContext.getPackageName()); 35 | } 36 | 37 | @Test 38 | public void addData(){ 39 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 40 | Service service; 41 | service = new SQLiteHelper(appContext); 42 | service.signUp("1806040103","小游", LocalDateTime.now()); 43 | } 44 | 45 | @Test 46 | public void userLogin(){ 47 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 48 | SQLiteHelper sqLiteHelper = new SQLiteHelper(appContext); 49 | System.out.println(); 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 36 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyou-project/android-course-design/caddcbae0cd19af083382942f34f1f1d2754fcbd/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/Application.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face; 2 | 3 | import com.xuexiang.xui.XUI; 4 | 5 | /** 6 | * application类 7 | * @author 小游 8 | * @date 2020/12/14 9 | */ 10 | public class Application extends android.app.Application { 11 | @Override 12 | public void onCreate() { 13 | super.onCreate(); 14 | //初始化UI框架 15 | XUI.init(this); 16 | //开启UI框架调试日志 17 | XUI.debug(true); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/activity/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.activity; 2 | 3 | import android.content.pm.PackageManager; 4 | import android.widget.Toast; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.appcompat.app.AppCompatActivity; 8 | import androidx.core.content.ContextCompat; 9 | 10 | public abstract class BaseActivity extends AppCompatActivity { 11 | /** 12 | * 权限检查 13 | * 14 | * @param neededPermissions 需要的权限 15 | * @return 是否全部被允许 16 | */ 17 | protected boolean checkPermissions(String[] neededPermissions) { 18 | if (neededPermissions == null || neededPermissions.length == 0) { 19 | return true; 20 | } 21 | boolean allGranted = true; 22 | for (String neededPermission : neededPermissions) { 23 | allGranted &= ContextCompat.checkSelfPermission(this, neededPermission) == PackageManager.PERMISSION_GRANTED; 24 | } 25 | return allGranted; 26 | } 27 | 28 | @Override 29 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 30 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 31 | boolean isAllGranted = true; 32 | for (int grantResult : grantResults) { 33 | isAllGranted &= (grantResult == PackageManager.PERMISSION_GRANTED); 34 | } 35 | afterRequestPermission(requestCode, isAllGranted); 36 | } 37 | 38 | /** 39 | * 请求权限的回调 40 | * 41 | * @param requestCode 请求码 42 | * @param isAllGranted 是否全部被同意 43 | */ 44 | abstract void afterRequestPermission(int requestCode, boolean isAllGranted); 45 | 46 | protected void showToast(String s) { 47 | Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show(); 48 | } 49 | protected void showLongToast(String s) { 50 | Toast.makeText(getApplicationContext(), s, Toast.LENGTH_LONG).show(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.activity; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | import androidx.core.app.ActivityCompat; 5 | import androidx.fragment.app.FragmentTransaction; 6 | 7 | import android.Manifest; 8 | import android.annotation.SuppressLint; 9 | import android.content.Intent; 10 | import android.os.Bundle; 11 | import android.util.Log; 12 | import android.view.View; 13 | 14 | import com.arcsoft.face.ActiveFileInfo; 15 | import com.arcsoft.face.ErrorInfo; 16 | import com.arcsoft.face.FaceEngine; 17 | import com.arcsoft.face.enums.RuntimeABI; 18 | import com.ashokvarma.bottomnavigation.BottomNavigationBar; 19 | import com.ashokvarma.bottomnavigation.BottomNavigationItem; 20 | import com.xiaoyou.face.R; 21 | import com.xiaoyou.face.adapter.FunctionAdapter; 22 | import com.xiaoyou.face.common.Constants; 23 | import com.xiaoyou.face.databinding.ActivityMainBinding; 24 | import com.xiaoyou.face.fragment.IndexFragment; 25 | import com.xiaoyou.face.fragment.MeFragment; 26 | import com.xiaoyou.face.fragment.ToolFragment; 27 | import com.xiaoyou.face.model.Channel; 28 | import com.xiaoyou.face.utils.ConfigUtil; 29 | import com.xuexiang.xui.XUI; 30 | 31 | import java.util.ArrayList; 32 | 33 | import io.reactivex.Observable; 34 | import io.reactivex.ObservableEmitter; 35 | import io.reactivex.ObservableOnSubscribe; 36 | import io.reactivex.Observer; 37 | import io.reactivex.android.schedulers.AndroidSchedulers; 38 | import io.reactivex.disposables.Disposable; 39 | import io.reactivex.schedulers.Schedulers; 40 | 41 | import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_270_ONLY; 42 | import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_ALL_OUT; 43 | 44 | 45 | /** 46 | * 主activity 47 | * @author 小游 48 | * @date 2020/12/14 49 | */ 50 | public class MainActivity extends BaseActivity { 51 | 52 | private static final String TAG = "ChooseFunctionActivity"; 53 | private static final int ACTION_REQUEST_PERMISSIONS = 0x001; 54 | // 在线激活所需的权限 55 | private static final String[] NEEDED_PERMISSIONS = new String[]{Manifest.permission.READ_PHONE_STATE}; 56 | boolean libraryExists = true; 57 | // Demo 所需的动态库文件 58 | private static final String[] LIBRARIES = new String[]{ 59 | // 人脸相关 60 | "libarcsoft_face_engine.so", 61 | "libarcsoft_face.so", 62 | // 图像库相关 63 | "libarcsoft_image_util.so", 64 | }; 65 | 66 | private ActivityMainBinding binding; 67 | 68 | @Override 69 | protected void onCreate(Bundle savedInstanceState) { 70 | // 不在AndroidManifest.xml里面设置因为这个会无法继承AppCompatActivity 71 | XUI.initTheme(this); 72 | super.onCreate(savedInstanceState); 73 | // 这里我们使用视图绑定来获取view里面的对象 https://developer.android.com/topic/libraries/view-binding#java 74 | binding = ActivityMainBinding.inflate(getLayoutInflater()); 75 | View view = binding.getRoot(); 76 | setContentView(view); 77 | // 创建一个自己的fragment 78 | IndexFragment indexFragment = new IndexFragment(); 79 | // 给我我们的fragment添加内容 80 | getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, indexFragment).commit(); 81 | // tab初始化 82 | initTab(); 83 | // 激活引擎 84 | activeEngine(getWindow().getDecorView()); 85 | //选择检测角度 默认270度 86 | ConfigUtil.setFtOrient(getApplicationContext(), ASF_OP_ALL_OUT); 87 | } 88 | 89 | /** 90 | * tab初始化 91 | */ 92 | private void initTab(){ 93 | BottomNavigationBar bar = binding.bottomNavigationBar; 94 | bar.addItem(new BottomNavigationItem(R.drawable.ic_face, "签到")) 95 | .addItem(new BottomNavigationItem(R.drawable.ic_pie_chart, "统计")) 96 | .addItem(new BottomNavigationItem(R.drawable.ic_search, "查询")) 97 | .setFirstSelectedPosition(0).initialise(); 98 | // 设置底部导航栏的点击事件 99 | bar.setTabSelectedListener(new BottomNavigationBar.OnTabSelectedListener() { 100 | @Override 101 | public void onTabSelected(int position) { 102 | // 获取transaction对象,用户切换fragment 103 | FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 104 | // 获取选择的位置,然后进行设置 105 | switch (position){ 106 | case 0: 107 | transaction.replace(R.id.fragment_container,new IndexFragment()); 108 | break; 109 | case 1: 110 | transaction.replace(R.id.fragment_container,new ToolFragment()); 111 | break; 112 | case 2: 113 | transaction.replace(R.id.fragment_container,new MeFragment()); 114 | default: 115 | break; 116 | } 117 | // 提交更改 118 | transaction.commit(); 119 | } 120 | @Override 121 | public void onTabUnselected(int position) { 122 | } 123 | 124 | @Override 125 | public void onTabReselected(int position) { 126 | } 127 | }); 128 | } 129 | 130 | 131 | /** 132 | * 激活引擎 133 | * @param view 134 | */ 135 | @SuppressLint("CheckResult") 136 | public void activeEngine(final View view) { 137 | if (!libraryExists) { 138 | showToast(getString(R.string.library_not_found)); 139 | return; 140 | } 141 | if (!checkPermissions(NEEDED_PERMISSIONS)) { 142 | ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS); 143 | return; 144 | } 145 | if (view != null) { 146 | view.setClickable(false); 147 | } 148 | Observable integerObservable = Observable.create(new ObservableOnSubscribe() { 149 | @Override 150 | public void subscribe(ObservableEmitter emitter) { 151 | RuntimeABI runtimeABI = FaceEngine.getRuntimeABI(); 152 | Log.i(TAG, "subscribe: getRuntimeABI() " + runtimeABI); 153 | 154 | long start = System.currentTimeMillis(); 155 | int activeCode = FaceEngine.activeOnline(MainActivity.this, Constants.APP_ID, Constants.SDK_KEY); 156 | Log.i(TAG, "subscribe cost: " + (System.currentTimeMillis() - start)); 157 | emitter.onNext(activeCode); 158 | } 159 | }); 160 | integerObservable.subscribeOn(Schedulers.io()); 161 | integerObservable.observeOn(AndroidSchedulers.mainThread()); 162 | integerObservable.subscribe(new Observer() { 163 | @Override 164 | public void onSubscribe(Disposable d) { 165 | 166 | } 167 | 168 | @Override 169 | public void onNext(Integer activeCode) { 170 | if (activeCode == ErrorInfo.MOK) { 171 | showToast(getString(R.string.active_success)); 172 | } else if (activeCode == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) { 173 | showToast(getString(R.string.already_activated)); 174 | } else { 175 | showToast(getString(R.string.active_failed, activeCode)); 176 | } 177 | 178 | if (view != null) { 179 | view.setClickable(true); 180 | } 181 | ActiveFileInfo activeFileInfo = new ActiveFileInfo(); 182 | int res = FaceEngine.getActiveFileInfo(MainActivity.this, activeFileInfo); 183 | if (res == ErrorInfo.MOK) { 184 | Log.i(TAG, activeFileInfo.toString()); 185 | } 186 | 187 | } 188 | 189 | @Override 190 | public void onError(Throwable e) { 191 | showToast(e.getMessage()); 192 | if (view != null) { 193 | view.setClickable(true); 194 | } 195 | } 196 | 197 | @Override 198 | public void onComplete() { 199 | 200 | } 201 | }); 202 | 203 | } 204 | 205 | @Override 206 | void afterRequestPermission(int requestCode, boolean isAllGranted) { 207 | 208 | } 209 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/activity/SignDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.activity; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | import androidx.core.content.ContextCompat; 5 | 6 | 7 | import android.annotation.SuppressLint; 8 | import android.os.Bundle; 9 | import android.util.Log; 10 | import android.view.View; 11 | 12 | 13 | import com.bin.david.form.data.CellInfo; 14 | import com.bin.david.form.data.format.bg.BaseCellBackgroundFormat; 15 | import com.xiaoyou.face.R; 16 | import com.xiaoyou.face.databinding.ActivitySignDetailBinding; 17 | import com.xiaoyou.face.model.Login; 18 | import com.xiaoyou.face.service.History; 19 | import com.xiaoyou.face.service.SQLiteHelper; 20 | import com.xiaoyou.face.service.Service; 21 | import com.xiaoyou.face.service.StudentInfo; 22 | import com.xiaoyou.face.service.StudentInfoTO; 23 | import com.xiaoyou.face.utils.Tools; 24 | import com.xuexiang.xui.XUI; 25 | 26 | import java.text.SimpleDateFormat; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | /** 31 | * 签到详情显示 32 | * @author 小游 33 | */ 34 | public class SignDetailActivity extends AppCompatActivity { 35 | 36 | private ActivitySignDetailBinding binding; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | // 不在AndroidManifest.xml里面设置因为这个会无法继承AppCompatActivity 41 | XUI.initTheme(this); 42 | super.onCreate(savedInstanceState); 43 | binding = ActivitySignDetailBinding.inflate(getLayoutInflater()); 44 | View view = binding.getRoot(); 45 | setContentView(view); 46 | // 视图初始化 47 | initView(); 48 | } 49 | 50 | /** 51 | * 视图初始化 52 | */ 53 | private void initView(){ 54 | // 导航栏按钮点击事件 55 | binding.navigation.setLeftClickListener(v->{finish();}); 56 | // 显示签到详情 57 | showDetail(); 58 | } 59 | 60 | /** 61 | * 显示签到的详情情况 62 | */ 63 | private void showDetail(){ 64 | List list = new ArrayList<>(); 65 | // 查询签到详情信息 66 | Service service = new SQLiteHelper(this); 67 | try { 68 | List histories =service.getCountToday(); 69 | @SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 70 | for (StudentInfoTO history : histories) { 71 | list.add(new Login(history.getStuId(),history.getName(),sdf.format(history.getDateTime()))); 72 | } 73 | }catch (Exception e){ 74 | return; 75 | } 76 | // 表格样式(占满全屏) 77 | binding.table.getConfig().setMinTableWidth(Tools.getWidth(this)); 78 | // 显示数据 79 | binding.table.setData(list); 80 | // 统计行 81 | binding.table.getConfig().setFixedTitle(true); 82 | // 格式化内容背景(这里我们设置背景交替) 83 | binding.table.getConfig().setContentCellBackgroundFormat(new BaseCellBackgroundFormat() { 84 | @Override 85 | public int getBackGroundColor(CellInfo cellInfo) { 86 | int color = R.color.white; 87 | if (cellInfo.row%2 ==0){ 88 | color = R.color.tab_background; 89 | } 90 | return ContextCompat.getColor(getApplicationContext(), color); 91 | } 92 | }); 93 | // 隐藏顶部的序号列 94 | binding.table.getConfig().setShowXSequence(false); 95 | 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/adapter/FunctionAdapter.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.adapter; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.BaseAdapter; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.xiaoyou.face.R; 13 | import com.xiaoyou.face.model.Channel; 14 | 15 | import java.util.ArrayList; 16 | 17 | /** 18 | * 九宫格布局的适配器 19 | * @author 小游 20 | * @date 2020/12/15 21 | */ 22 | public class FunctionAdapter extends BaseAdapter { 23 | 24 | private final ArrayList channelList; 25 | private final LayoutInflater layoutInflater; 26 | 27 | /** 28 | * adapter初始化,我们传入一个列表和context对象 29 | * @param list 数据 30 | * @param context context对象 31 | */ 32 | public FunctionAdapter(ArrayList list, Context context){ 33 | channelList = list; 34 | layoutInflater = LayoutInflater.from(context); 35 | } 36 | 37 | /** 38 | * 设置list的数目 39 | * @return 返回数目 40 | */ 41 | @Override 42 | public int getCount() { 43 | return channelList.size(); 44 | } 45 | 46 | @Override 47 | public Object getItem(int position) { 48 | return null; 49 | } 50 | 51 | @Override 52 | public long getItemId(int position) { 53 | return 0; 54 | } 55 | 56 | @SuppressLint("InflateParams") 57 | @Override 58 | public View getView(int position, View convertView, ViewGroup parent) { 59 | ViewHolder holder = null; 60 | 61 | // 把view放到布局里面缓存 62 | if(convertView == null){ 63 | //加载布局 64 | convertView =layoutInflater.inflate(R.layout.item_grid,null); 65 | // 缓存布局 66 | holder = new ViewHolder(); 67 | holder.imgChannel =convertView.findViewById(R.id.img); 68 | holder.decChannel =convertView.findViewById(R.id.dec); 69 | convertView.setTag(holder); 70 | }else{ 71 | holder = (ViewHolder)convertView.getTag(); 72 | } 73 | 74 | //设置图标和文字 75 | Channel channel = channelList.get(position); 76 | // 设置图标和文字 77 | if(channel != null){ 78 | holder.decChannel.setText(channel.getDec()); 79 | holder.imgChannel.setImageResource(channel.getImgId()); 80 | } 81 | return convertView; 82 | } 83 | 84 | /** 85 | * view holder布局 86 | */ 87 | static class ViewHolder{ 88 | ImageView imgChannel; 89 | TextView decChannel; 90 | } 91 | 92 | // /** 93 | // * 94 | // */ 95 | // interface OnItemClickListener{ 96 | // void onClick(int posttion); 97 | // } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/common/Constants.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.common; 2 | 3 | public class Constants { 4 | 5 | public static final String APP_ID = "xxxxxx"; 6 | public static final String SDK_KEY = "xxxxx"; 7 | 8 | /** 9 | * IR预览数据相对于RGB预览数据的横向偏移量,注意:是预览数据,一般的摄像头的预览数据都是 width > height 10 | */ 11 | public static final int HORIZONTAL_OFFSET = 0; 12 | /** 13 | * IR预览数据相对于RGB预览数据的纵向偏移量,注意:是预览数据,一般的摄像头的预览数据都是 width > height 14 | */ 15 | public static final int VERTICAL_OFFSET = 0; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/engine/GlideEngine.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.engine; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.net.Uri; 6 | import android.widget.ImageView; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import com.bumptech.glide.Glide; 11 | import com.huantansheng.easyphotos.engine.ImageEngine; 12 | 13 | import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade; 14 | 15 | /** 16 | * 自定义图片引擎 17 | * 18 | * @author 小游 19 | * @date 2020/12/15 20 | */ 21 | public class GlideEngine implements ImageEngine { 22 | //单例 23 | private static GlideEngine instance = null; 24 | //单例模式,私有构造方法 25 | private GlideEngine() { 26 | } 27 | //获取单例 28 | public static GlideEngine getInstance() { 29 | if (null == instance) { 30 | synchronized (GlideEngine.class) { 31 | if (null == instance) { 32 | instance = new GlideEngine(); 33 | } 34 | } 35 | } 36 | return instance; 37 | } 38 | 39 | /** 40 | * 加载图片到ImageView 41 | * 42 | * @param context 上下文 43 | * @param uri 图片路径Uri 44 | * @param imageView 加载到的ImageView 45 | */ 46 | //安卓10推荐uri,并且path的方式不再可用 47 | @Override 48 | public void loadPhoto(Context context, Uri uri, ImageView imageView) { 49 | Glide.with(context).load(uri).transition(withCrossFade()).into(imageView); 50 | } 51 | 52 | /** 53 | * 加载gif动图图片到ImageView,gif动图不动 54 | * 55 | * @param context 上下文 56 | * @param gifUri gif动图路径Uri 57 | * @param imageView 加载到的ImageView 58 | *

59 | * 备注:不支持动图显示的情况下可以不写 60 | */ 61 | //安卓10推荐uri,并且path的方式不再可用 62 | @Override 63 | public void loadGifAsBitmap(Context context, Uri gifUri, ImageView imageView) { 64 | Glide.with(context).asBitmap().load(gifUri).into(imageView); 65 | } 66 | 67 | /** 68 | * 加载gif动图到ImageView,gif动图动 69 | * 70 | * @param context 上下文 71 | * @param gifUri gif动图路径Uri 72 | * @param imageView 加载动图的ImageView 73 | *

74 | * 备注:不支持动图显示的情况下可以不写 75 | */ 76 | //安卓10推荐uri,并且path的方式不再可用 77 | @Override 78 | public void loadGif(Context context, Uri gifUri, ImageView imageView) { 79 | Glide.with(context).asGif().load(gifUri).transition(withCrossFade()).into(imageView); 80 | } 81 | 82 | 83 | /** 84 | * 获取图片加载框架中的缓存Bitmap 85 | * 86 | * @param context 上下文 87 | * @param uri 图片路径 88 | * @param width 图片宽度 89 | * @param height 图片高度 90 | * @return Bitmap 91 | * @throws Exception 异常直接抛出,EasyPhotos内部处理 92 | */ 93 | //安卓10推荐uri,并且path的方式不再可用 94 | @Override 95 | public Bitmap getCacheBitmap(Context context, Uri uri, int width, int height) throws Exception { 96 | return Glide.with(context).asBitmap().load(uri).submit(width, height).get(); 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/faceserver/CompareResult.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.faceserver; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * 比对结果 10 | * @author 小游 11 | * @date 2020/12/16 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class CompareResult { 17 | /** 18 | * id 19 | */ 20 | private String id; 21 | /** 22 | * 姓名 23 | */ 24 | private String userName; 25 | /** 26 | * 学号 27 | */ 28 | private String userNo; 29 | /** 30 | * 相似度 31 | */ 32 | private float similar; 33 | /** 34 | * 追踪id 35 | */ 36 | private int trackId; 37 | 38 | public CompareResult(String id,String stuId,String name, float similar) { 39 | this.id = id; 40 | this.userNo =stuId; 41 | this.userName =name; 42 | this.similar = similar; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/fragment/IndexFragment.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.fragment; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | 7 | import androidx.fragment.app.Fragment; 8 | 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.RelativeLayout; 13 | import android.widget.TextView; 14 | 15 | import com.haibin.calendarview.Calendar; 16 | import com.haibin.calendarview.CalendarLayout; 17 | import com.haibin.calendarview.CalendarView; 18 | import com.xiaoyou.face.R; 19 | import com.xiaoyou.face.activity.RegisterAndRecognizeActivity; 20 | import com.xiaoyou.face.activity.SignDetailActivity; 21 | import com.xiaoyou.face.adapter.FunctionAdapter; 22 | import com.xiaoyou.face.databinding.FragmentIndexBinding; 23 | import com.xiaoyou.face.model.Channel; 24 | import com.xiaoyou.face.service.DateHistoryTO; 25 | import com.xiaoyou.face.service.SQLiteHelper; 26 | import com.xiaoyou.face.service.Service; 27 | import com.xiaoyou.face.utils.Tools; 28 | 29 | import java.util.ArrayList; 30 | import java.util.HashMap; 31 | import java.util.List; 32 | import java.util.Map; 33 | 34 | /** 35 | * 打卡的fragment类 36 | * @author 小游 37 | * @date 2020/12/14 38 | */ 39 | public class IndexFragment extends Fragment implements 40 | CalendarView.OnCalendarSelectListener, 41 | CalendarView.OnYearChangeListener, 42 | View.OnClickListener{ 43 | 44 | /** 45 | * 日历组件显示的时间map值 46 | */ 47 | Map timeMap; 48 | 49 | private FragmentIndexBinding binding; 50 | TextView mTextMonthDay; 51 | TextView mTextYear; 52 | TextView mTextLunar; 53 | TextView mTextCurrentDay; 54 | CalendarView mCalendarView; 55 | RelativeLayout mRelativeTool; 56 | private int mYear; 57 | CalendarLayout mCalendarLayout; 58 | 59 | private final static int REQUEST_CODE = 45; 60 | 61 | 62 | @Override 63 | public void onCreate(Bundle savedInstanceState) { 64 | super.onCreate(savedInstanceState); 65 | } 66 | 67 | @Override 68 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 69 | // 这里我们使用bindview 来进行视图绑定 70 | binding = FragmentIndexBinding.inflate(inflater, container, false); 71 | // 我们这里给控件赋值 72 | // 月份显示 73 | mTextMonthDay = binding.tvMonthDay; 74 | // 年份显示 75 | mTextYear = binding.tvYear; 76 | // 时间显示 77 | mTextLunar = binding.tvLunar; 78 | // 现在时间 79 | mTextCurrentDay = binding.tvCurrentDay; 80 | // 日历视图组件 81 | mCalendarView = binding.calendarView; 82 | // 时间显示控件 83 | mRelativeTool = binding.rlTool; 84 | // 日历布局 85 | mCalendarLayout = binding.calendarLayout; 86 | // 数据初始化 87 | initView(); 88 | initData(); 89 | // 宫格布局初始化 90 | // 9宫格布局初始化 91 | ArrayList channelList = new ArrayList<>(); 92 | channelList.add(new Channel(R.mipmap.face,"人脸录入")); 93 | channelList.add(new Channel(R.mipmap.login,"开始签到")); 94 | channelList.add(new Channel(R.mipmap.statistics,"签到详情")); 95 | binding.toolList.setAdapter(new FunctionAdapter(channelList,getContext())); 96 | // grad 布局点击事件监听 97 | binding.toolList.setOnItemClickListener((parent, view, position, id) -> { 98 | switch (position){ 99 | case 0: 100 | startActivity(new Intent(getContext(), RegisterAndRecognizeActivity.class)); 101 | break; 102 | case 1: 103 | // 签到不需要显示录入按钮 104 | Intent intent = new Intent(getContext(), RegisterAndRecognizeActivity.class); 105 | intent.putExtra("login",false); 106 | startActivity(intent); 107 | break; 108 | case 2: 109 | startActivity(new Intent(getContext(), SignDetailActivity.class)); 110 | break; 111 | default: 112 | break; 113 | } 114 | }); 115 | // 返回视图view 116 | return binding.getRoot(); 117 | } 118 | 119 | @Override 120 | public void onClick(View v) { 121 | 122 | } 123 | 124 | @Override 125 | public void onCalendarOutOfRange(Calendar calendar) { 126 | 127 | } 128 | 129 | /** 130 | * 日历视图点击事件 131 | * @param calendar 日历控件 132 | * @param isClick 是否点击 133 | */ 134 | @SuppressLint("SetTextI18n") 135 | @Override 136 | public void onCalendarSelect(Calendar calendar, boolean isClick) { 137 | // 显示时间控件 138 | mTextLunar.setVisibility(View.VISIBLE); 139 | mTextYear.setVisibility(View.VISIBLE); 140 | 141 | // 我们这里修改导航栏的时间显示 142 | mTextMonthDay.setText(calendar.getMonth() + "月" + calendar.getDay() + "日"); 143 | mTextYear.setText(String.valueOf(calendar.getYear())); 144 | mTextLunar.setText(calendar.getLunar()); 145 | mYear = calendar.getYear(); 146 | } 147 | 148 | @Override 149 | public void onYearChange(int year) { 150 | // 年份视图点击的时候修改顶部的时间 151 | mTextMonthDay.setText(String.valueOf(year)); 152 | } 153 | 154 | /** 155 | * 设置标记 156 | * @param year 年份 157 | * @param month 月份 158 | * @param day 日 159 | * @param color 颜色 160 | * @return calendar对象 161 | */ 162 | private Calendar getSchemeCalendar(int year, int month, int day, int color) { 163 | Calendar calendar = new Calendar(); 164 | calendar.setYear(year); 165 | calendar.setMonth(month); 166 | calendar.setDay(day); 167 | // 如果单独标记颜色、则会使用这个颜色 168 | calendar.setSchemeColor(color); 169 | return calendar; 170 | } 171 | 172 | 173 | /** 174 | * 视图初始化 175 | */ 176 | @SuppressLint("SetTextI18n") 177 | protected void initView() { 178 | // 点击月份显示月份切换 179 | mTextMonthDay.setOnClickListener(v -> { 180 | if (!mCalendarLayout.isExpand()) { 181 | mCalendarLayout.expand(); 182 | return; 183 | } 184 | // 显示月份 185 | mCalendarView.showYearSelectLayout(mYear); 186 | // 隐藏隐藏年份和时间 187 | mTextLunar.setVisibility(View.GONE); 188 | mTextYear.setVisibility(View.GONE); 189 | // 月份哪里显示时间 190 | mTextMonthDay.setText(String.valueOf(mYear)); 191 | }); 192 | // 点击今天就直接跳转到今天 193 | binding.flCurrent.setOnClickListener(v -> mCalendarView.scrollToCurrent()); 194 | 195 | // 日历点击事件(包括年份点击) 196 | mCalendarView.setOnCalendarSelectListener(this); 197 | mCalendarView.setOnYearChangeListener(this); 198 | 199 | // 这里我们显示当前时间到顶部导航栏 200 | mTextYear.setText(String.valueOf(mCalendarView.getCurYear())); 201 | mYear = mCalendarView.getCurYear(); 202 | mTextMonthDay.setText(mCalendarView.getCurMonth() + "月" + mCalendarView.getCurDay() + "日"); 203 | mTextLunar.setText("今日"); 204 | // 显示今天日期 205 | mTextCurrentDay.setText(String.valueOf(mCalendarView.getCurDay())); 206 | } 207 | 208 | 209 | /** 210 | * 日历初始化 211 | */ 212 | private void initData(){ 213 | // int year = mCalendarView.getCurYear(); 214 | // int month = mCalendarView.getCurMonth(); 215 | Service service = new SQLiteHelper(getContext()); 216 | List calendar = service.getCalendar(); 217 | timeMap = new HashMap<>(); 218 | // 添加时间标记 219 | for (DateHistoryTO history : calendar) { 220 | addMark(history.getYear(),history.getMonth(),history.getDay()); 221 | } 222 | //此方法在巨大的数据量上不影响遍历性能,推荐使用 223 | mCalendarView.setSchemeDate(timeMap); 224 | } 225 | 226 | /** 227 | * 添加时间标记 228 | */ 229 | private void addMark(int year, int month, int day){ 230 | timeMap.put(getSchemeCalendar(year, month, day, Tools.getRandomColor()).toString(), 231 | getSchemeCalendar(year, month, day, Tools.getRandomColor())); 232 | } 233 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/fragment/MeFragment.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.fragment; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | 7 | import androidx.core.content.ContextCompat; 8 | import androidx.fragment.app.Fragment; 9 | 10 | import android.util.Log; 11 | import android.view.KeyEvent; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.view.inputmethod.InputMethodManager; 16 | import android.widget.TextView; 17 | 18 | import com.bin.david.form.data.CellInfo; 19 | import com.bin.david.form.data.format.bg.BaseCellBackgroundFormat; 20 | import com.bin.david.form.data.style.FontStyle; 21 | import com.xiaoyou.face.R; 22 | import com.xiaoyou.face.databinding.FragmentMeBinding; 23 | import com.xiaoyou.face.model.Login; 24 | import com.xiaoyou.face.service.SQLiteHelper; 25 | import com.xiaoyou.face.service.Service; 26 | import com.xiaoyou.face.service.StudentInfoTO; 27 | import com.xiaoyou.face.utils.ToastUtils; 28 | import com.xiaoyou.face.utils.Tools; 29 | 30 | import java.text.ParseException; 31 | import java.text.SimpleDateFormat; 32 | import java.util.ArrayList; 33 | import java.util.List; 34 | import java.util.Objects; 35 | 36 | /** 37 | * 查询界面 38 | * @author 小游 39 | */ 40 | public class MeFragment extends Fragment { 41 | 42 | private FragmentMeBinding binding; 43 | 44 | @Override 45 | public void onCreate(Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | } 48 | 49 | @Override 50 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 51 | Bundle savedInstanceState) { 52 | // 这里我们使用bindview 来进行视图绑定 53 | binding = FragmentMeBinding.inflate(inflater, container, false); 54 | initView(); 55 | // 返回视图view 56 | return binding.getRoot(); 57 | } 58 | 59 | /** 60 | * 视图初始化 61 | */ 62 | private void initView(){ 63 | // 表格样式(占满全屏) 64 | binding.table.getConfig().setMinTableWidth(Tools.getWidth(getContext())); 65 | // 搜索按钮点击事件 66 | binding.search.setOnClickListener(v->{ 67 | search(); 68 | }); 69 | // 编辑框回车事件 70 | binding.editKey.setOnEditorActionListener((v, actionId, event) -> { 71 | search(); 72 | return true; 73 | }); 74 | } 75 | 76 | /** 77 | * 搜索 78 | */ 79 | private void search(){ 80 | int type = binding.searchType.getSelectedIndex(); 81 | String key =binding.editKey.getEditValue(); 82 | // 自动隐藏键盘 83 | InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 84 | assert imm != null; 85 | imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); 86 | // 初始化数据库查找 87 | Service service = new SQLiteHelper(getContext()); 88 | List list = new ArrayList<>(); 89 | try { 90 | // 从数据库中获取数据 91 | list = service.queryStudentInfo(type==0?key:"", type==1?key:""); 92 | } catch (ParseException e) { 93 | e.printStackTrace(); 94 | } 95 | List lists = new ArrayList<>(); 96 | @SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 97 | for (StudentInfoTO studentInfo : list) { 98 | lists.add(new Login(studentInfo.getStuId(),studentInfo.getName(),sdf.format(studentInfo.getDateTime()))); 99 | } 100 | // 表格显示数据 101 | showDetail(lists); 102 | } 103 | 104 | 105 | /** 106 | * 显示签到的详情情况 107 | */ 108 | private void showDetail(List list){ 109 | // 显示数据 110 | binding.table.setData(list); 111 | // 统计行 112 | binding.table.getConfig().setFixedTitle(true); 113 | // 格式化内容背景(这里我们设置背景交替) 114 | binding.table.getConfig().setContentCellBackgroundFormat(new BaseCellBackgroundFormat() { 115 | @Override 116 | public int getBackGroundColor(CellInfo cellInfo) { 117 | int color = R.color.white; 118 | if (cellInfo.row%2 ==0){ 119 | color = R.color.tab_background; 120 | } 121 | return ContextCompat.getColor(Objects.requireNonNull(getContext()), color); 122 | } 123 | }); 124 | // 隐藏顶部的序号列 125 | binding.table.getConfig().setShowXSequence(false); 126 | } 127 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/fragment/ToolFragment.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.fragment; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.graphics.Color; 5 | import android.graphics.Typeface; 6 | import android.os.Bundle; 7 | 8 | import androidx.fragment.app.Fragment; 9 | 10 | import android.util.Log; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | import com.github.mikephil.charting.animation.Easing; 16 | import com.github.mikephil.charting.charts.BarChart; 17 | import com.github.mikephil.charting.charts.PieChart; 18 | import com.github.mikephil.charting.components.Legend; 19 | import com.github.mikephil.charting.components.XAxis; 20 | import com.github.mikephil.charting.components.YAxis; 21 | import com.github.mikephil.charting.data.BarData; 22 | import com.github.mikephil.charting.data.BarDataSet; 23 | import com.github.mikephil.charting.data.BarEntry; 24 | import com.github.mikephil.charting.data.PieData; 25 | import com.github.mikephil.charting.data.PieDataSet; 26 | import com.github.mikephil.charting.data.PieEntry; 27 | import com.github.mikephil.charting.formatter.ValueFormatter; 28 | import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; 29 | import com.xiaoyou.face.databinding.FragmentToolBinding; 30 | import com.xiaoyou.face.service.DateHistoryTO; 31 | import com.xiaoyou.face.service.History; 32 | import com.xiaoyou.face.service.SQLiteHelper; 33 | import com.xiaoyou.face.service.Service; 34 | import com.xuexiang.xui.widget.textview.supertextview.SuperButton; 35 | 36 | import java.text.SimpleDateFormat; 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | 40 | 41 | /** 42 | * 工具界面点击效果 43 | * @author 小游 44 | */ 45 | public class ToolFragment extends Fragment { 46 | private FragmentToolBinding binding; 47 | 48 | @Override 49 | public void onCreate(Bundle savedInstanceState) { 50 | super.onCreate(savedInstanceState); 51 | } 52 | 53 | @Override 54 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 55 | // 这里我们使用bindview 来进行视图绑定 56 | binding = FragmentToolBinding.inflate(inflater, container, false); 57 | initView(); 58 | // 返回视图view 59 | return binding.getRoot(); 60 | } 61 | 62 | /** 63 | * 视图初始化 64 | */ 65 | private void initView(){ 66 | // 指示器绑定 67 | binding.functionChoose.setTabTitles(new String[]{"考勤情况统计","考勤历史"}); 68 | // 指示器点击事件 69 | binding.functionChoose.setOnTabClickListener(((title, position) -> { 70 | switch (position){ 71 | case 0: 72 | showRingPieChart(); 73 | break; 74 | case 1: 75 | showAttendanceHistory(); 76 | break; 77 | default: 78 | break; 79 | } 80 | })); 81 | // 显示考勤情况统计 82 | showRingPieChart(); 83 | } 84 | 85 | /** 86 | * 显示考勤情况统计 87 | */ 88 | private void showRingPieChart() { 89 | binding.flContainer.setVisibility(View.VISIBLE); 90 | binding.linearContainer.setVisibility(View.INVISIBLE); 91 | // 设置每份所占数量 92 | List yvals = new ArrayList<>(); 93 | // 获取签到记录 94 | Service service = new SQLiteHelper(getContext()); 95 | History history = service.getTodayHistory(); 96 | 97 | yvals.add(new PieEntry(history.getIsSignUp(), "已签到")); 98 | yvals.add(new PieEntry(history.getNotSigUp(), "未签到")); 99 | //设置每份的颜色 100 | List colors = new ArrayList<>(); 101 | colors.add(Color.parseColor("#409EFF")); 102 | colors.add(Color.parseColor("#F56C6C")); 103 | // 图表初始化 104 | PieChart pieChart = binding.flContainer; 105 | // 是否显示中间的洞 106 | pieChart.setDrawHoleEnabled(true); 107 | //设置中间洞的大小 108 | pieChart.setHoleRadius(30f); 109 | 110 | // 半透明圈 111 | pieChart.setTransparentCircleRadius(35f); 112 | //设置半透明圆圈的颜色 113 | pieChart.setTransparentCircleColor(Color.WHITE); 114 | //设置半透明圆圈的透明度 115 | pieChart.setTransparentCircleAlpha(125); 116 | 117 | 118 | //饼状图中间可以添加文字 119 | pieChart.setDrawCenterText(true); 120 | //设置中间文字 121 | pieChart.setCenterText("签到情况统计"); 122 | //中间问题的颜色 123 | pieChart.setCenterTextColor(Color.parseColor("#a1a1a1")); 124 | //中间文字的大小px 125 | pieChart.setCenterTextSizePixels(25); 126 | pieChart.setCenterTextRadiusPercent(1f); 127 | //中间文字的样式 128 | pieChart.setCenterTextTypeface(Typeface.DEFAULT); 129 | //中间文字的偏移量 130 | pieChart.setCenterTextOffset(0, 0); 131 | 132 | // 初始旋转角度 133 | pieChart.setRotationAngle(0); 134 | // 可以手动旋转 135 | pieChart.setRotationEnabled(true); 136 | //显示成百分比 137 | pieChart.setUsePercentValues(true); 138 | //取消右下角描述 139 | pieChart.getDescription().setEnabled(false); 140 | 141 | //是否显示每个部分的文字描述 142 | pieChart.setDrawEntryLabels(true); 143 | //描述文字的颜色 144 | pieChart.setEntryLabelColor(Color.WHITE); 145 | //描述文字的大小 146 | pieChart.setEntryLabelTextSize(14); 147 | //描述文字的样式 148 | pieChart.setEntryLabelTypeface(Typeface.DEFAULT_BOLD); 149 | 150 | //图相对于上下左右的偏移 151 | pieChart.setExtraOffsets(20, 0, 20, 8); 152 | //图标的背景色 153 | pieChart.setBackgroundColor(Color.TRANSPARENT); 154 | // 设置pieChart图表转动阻力摩擦系数[0,1] 155 | pieChart.setDragDecelerationFrictionCoef(0.75f); 156 | 157 | //获取图例 158 | Legend legend = pieChart.getLegend(); 159 | //设置图例水平显示 160 | legend.setOrientation(Legend.LegendOrientation.VERTICAL); 161 | //顶部 162 | legend.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); 163 | //右对其 164 | legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); 165 | 166 | //x轴的间距 167 | legend.setXEntrySpace(7f); 168 | //y轴的间距 169 | legend.setYEntrySpace(10f); 170 | //图例的y偏移量 171 | legend.setYOffset(10f); 172 | //图例x的偏移量 173 | legend.setXOffset(10f); 174 | //图例文字的颜色 175 | legend.setTextColor(Color.parseColor("#a1a1a1")); 176 | //图例文字的大小 177 | legend.setTextSize(13); 178 | 179 | //数据集合 180 | PieDataSet dataset = new PieDataSet(yvals, ""); 181 | //填充每个区域的颜色 182 | dataset.setColors(colors); 183 | //是否在图上显示数值 184 | dataset.setDrawValues(true); 185 | // 文字的大小 186 | dataset.setValueTextSize(14); 187 | // 文字的颜色 188 | dataset.setValueTextColor(Color.parseColor("#3498db")); 189 | //文字的样式 190 | dataset.setValueTypeface(Typeface.DEFAULT_BOLD); 191 | 192 | // 当值位置为外边线时,表示线的前半段长度。 193 | dataset.setValueLinePart1Length(0.4f); 194 | // 当值位置为外边线时,表示线的后半段长度。 195 | dataset.setValueLinePart2Length(0.4f); 196 | // 当ValuePosits为OutsiDice时,指示偏移为切片大小的百分比 197 | dataset.setValueLinePart1OffsetPercentage(80f); 198 | // 当值位置为外边线时,表示线的颜色。 199 | dataset.setValueLineColor(Color.parseColor("#a1a1a1")); 200 | // 设置Y值的位置是在圆内还是圆外 201 | dataset.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); 202 | // 设置Y轴描述线和填充区域的颜色一致 203 | dataset.setUsingSliceColorAsValueLineColor(true); 204 | // 设置每条之前的间隙 205 | dataset.setSliceSpace(0); 206 | 207 | //设置饼状Item被选中时变化的距离 208 | dataset.setSelectionShift(5f); 209 | //填充数据 210 | PieData pieData = new PieData(dataset); 211 | // 格式化显示的数据为%百分比 212 | pieChart.setUsePercentValues(false); 213 | // 显示图表 214 | pieChart.setData(pieData); 215 | // 显示动画效果 216 | pieChart.animateY(500, Easing.EaseInOutQuad); 217 | } 218 | 219 | /** 220 | * 显示考勤历史 221 | */ 222 | private void showAttendanceHistory(){ 223 | // 设置控件显示 224 | binding.flContainer.setVisibility(View.INVISIBLE); 225 | binding.linearContainer.setVisibility(View.VISIBLE); 226 | //寻找到控件 227 | BarChart attendanceChart = binding.linearContainer; 228 | 229 | // 图例顶部显示 230 | 231 | //获取图例 232 | Legend legend = attendanceChart.getLegend(); 233 | //设置图例水平显示 234 | legend.setOrientation(Legend.LegendOrientation.VERTICAL); 235 | //顶部 236 | legend.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); 237 | //右对其 238 | legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); 239 | 240 | // 不显示描述 241 | attendanceChart.getDescription().setEnabled(false); 242 | //y轴右边关闭 243 | YAxis leftAxis = attendanceChart.getAxisLeft(); 244 | // 从零开始 245 | leftAxis.setAxisMinimum(0f); 246 | attendanceChart.getAxisRight().setEnabled(false); 247 | //x轴设置无网格 248 | XAxis xAxis = attendanceChart.getXAxis(); 249 | xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); 250 | xAxis.setDrawGridLines(false); 251 | // 获取考勤历史 252 | Service service = new SQLiteHelper(getContext()); 253 | List histories = service.getHistory(); 254 | 255 | // 设置x做显示 256 | ArrayList data2 = new ArrayList<>(); 257 | for (DateHistoryTO history : histories) { 258 | data2.add(history.getMonth()+"-"+history.getDay()); 259 | } 260 | xAxis.setValueFormatter(new ValueFormatter() { 261 | @Override 262 | public String getFormattedValue(float value) { 263 | return data2.get((int) value); 264 | } 265 | }); 266 | //在放大到为轴设置的小数位数不再允许在两个轴值之间进行区分的点时,可以使用此方法来避免值重复。 267 | //创建两个数据集 268 | xAxis.setGranularity(1f); 269 | //数据集1 270 | List valsComp1 = new ArrayList(); 271 | for(int i=0; i< histories.size();i++){ 272 | valsComp1.add(new BarEntry(i,new float[] { histories.get(i).getIsSign(), histories.get(i).getUnSign()})); 273 | } 274 | 275 | //创建条形图对象 276 | BarDataSet setComp1 = new BarDataSet(valsComp1, ""); 277 | setComp1.setDrawIcons(false); 278 | setComp1.setColors(Color.parseColor("#67c23a"),Color.parseColor("#f56c6c")); 279 | setComp1.setStackLabels(new String[]{"已签到", "未签到"}); 280 | ArrayList dataSets = new ArrayList<>(); 281 | dataSets.add(setComp1); 282 | //显示 283 | BarData data = new BarData(dataSets); 284 | attendanceChart.setData(data); 285 | attendanceChart.setFitBars(true); 286 | attendanceChart.invalidate(); 287 | //设置动画样式 288 | attendanceChart.animateY(2000,Easing. EaseInOutQuad); 289 | } 290 | 291 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/model/Channel.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * 宫格布局内容 8 | * @author 小游 9 | * @date 2020/12/15 10 | */ 11 | @Data 12 | @AllArgsConstructor 13 | public class Channel { 14 | private int imgId; 15 | 16 | private String dec; 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/model/DrawInfo.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.model; 2 | 3 | import android.graphics.Rect; 4 | 5 | /** 6 | * 画笔信息 7 | * @author 小游 8 | */ 9 | public class DrawInfo { 10 | private Rect rect; 11 | private int sex; 12 | private int age; 13 | private int liveness; 14 | private int color; 15 | private String name = null; 16 | 17 | public DrawInfo(Rect rect, int sex, int age, int liveness, int color, String name) { 18 | this.rect = rect; 19 | this.sex = sex; 20 | this.age = age; 21 | this.liveness = liveness; 22 | this.color = color; 23 | this.name = name; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | public Rect getRect() { 35 | return rect; 36 | } 37 | 38 | public void setRect(Rect rect) { 39 | this.rect = rect; 40 | } 41 | 42 | public int getSex() { 43 | return sex; 44 | } 45 | 46 | public void setSex(int sex) { 47 | this.sex = sex; 48 | } 49 | 50 | public int getAge() { 51 | return age; 52 | } 53 | 54 | public void setAge(int age) { 55 | this.age = age; 56 | } 57 | 58 | public int getLiveness() { 59 | return liveness; 60 | } 61 | 62 | public void setLiveness(int liveness) { 63 | this.liveness = liveness; 64 | } 65 | 66 | public int getColor() { 67 | return color; 68 | } 69 | 70 | public void setColor(int color) { 71 | this.color = color; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/model/FacePreviewInfo.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.model; 2 | 3 | import com.arcsoft.face.FaceInfo; 4 | 5 | public class FacePreviewInfo { 6 | private FaceInfo faceInfo; 7 | private int trackId; 8 | 9 | public FacePreviewInfo(FaceInfo faceInfo, int trackId) { 10 | this.faceInfo = faceInfo; 11 | this.trackId = trackId; 12 | } 13 | 14 | public FaceInfo getFaceInfo() { 15 | return faceInfo; 16 | } 17 | 18 | public void setFaceInfo(FaceInfo faceInfo) { 19 | this.faceInfo = faceInfo; 20 | } 21 | 22 | 23 | public int getTrackId() { 24 | return trackId; 25 | } 26 | 27 | public void setTrackId(int trackId) { 28 | this.trackId = trackId; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/model/FaceRegisterInfo.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.model; 2 | 3 | /** 4 | * 之前注册的人脸信息 5 | * @author 小游 6 | */ 7 | public class FaceRegisterInfo { 8 | private byte[] featureData; 9 | private String name; 10 | 11 | public FaceRegisterInfo(byte[] faceFeature, String name) { 12 | this.featureData = faceFeature; 13 | this.name = name; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | public byte[] getFeatureData() { 25 | return featureData; 26 | } 27 | 28 | public void setFeatureData(byte[] featureData) { 29 | this.featureData = featureData; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/model/Login.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.model; 2 | 3 | import com.bin.david.form.annotation.SmartColumn; 4 | import com.bin.david.form.annotation.SmartTable; 5 | 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | 9 | /** 10 | * 用户签到信息 11 | * @author 小游 12 | * @date 2020/12/16 13 | */ 14 | @Data 15 | @AllArgsConstructor 16 | @SmartTable() 17 | public class Login { 18 | @SmartColumn(id =1,name = "学号") 19 | String no; 20 | @SmartColumn(id =2,name = "姓名") 21 | String name; 22 | @SmartColumn(id =3,name = "签到时间") 23 | String time; 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/service/DateFormatUtils.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.service; 2 | 3 | import android.os.Build; 4 | 5 | import androidx.annotation.RequiresApi; 6 | 7 | import java.time.LocalDateTime; 8 | import java.time.format.DateTimeFormatter; 9 | import java.util.Date; 10 | 11 | /** 12 | * @author lenyuqin 13 | * @data 2020/12/18 14 | */ 15 | public class DateFormatUtils { 16 | 17 | 18 | /** 19 | * 20 | * @return 返回当前时间的字符串 21 | */ 22 | @RequiresApi(api = Build.VERSION_CODES.O) 23 | public static String getTodayDate() { 24 | LocalDateTime time = LocalDateTime.now(); 25 | DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 26 | return dateTimeFormatter.format(time); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/service/DateHistoryTO.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.service; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @author lenyuqin 9 | * @data 2020/12/19 10 | */ 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class DateHistoryTO { 15 | private int year; 16 | private int day; 17 | private int month; 18 | private int isSign; 19 | private int unSign; 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/service/History.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.service; 2 | 3 | import java.time.LocalDate; 4 | import java.time.LocalDateTime; 5 | 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | /** 11 | * @author lenyuqin 12 | * @data 2020/12/18 13 | */ 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class History { 18 | private LocalDate date; 19 | private int isSignUp; 20 | private int notSigUp; 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/service/Is_Sign.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.service; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * @author lenyuqin 7 | * @data 2020/12/16 8 | */ 9 | @Getter 10 | public enum Is_Sign { 11 | //打卡成功 12 | TURE(1), 13 | //打卡失败 14 | FALSE(0); 15 | 16 | private final Integer code; 17 | 18 | 19 | Is_Sign(Integer code) { 20 | this.code = code; 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/service/RegisterInfo.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.service; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * 注册 9 | * @author 小游 10 | * @date 2020/12/16 11 | */ 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class RegisterInfo { 16 | private int id; 17 | private String stuId; 18 | private String name; 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/service/SQLiteHelper.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.service; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.ContentValues; 5 | import android.content.Context; 6 | import android.database.Cursor; 7 | import android.database.sqlite.SQLiteDatabase; 8 | import android.database.sqlite.SQLiteOpenHelper; 9 | import android.os.Build; 10 | import android.util.Log; 11 | 12 | import androidx.annotation.RequiresApi; 13 | 14 | import java.text.ParseException; 15 | import java.text.SimpleDateFormat; 16 | import java.time.LocalDate; 17 | import java.time.LocalDateTime; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | /** 22 | * @author lenyuqin 23 | * @data 2020/12/16 24 | */ 25 | public class SQLiteHelper extends SQLiteOpenHelper implements Service { 26 | private final static String DATABASE_NAME = "FaceCheck"; 27 | private final static int DATABASE_VERSION = 1; 28 | private final static String TABLE_ATTENDANCE = "attendance"; 29 | private final static String TABLE_STUDENT = "student"; 30 | 31 | //创建数据库,里面添加了3个参数,分别是:Msgone VARCHAR类型,30长度当然这了可以自定义 32 | //Msgtwo VARCHAR(20) Msgthree VARCHAR(30)) NOT NULL不能为空 33 | String sql = "CREATE TABLE attendance (stu_id int(11) NOT NULL ," + 34 | " name varchar(20) DEFAULT NULL ," + 35 | " is_Sign bit(1) DEFAULT NULL ," + 36 | " day int(5) DEFAULT NULL ," + 37 | " date date DEFAULT NULL ," + 38 | " month int(5) DEFAULT NULL ," + 39 | " year int(5) DEFAULT NULL)"; 40 | 41 | String createStudent = "CREATE TABLE student (" + 42 | " id int(11) NOT NULL," + 43 | " stu_id varchar(20) NOT NULL," + 44 | " name varchar(20) DEFAULT NULL," + 45 | " PRIMARY KEY (id))"; 46 | 47 | //构造函数,创建数据库 48 | public SQLiteHelper(Context context) { 49 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 50 | } 51 | 52 | 53 | //建表 54 | @Override 55 | public void onCreate(SQLiteDatabase db) { 56 | db.execSQL(sql); 57 | db.execSQL(createStudent); 58 | } 59 | 60 | @Override 61 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 62 | String sql = "DROP TABLE IF EXISTS " + TABLE_ATTENDANCE; 63 | String sql2 = "DROP TABLE IF EXISTS " + TABLE_STUDENT; 64 | db.execSQL(sql); 65 | db.execSQL(sql2); 66 | onCreate(db); 67 | } 68 | 69 | 70 | //查询本月的数据,查询一个字段,返回日子的int集合 根据月去查询去除相同日子,返回日子 71 | @RequiresApi(api = Build.VERSION_CODES.O) 72 | public List queryByMouth() { 73 | SQLiteDatabase db = this.getReadableDatabase(); 74 | String[] selectionArgs = new String[1]; 75 | selectionArgs[1] = String.valueOf(LocalDate.now().getMonthValue()); 76 | Cursor cursor = db.rawQuery("SELECT DISTINCT day FROM " + TABLE_ATTENDANCE + " WHERE month = ?", selectionArgs); 77 | ArrayList result = new ArrayList<>(); 78 | while (cursor.isLast()) { 79 | String columnName = cursor.getColumnName(1); 80 | result.add(Integer.valueOf(columnName)); 81 | cursor.moveToNext(); 82 | } 83 | return result; 84 | } 85 | 86 | 87 | /** 88 | * 学生注册 89 | * 90 | * @param registerInfo 学生注册信息 91 | * @return 插入条数 92 | */ 93 | @Override 94 | @RequiresApi(api = Build.VERSION_CODES.O) 95 | public long studentRegister(RegisterInfo registerInfo) { 96 | SQLiteDatabase db = this.getWritableDatabase(); 97 | ContentValues cv = new ContentValues(); 98 | cv.put("id", registerInfo.getId()); 99 | cv.put("stu_id", registerInfo.getStuId()); 100 | cv.put("name", registerInfo.getName()); 101 | return db.insert(TABLE_STUDENT, null, cv); 102 | } 103 | 104 | /** 105 | * 查询学生信息 106 | * 107 | * @param id 注册id 108 | * @return 学生id 109 | */ 110 | @Override 111 | @RequiresApi(api = Build.VERSION_CODES.O) 112 | public RegisterInfo getStudentInfo(int id) { 113 | SQLiteDatabase db = this.getReadableDatabase(); 114 | Cursor cursor = db.query(TABLE_STUDENT, null, "id=?", new String[]{"" + id}, null, null, null); 115 | RegisterInfo registerInfo = new RegisterInfo(); 116 | if (cursor.getCount() != 0) { 117 | if (cursor.moveToNext()) { 118 | registerInfo.setId(cursor.getInt(0)); 119 | registerInfo.setStuId(cursor.getString(1)); 120 | registerInfo.setName(cursor.getString(2)); 121 | } 122 | } 123 | return registerInfo; 124 | } 125 | 126 | 127 | /** 128 | * 首页签到部分,返回具体时间 129 | * @return 返回已签到的日子 130 | */ 131 | @Override 132 | @RequiresApi(api = Build.VERSION_CODES.O) 133 | public List getCalendar() { 134 | SQLiteDatabase db = this.getReadableDatabase(); 135 | String[] selectionArgs = new String[1]; 136 | selectionArgs[0] = String.valueOf(LocalDate.now().getYear()); 137 | @SuppressLint("Recycle") Cursor cursor = db.rawQuery("SELECT distinct day,month,year FROM " + TABLE_ATTENDANCE + " WHERE year=?", selectionArgs); 138 | List historyArrayList = new ArrayList<>(); 139 | while (cursor.moveToNext()) { 140 | DateHistoryTO history = new DateHistoryTO(); 141 | history.setYear(cursor.getInt(2)); 142 | history.setDay(cursor.getInt(0)); 143 | history.setMonth(cursor.getInt(1)); 144 | historyArrayList.add(history); 145 | } 146 | return historyArrayList; 147 | } 148 | 149 | 150 | /** 151 | * 签到统计部分 152 | * 153 | * @return 学号,姓名,签到时间 154 | */ 155 | @Override 156 | @RequiresApi(api = Build.VERSION_CODES.O) 157 | public List getCountToday() throws ParseException { 158 | SQLiteDatabase db = this.getReadableDatabase(); 159 | @SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 160 | String[] selectionArgs = new String[3]; 161 | selectionArgs[0] = String.valueOf(LocalDateTime.now().getDayOfMonth()); 162 | selectionArgs[1] = String.valueOf(LocalDate.now().getMonthValue()); 163 | selectionArgs[2] = String.valueOf(LocalDate.now().getYear()); 164 | @SuppressLint("Recycle") Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_ATTENDANCE + " WHERE day= ? and month = ? and year = ?", selectionArgs); 165 | ArrayList studentInfoTO = new ArrayList<>(); 166 | while (cursor.moveToNext()) { 167 | StudentInfoTO studentInfoTO1 = new StudentInfoTO(); 168 | studentInfoTO1.setDateTime(sdf.parse(cursor.getString(4))); 169 | studentInfoTO1.setStuId(cursor.getString(0)); 170 | studentInfoTO1.setName(cursor.getString(1)); 171 | studentInfoTO.add(studentInfoTO1); 172 | } 173 | return studentInfoTO; 174 | } 175 | 176 | 177 | /** 178 | * 考勤情况统计 179 | * 180 | * @return 当天的签到信息 181 | */ 182 | @Override 183 | @RequiresApi(api = Build.VERSION_CODES.O) 184 | public History getTodayHistory() { 185 | @SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 186 | SQLiteDatabase db = this.getReadableDatabase(); 187 | String[] selectionArgs = new String[3]; 188 | selectionArgs[0] = String.valueOf(LocalDateTime.now().getDayOfMonth()); 189 | selectionArgs[1] = String.valueOf(LocalDate.now().getMonthValue()); 190 | selectionArgs[2] = String.valueOf(LocalDate.now().getYear()); 191 | @SuppressLint("Recycle") Cursor cursor1 = db.rawQuery("SELECT * FROM " + TABLE_ATTENDANCE + " WHERE day= ? and month = ? and year = ? ", selectionArgs); 192 | @SuppressLint("Recycle") Cursor curso2r = db.rawQuery("SELECT * FROM " + TABLE_STUDENT, null); 193 | int count = curso2r.getCount(); 194 | History history = new History(); 195 | history.setDate(LocalDate.now()); 196 | history.setIsSignUp(cursor1.getCount()); 197 | history.setNotSigUp(count - cursor1.getCount()); 198 | return history; 199 | } 200 | 201 | /** 202 | * 考勤历史(默认返回今年的),用于日历 203 | * 204 | * @return 返回一个历史数据list 205 | */ 206 | @Override 207 | @RequiresApi(api = Build.VERSION_CODES.O) 208 | public List getHistory() { 209 | // 先获取当前月份 210 | SQLiteDatabase db = this.getReadableDatabase(); 211 | String[] selectionArgs = new String[2]; 212 | selectionArgs[0] = String.valueOf(LocalDate.now().getMonthValue()); 213 | selectionArgs[1] = String.valueOf(LocalDate.now().getYear()); 214 | @SuppressLint("Recycle") Cursor cursor = db.rawQuery("SELECT count(stu_id) as is_sign,month,day FROM " + TABLE_ATTENDANCE + " where month=? and year = ? group by day;", selectionArgs); 215 | @SuppressLint("Recycle") Cursor cursor1 = db.rawQuery("SELECT count(*) FROM student;", null); 216 | cursor1.moveToNext(); 217 | List historyArrayList = new ArrayList<>(); 218 | int total = cursor1.getInt(0); 219 | while (cursor.moveToNext()) { 220 | DateHistoryTO history = new DateHistoryTO(); 221 | history.setUnSign(total-cursor.getInt(0)); 222 | history.setIsSign(cursor.getInt(0)); 223 | history.setDay(cursor.getInt(2)); 224 | history.setMonth(cursor.getInt(1)); 225 | historyArrayList.add(history); 226 | } 227 | return historyArrayList; 228 | } 229 | 230 | 231 | /** 232 | * 查询 233 | * 234 | * @param stuId 学号 235 | * @param name 用户名 236 | * @return 学生信息的list 237 | */ 238 | @Override 239 | @RequiresApi(api = Build.VERSION_CODES.O) 240 | public List queryStudentInfo(String stuId, String name) throws ParseException { 241 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 242 | SQLiteDatabase db = this.getReadableDatabase(); 243 | String[] selectionArgs = new String[2]; 244 | selectionArgs[0] = String.valueOf(stuId); 245 | selectionArgs[1] = String.valueOf(name); 246 | String currentSqlSel; 247 | if ("".equals(name)){ 248 | currentSqlSel = "SELECT * FROM " + TABLE_ATTENDANCE + " where stu_id like '%" + stuId + "%'"; 249 | } else { 250 | currentSqlSel = "SELECT * FROM " + TABLE_ATTENDANCE + " where name Like '%" + name + "%'"; 251 | } 252 | @SuppressLint("Recycle") Cursor cursor = db.rawQuery(currentSqlSel, null); 253 | ArrayList studentInfoTOS = new ArrayList<>(); 254 | while (cursor.moveToNext()) { 255 | StudentInfoTO studentInfo = new StudentInfoTO(); 256 | String datetime = cursor.getString(4); 257 | studentInfo.setName(cursor.getString(1)); 258 | studentInfo.setStuId(cursor.getString(0)); 259 | studentInfo.setDateTime(sdf.parse(datetime)); 260 | studentInfoTOS.add(studentInfo); 261 | } 262 | return studentInfoTOS; 263 | } 264 | 265 | /** 266 | * 签到 267 | * 268 | * @param stuId 学号 269 | * @param name 用户名 270 | * @param data 当前时间 271 | * @return 是否插入成功 272 | */ 273 | @Override 274 | @RequiresApi(api = Build.VERSION_CODES.O) 275 | public Boolean signUp(String stuId, String name, LocalDateTime data) { 276 | SQLiteDatabase db = this.getWritableDatabase(); 277 | ContentValues cv = new ContentValues(); 278 | cv.put("stu_id", stuId); 279 | cv.put("name", name); 280 | cv.put("is_Sign", Is_Sign.TURE.getCode()); 281 | cv.put("day", data.getDayOfMonth()); 282 | cv.put("month", data.getMonthValue()); 283 | cv.put("year", data.getYear()); 284 | cv.put("date", DateFormatUtils.getTodayDate()); 285 | return db.insert(TABLE_ATTENDANCE, null, cv) == 1; 286 | } 287 | 288 | /** 289 | * 查询是否签到 290 | * 291 | * @param stuId 学生学号 292 | * @param data 当前时间 293 | * @return 是否签到 294 | */ 295 | @Override 296 | @RequiresApi(api = Build.VERSION_CODES.O) 297 | public Boolean isSignUp(String stuId, LocalDateTime data) { 298 | SQLiteDatabase db = this.getReadableDatabase(); 299 | String[] selectionArgs = new String[4]; 300 | selectionArgs[0] = String.valueOf(stuId); 301 | selectionArgs[1] = String.valueOf(LocalDateTime.now().getDayOfMonth()); 302 | selectionArgs[2] = String.valueOf(LocalDate.now().getMonthValue()); 303 | selectionArgs[3] = String.valueOf(LocalDate.now().getYear()); 304 | @SuppressLint("Recycle") Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_ATTENDANCE + " WHERE stu_id = ? and day= ? and month = ? and year = ? ", selectionArgs); 305 | return cursor.getCount() == 1; 306 | } 307 | 308 | 309 | } 310 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/service/Service.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.service; 2 | 3 | import java.text.ParseException; 4 | import java.time.LocalDate; 5 | import java.time.LocalDateTime; 6 | import java.util.List; 7 | 8 | /** 9 | * @author lenyuqin 10 | * @data 2020/12/18 11 | */ 12 | public interface Service { 13 | /** 14 | * 学生注册 15 | * 16 | * @param registerInfo 学生注册信息 17 | * @return 插入条数 18 | */ 19 | long studentRegister(RegisterInfo registerInfo); 20 | 21 | 22 | /** 23 | * 查询学生信息 24 | * 25 | * @param id 注册id 26 | * @return 学生id 27 | */ 28 | RegisterInfo getStudentInfo(int id); 29 | 30 | 31 | /** 32 | * 首页签到部分,返回具体时间 33 | * 34 | * @return 返回已签到的日子 35 | */ 36 | List getCalendar(); 37 | 38 | 39 | /** 40 | * 签到统计部分 41 | * 42 | * @return 学号,姓名,签到时间 43 | */ 44 | List getCountToday() throws ParseException; 45 | 46 | 47 | /** 48 | * 考勤情况统计 49 | * 50 | * @return 当天的签到信息 51 | */ 52 | History getTodayHistory(); 53 | 54 | 55 | /** 56 | * 考勤历史(默认返回今年的) 用于日历 57 | * 58 | * @return 返回一个历史数据list 59 | */ 60 | List getHistory(); 61 | 62 | 63 | /** 64 | * 查询 65 | * 66 | * @param stuId 学号 67 | * @param name 用户名 68 | * @return 学生信息的list 69 | */ 70 | List queryStudentInfo(String stuId, String name) throws ParseException; 71 | 72 | 73 | /** 74 | * 签到 75 | * 76 | * @param stuId 学号 77 | * @param name 用户名 78 | * @param data 当前时间 79 | * @return 是否插入成功 80 | */ 81 | Boolean signUp(String stuId, String name, LocalDateTime data); 82 | 83 | 84 | /** 85 | * 查询是否签到 86 | * 87 | * @param stuId 学生学号 88 | * @param data 当前时间 89 | * @return 是否签到 90 | */ 91 | Boolean isSignUp(String stuId, LocalDateTime data); 92 | 93 | 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/service/StudentInfo.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.service; 2 | 3 | import java.time.LocalDate; 4 | import java.time.LocalDateTime; 5 | import java.util.Date; 6 | 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | 11 | /** 12 | * @author lenyuqin 13 | * @data 2020/12/16 14 | */ 15 | @Data 16 | @AllArgsConstructor 17 | public class StudentInfo { 18 | //学生学号 19 | private String stuId; 20 | //学生名字 21 | private String name; 22 | //是否打卡 1打卡成功,0打卡失败 23 | private int isSign; 24 | //打卡时间 25 | private LocalDate dateTime; 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/service/StudentInfoTO.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.service; 2 | 3 | import java.util.Date; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | /** 10 | * @author lenyuqin 11 | * @data 2020/12/18 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class StudentInfoTO { 17 | //学生学号 18 | private String stuId; 19 | //学生名字 20 | private String name; 21 | //打卡时间 22 | private Date dateTime; 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/ConfigUtil.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import com.arcsoft.face.enums.DetectFaceOrientPriority; 7 | 8 | public class ConfigUtil { 9 | private static final String APP_NAME = "faceapp"; 10 | private static final String TRACKED_FACE_COUNT = "trackedFaceCount"; 11 | private static final String FT_ORIENT = "ftOrientPriority"; 12 | private static final String MAC_PRIORITY = "macPriority"; 13 | 14 | public static boolean setTrackedFaceCount(Context context, int trackedFaceCount) { 15 | if (context == null) { 16 | return false; 17 | } 18 | SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE); 19 | return sharedPreferences.edit() 20 | .putInt(TRACKED_FACE_COUNT, trackedFaceCount) 21 | .commit(); 22 | } 23 | 24 | public static int getTrackedFaceCount(Context context) { 25 | if (context == null) { 26 | return 0; 27 | } 28 | SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE); 29 | return sharedPreferences.getInt(TRACKED_FACE_COUNT, 0); 30 | } 31 | 32 | public static boolean setFtOrient(Context context, DetectFaceOrientPriority ftOrient) { 33 | if (context == null) { 34 | return false; 35 | } 36 | SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE); 37 | return sharedPreferences.edit().putString(FT_ORIENT, ftOrient.name()).commit(); 38 | } 39 | 40 | public static DetectFaceOrientPriority getFtOrient(Context context) { 41 | if (context == null) { 42 | return DetectFaceOrientPriority.ASF_OP_270_ONLY; 43 | } 44 | SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE); 45 | return DetectFaceOrientPriority.valueOf(sharedPreferences.getString(FT_ORIENT, DetectFaceOrientPriority.ASF_OP_270_ONLY.name())); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/DrawHelper.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.Path; 6 | import android.graphics.Rect; 7 | import android.hardware.Camera; 8 | 9 | import com.arcsoft.face.AgeInfo; 10 | import com.arcsoft.face.GenderInfo; 11 | import com.arcsoft.face.LivenessInfo; 12 | import com.xiaoyou.face.model.DrawInfo; 13 | import com.xiaoyou.face.widget.FaceRectView; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * 绘制人脸框帮助类,用于在{@link com.xiaoyou.face.widget.FaceRectView}上绘制矩形 19 | */ 20 | public class DrawHelper { 21 | private int previewWidth, previewHeight, canvasWidth, canvasHeight, cameraDisplayOrientation, cameraId; 22 | private boolean isMirror; 23 | private boolean mirrorHorizontal = false, mirrorVertical = false; 24 | 25 | /** 26 | * 创建一个绘制辅助类对象,并且设置绘制相关的参数 27 | * 28 | * @param previewWidth 预览宽度 29 | * @param previewHeight 预览高度 30 | * @param canvasWidth 绘制控件的宽度 31 | * @param canvasHeight 绘制控件的高度 32 | * @param cameraDisplayOrientation 旋转角度 33 | * @param cameraId 相机ID 34 | * @param isMirror 是否水平镜像显示(若相机是镜像显示的,设为true,用于纠正) 35 | * @param mirrorHorizontal 为兼容部分设备使用,水平再次镜像 36 | * @param mirrorVertical 为兼容部分设备使用,垂直再次镜像 37 | */ 38 | public DrawHelper(int previewWidth, int previewHeight, int canvasWidth, 39 | int canvasHeight, int cameraDisplayOrientation, int cameraId, 40 | boolean isMirror, boolean mirrorHorizontal, boolean mirrorVertical) { 41 | this.previewWidth = previewWidth; 42 | this.previewHeight = previewHeight; 43 | this.canvasWidth = canvasWidth; 44 | this.canvasHeight = canvasHeight; 45 | this.cameraDisplayOrientation = cameraDisplayOrientation; 46 | this.cameraId = cameraId; 47 | this.isMirror = isMirror; 48 | this.mirrorHorizontal = mirrorHorizontal; 49 | this.mirrorVertical = mirrorVertical; 50 | } 51 | 52 | public void draw(FaceRectView faceRectView, List drawInfoList) { 53 | if (faceRectView == null) { 54 | return; 55 | } 56 | faceRectView.clearFaceInfo(); 57 | if (drawInfoList == null || drawInfoList.size() == 0) { 58 | return; 59 | } 60 | faceRectView.addFaceInfo(drawInfoList); 61 | } 62 | 63 | /** 64 | * 调整人脸框用来绘制 65 | * 66 | * @param ftRect FT人脸框 67 | * @return 调整后的需要被绘制到View上的rect 68 | */ 69 | public Rect adjustRect(Rect ftRect) { 70 | int previewWidth = this.previewWidth; 71 | int previewHeight = this.previewHeight; 72 | int canvasWidth = this.canvasWidth; 73 | int canvasHeight = this.canvasHeight; 74 | int cameraDisplayOrientation = this.cameraDisplayOrientation; 75 | int cameraId = this.cameraId; 76 | boolean isMirror = this.isMirror; 77 | boolean mirrorHorizontal = this.mirrorHorizontal; 78 | boolean mirrorVertical = this.mirrorVertical; 79 | 80 | if (ftRect == null) { 81 | return null; 82 | } 83 | 84 | Rect rect = new Rect(ftRect); 85 | float horizontalRatio; 86 | float verticalRatio; 87 | if (cameraDisplayOrientation % 180 == 0) { 88 | horizontalRatio = (float) canvasWidth / (float) previewWidth; 89 | verticalRatio = (float) canvasHeight / (float) previewHeight; 90 | } else { 91 | horizontalRatio = (float) canvasHeight / (float) previewWidth; 92 | verticalRatio = (float) canvasWidth / (float) previewHeight; 93 | } 94 | rect.left *= horizontalRatio; 95 | rect.right *= horizontalRatio; 96 | rect.top *= verticalRatio; 97 | rect.bottom *= verticalRatio; 98 | 99 | Rect newRect = new Rect(); 100 | switch (cameraDisplayOrientation) { 101 | case 0: 102 | if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) { 103 | newRect.left = canvasWidth - rect.right; 104 | newRect.right = canvasWidth - rect.left; 105 | } else { 106 | newRect.left = rect.left; 107 | newRect.right = rect.right; 108 | } 109 | newRect.top = rect.top; 110 | newRect.bottom = rect.bottom; 111 | break; 112 | case 90: 113 | newRect.right = canvasWidth - rect.top; 114 | newRect.left = canvasWidth - rect.bottom; 115 | if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) { 116 | newRect.top = canvasHeight - rect.right; 117 | newRect.bottom = canvasHeight - rect.left; 118 | } else { 119 | newRect.top = rect.left; 120 | newRect.bottom = rect.right; 121 | } 122 | break; 123 | case 180: 124 | newRect.top = canvasHeight - rect.bottom; 125 | newRect.bottom = canvasHeight - rect.top; 126 | if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) { 127 | newRect.left = rect.left; 128 | newRect.right = rect.right; 129 | } else { 130 | newRect.left = canvasWidth - rect.right; 131 | newRect.right = canvasWidth - rect.left; 132 | } 133 | break; 134 | case 270: 135 | newRect.left = rect.top; 136 | newRect.right = rect.bottom; 137 | if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) { 138 | newRect.top = rect.left; 139 | newRect.bottom = rect.right; 140 | } else { 141 | newRect.top = canvasHeight - rect.right; 142 | newRect.bottom = canvasHeight - rect.left; 143 | } 144 | break; 145 | default: 146 | break; 147 | } 148 | 149 | /** 150 | * isMirror mirrorHorizontal finalIsMirrorHorizontal 151 | * true true false 152 | * false false false 153 | * true false true 154 | * false true true 155 | * 156 | * XOR 157 | */ 158 | if (isMirror ^ mirrorHorizontal) { 159 | int left = newRect.left; 160 | int right = newRect.right; 161 | newRect.left = canvasWidth - right; 162 | newRect.right = canvasWidth - left; 163 | } 164 | if (mirrorVertical) { 165 | int top = newRect.top; 166 | int bottom = newRect.bottom; 167 | newRect.top = canvasHeight - bottom; 168 | newRect.bottom = canvasHeight - top; 169 | } 170 | return newRect; 171 | } 172 | 173 | /** 174 | * 绘制数据信息到view上,若 {@link DrawInfo#getName()} 不为null则绘制 {@link DrawInfo#getName()} 175 | * 176 | * @param canvas 需要被绘制的view的canvas 177 | * @param drawInfo 绘制信息 178 | * @param faceRectThickness 人脸框厚度 179 | * @param paint 画笔 180 | */ 181 | public static void drawFaceRect(Canvas canvas, DrawInfo drawInfo, int faceRectThickness, Paint paint) { 182 | if (canvas == null || drawInfo == null) { 183 | return; 184 | } 185 | paint.setStyle(Paint.Style.STROKE); 186 | paint.setStrokeWidth(faceRectThickness); 187 | paint.setColor(drawInfo.getColor()); 188 | paint.setAntiAlias(true); 189 | 190 | Path mPath = new Path(); 191 | // 左上 192 | Rect rect = drawInfo.getRect(); 193 | mPath.moveTo(rect.left, rect.top + rect.height() / 4); 194 | mPath.lineTo(rect.left, rect.top); 195 | mPath.lineTo(rect.left + rect.width() / 4, rect.top); 196 | // 右上 197 | mPath.moveTo(rect.right - rect.width() / 4, rect.top); 198 | mPath.lineTo(rect.right, rect.top); 199 | mPath.lineTo(rect.right, rect.top + rect.height() / 4); 200 | // 右下 201 | mPath.moveTo(rect.right, rect.bottom - rect.height() / 4); 202 | mPath.lineTo(rect.right, rect.bottom); 203 | mPath.lineTo(rect.right - rect.width() / 4, rect.bottom); 204 | // 左下 205 | mPath.moveTo(rect.left + rect.width() / 4, rect.bottom); 206 | mPath.lineTo(rect.left, rect.bottom); 207 | mPath.lineTo(rect.left, rect.bottom - rect.height() / 4); 208 | canvas.drawPath(mPath, paint); 209 | 210 | // 绘制文字,用最细的即可,避免在某些低像素设备上文字模糊 211 | paint.setStrokeWidth(1); 212 | 213 | if (drawInfo.getName() == null) { 214 | paint.setStyle(Paint.Style.FILL_AND_STROKE); 215 | paint.setTextSize(rect.width() / 8); 216 | 217 | String str = (drawInfo.getSex() == GenderInfo.MALE ? "MALE" : (drawInfo.getSex() == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN")) 218 | + "," 219 | + (drawInfo.getAge() == AgeInfo.UNKNOWN_AGE ? "UNKNOWN" : drawInfo.getAge()) 220 | + "," 221 | + (drawInfo.getLiveness() == LivenessInfo.ALIVE ? "ALIVE" : (drawInfo.getLiveness() == LivenessInfo.NOT_ALIVE ? "NOT_ALIVE" : "UNKNOWN")); 222 | canvas.drawText(str, rect.left, rect.top - 10, paint); 223 | } else { 224 | paint.setStyle(Paint.Style.FILL_AND_STROKE); 225 | paint.setTextSize(rect.width() / 8); 226 | canvas.drawText(drawInfo.getName(), rect.left, rect.top - 10, paint); 227 | } 228 | } 229 | 230 | public void setPreviewWidth(int previewWidth) { 231 | this.previewWidth = previewWidth; 232 | } 233 | 234 | public void setPreviewHeight(int previewHeight) { 235 | this.previewHeight = previewHeight; 236 | } 237 | 238 | public void setCanvasWidth(int canvasWidth) { 239 | this.canvasWidth = canvasWidth; 240 | } 241 | 242 | public void setCanvasHeight(int canvasHeight) { 243 | this.canvasHeight = canvasHeight; 244 | } 245 | 246 | public void setCameraDisplayOrientation(int cameraDisplayOrientation) { 247 | this.cameraDisplayOrientation = cameraDisplayOrientation; 248 | } 249 | 250 | public void setCameraId(int cameraId) { 251 | this.cameraId = cameraId; 252 | } 253 | 254 | public void setMirror(boolean mirror) { 255 | isMirror = mirror; 256 | } 257 | 258 | public int getPreviewWidth() { 259 | return previewWidth; 260 | } 261 | 262 | public int getPreviewHeight() { 263 | return previewHeight; 264 | } 265 | 266 | public int getCanvasWidth() { 267 | return canvasWidth; 268 | } 269 | 270 | public int getCanvasHeight() { 271 | return canvasHeight; 272 | } 273 | 274 | public int getCameraDisplayOrientation() { 275 | return cameraDisplayOrientation; 276 | } 277 | 278 | public int getCameraId() { 279 | return cameraId; 280 | } 281 | 282 | public boolean isMirror() { 283 | return isMirror; 284 | } 285 | 286 | public boolean isMirrorHorizontal() { 287 | return mirrorHorizontal; 288 | } 289 | 290 | public void setMirrorHorizontal(boolean mirrorHorizontal) { 291 | this.mirrorHorizontal = mirrorHorizontal; 292 | } 293 | 294 | public boolean isMirrorVertical() { 295 | return mirrorVertical; 296 | } 297 | 298 | public void setMirrorVertical(boolean mirrorVertical) { 299 | this.mirrorVertical = mirrorVertical; 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/PieChartManager.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils; 2 | 3 | import android.graphics.Color; 4 | import android.graphics.Typeface; 5 | 6 | import com.github.mikephil.charting.charts.PieChart; 7 | import com.github.mikephil.charting.components.Legend; 8 | import com.github.mikephil.charting.data.PieData; 9 | import com.github.mikephil.charting.data.PieDataSet; 10 | import com.github.mikephil.charting.data.PieEntry; 11 | import com.github.mikephil.charting.formatter.PercentFormatter; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * 圆环管理类 17 | * @author 小游 18 | * @date 2020/12/16 19 | */ 20 | public class PieChartManager { 21 | public PieChart pieChart; 22 | 23 | public PieChartManager(PieChart pieChart) { 24 | this.pieChart = pieChart; 25 | initPieChart(); 26 | } 27 | 28 | //初始化 29 | private void initPieChart() { 30 | // 是否显示中间的洞 31 | pieChart.setDrawHoleEnabled(false); 32 | //设置中间洞的大小 33 | pieChart.setHoleRadius(40f); 34 | 35 | // 半透明圈 36 | pieChart.setTransparentCircleRadius(30f); 37 | //设置半透明圆圈的颜色 38 | pieChart.setTransparentCircleColor(Color.WHITE); 39 | //设置半透明圆圈的透明度 40 | pieChart.setTransparentCircleAlpha(125); 41 | 42 | 43 | //饼状图中间可以添加文字 44 | pieChart.setDrawCenterText(false); 45 | //设置中间文字 46 | pieChart.setCenterText("民族"); 47 | //中间问题的颜色 48 | pieChart.setCenterTextColor(Color.parseColor("#a1a1a1")); 49 | //中间文字的大小px 50 | pieChart.setCenterTextSizePixels(36); 51 | pieChart.setCenterTextRadiusPercent(1f); 52 | //中间文字的样式 53 | pieChart.setCenterTextTypeface(Typeface.DEFAULT); 54 | //中间文字的偏移量 55 | pieChart.setCenterTextOffset(0, 0); 56 | 57 | // 初始旋转角度 58 | pieChart.setRotationAngle(0); 59 | // 可以手动旋转 60 | pieChart.setRotationEnabled(true); 61 | //显示成百分比 62 | pieChart.setUsePercentValues(true); 63 | //取消右下角描述 64 | pieChart.getDescription().setEnabled(false); 65 | 66 | //是否显示每个部分的文字描述 67 | pieChart.setDrawEntryLabels(false); 68 | //描述文字的颜色 69 | pieChart.setEntryLabelColor(Color.RED); 70 | //描述文字的大小 71 | pieChart.setEntryLabelTextSize(14); 72 | //描述文字的样式 73 | pieChart.setEntryLabelTypeface(Typeface.DEFAULT_BOLD); 74 | 75 | //图相对于上下左右的偏移 76 | pieChart.setExtraOffsets(20, 8, 75, 8); 77 | //图标的背景色 78 | pieChart.setBackgroundColor(Color.TRANSPARENT); 79 | // 设置pieChart图表转动阻力摩擦系数[0,1] 80 | pieChart.setDragDecelerationFrictionCoef(0.75f); 81 | 82 | //获取图例 83 | Legend legend = pieChart.getLegend(); 84 | //设置图例水平显示 85 | legend.setOrientation(Legend.LegendOrientation.VERTICAL); 86 | //顶部 87 | legend.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); 88 | //右对其 89 | legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); 90 | 91 | //x轴的间距 92 | legend.setXEntrySpace(7f); 93 | //y轴的间距 94 | legend.setYEntrySpace(10f); 95 | //图例的y偏移量 96 | legend.setYOffset(10f); 97 | //图例x的偏移量 98 | legend.setXOffset(10f); 99 | //图例文字的颜色 100 | legend.setTextColor(Color.parseColor("#a1a1a1")); 101 | //图例文字的大小 102 | legend.setTextSize(13); 103 | 104 | } 105 | 106 | 107 | /** 108 | * 显示实心圆 109 | * @param yvals 110 | * @param colors 111 | */ 112 | public void showSolidPieChart(List yvals, List colors) { 113 | //数据集合 114 | PieDataSet dataset = new PieDataSet(yvals, ""); 115 | //填充每个区域的颜色 116 | dataset.setColors(colors); 117 | //是否在图上显示数值 118 | dataset.setDrawValues(true); 119 | // 文字的大小 120 | dataset.setValueTextSize(14); 121 | // 文字的颜色 122 | dataset.setValueTextColor(Color.RED); 123 | // 文字的样式 124 | dataset.setValueTypeface(Typeface.DEFAULT_BOLD); 125 | 126 | // 当值位置为外边线时,表示线的前半段长度。 127 | dataset.setValueLinePart1Length(0.4f); 128 | // 当值位置为外边线时,表示线的后半段长度。 129 | dataset.setValueLinePart2Length(0.4f); 130 | // 当ValuePosits为OutsiDice时,指示偏移为切片大小的百分比 131 | dataset.setValueLinePart1OffsetPercentage(80f); 132 | // 当值位置为外边线时,表示线的颜色。 133 | dataset.setValueLineColor(Color.parseColor("#a1a1a1")); 134 | // 设置Y值的位置是在圆内还是圆外 135 | dataset.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); 136 | // 设置Y轴描述线和填充区域的颜色一致 137 | dataset.setUsingSliceColorAsValueLineColor(false); 138 | // 设置每条之前的间隙 139 | dataset.setSliceSpace(0); 140 | 141 | //设置饼状Item被选中时变化的距离 142 | dataset.setSelectionShift(5f); 143 | //填充数据 144 | PieData pieData = new PieData(dataset); 145 | // 格式化显示的数据为%百分比 146 | pieData.setValueFormatter(new PercentFormatter()); 147 | // 显示试图 148 | pieChart.setData(pieData); 149 | } 150 | 151 | 152 | /** 153 | * 显示圆环 154 | * @param yvals 155 | * @param colors 156 | */ 157 | public void showRingPieChart(List yvals, List colors){ 158 | //显示为圆环 159 | pieChart.setDrawHoleEnabled(true); 160 | //设置中间洞的大小 161 | pieChart.setHoleRadius(10f); 162 | 163 | //数据集合 164 | PieDataSet dataset = new PieDataSet(yvals, ""); 165 | //填充每个区域的颜色 166 | dataset.setColors(colors); 167 | //是否在图上显示数值 168 | dataset.setDrawValues(true); 169 | // 文字的大小 170 | dataset.setValueTextSize(14); 171 | // 文字的颜色 172 | dataset.setValueTextColor(Color.RED); 173 | // 文字的样式 174 | dataset.setValueTypeface(Typeface.DEFAULT_BOLD); 175 | 176 | // 当值位置为外边线时,表示线的前半段长度。 177 | dataset.setValueLinePart1Length(0.4f); 178 | // 当值位置为外边线时,表示线的后半段长度。 179 | dataset.setValueLinePart2Length(0.4f); 180 | // 当ValuePosits为OutsiDice时,指示偏移为切片大小的百分比 181 | dataset.setValueLinePart1OffsetPercentage(80f); 182 | // 当值位置为外边线时,表示线的颜色。 183 | dataset.setValueLineColor(Color.parseColor("#a1a1a1")); 184 | // 设置Y值的位置是在圆内还是圆外 185 | dataset.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); 186 | // 设置Y轴描述线和填充区域的颜色一致 187 | dataset.setUsingSliceColorAsValueLineColor(false); 188 | // 设置每条之前的间隙 189 | dataset.setSliceSpace(0); 190 | 191 | //设置饼状Item被选中时变化的距离 192 | dataset.setSelectionShift(5f); 193 | //填充数据 194 | PieData pieData = new PieData(dataset); 195 | // 格式化显示的数据为%百分比 196 | pieData.setValueFormatter(new PercentFormatter()); 197 | // 显示试图 198 | pieChart.setData(pieData); 199 | 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/ToastUtils.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils; 2 | 3 | import android.annotation.SuppressLint; 4 | 5 | import androidx.annotation.MainThread; 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.StringRes; 8 | 9 | import com.xuexiang.xui.XUI; 10 | import com.xuexiang.xui.widget.toast.XToast; 11 | 12 | /** 13 | * 自定义弹框函数 14 | * @author 小游 15 | * @date 2020/12/15 16 | */ 17 | @SuppressLint("CheckResult") 18 | public final class ToastUtils { 19 | 20 | private ToastUtils() { 21 | throw new UnsupportedOperationException("u can't instantiate me..."); 22 | } 23 | 24 | static { 25 | XToast.Config.get() 26 | .setAlpha(200) 27 | .setToastTypeface(XUI.getDefaultTypeface()) 28 | .allowQueue(false); 29 | } 30 | 31 | 32 | @MainThread 33 | public static void toast(@NonNull CharSequence message) { 34 | XToast.normal(XUI.getContext(), message).show(); 35 | } 36 | 37 | @MainThread 38 | public static void toast(@StringRes int message) { 39 | XToast.normal(XUI.getContext(), message).show(); 40 | } 41 | 42 | @MainThread 43 | public static void toast(@NonNull CharSequence message, int duration) { 44 | XToast.normal(XUI.getContext(), message, duration).show(); 45 | } 46 | 47 | @MainThread 48 | public static void toast(@StringRes int message, int duration) { 49 | XToast.normal(XUI.getContext(), message, duration).show(); 50 | } 51 | 52 | //=============// 53 | 54 | @MainThread 55 | public static void error(@NonNull CharSequence message) { 56 | XToast.error(XUI.getContext(), message).show(); 57 | } 58 | 59 | @MainThread 60 | public static void error(@NonNull Exception error) { 61 | XToast.error(XUI.getContext(), error.getMessage()).show(); 62 | } 63 | 64 | @MainThread 65 | public static void error(@StringRes int message) { 66 | XToast.error(XUI.getContext(), message).show(); 67 | } 68 | 69 | @MainThread 70 | public static void error(@NonNull CharSequence message, int duration) { 71 | XToast.error(XUI.getContext(), message, duration).show(); 72 | } 73 | 74 | @MainThread 75 | public static void error(@StringRes int message, int duration) { 76 | XToast.error(XUI.getContext(), message, duration).show(); 77 | } 78 | 79 | //=============// 80 | 81 | @MainThread 82 | public static void success(@NonNull CharSequence message) { 83 | XToast.success(XUI.getContext(), message).show(); 84 | } 85 | 86 | @MainThread 87 | public static void success(@StringRes int message) { 88 | XToast.success(XUI.getContext(), message).show(); 89 | } 90 | 91 | @MainThread 92 | public static void success(@NonNull CharSequence message, int duration) { 93 | XToast.success(XUI.getContext(), message, duration).show(); 94 | } 95 | 96 | @MainThread 97 | public static void success(@StringRes int message, int duration) { 98 | XToast.success(XUI.getContext(), message, duration).show(); 99 | } 100 | 101 | //=============// 102 | 103 | @MainThread 104 | public static void info(@NonNull CharSequence message) { 105 | XToast.info(XUI.getContext(), message).show(); 106 | } 107 | 108 | @MainThread 109 | public static void info(@StringRes int message) { 110 | XToast.info(XUI.getContext(), message).show(); 111 | } 112 | 113 | @MainThread 114 | public static void info(@NonNull CharSequence message, int duration) { 115 | XToast.info(XUI.getContext(), message, duration).show(); 116 | } 117 | 118 | @MainThread 119 | public static void info(@StringRes int message, int duration) { 120 | XToast.info(XUI.getContext(), message, duration).show(); 121 | } 122 | 123 | //=============// 124 | 125 | @MainThread 126 | public static void warning(@NonNull CharSequence message) { 127 | XToast.warning(XUI.getContext(), message).show(); 128 | } 129 | 130 | @MainThread 131 | public static void warning(@StringRes int message) { 132 | XToast.warning(XUI.getContext(), message).show(); 133 | } 134 | 135 | @MainThread 136 | public static void warning(@NonNull CharSequence message, int duration) { 137 | XToast.warning(XUI.getContext(), message, duration).show(); 138 | } 139 | 140 | @MainThread 141 | public static void warning(@StringRes int message, int duration) { 142 | XToast.warning(XUI.getContext(), message, duration).show(); 143 | } 144 | 145 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/Tools.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.Color; 6 | import android.util.DisplayMetrics; 7 | 8 | import java.util.Random; 9 | 10 | /** 11 | * 一些常用的工具类 12 | * @author 小游 13 | * @date 2020/12/18 14 | */ 15 | public class Tools { 16 | 17 | /** 18 | * 随机获取一个颜色 19 | * @return 颜色值 20 | */ 21 | public static int getRandomColor(){ 22 | // 存储颜色数组 23 | String[] colors = new String[]{ 24 | "#1abc9c", 25 | "#2ecc71", 26 | "#3498db", 27 | "#9b59b6", 28 | "#34495e", 29 | "#f1c40f", 30 | "#e67e22", 31 | "#e74c3c", 32 | "#ecf0f1", 33 | "#95a5a6", 34 | "#95a5a6", 35 | }; 36 | // 随机获取一个颜色 37 | Random random = new Random(); 38 | return Color.parseColor(colors[random.nextInt(colors.length)]); 39 | } 40 | 41 | 42 | /** 43 | * 获取系统的屏幕宽度 44 | * @param context context对象 45 | * @return 返回当前宽度(单位px) 46 | */ 47 | public static int getWidth(Context context){ 48 | Resources resources = context.getResources(); 49 | DisplayMetrics dm = resources.getDisplayMetrics(); 50 | return dm.widthPixels; 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/TrackUtil.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils; 2 | 3 | import com.arcsoft.face.FaceInfo; 4 | 5 | import java.util.List; 6 | 7 | public class TrackUtil { 8 | 9 | public static boolean isSameFace(FaceInfo faceInfo1, FaceInfo faceInfo2) { 10 | return faceInfo1.getFaceId() == faceInfo2.getFaceId(); 11 | } 12 | 13 | public static void keepMaxFace(List ftFaceList) { 14 | if (ftFaceList == null || ftFaceList.size() <= 1) { 15 | return; 16 | } 17 | FaceInfo maxFaceInfo = ftFaceList.get(0); 18 | for (FaceInfo faceInfo : ftFaceList) { 19 | if (faceInfo.getRect().width() > maxFaceInfo.getRect().width()) { 20 | maxFaceInfo = faceInfo; 21 | } 22 | } 23 | ftFaceList.clear(); 24 | ftFaceList.add(maxFaceInfo); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/TypeChange.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.InputStream; 8 | 9 | /** 10 | * 类型转换函数 11 | * 12 | * @author 小游 13 | * @date 2020/12/15 14 | */ 15 | public class TypeChange { 16 | /** 17 | * bitmap对象转string 18 | * @param bm bitmap对象 19 | * @return 返回InputStream 对象 20 | */ 21 | public static InputStream bitmap2InputStream(Bitmap bm) { 22 | ByteArrayOutputStream bao = new ByteArrayOutputStream(); 23 | bm.compress(Bitmap.CompressFormat.JPEG, 100, bao); 24 | return new ByteArrayInputStream(bao.toByteArray()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/camera/CameraHelper.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils.camera; 2 | 3 | import android.graphics.ImageFormat; 4 | import android.graphics.Point; 5 | import android.graphics.SurfaceTexture; 6 | import android.hardware.Camera; 7 | import android.util.Log; 8 | import android.view.Surface; 9 | import android.view.SurfaceHolder; 10 | import android.view.SurfaceView; 11 | import android.view.TextureView; 12 | import android.view.View; 13 | 14 | import java.io.IOException; 15 | import java.util.Arrays; 16 | import java.util.Comparator; 17 | import java.util.List; 18 | 19 | /** 20 | * 相机辅助类,和{@link CameraListener}共同使用,获取nv21数据等操作 21 | */ 22 | public class CameraHelper implements Camera.PreviewCallback { 23 | private static final String TAG = "CameraHelper"; 24 | private Camera mCamera; 25 | private int mCameraId; 26 | private Point previewViewSize; 27 | private View previewDisplayView; 28 | private Camera.Size previewSize; 29 | private Point specificPreviewSize; 30 | private int displayOrientation = 0; 31 | private int rotation; 32 | private int additionalRotation; 33 | private boolean isMirror = false; 34 | 35 | private Integer specificCameraId = null; 36 | private CameraListener cameraListener; 37 | 38 | private CameraHelper(Builder builder) { 39 | previewDisplayView = builder.previewDisplayView; 40 | specificCameraId = builder.specificCameraId; 41 | cameraListener = builder.cameraListener; 42 | rotation = builder.rotation; 43 | additionalRotation = builder.additionalRotation; 44 | previewViewSize = builder.previewViewSize; 45 | specificPreviewSize = builder.previewSize; 46 | if (builder.previewDisplayView instanceof TextureView) { 47 | isMirror = builder.isMirror; 48 | } else if (isMirror) { 49 | throw new RuntimeException("mirror is effective only when the preview is on a textureView"); 50 | } 51 | } 52 | 53 | public void init() { 54 | if (previewDisplayView instanceof TextureView) { 55 | ((TextureView) this.previewDisplayView).setSurfaceTextureListener(textureListener); 56 | } else if (previewDisplayView instanceof SurfaceView) { 57 | ((SurfaceView) previewDisplayView).getHolder().addCallback(surfaceCallback); 58 | } 59 | 60 | if (isMirror) { 61 | previewDisplayView.setScaleX(-1); 62 | } 63 | } 64 | 65 | public void start() { 66 | synchronized (this) { 67 | if (mCamera != null) { 68 | return; 69 | } 70 | //相机数量为2则打开1,1则打开0,相机ID 1为前置,0为后置 71 | mCameraId = Camera.getNumberOfCameras() - 1; 72 | //若指定了相机ID且该相机存在,则打开指定的相机 73 | if (specificCameraId != null && specificCameraId <= mCameraId) { 74 | mCameraId = specificCameraId; 75 | } 76 | 77 | //没有相机 78 | if (mCameraId == -1) { 79 | if (cameraListener != null) { 80 | cameraListener.onCameraError(new Exception("camera not found")); 81 | } 82 | return; 83 | } 84 | if (mCamera == null) { 85 | mCamera = Camera.open(mCameraId); 86 | } 87 | 88 | displayOrientation = getCameraOri(rotation); 89 | mCamera.setDisplayOrientation(displayOrientation); 90 | try { 91 | Camera.Parameters parameters = mCamera.getParameters(); 92 | parameters.setPreviewFormat(ImageFormat.NV21); 93 | 94 | //预览大小设置 95 | previewSize = parameters.getPreviewSize(); 96 | List supportedPreviewSizes = parameters.getSupportedPreviewSizes(); 97 | if (supportedPreviewSizes != null && supportedPreviewSizes.size() > 0) { 98 | previewSize = getBestSupportedSize(supportedPreviewSizes, previewViewSize); 99 | } 100 | parameters.setPreviewSize(previewSize.width, previewSize.height); 101 | 102 | //对焦模式设置 103 | List supportedFocusModes = parameters.getSupportedFocusModes(); 104 | if (supportedFocusModes != null && supportedFocusModes.size() > 0) { 105 | if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { 106 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 107 | } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { 108 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); 109 | } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { 110 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); 111 | } 112 | } 113 | mCamera.setParameters(parameters); 114 | if (previewDisplayView instanceof TextureView) { 115 | mCamera.setPreviewTexture(((TextureView) previewDisplayView).getSurfaceTexture()); 116 | } else { 117 | mCamera.setPreviewDisplay(((SurfaceView) previewDisplayView).getHolder()); 118 | } 119 | mCamera.setPreviewCallback(this); 120 | mCamera.startPreview(); 121 | if (cameraListener != null) { 122 | cameraListener.onCameraOpened(mCamera, mCameraId, displayOrientation, isMirror); 123 | } 124 | } catch (Exception e) { 125 | if (cameraListener != null) { 126 | cameraListener.onCameraError(e); 127 | } 128 | } 129 | } 130 | } 131 | 132 | private int getCameraOri(int rotation) { 133 | int degrees = rotation * 90; 134 | switch (rotation) { 135 | case Surface.ROTATION_0: 136 | degrees = 0; 137 | break; 138 | case Surface.ROTATION_90: 139 | degrees = 90; 140 | break; 141 | case Surface.ROTATION_180: 142 | degrees = 180; 143 | break; 144 | case Surface.ROTATION_270: 145 | degrees = 270; 146 | break; 147 | default: 148 | break; 149 | } 150 | additionalRotation /= 90; 151 | additionalRotation *= 90; 152 | degrees += additionalRotation; 153 | int result; 154 | Camera.CameraInfo info = new Camera.CameraInfo(); 155 | Camera.getCameraInfo(mCameraId, info); 156 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 157 | result = (info.orientation + degrees) % 360; 158 | result = (360 - result) % 360; 159 | } else { 160 | result = (info.orientation - degrees + 360) % 360; 161 | } 162 | return result; 163 | } 164 | 165 | public void stop() { 166 | synchronized (this) { 167 | if (mCamera == null) { 168 | return; 169 | } 170 | mCamera.setPreviewCallback(null); 171 | mCamera.stopPreview(); 172 | mCamera.release(); 173 | mCamera = null; 174 | if (cameraListener != null) { 175 | cameraListener.onCameraClosed(); 176 | } 177 | } 178 | } 179 | 180 | public boolean isStopped() { 181 | synchronized (this) { 182 | return mCamera == null; 183 | } 184 | } 185 | 186 | public void release() { 187 | synchronized (this) { 188 | stop(); 189 | previewDisplayView = null; 190 | specificCameraId = null; 191 | cameraListener = null; 192 | previewViewSize = null; 193 | specificPreviewSize = null; 194 | previewSize = null; 195 | } 196 | } 197 | 198 | private Camera.Size getBestSupportedSize(List sizes, Point previewViewSize) { 199 | if (sizes == null || sizes.size() == 0) { 200 | return mCamera.getParameters().getPreviewSize(); 201 | } 202 | Camera.Size[] tempSizes = sizes.toArray(new Camera.Size[0]); 203 | Arrays.sort(tempSizes, new Comparator() { 204 | @Override 205 | public int compare(Camera.Size o1, Camera.Size o2) { 206 | if (o1.width > o2.width) { 207 | return -1; 208 | } else if (o1.width == o2.width) { 209 | return o1.height > o2.height ? -1 : 1; 210 | } else { 211 | return 1; 212 | } 213 | } 214 | }); 215 | sizes = Arrays.asList(tempSizes); 216 | 217 | Camera.Size bestSize = sizes.get(0); 218 | float previewViewRatio; 219 | if (previewViewSize != null) { 220 | previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y; 221 | } else { 222 | previewViewRatio = (float) bestSize.width / (float) bestSize.height; 223 | } 224 | 225 | if (previewViewRatio > 1) { 226 | previewViewRatio = 1 / previewViewRatio; 227 | } 228 | boolean isNormalRotate = (additionalRotation % 180 == 0); 229 | 230 | for (Camera.Size s : sizes) { 231 | if (specificPreviewSize != null && specificPreviewSize.x == s.width && specificPreviewSize.y == s.height) { 232 | return s; 233 | } 234 | if (isNormalRotate) { 235 | if (Math.abs((s.height / (float) s.width) - previewViewRatio) < Math.abs(bestSize.height / (float) bestSize.width - previewViewRatio)) { 236 | bestSize = s; 237 | } 238 | } else { 239 | if (Math.abs((s.width / (float) s.height) - previewViewRatio) < Math.abs(bestSize.width / (float) bestSize.height - previewViewRatio)) { 240 | bestSize = s; 241 | } 242 | } 243 | } 244 | return bestSize; 245 | } 246 | 247 | public List getSupportedPreviewSizes() { 248 | if (mCamera == null) { 249 | return null; 250 | } 251 | return mCamera.getParameters().getSupportedPreviewSizes(); 252 | } 253 | 254 | public List getSupportedPictureSizes() { 255 | if (mCamera == null) { 256 | return null; 257 | } 258 | return mCamera.getParameters().getSupportedPictureSizes(); 259 | } 260 | 261 | 262 | @Override 263 | public void onPreviewFrame(byte[] nv21, Camera camera) { 264 | if (cameraListener != null) { 265 | cameraListener.onPreview(nv21, camera); 266 | } 267 | } 268 | 269 | private TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() { 270 | @Override 271 | public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { 272 | // start(); 273 | if (mCamera != null) { 274 | try { 275 | mCamera.setPreviewTexture(surfaceTexture); 276 | } catch (IOException e) { 277 | e.printStackTrace(); 278 | } 279 | } 280 | } 281 | 282 | @Override 283 | public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { 284 | Log.i(TAG, "onSurfaceTextureSizeChanged: " + width + " " + height); 285 | } 286 | 287 | @Override 288 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { 289 | stop(); 290 | return false; 291 | } 292 | 293 | @Override 294 | public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { 295 | 296 | } 297 | }; 298 | private SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() { 299 | @Override 300 | public void surfaceCreated(SurfaceHolder holder) { 301 | // start(); 302 | if (mCamera != null) { 303 | try { 304 | mCamera.setPreviewDisplay(holder); 305 | } catch (IOException e) { 306 | e.printStackTrace(); 307 | } 308 | } 309 | } 310 | 311 | @Override 312 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 313 | 314 | } 315 | 316 | @Override 317 | public void surfaceDestroyed(SurfaceHolder holder) { 318 | stop(); 319 | } 320 | }; 321 | 322 | public void changeDisplayOrientation(int rotation) { 323 | if (mCamera != null) { 324 | this.rotation = rotation; 325 | displayOrientation = getCameraOri(rotation); 326 | mCamera.setDisplayOrientation(displayOrientation); 327 | if (cameraListener != null) { 328 | cameraListener.onCameraConfigurationChanged(mCameraId, displayOrientation); 329 | } 330 | } 331 | } 332 | public boolean switchCamera() { 333 | if (Camera.getNumberOfCameras() < 2) { 334 | return false; 335 | } 336 | // cameraId ,0为后置,1为前置 337 | specificCameraId = 1 - mCameraId; 338 | stop(); 339 | start(); 340 | return true; 341 | } 342 | 343 | public static final class Builder { 344 | 345 | /** 346 | * 预览显示的view,目前仅支持surfaceView和textureView 347 | */ 348 | private View previewDisplayView; 349 | 350 | /** 351 | * 是否镜像显示,只支持textureView 352 | */ 353 | private boolean isMirror; 354 | /** 355 | * 指定的相机ID 356 | */ 357 | private Integer specificCameraId; 358 | /** 359 | * 事件回调 360 | */ 361 | private CameraListener cameraListener; 362 | /** 363 | * 屏幕的长宽,在选择最佳相机比例时用到 364 | */ 365 | private Point previewViewSize; 366 | /** 367 | * 传入getWindowManager().getDefaultDisplay().getRotation()的值即可 368 | */ 369 | private int rotation; 370 | /** 371 | * 指定的预览宽高,若系统支持则会以这个预览宽高进行预览 372 | */ 373 | private Point previewSize; 374 | 375 | /** 376 | * 额外的旋转角度(用于适配一些定制设备) 377 | */ 378 | private int additionalRotation; 379 | 380 | public Builder() { 381 | } 382 | 383 | 384 | public Builder previewOn(View val) { 385 | if (val instanceof SurfaceView || val instanceof TextureView) { 386 | previewDisplayView = val; 387 | return this; 388 | } else { 389 | throw new RuntimeException("you must preview on a textureView or a surfaceView"); 390 | } 391 | } 392 | 393 | 394 | public Builder isMirror(boolean val) { 395 | isMirror = val; 396 | return this; 397 | } 398 | 399 | public Builder previewSize(Point val) { 400 | previewSize = val; 401 | return this; 402 | } 403 | 404 | public Builder previewViewSize(Point val) { 405 | previewViewSize = val; 406 | return this; 407 | } 408 | 409 | public Builder rotation(int val) { 410 | rotation = val; 411 | return this; 412 | } 413 | 414 | public Builder additionalRotation(int val) { 415 | additionalRotation = val; 416 | return this; 417 | } 418 | 419 | public Builder specificCameraId(Integer val) { 420 | specificCameraId = val; 421 | return this; 422 | } 423 | 424 | public Builder cameraListener(CameraListener val) { 425 | cameraListener = val; 426 | return this; 427 | } 428 | 429 | public CameraHelper build() { 430 | if (previewViewSize == null) { 431 | Log.e(TAG, "previewViewSize is null, now use default previewSize"); 432 | } 433 | if (cameraListener == null) { 434 | Log.e(TAG, "cameraListener is null, callback will not be called"); 435 | } 436 | if (previewDisplayView == null) { 437 | throw new RuntimeException("you must preview on a textureView or a surfaceView"); 438 | } 439 | return new CameraHelper(this); 440 | } 441 | } 442 | 443 | } 444 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/camera/CameraListener.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils.camera; 2 | 3 | import android.hardware.Camera; 4 | 5 | 6 | public interface CameraListener { 7 | /** 8 | * 当打开时执行 9 | * @param camera 相机实例 10 | * @param cameraId 相机ID 11 | * @param displayOrientation 相机预览旋转角度 12 | * @param isMirror 是否镜像显示 13 | */ 14 | void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror); 15 | 16 | /** 17 | * 预览数据回调 18 | * @param data 预览数据 19 | * @param camera 相机实例 20 | */ 21 | void onPreview(byte[] data, Camera camera); 22 | 23 | /** 24 | * 当相机关闭时执行 25 | */ 26 | void onCameraClosed(); 27 | 28 | /** 29 | * 当出现异常时执行 30 | * @param e 相机相关异常 31 | */ 32 | void onCameraError(Exception e); 33 | 34 | /** 35 | * 属性变化时调用 36 | * @param cameraID 相机ID 37 | * @param displayOrientation 相机旋转方向 38 | */ 39 | void onCameraConfigurationChanged(int cameraID, int displayOrientation); 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/face/FaceListener.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils.face; 2 | 3 | 4 | import androidx.annotation.Nullable; 5 | 6 | import com.arcsoft.face.FaceFeature; 7 | import com.arcsoft.face.LivenessInfo; 8 | 9 | /** 10 | * 人脸处理回调 11 | */ 12 | public interface FaceListener { 13 | /** 14 | * 当出现异常时执行 15 | * 16 | * @param e 异常信息 17 | */ 18 | void onFail(Exception e); 19 | 20 | 21 | /** 22 | * 请求人脸特征后的回调 23 | * 24 | * @param faceFeature 人脸特征数据 25 | * @param requestId 请求码 26 | * @param errorCode 错误码 27 | */ 28 | void onFaceFeatureInfoGet(@Nullable FaceFeature faceFeature, Integer requestId, Integer errorCode); 29 | 30 | /** 31 | * 请求活体检测后的回调 32 | * 33 | * @param livenessInfo 活体检测结果 34 | * @param requestId 请求码 35 | * @param errorCode 错误码 36 | */ 37 | void onFaceLivenessInfoGet(@Nullable LivenessInfo livenessInfo, Integer requestId, Integer errorCode); 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/face/LivenessType.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils.face; 2 | 3 | /** 4 | * 活体检测类型 5 | */ 6 | public enum LivenessType { 7 | /** 8 | * RGB活体检测 9 | */ 10 | RGB, 11 | /** 12 | * 红外活体检测 13 | */ 14 | IR 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/face/RecognizeColor.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils.face; 2 | 3 | import android.graphics.Color; 4 | 5 | /** 6 | * 识别过程中人脸框的颜色 7 | */ 8 | public class RecognizeColor { 9 | /** 10 | * 未知情况的颜色 11 | */ 12 | public static final int COLOR_UNKNOWN = Color.YELLOW; 13 | /** 14 | * 成功的颜色 15 | */ 16 | public static final int COLOR_SUCCESS = Color.GREEN; 17 | /** 18 | * 失败的颜色 19 | */ 20 | public static final int COLOR_FAILED = Color.RED; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/face/RequestFeatureStatus.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils.face; 2 | 3 | /** 4 | * 请求的结果状态 5 | * @author 小游 6 | */ 7 | public class RequestFeatureStatus { 8 | public static final int SEARCHING = 0; 9 | public static final int SUCCEED = 1; 10 | public static final int FAILED = 2; 11 | public static final int TO_RETRY = 3; 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/utils/face/RequestLivenessStatus.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.utils.face; 2 | 3 | public class RequestLivenessStatus { 4 | public static final int ANALYZING = 10; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/view/ColorfulMonthView.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | 6 | import com.haibin.calendarview.Calendar; 7 | import com.haibin.calendarview.MonthView; 8 | 9 | /** 10 | * 高仿魅族日历布局 11 | * Created by huanghaibin on 2017/11/15. 12 | */ 13 | 14 | public class ColorfulMonthView extends MonthView { 15 | 16 | private int mRadius; 17 | 18 | public ColorfulMonthView(Context context) { 19 | super(context); 20 | } 21 | 22 | @Override 23 | protected void onPreviewHook() { 24 | mRadius = Math.min(mItemWidth, mItemHeight) / 5 * 2; 25 | } 26 | 27 | /** 28 | * 如果需要点击Scheme没有效果,则return true 29 | * 30 | * @param canvas canvas 31 | * @param calendar 日历日历calendar 32 | * @param x 日历Card x起点坐标 33 | * @param y 日历Card y起点坐标 34 | * @param hasScheme hasScheme 非标记的日期 35 | * @return false 则不绘制onDrawScheme,因为这里背景色是互斥的 36 | */ 37 | @Override 38 | protected boolean onDrawSelected(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme) { 39 | int cx = x + mItemWidth / 2; 40 | int cy = y + mItemHeight / 2; 41 | canvas.drawCircle(cx, cy, mRadius, mSelectedPaint); 42 | return true; 43 | } 44 | 45 | @Override 46 | protected void onDrawScheme(Canvas canvas, Calendar calendar, int x, int y) { 47 | int cx = x + mItemWidth / 2; 48 | int cy = y + mItemHeight / 2; 49 | canvas.drawCircle(cx, cy, mRadius, mSchemePaint); 50 | } 51 | 52 | @SuppressWarnings("IntegerDivisionInFloatingPointContext") 53 | @Override 54 | protected void onDrawText(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme, boolean isSelected) { 55 | int cx = x + mItemWidth / 2; 56 | int top = y - mItemHeight / 8; 57 | if (isSelected) { 58 | canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top, 59 | calendar.isCurrentDay() ? mCurDayTextPaint : mSelectTextPaint); 60 | canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + y + mItemHeight / 10, 61 | calendar.isCurrentDay() ? mCurDayLunarTextPaint : mSelectedLunarTextPaint); 62 | } else if (hasScheme) { 63 | canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top, 64 | calendar.isCurrentDay() ? mCurDayTextPaint : 65 | calendar.isCurrentMonth() ? mSchemeTextPaint : mOtherMonthTextPaint); 66 | 67 | canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + y + mItemHeight / 10, mSchemeLunarTextPaint); 68 | } else { 69 | canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top, 70 | calendar.isCurrentDay() ? mCurDayTextPaint : 71 | calendar.isCurrentMonth() ? mCurMonthTextPaint : mOtherMonthTextPaint); 72 | canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + y + mItemHeight / 10, 73 | calendar.isCurrentDay() ? mCurDayLunarTextPaint : mCurMonthLunarTextPaint); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/view/ColorfulWeekView.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | 6 | import com.haibin.calendarview.Calendar; 7 | import com.haibin.calendarview.WeekView; 8 | 9 | /** 10 | * 多彩周视图 11 | * Created by huanghaibin on 2017/11/29. 12 | */ 13 | 14 | public class ColorfulWeekView extends WeekView { 15 | 16 | private int mRadius; 17 | 18 | public ColorfulWeekView(Context context) { 19 | super(context); 20 | // //兼容硬件加速无效的代码 21 | // setLayerType(View.LAYER_TYPE_SOFTWARE, mSelectedPaint); 22 | // //4.0以上硬件加速会导致无效 23 | // mSelectedPaint.setMaskFilter(new BlurMaskFilter(30, BlurMaskFilter.Blur.SOLID)); 24 | // 25 | // setLayerType(View.LAYER_TYPE_SOFTWARE, mSchemePaint); 26 | // mSchemePaint.setMaskFilter(new BlurMaskFilter(30, BlurMaskFilter.Blur.SOLID)); 27 | } 28 | 29 | @Override 30 | protected void onPreviewHook() { 31 | mRadius = Math.min(mItemWidth, mItemHeight) / 5 * 2; 32 | } 33 | 34 | /** 35 | * 如果需要点击Scheme没有效果,则return true 36 | * 37 | * @param canvas canvas 38 | * @param calendar 日历日历calendar 39 | * @param x 日历Card x起点坐标 40 | * @param hasScheme hasScheme 非标记的日期 41 | * @return false 则不绘制onDrawScheme,因为这里背景色是互斥的 42 | */ 43 | @Override 44 | protected boolean onDrawSelected(Canvas canvas, Calendar calendar, int x, boolean hasScheme) { 45 | int cx = x + mItemWidth / 2; 46 | int cy = mItemHeight / 2; 47 | canvas.drawCircle(cx, cy, mRadius, mSelectedPaint); 48 | return true; 49 | } 50 | 51 | 52 | @Override 53 | protected void onDrawScheme(Canvas canvas, Calendar calendar, int x) { 54 | int cx = x + mItemWidth / 2; 55 | int cy = mItemHeight / 2; 56 | canvas.drawCircle(cx, cy, mRadius, mSchemePaint); 57 | } 58 | 59 | @SuppressWarnings("IntegerDivisionInFloatingPointContext") 60 | @Override 61 | protected void onDrawText(Canvas canvas, Calendar calendar, int x, boolean hasScheme, boolean isSelected) { 62 | int cx = x + mItemWidth / 2; 63 | int top = -mItemHeight / 8; 64 | if (isSelected) { 65 | canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top, 66 | calendar.isCurrentDay() ? mCurDayTextPaint : mSelectTextPaint); 67 | canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + mItemHeight / 10, 68 | calendar.isCurrentDay() ? mCurDayLunarTextPaint : mSelectedLunarTextPaint); 69 | } else if (hasScheme) { 70 | canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top, 71 | calendar.isCurrentDay() ? mCurDayTextPaint : 72 | calendar.isCurrentMonth() ? mSchemeTextPaint : mSchemeTextPaint); 73 | 74 | canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + mItemHeight / 10, mSchemeLunarTextPaint); 75 | } else { 76 | canvas.drawText(String.valueOf(calendar.getDay()), cx, mTextBaseLine + top, 77 | calendar.isCurrentDay() ? mCurDayTextPaint : 78 | calendar.isCurrentMonth() ? mCurMonthTextPaint : mCurMonthTextPaint); 79 | canvas.drawText(calendar.getLunar(), cx, mTextBaseLine + mItemHeight / 10, 80 | calendar.isCurrentDay() ? mCurDayLunarTextPaint : mCurMonthLunarTextPaint); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/widget/FaceRectView.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | 11 | import androidx.annotation.Nullable; 12 | 13 | import com.xiaoyou.face.model.DrawInfo; 14 | import com.xiaoyou.face.utils.DrawHelper; 15 | 16 | import java.util.List; 17 | import java.util.concurrent.CopyOnWriteArrayList; 18 | 19 | /** 20 | * 用于显示人脸信息的控件 21 | */ 22 | public class FaceRectView extends View { 23 | private CopyOnWriteArrayList drawInfoList = new CopyOnWriteArrayList<>(); 24 | 25 | // 画笔,复用 26 | private Paint paint; 27 | 28 | // 默认人脸框厚度 29 | private static final int DEFAULT_FACE_RECT_THICKNESS = 6; 30 | 31 | public FaceRectView(Context context) { 32 | this(context, null); 33 | } 34 | 35 | public FaceRectView(Context context, @Nullable AttributeSet attrs) { 36 | super(context, attrs); 37 | paint = new Paint(); 38 | } 39 | 40 | @Override 41 | protected void onDraw(Canvas canvas) { 42 | super.onDraw(canvas); 43 | if (drawInfoList != null && drawInfoList.size() > 0) { 44 | for (int i = 0; i < drawInfoList.size(); i++) { 45 | DrawHelper.drawFaceRect(canvas, drawInfoList.get(i), DEFAULT_FACE_RECT_THICKNESS, paint); 46 | } 47 | } 48 | } 49 | 50 | public void clearFaceInfo() { 51 | drawInfoList.clear(); 52 | postInvalidate(); 53 | } 54 | 55 | public void addFaceInfo(DrawInfo faceInfo) { 56 | drawInfoList.add(faceInfo); 57 | postInvalidate(); 58 | } 59 | 60 | public void addFaceInfo(List faceInfoList) { 61 | drawInfoList.addAll(faceInfoList); 62 | postInvalidate(); 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xiaoyou/face/widget/FaceSearchResultAdapter.java: -------------------------------------------------------------------------------- 1 | package com.xiaoyou.face.widget; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.recyclerview.widget.RecyclerView; 14 | 15 | import com.bumptech.glide.Glide; 16 | import com.xiaoyou.face.R; 17 | import com.xiaoyou.face.faceserver.CompareResult; 18 | import com.xiaoyou.face.faceserver.FaceServer; 19 | 20 | import java.io.File; 21 | import java.util.List; 22 | 23 | /** 24 | * 人脸搜索显示的adapter 25 | * @author 小游 26 | * @date 2020/11/16 27 | */ 28 | public class FaceSearchResultAdapter extends RecyclerView.Adapter { 29 | private final List compareResultList; 30 | private final LayoutInflater inflater; 31 | 32 | public FaceSearchResultAdapter(List compareResultList, Context context) { 33 | inflater = LayoutInflater.from(context); 34 | this.compareResultList = compareResultList; 35 | } 36 | 37 | 38 | @NonNull 39 | @Override 40 | public CompareResultHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 41 | // 这里我们查找控件并注册 42 | View itemView = inflater.inflate(R.layout.recycler_item_search_result, null, false); 43 | CompareResultHolder compareResultHolder = new CompareResultHolder(itemView); 44 | compareResultHolder.textView = itemView.findViewById(R.id.tv_item_name); 45 | compareResultHolder.textNo = itemView.findViewById(R.id.tv_item_no); 46 | compareResultHolder.textBelieve = itemView.findViewById(R.id.tv_item_believe); 47 | compareResultHolder.imageView = itemView.findViewById(R.id.iv_item_head_img); 48 | return compareResultHolder; 49 | } 50 | 51 | @SuppressLint("SetTextI18n") 52 | @Override 53 | public void onBindViewHolder(@NonNull CompareResultHolder holder, int position) { 54 | if (compareResultList == null) { 55 | return; 56 | } 57 | 58 | // 这里我们加载图片并显示(我们这里使用学号来存储图片) 59 | File imgFile = new File(FaceServer.ROOT_PATH + File.separator + FaceServer.SAVE_IMG_DIR + File.separator + compareResultList.get(position).getId() + FaceServer.IMG_SUFFIX); 60 | Glide.with(holder.imageView) 61 | .load(imgFile) 62 | .into(holder.imageView); 63 | 64 | // 这里我们显示学号和姓名 65 | holder.textView.setText("姓名:"+compareResultList.get(position).getUserName()); 66 | holder.textNo.setText("学号:"+compareResultList.get(position).getUserNo()); 67 | holder.textBelieve.setText("相似度:"+compareResultList.get(position).getSimilar()); 68 | } 69 | 70 | @Override 71 | public int getItemCount() { 72 | return compareResultList == null ? 0 : compareResultList.size(); 73 | } 74 | 75 | static class CompareResultHolder extends RecyclerView.ViewHolder { 76 | TextView textView; 77 | TextView textNo; 78 | TextView textBelieve; 79 | ImageView imageView; 80 | CompareResultHolder(@NonNull View itemView) { 81 | super(itemView); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libarcsoft_face.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyou-project/android-course-design/caddcbae0cd19af083382942f34f1f1d2754fcbd/app/src/main/jniLibs/arm64-v8a/libarcsoft_face.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libarcsoft_face_engine.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyou-project/android-course-design/caddcbae0cd19af083382942f34f1f1d2754fcbd/app/src/main/jniLibs/arm64-v8a/libarcsoft_face_engine.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libarcsoft_image_util.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyou-project/android-course-design/caddcbae0cd19af083382942f34f1f1d2754fcbd/app/src/main/jniLibs/arm64-v8a/libarcsoft_image_util.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libarcsoft_face.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyou-project/android-course-design/caddcbae0cd19af083382942f34f1f1d2754fcbd/app/src/main/jniLibs/armeabi-v7a/libarcsoft_face.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libarcsoft_face_engine.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyou-project/android-course-design/caddcbae0cd19af083382942f34f1f1d2754fcbd/app/src/main/jniLibs/armeabi-v7a/libarcsoft_face_engine.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libarcsoft_image_util.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyou-project/android-course-design/caddcbae0cd19af083382942f34f1f1d2754fcbd/app/src/main/jniLibs/armeabi-v7a/libarcsoft_image_util.so -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_reaction.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_calendar.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_camera.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_credit.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_face.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_faces_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_photo_camera.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pie_chart.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_switch_camera.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_tool_box.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | 15 | 16 | 17 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_register_and_recognize.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 22 | 25 | 29 | 30 | 34 | 35 | 45 | 46 | 47 | 55 | 56 | 57 | 58 | 59 | 60 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_sign_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 17 | 18 | 19 | 29 | 30 | 40 | 41 | 49 | 50 | 59 | 60 | 61 | 62 | 69 | 70 | 77 | 78 | 88 | 89 | 90 | 91 | 92 | 99 | 100 | 126 | 127 | 128 | 129 | 136 | 137 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_me.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 22 | 23 | 24 | 28 | 29 | 36 | 37 | 47 | 48 |