工具类
该工具类主要是对AI左右手进行辅助判断,因为模型的训练量不足,导致预测并不很准确。所以在这里使用一个队列工具类来获取最近三次的预测结果,之后再选择结果数最多的项来作为我们的预测结果。
public class QueueUtil { private static final ArrayBlockingQueue<Integer> handList = new ArrayBlockingQueue<>(3); private static int handLeft = 0; private static int handRight = 0; public static int getRecentHand(int labelHand) { int poll; if (!handList.offer(labelHand)) { poll = handList.remove(); handList.offer(labelHand); if (poll == labelHand) { return compareRecentHand(handLeft, handRight); } else { switch (poll) { case OperatingHandClassifier.labelRight: handRight--; break; case OperatingHandClassifier.labelLeft: handLeft--; break; default: break; } } } switch (labelHand) { case OperatingHandClassifier.labelRight: handRight++; break; case OperatingHandClassifier.labelLeft: handLeft++; break; default: break; } return compareRecentHand(handLeft, handRight); } private static int compareRecentHand(int handLeft, int handRight) { if (handLeft > handRight) { return OperatingHandClassifier.labelLeft; } else { return OperatingHandClassifier.labelRight; } } } 复制代码
封装到BaseActivity
为方便我们的使用,这里将调用模型进行预测的相关代码封装到 BaseActivity
中,等我们需要使用的时候,在继承其的相应 Activity 中加上该注解即可调用该功能。
关于该功能的完整代码,可以查看我们的大项目 dyjcow/qxy_potato at feature_AIDialog_DYJ (github.com)
public abstract class BaseActivity<P extends BasePresenter<? extends BaseView>, VB extends ViewBinding> extends AppCompatActivity implements BaseView, MotionEventTracker.ITrackDataReadyListener { /** * presenter层的引用 */ protected P presenter; private VB binding; private OperatingHandClassifier classifier; private MotionEventTracker tracker; public int hand = 1; /** * {@inheritDoc} * <p> * Perform initialization of all fragments. * * @param savedInstanceState */ @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (this.getClass().isAnnotationPresent(BindEventBus.class)) { EventBus.getDefault().register(this); }else if (this.getClass().isAnnotationPresent(InitAIHand.class)){ classifier = new OperatingHandClassifier(this); classifier.checkAndInit(); tracker = new MotionEventTracker(this); tracker.checkAndInit(this); } DisplayUtil.setCustomDensity(this); UltimateBarX.statusBarOnly(this) .light(true) .transparent() .apply(); //强制使用竖屏 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); binding = ViewBindingUtil.inflateWithGeneric(this, getLayoutInflater()); setContentView(binding.getRoot()); presenter = createPresenter(); initView(); initData(); } /** * 初始化presenter,也是与Activity的绑定 * * @return 返回new的Presenter层的值 */ protected abstract P createPresenter(); /** * 载入view的一些操作 */ protected abstract void initView(); /** * 载入数据操作 */ protected abstract void initData(); /** * 解除presenter与Activity的绑定 */ @Override protected void onDestroy() { super.onDestroy(); if (this.getClass().isAnnotationPresent(BindEventBus.class)) { EventBus.getDefault().unregister(this); }else if (this.getClass().isAnnotationPresent(InitAIHand.class)){ classifier.close(); } if (presenter != null) { presenter.detachView(); } } @Override public void showLoading() { MyUtil.showLoading(this); } @Override public void SuccessHideLoading() { MyUtil.dismissSuccessLoading(); } @Override public void FailedHideLoading() { MyUtil.dismissFailedLoading(); } /** * 错误 * * @param bean 错误信息 */ @Override public void onErrorCode(BaseBean bean) { ToastUtil.showToast(bean.msg); } public VB getBinding() { return binding; } /** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to call this implementation for touch screen events * that should be handled normally. * * @param ev The touch screen event. * @return boolean Return true if this event was consumed. */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (tracker != null && ev != null){ tracker.recordMotionEvent(ev); } return super.dispatchTouchEvent(ev); } @Override public void onTrackDataReady(@NonNull JSONArray dataList) { if (classifier != null){ classifier.classifyAsync(dataList).addOnSuccessListener(result -> { hand = QueueUtil.getRecentHand(result.getLabelInt()); LogUtil.d(MotionEventTracker.TAG,result.getLabel()); }).addOnFailureListener(e -> LogUtil.e(MotionEventTracker.TAG,e.toString())); } } } 复制代码
编写弹窗代码
该弹窗实际上使用了开源库实现滚动效果
public class MyUtil{ ... public static void showOneOptionPicker(List<?> list, int handLabel) { OptionsPickerBuilder builder = new OptionsPickerBuilder(ActivityUtil.getCurrentActivity(), (options1, options2, options3, v) -> { //返回的分别是三个级别的选中位置 BaseEvent<?> event = new BaseEvent<>(EventCode.SELECT_VERSION, list.get(options1)); EventBusUtil.sendEvent(event); }); pvOptions = builder .setDividerColor(Color.BLACK) .setTextColorCenter(Color.BLACK) //设置选中项文字颜色 .setContentTextSize(19) .setDividerColor(Color.GRAY) .setDividerType(WheelView.DividerType.WRAP) .isAlphaGradient(true) .setLayoutRes(R.layout.layout_pickview_dialog, v -> { //根据传入的左右手的值来选择对应的位置控件 TextView textView; if (handLabel == OperatingHandClassifier.labelRight){ textView = v.findViewById(R.id.btnSubmitRight); }else { textView = v.findViewById(R.id.btnSubmitLeft); } //设置好控件后,让其显示 textView.setVisibility(View.VISIBLE); textView.setOnClickListener(v1 -> { pvOptions.returnData(); pvOptions.dismiss(); }); }) .build(); pvOptions.setPicker(list);//一级选择器 pvOptions.show(); } ... } 复制代码
下面我们看一下对应的布局代码
这里是按照原控件的布局代码来做的一个该着,下边的 WheelView
仍旧使用的是三个,未做优化改造。
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/shape_sheet_dialog_bg_white"> <TextView android:id="@+id/btnSubmitLeft" android:text="@string/pick_submit" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="invisible" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/optionspicker" android:layout_marginStart="20dp" app:layout_constraintStart_toStartOf="parent" android:layout_marginBottom="10dp" android:layout_marginTop="15dp" /> <TextView android:id="@+id/btnSubmitRight" android:text="@string/pick_submit" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="invisible" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/optionspicker" android:layout_marginEnd="20dp" app:layout_constraintEnd_toEndOf="parent" android:layout_marginBottom="10dp" android:layout_marginTop="15dp" /> <LinearLayout android:id="@+id/optionspicker" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:gravity="center" android:minHeight="180dp" android:orientation="horizontal" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/btnSubmitRight"> <com.contrarywind.view.WheelView android:id="@+id/options1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> <com.contrarywind.view.WheelView android:id="@+id/options2" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> <com.contrarywind.view.WheelView android:id="@+id/options3" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout> 复制代码
效果
当我们左手点击的时候,确认按钮在左边,右手点击的时候,确认按钮在右边