Commit dfa400a3 authored by 姜天宇's avatar 姜天宇

feat(v1.0.2): 增加信息页、注册页、裁剪页

1.增加系统信息页
2.增加设置-注册页
3.对接注册、找回接口
4.增加秤盘裁剪页
parent d1d6a28b
......@@ -12,7 +12,9 @@ import android.util.Size;
import androidx.camera.camera2.interop.Camera2Interop;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraProvider;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraState;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
......@@ -50,6 +52,10 @@ public class CameraxController {
* 获取相机服务provider
*/
private ListenableFuture<ProcessCameraProvider> cameraProviderFutureListener;
/**
* 相机
*/
private Camera camera;
/**
* 相机的生命周期
*/
......@@ -66,6 +72,14 @@ public class CameraxController {
* 图片分析回调
*/
private OnImageAnalyzeListener onImageAnalyzeListener;
/**
* 帧间隔
*/
private int frameInterval = 5;
/**
* 相机状态
*/
private CameraState cameraState = null;
// private SurfaceTexture mColorSurfaceTexture = new SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
......@@ -110,6 +124,16 @@ public class CameraxController {
return this;
}
/**
* 设置帧间隔
* @param frameInterval
* @return
*/
public CameraxController setFrameInterval(int frameInterval) {
this.frameInterval = frameInterval;
return this;
}
/**
* 相机初始化
*/
......@@ -121,8 +145,10 @@ public class CameraxController {
// 创建图像分析器
ImageAnalysis imageAnalysis = createImageAnalysis();
// 绑定相机
Camera camera = bindCamera(cameraProvider, imageAnalysis);
camera = bindCamera(cameraProvider, imageAnalysis);
if (camera != null) {
// 禁用自动对焦
camera.getCameraControl().cancelFocusAndMetering();
// 观察相机状态
observeCameraLifecycle(camera);
}
......@@ -151,21 +177,10 @@ public class CameraxController {
long startTime = System.currentTimeMillis();
// 处理图片
count ++;
if (count % 5 == 0) {
if (count % frameInterval == 0) {
analyzeImage(imageProxy);
}
imageProxy.close();
// 休眠
/*long costTime = System.currentTimeMillis() - startTime;
// System.out.println("处理时间:" + costTime);
long leftTime = INTERVAL_FRAMES_ANALYZE - costTime;
if (leftTime <= INTERVAL_FRAMES_ANALYZE && leftTime >= 0){
try {
Thread.sleep(leftTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
});
return imageAnalysis;
......@@ -224,9 +239,14 @@ public class CameraxController {
*/
private void observeCameraLifecycle(Camera camera){
camera.getCameraInfo().getCameraState().removeObservers(lifecycleOwner);
camera.getCameraInfo().getCameraState().observe(lifecycleOwner, cameraState -> {
XLog.i("相机状态:" + cameraState.getType());
switch (cameraState.getType()){
camera.getCameraInfo().getCameraState().observe(lifecycleOwner, state -> {
XLog.i("相机状态:" + state.getType());
cameraState = state;
switch (state.getType()){
case OPEN:
// 相机开启
break;
case CLOSED:
// 相机关闭
break;
......@@ -235,9 +255,43 @@ public class CameraxController {
break;
}
if (cameraState.getError() != null){
XLog.i("相机异常:" + cameraState.getType());
if (state.getError() != null){
XLog.i("相机异常:" + state.getType());
}
});
}
/**
* 相机是否开启
* @return
*/
public boolean isOpen(){
if (cameraState == null){
return false;
}
if (cameraState.getType() == CameraState.Type.OPEN || cameraState.getType() == CameraState.Type.OPENING){
return true;
}
return false;
}
public void release(){
ProcessCameraProvider cameraProvider = null;
try {
cameraProvider = cameraProviderFutureListener.get();
} catch (Exception e) {
XLog.e(e.toString());
}
if (camera != null){
// 移除camera得生命周期监听
camera.getCameraInfo().getCameraState().removeObservers(lifecycleOwner);
}
if (cameraProvider != null){
cameraProvider.unbindAll();
}
onImageAnalyzeListener = null;
cameraState = null;
executors.shutdown();
instance = null;
}
}
package com.wmdigit.common.model;
/**
* 裁剪坐标
* @author dizi
*/
public class CropValue {
private int left, top, width, height;
public CropValue(int left, int top, int width, int height) {
this.left = left;
this.top = top;
this.width = width;
this.height = height;
}
public int getLeft() {
return left;
}
public void setLeft(int left) {
this.left = left;
}
public int getTop() {
return top;
}
public void setTop(int top) {
this.top = top;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
package com.wmdigit.common.utils;
import android.os.Parcel;
import android.os.Parcelable;
/**
* @author dizi
*/
public class ParcelHelper {
/**
* 深度拷贝
* @param input
* @return
* @param <T>
*/
public static <T> T copy(Parcelable input) {
Parcel parcel = null;
try {
parcel = Parcel.obtain();
parcel.writeParcelable(input, 0);
parcel.setDataPosition(0);
return parcel.readParcelable(input.getClass().getClassLoader());
} finally {
if (parcel!=null){
parcel.recycle();
}
}
}
}
package com.wmdigit.common.view.imageview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.AttributeSet;
import androidx.databinding.BindingAdapter;
import androidx.databinding.InverseBindingAdapter;
import androidx.databinding.InverseBindingListener;
import com.elvishew.xlog.XLog;
import com.wmdigit.common.model.CropValue;
/**
* 支持双向数据绑定的CropView
* @author dizi
*/
public class BidirectionalDataBindingCropView extends CropView{
private static InverseBindingListener inverseBindingListener;
public BidirectionalDataBindingCropView(Context context) {
super(context);
this.setOnActionUpListener(rect -> {
if (inverseBindingListener != null){
inverseBindingListener.onChange();
}
});
}
public BidirectionalDataBindingCropView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOnActionUpListener(rect -> {
if (inverseBindingListener != null){
inverseBindingListener.onChange();
}
});
}
public BidirectionalDataBindingCropView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.setOnActionUpListener(rect -> {
if (inverseBindingListener != null){
inverseBindingListener.onChange();
}
});
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
inverseBindingListener = null;
this.setOnActionUpListener(null);
}
/**
* 设置图片帧
* @param view
* @param bitmap
*/
@BindingAdapter("frame")
public static void setFrame(CropView view, Bitmap bitmap){
if (bitmap != null && !bitmap.isRecycled()){
view.setImageBitmap(bitmap);
}
}
/**
* 预设裁剪坐标
* @param view
* @param value
*/
@BindingAdapter("preset_crop_value")
public static void presetCropValue(CropView view, CropValue value){
if (value != null){
view.setPresetCropCoordinate(new Rect(value.getLeft(), value.getTop(), value.getWidth() + value.getLeft(), value.getHeight() + value.getTop()));
}
}
@InverseBindingAdapter(attribute = "preset_crop_value", event = "preset_crop_valueAttrChanged")
public static CropValue getCropValue(CropView view){
Rect rect = view.getCropRect();
CropValue cropValue = new CropValue( 0, 0, 0, 0);
if (rect != null){
cropValue.setLeft(rect.left);
cropValue.setTop(rect.top);
cropValue.setWidth(rect.width());
cropValue.setHeight(rect.height());
}
return cropValue;
}
@BindingAdapter(value = {"preset_crop_valueAttrChanged"})
public static void setCropValueChanged(CropView view, InverseBindingListener listener){
if (listener != null) {
inverseBindingListener = listener;
}
}
}
......@@ -33,4 +33,43 @@
<item name="android:textColor">@color/white</item>
</style>
<!--自定义裁剪View-->
<declare-styleable name="CropView">
<attr name="img_src" format="reference" />
<attr name="crop_mode">
<enum name="fit_image" value="0" />
<enum name="ratio_2_3" value="1" />
<enum name="ratio_3_2" value="2" />
<enum name="ratio_4_3" value="3" />
<enum name="ratio_3_4" value="4" />
<enum name="square" value="5" />
<enum name="ratio_16_9" value="6" />
<enum name="ratio_9_16" value="7" />
<enum name="free" value="8" />
</attr>
<attr name="background_color" format="reference|color" />
<attr name="overlay_color" format="reference|color" />
<attr name="framecolor" format="reference|color" />
<attr name="handle_color" format="reference|color" />
<attr name="handle_width" format="dimension" />
<attr name="handle_size" format="dimension" />
<attr name="guide_color" format="reference|color" />
<attr name="guide_show_mode">
<enum name="show_always" value="1" />
<enum name="show_on_touch" value="2" />
<enum name="not_show" value="3" />
</attr>
<attr name="handle_show_mode">
<enum name="show_always" value="1" />
<enum name="show_on_touch" value="2" />
<enum name="not_show" value="3" />
</attr>
<attr name="touch_padding" format="dimension" />
<attr name="min_frame_size" format="dimension" />
<attr name="frame_stroke_weight" format="dimension" />
<attr name="guide_stroke_weight" format="dimension" />
<attr name="crop_enabled" format="boolean" />
<attr name="initial_frame_scale" format="float" />
</declare-styleable>
</resources>
\ No newline at end of file
package com.wmdigit.data.mmkv.repository;
import com.elvishew.xlog.XLog;
import com.tencent.mmkv.MMKV;
import com.wmdigit.common.model.CropValue;
import com.wmdigit.data.mmkv.constant.MmkvCons;
/**
......@@ -26,8 +28,33 @@ public class CropLocalRepository {
* 获取是否裁剪
* @return
*/
public boolean getIsCrop(){
public boolean getHasCropped(){
return MMKV.defaultMMKV().getInt(MmkvCons.MMKV_KEY_CROP_WIDTH, 0) > 0;
}
/**
* 保存裁剪坐标
* @param value
*/
public void saveCropValue(CropValue value){
MMKV.defaultMMKV().putInt(MmkvCons.MMKV_KEY_CROP_WIDTH, value.getWidth());
MMKV.defaultMMKV().putInt(MmkvCons.MMKV_KEY_CROP_HEIGHT, value.getHeight());
MMKV.defaultMMKV().putInt(MmkvCons.MMKV_KEY_CROP_X, value.getLeft());
MMKV.defaultMMKV().putInt(MmkvCons.MMKV_KEY_CROP_Y, value.getTop());
}
/**
* 获取裁剪坐标
* @return
*/
public CropValue getCropValue(){
CropValue value = new CropValue(
MMKV.defaultMMKV().getInt(MmkvCons.MMKV_KEY_CROP_X, 0),
MMKV.defaultMMKV().getInt(MmkvCons.MMKV_KEY_CROP_Y, 0),
MMKV.defaultMMKV().getInt(MmkvCons.MMKV_KEY_CROP_WIDTH, 0),
MMKV.defaultMMKV().getInt(MmkvCons.MMKV_KEY_CROP_HEIGHT, 0)
);
return value;
}
}
......@@ -4,5 +4,5 @@ v1.0.1 2024/08/01 1.增加camerax
v1.0.2 2024/08/06 1.增加系统信息页
2.增加设置-注册页
3.对接注册、找回接口
4.todo 增加秤盘裁剪页
4.增加秤盘裁剪页
5.todo 增加AIDL服务
\ No newline at end of file
package com.wmdigit.setting.fragment;
import com.elvishew.xlog.XLog;
import com.wmdigit.camera.CameraxController;
import com.wmdigit.common.base.mvvm.BaseMvvmFragment;
import com.wmdigit.setting.R;
import com.wmdigit.setting.databinding.FragmentCameraCropBinding;
......@@ -12,6 +14,7 @@ import com.wmdigit.setting.viewmodel.CameraCropViewModel;
*/
public class CameraCropFragment extends BaseMvvmFragment<CameraCropViewModel, FragmentCameraCropBinding> {
private boolean isCameraNeedRelease = false;
@Override
protected int getLayoutId() {
......@@ -26,11 +29,48 @@ public class CameraCropFragment extends BaseMvvmFragment<CameraCropViewModel, Fr
@Override
protected void initData() {
mDataBinding.setViewModel(mViewModel);
// 检查Camera是否开启
if(!CameraxController.getInstance(requireContext()).isOpen()){
isCameraNeedRelease = true;
// 将相机生命周期和页面绑定,开启相机
CameraxController.getInstance()
.setLifecycleOwner(this)
.setOnImageAnalyzeListener(mViewModel.getOnImageAnalyzeListener())
.setFrameInterval(2)
.apply();
}
else{
// 注册相机图片监听
CameraxController.getInstance()
.setFrameInterval(2)
.setOnImageAnalyzeListener(mViewModel.getOnImageAnalyzeListener());
}
// 设置预设坐标
mViewModel.setPresetCropValue();
}
@Override
protected void initObserve() {
super.initObserve();
// 观察裁剪坐标变化
mViewModel.croppedValue.observe(this, value -> {
mViewModel.saveCropValue(value);
});
}
@Override
protected void initListener() {
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (isCameraNeedRelease) {
CameraxController.getInstance().release();
}
else{
CameraxController.getInstance().setOnImageAnalyzeListener(null);
}
}
@Override
......
package com.wmdigit.setting.fragment;
import android.graphics.Bitmap;
import com.elvishew.xlog.XLog;
import com.wmdigit.common.base.mvvm.BaseMvvmFragment;
import com.wmdigit.setting.R;
import com.wmdigit.setting.SettingActivity;
import com.wmdigit.setting.databinding.FragmentSystemInfoBinding;
import com.wmdigit.setting.viewmodel.SystemInfoViewModel;
......@@ -33,6 +29,7 @@ public class SystemInfoFragment extends BaseMvvmFragment<SystemInfoViewModel, Fr
protected void initData() {
// 绑定ViewModel和DataBinding
mDataBinding.setViewModel(mViewModel);
}
@Override
......
package com.wmdigit.setting.viewmodel;
import android.app.Application;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import com.wmdigit.camera.listener.OnImageAnalyzeListener;
import com.wmdigit.common.base.mvvm.BaseViewModel;
import com.wmdigit.common.base.mvvm.SingleLiveEvent;
import com.wmdigit.common.model.CropValue;
import com.wmdigit.common.utils.ParcelHelper;
import com.wmdigit.data.mmkv.repository.CropLocalRepository;
/**
* 相机裁剪ViewModel
* @author dizi
*/
public class CameraCropViewModel extends BaseViewModel {
/**
* 相机当前帧
*/
public SingleLiveEvent<Bitmap> currentFrame = new SingleLiveEvent<>();
/**
* 裁剪坐标
*/
public MutableLiveData<CropValue> croppedValue = new MutableLiveData<>();
/**
* 相机图片回调
*/
private final OnImageAnalyzeListener onImageAnalyzeListener = bitmap -> {
Bitmap cloneBitmap = ParcelHelper.copy(bitmap);
currentFrame.postValue(cloneBitmap);
};
public CameraCropViewModel(@NonNull Application application) {
super(application);
}
public OnImageAnalyzeListener getOnImageAnalyzeListener() {
return onImageAnalyzeListener;
}
/**
* 设置预设裁剪坐标
*/
public void setPresetCropValue(){
if (CropLocalRepository.getInstance().getHasCropped()) {
croppedValue.postValue(CropLocalRepository.getInstance().getCropValue());
}
}
/**
* 保存裁剪坐标
* @param value
*/
public void saveCropValue(CropValue value){
CropLocalRepository.getInstance().saveCropValue(value);
}
}
......@@ -9,7 +9,6 @@ import androidx.databinding.ObservableField;
import com.elvishew.xlog.XLog;
import com.wmdigit.common.base.mvvm.BaseViewModel;
import com.wmdigit.common.utils.DeviceUtils;
import com.wmdigit.data.mmkv.constant.MmkvCons;
import com.wmdigit.data.mmkv.repository.CropLocalRepository;
import com.wmdigit.data.mmkv.repository.UserLocalRepository;
......@@ -87,7 +86,7 @@ public class SystemInfoViewModel extends BaseViewModel {
tenant.set(array[3]);
}
// 获取裁剪状态
isCrop.set(CropLocalRepository.getInstance().getIsCrop());
isCrop.set(CropLocalRepository.getInstance().getHasCropped());
}
/**
......
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
......@@ -9,10 +10,89 @@
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/fragment_root"
tools:context=".fragment.CameraCropFragment">
<androidx.constraintlayout.widget.ConstraintLayout
style="@style/setting_module"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/dp_20">
<!--绿色图标-->
<View
android:id="@+id/icon_camera_crop"
style="@style/icon_title_green"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/tv_camera_crop"
app:layout_constraintBottom_toBottomOf="@+id/tv_camera_crop"/>
<!--标题-->
<TextView
android:id="@+id/tv_camera_crop"
style="@style/text_base.title.module_title"
android:text="@string/module_setting_camera_area"
app:layout_constraintStart_toEndOf="@+id/icon_camera_crop"
app:layout_constraintTop_toTopOf="parent" />
<!--占位符1-->
<View
android:id="@+id/v_placeholder_1"
android:layout_width="@dimen/dp_0"
android:layout_height="@dimen/dp_0"
android:layout_marginStart="@dimen/dp_200"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<!--占位符2-->
<TextView
android:id="@+id/v_placeholder_2"
style="@style/text_base.content"
android:text="@string/module_setting_store_code2"
android:layout_marginEnd="@dimen/dp_10"
android:visibility="invisible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/v_placeholder_1"/>
<!--裁剪View-->
<com.wmdigit.common.view.imageview.BidirectionalDataBindingCropView
android:id="@+id/crop_view"
android:layout_width="640px"
android:layout_height="480px"
android:background="@color/black"
app:background_color="#66FFFFFF"
app:crop_enabled="true"
app:crop_mode="free"
app:frame_stroke_weight="@dimen/dp_2"
app:guide_color="#66FFFFFF"
app:guide_show_mode="show_always"
app:guide_stroke_weight="@dimen/dp_2"
app:handle_color="@android:color/white"
app:handle_show_mode="show_always"
app:handle_size="@dimen/dp_24"
app:handle_width="@dimen/dp_3"
app:initial_frame_scale="0.75"
app:min_frame_size="@dimen/dp_100"
app:overlay_color="#AA1C1C1C"
app:touch_padding="@dimen/dp_8"
android:layout_marginTop="@dimen/dp_40"
app:frame="@{viewModel.currentFrame}"
app:preset_crop_value="@={viewModel.croppedValue}"
app:layout_constraintStart_toStartOf="@+id/v_placeholder_2"
app:layout_constraintTop_toBottomOf="@+id/tv_camera_crop"
/>
<!--提示-->
<TextView
style="@style/text_base.content"
android:layout_marginTop="@dimen/dp_20"
android:text="@string/module_setting_tip_crop_camera"
app:layout_constraintStart_toStartOf="@+id/crop_view"
app:layout_constraintEnd_toEndOf="@+id/crop_view"
app:layout_constraintTop_toBottomOf="@+id/crop_view"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</layout>
\ No newline at end of file
......@@ -247,7 +247,7 @@
<EditText
android:id="@+id/edt_camera_crop"
style="@style/edittext_base.w492"
android:textColor="@{viewModel.isCrop? @color/green : @color/red }"
android:textColor="@{viewModel.isCrop? @color/green_008e75 : @color/red }"
android:text="@{viewModel.isCrop? @string/cropped : @string/not_crop}"
android:layout_marginEnd="@dimen/dp_100"
app:layout_constraintTop_toTopOf="@+id/edt_active_status"
......
......@@ -26,6 +26,7 @@
<string name="module_setting_please_input_tenant">输入租户号</string>
<string name="module_setting_forget_register_info">忘记注册信息</string>
<string name="module_setting_forget_register">注册</string>
<string name="module_setting_tip_crop_camera">调整识别区域,使其完全包裹置物台</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment