1.集成科大讯飞
在讯飞开放平台注册并实现身份认证, 进入控制台页面创立一个利用
按要求实现填写点击创立即可, 接着进入刚刚创立的利用, 在这里能够看到所有的性能详情.
在右侧可看到要害的APPID APISecret APIKey
我打算应用三个性能: 语音听写 语音合成 语音评测, 所以接下来进入聚合SDK下载页下载组合SDK
抉择好利用、平台、须要的AI能力后即可下载
下载后将压缩包解压后的目录后果如下:
其中sample
目录下是一个官网提供的实例Demo, 咱们能够将其独自用AS关上(报错没关系,能看清代码即可), 之后的封装都是参照官网Demo进行的. 官网传送门: 官网文档
SDK包阐明:
接着将SDK导入咱们的我的项目, 首先将在官网下载的Android SDK 压缩包中libs目录下所有子文件拷贝至Android工程的libs目录下:
用AS关上咱们的我的项目, 右键msc.jar增加Add As Library
接着再咱们我的项目的main目录下新建Jnilibs目录, 将libs目录下的两个文件夹拷贝过去:
如运行有报错, 可再在Module级的build.gradle中增加:
....buildTypes {...}// 新加: // 指定libs文件夹地位 sourceSets { main{ jniLibs.srcDirs = ['libs'] } }
最初, 初始化讯飞SDK即可, 倡议在Application下初始化:
@Override public void onCreate() { super.onCreate(); mContext = this.getApplication(); // 这里实现SDK初始化, // 请勿在“=”与appid之间增加任何空字符或者本义符 SpeechUtility.createUtility(mContext, SpeechConstant.APPID + "=" + mContext.getString(R.string.APPID)); }
倡议将常量字符对立寄存在res/values/string.xml下
至此, 集成SDK已实现, 接下来进行性能封装:
2.性能封装
在咱们的包目录下创立一个utils包, 官网Demo中有两个好用的工具类, 将他们增加到utils包下:
在咱们的包目录下新建iflytek目录,用于寄存讯飞相干的类,首先封装语音听写:
新建接口: RecognizeListener, 新建类: RecognizeSpeechManager
/** * 听写回调 */public interface RecognizeListener { void onNewResult(String result); void onTotalResult(String result,boolean isLast); void onError(SpeechError speechError);}
/** * 音频读写转换 */public class RecognizeSpeechManager implements RecognizerListener, InitListener { private static final String TAG = "RecognizeSpeechManager"; // 后果回调对象 private RecognizeListener recognizeListener; // 语音听写对象 private SpeechRecognizer iat; private StringBuffer charBufffer = new StringBuffer(); // 上下文的弱援用,以便在不应用时回收,防止内存泄露 (当一个对象仅仅被弱援用指向,而没有其余强援用指向时,在下一次gc运行时将会被回收) private WeakReference<Context> bindContext; // 单例 private static RecognizeSpeechManager instance; private RecognizeSpeechManager() { } /** * 单例办法 */ public static RecognizeSpeechManager instance() { if (instance == null) { instance = new RecognizeSpeechManager(); } return instance; } /** * 设置后果回调对象 */ public void setRecognizeListener(RecognizeListener recognizeListener) { this.recognizeListener = recognizeListener; } /** * 初始化 */ public void init(Context context) { if (bindContext == null) { bindContext = new WeakReference<Context>(context); } if (iat == null) { iat = SpeechRecognizer.createRecognizer(bindContext.get(), this); } } @Override public void onInit(int code) { if (code != ErrorCode.SUCCESS) { Log.d(TAG, "init error code " + code); } } /** * 开始监听 * ErrorCode.SUCCESS 监听胜利状态码 */ public int startRecognize() { setParam(); return iat.startListening(this); } /** * 勾销听写 */ public void cancelRecognize() { iat.cancel(); } /** * 进行听写 */ public void stopRecognize() { iat.stopListening(); } public void release() { iat.cancel(); iat.destroy(); // iat = null; bindContext.clear(); // bindContext = null; charBufffer.delete(0, charBufffer.length()); } @Override public void onVolumeChanged(int i, byte[] bytes) { } @Override public void onBeginOfSpeech() { Log.d(TAG, "onBeginOfSpeech"); } @Override public void onEndOfSpeech() { Log.d(TAG, "onEndOfSpeech isListening " + iat.isListening()); } @Override public void onResult(RecognizerResult results, boolean b) { if (recognizeListener != null) { recognizeListener.onNewResult(printResult(results)); recognizeListener.onTotalResult(charBufffer.toString(), iat.isListening()); } } @Override public void onError(SpeechError speechError) { if (recognizeListener != null) { recognizeListener.onError(speechError); } } @Override public void onEvent(int i, int i1, int i2, Bundle bundle) { Log.d(TAG, "onEvent type " + i); } private String printResult(RecognizerResult results) { String text = JsonParser.parseIatResult(results.getResultString()); Log.d(TAG, "printResult " + text + " isListening " + iat.isListening()); String sn = null; // 读取json后果中的sn字段 try { JSONObject resultJson = new JSONObject(results.getResultString()); sn = resultJson.optString("sn"); } catch (JSONException e) { e.printStackTrace(); } if (!TextUtils.isEmpty(text)) { charBufffer.append(text); } return text; } /** * 参数设置 * * @return */ private void setParam() { // 清空参数 iat.setParameter(SpeechConstant.PARAMS, null); // 设置听写引擎 iat.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD); // 设置返回后果格局 iat.setParameter(SpeechConstant.RESULT_TYPE, "json"); iat.setParameter(SpeechConstant.LANGUAGE, "zh_cn"); iat.setParameter(SpeechConstant.ACCENT, "mandarin"); //此处用于设置dialog中不显示错误码信息 //iat.setParameter("view_tips_plain","false"); // 设置语音前端点:静音超时工夫,即用户多长时间不谈话则当做超时解决 iat.setParameter(SpeechConstant.VAD_BOS, "10000"); // 设置语音后端点:后端点静音检测时间,即用户进行谈话多长时间内即认为不再输出, 主动进行录音 iat.setParameter(SpeechConstant.VAD_EOS, "10000"); // 设置标点符号,设置为"0"返回后果无标点,设置为"1"返回后果有标点 iat.setParameter(SpeechConstant.ASR_PTT, "1"); // 设置音频保留门路,保留音频格式反对pcm、wav,设置门路为sd卡请留神WRITE_EXTERNAL_STORAGE权限 /* iat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav"); iat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/iat.wav");*/ }}
应用(其中采纳了ViewModel和butterKnife):
public class HomeFragment extends Fragment implements RecognizeListener { //UI视图的展现和事件蕴含在Fragment或Activity中 private Unbinder unbinder; private HomeViewModel homeViewModel; @BindView(R.id.tvContent) TextView tvContent; public Context mContext; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mContext = this.getContext(); //构建ViewModel实例 homeViewModel = ViewModelProviders.of(this).get(HomeViewModel.class); //创立视图对象 View root = inflater.inflate(R.layout.fragment_home, container, false); // fragment绑定butterKnife unbinder = ButterKnife.bind(this,root); // 让UI察看ViewModel中数据的变动,并实时更新UI homeViewModel.getRecognizeText().observe(getViewLifecycleOwner(), new Observer<String>() { @Override public void onChanged(@Nullable String s) { tvContent.setText(s); } }); //初始化讯飞音频读写治理类 RecognizeSpeechManager.instance().init(mContext); RecognizeSpeechManager.instance().setRecognizeListener(this); //返回视图对象 return root; } @OnClick({R.id.btStart, R.id.btCancel, R.id.btStop}) public void onClick(View v) { switch (v.getId()){ case R.id.btStart: RecognizeSpeechManager.instance().startRecognize(); break; case R.id.btCancel: RecognizeSpeechManager.instance().cancelRecognize(); break; case R.id.btStop: RecognizeSpeechManager.instance().stopRecognize(); break; } } @Override public void onDestroy() { super.onDestroy(); if(unbinder != null) { unbinder.unbind();//视图销毁时必须解绑 } RecognizeSpeechManager.instance().release(); } @Override public void onNewResult(String result) { homeViewModel.setRecognizeText(homeViewModel.getRecognizeText().getValue() + "最新翻译:" + result + "\n"); } @Override public void onTotalResult(String result, boolean isLast) { homeViewModel.setRecognizeText(homeViewModel.getRecognizeText().getValue() + "所有翻译:" + result + "\n"); } @Override public void onError(SpeechError speechError) { Toast.makeText(mContext, "出错了 " + speechError, Toast.LENGTH_SHORT).show(); }}
public class HomeViewModel extends ViewModel { // 数据获取和解决蕴含在ViewModel中 // 辨认后果数据 private MutableLiveData<String> recognizeText; public HomeViewModel() { mText = new MutableLiveData<>(); recognizeText = new MutableLiveData<>(); recognizeText.setValue(""); } // get办法 public LiveData<String> getRecognizeText () { return recognizeText; } // set办法 public void setRecognizeText (String recognizeText) { this.recognizeText.setValue(recognizeText); }}
封装语音合成:
新建接口: SynthesizeListener 新建类: SynthesizeSpeechManager
/** * 合成回调 */public interface SynthesizeListener { void onError(SpeechError speechError);}
/** * 语音合成 */public class SynthesizeSpeechManager implements SynthesizerListener, InitListener { private static final String TAG = "SynthesizeSpeechManager"; // 默认发音人 private String voicer = "xiaoyan"; // 后果回调对象 private SynthesizeListener synthesizeListener; // 语音合成对象 private SpeechSynthesizer tts; // 上下文的弱援用,以便在不应用时回收,防止内存泄露 private WeakReference<Context> bindContext; // 单例 private static SynthesizeSpeechManager instance; private SynthesizeSpeechManager() { } /** * 单例办法 */ public static SynthesizeSpeechManager instance() { if (instance == null) { instance = new SynthesizeSpeechManager(); } return instance; } /** * 设置后果回调对象 */ public void setSynthesizeListener(SynthesizeListener synthesizeListener) { this.synthesizeListener = synthesizeListener; } /** * 初始化 */ public void init(Context context) { if (bindContext == null) { bindContext = new WeakReference<Context>(context); } if (tts == null) { tts = SpeechSynthesizer.createSynthesizer(bindContext.get(), this); } } @Override public void onInit(int code) { if (code != ErrorCode.SUCCESS) { Log.d(TAG, "init error code " + code); } } // 接着须要实现自定义接口的三个办法 /** * 开始合成 */ public int startSpeak(String texts) { setParam(); return tts.startSpeaking(texts, this); } /** * 勾销合成 */ public void stopSpeak() { tts.stopSpeaking(); } /** * 暂停播放 */ public void pauseSpeak() { tts.pauseSpeaking(); } /** * 持续播放 */ public void resumeSpeak() { tts.resumeSpeaking(); } /** * 垃圾回收 */ public void release() { tts.stopSpeaking(); tts.destroy(); // tts = null; bindContext.clear(); // bindContext = null; } @Override public void onSpeakBegin() { Log.d(TAG, "开始播放"); } @Override public void onBufferProgress(int percent, int beginPos, int endPos, String info) { Log.d(TAG, "合成进度: percent =" + percent); } @Override public void onSpeakPaused() { Log.d(TAG, "暂停播放"); } @Override public void onSpeakResumed() { Log.d(TAG, "持续播放"); } @Override public void onSpeakProgress(int percent, int beginPos, int endPos) { Log.e(TAG, "播放进度: percent =" + percent); } @Override public void onCompleted(SpeechError speechError) { Log.d(TAG, "播放实现"); if (speechError != null) { Log.d(TAG, speechError.getPlainDescription(true)); synthesizeListener.onError(speechError); } } @Override public void onEvent(int eventType, int arg1, int arg2, Bundle bundle) { // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查问会话日志,定位出错起因 if(bundle != null) { Log.d(TAG, "session id =" + bundle.getString(SpeechEvent.KEY_EVENT_SESSION_ID)); Log.e(TAG, "EVENT_TTS_BUFFER = " + Objects.requireNonNull(bundle.getByteArray(SpeechEvent.KEY_EVENT_TTS_BUFFER)).length); } } /** * 参数设置 */ private void setParam() { // 清空参数 tts.setParameter(SpeechConstant.PARAMS, null); // 设置合成引擎 tts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD); // 反对实时音频返回,仅在 synthesizeToUri 条件下反对 tts.setParameter(SpeechConstant.TTS_DATA_NOTIFY, "1"); // mTts.setParameter(SpeechConstant.TTS_BUFFER_TIME,"1"); // 设置在线合成发音人 tts.setParameter(SpeechConstant.VOICE_NAME, voicer); //设置合成语速 tts.setParameter(SpeechConstant.SPEED, "50"); //设置合成音调 tts.setParameter(SpeechConstant.PITCH, "50"); //设置合成音量 tts.setParameter(SpeechConstant.VOLUME, "50"); //设置播放器音频流类型 tts.setParameter(SpeechConstant.STREAM_TYPE, "3"); // 设置播放合成音频打断音乐播放,默认为true tts.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "false"); // 设置音频保留门路,保留音频格式反对pcm、wav,设置门路为sd卡请留神WRITE_EXTERNAL_STORAGE权限 /* mTts.setParameter(SpeechConstant.AUDIO_FORMAT, "pcm"); mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, getExternalFilesDir("msc").getAbsolutePath() + "/tts.pcm"); */ }}
应用:
public class DashboardFragment extends Fragment implements SynthesizeListener { private Unbinder unbinder; private DashboardViewModel dashboardViewModel; @BindView(R.id.etEva) EditText editText; public Context mContext; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mContext = this.getContext(); dashboardViewModel = ViewModelProviders.of(this).get(DashboardViewModel.class); View root = inflater.inflate(R.layout.fragment_dashboard, container, false); unbinder = ButterKnife.bind(this,root); dashboardViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() { @Override public void onChanged(@Nullable String s) { editText.setText(s); } }); //初始化讯飞音频合成治理类 SynthesizeSpeechManager.instance().init(mContext); SynthesizeSpeechManager.instance().setSynthesizeListener(this); return root; } @OnClick({R.id.btParse, R.id.btPaused, R.id.btResumed}) public void onClick(View v) { switch (v.getId()){ case R.id.btParse: SynthesizeSpeechManager.instance().startSpeak(dashboardViewModel.getText().getValue()); break; case R.id.btPaused: SynthesizeSpeechManager.instance().pauseSpeak(); break; case R.id.btResumed: SynthesizeSpeechManager.instance().resumeSpeak(); break; } } @Override public void onDestroy() { super.onDestroy(); if(unbinder != null) { unbinder.unbind();//视图销毁时必须解绑 } SynthesizeSpeechManager.instance().release(); } @Override public void onError(SpeechError speechError) { Toast.makeText(mContext, "出错了 " + speechError, Toast.LENGTH_SHORT).show(); }}
public class DashboardViewModel extends ViewModel { private MutableLiveData<String> mText; public DashboardViewModel() { mText = new MutableLiveData<>(); mText.setValue("This is dashboard fragment"); } public LiveData<String> getText() { return mText; }}
封装语音评测:
语音评测绝对简单, 须要将demo中ise.result包下的内容全副移到咱们的我的项目中(间接复制文件必定是须要改包名的, 怕出错就手动一个一个新建吧)
在咱们的我的项目iflytek包下新建ise包, 增加上图内容:
在iflytek包下, 新建接口: EvaluateListener 新建类: EvaluateSpeechManager
public interface EvaluateListener { void onNewResult(String result); void onTotalResult(String result,boolean isLast); void onError(SpeechError speechError);}
/** * 语音评测 */public class EvaluateSpeechManager implements EvaluatorListener { private static final String TAG = "EvaluatSpeechManager"; private final static String PREFER_NAME = "ise_settings"; private final static int REQUEST_CODE_SETTINGS = 1; // 上下文的弱援用,以便在不应用时回收,防止内存泄露 private WeakReference<Context> bindContext; // 后果回调对象 private EvaluateListener evaluateListener; // 语音评测对象 private SpeechEvaluator ise; // 解析后果 private String lastResult; // 单例 private static EvaluateSpeechManager instance; private EvaluateSpeechManager() { } /** * 单例办法 */ public static EvaluateSpeechManager instance() { if (instance == null) { instance = new EvaluateSpeechManager(); } return instance; } /** * 设置后果回调对象 */ public void setEvaluateListener(EvaluateListener evaluateListener) { this.evaluateListener = evaluateListener; } /** * 初始化 */ public void init(Context context) { if (bindContext == null) { bindContext = new WeakReference<Context>(context); } if (ise == null) { ise = SpeechEvaluator.createEvaluator(bindContext.get(), null); } } /** * 开始评测 * @String category 评测类型 * - read_syllable : 字 * - read_word : 词 * - read_sentence : 句 * - read_chapter : 诗 * @String evaText 评测内容 */ public int startEvaluate(String category, String evaText) { lastResult = null; assert ise!=null; setParams(category); return ise.startEvaluating(evaText, null, this); } /** * 进行评测 */ public void stopEvaluate() { ise.stopEvaluating(); } /** * 勾销评测 */ public void cancelEvaluate() { ise.cancel(); lastResult = null; } /** * 后果解析 */ public Result parseResult() { if(lastResult == null) { return new FinalResult(); } XmlResultParser resultParser = new XmlResultParser(); return resultParser.parse(lastResult); } public void release() { ise.cancel(); ise.destroy(); // ise = null; bindContext.clear(); // bindContext = null; } @Override public void onVolumeChanged(int volume, byte[] data) { Log.d(TAG, "以后正在谈话,音量大小 = " + volume + " 返回音频数据 = " + data.length); } @Override public void onBeginOfSpeech() { Log.d(TAG, "evaluator begin"); } @Override public void onEndOfSpeech() { Log.d(TAG, "onEndOfSpeech isListening " + ise.isEvaluating()); } @Override public void onResult(EvaluatorResult evaluatorResult, boolean isLast) { Log.d(TAG, "evaluator result :" + isLast); StringBuilder builder = new StringBuilder(); builder.append(evaluatorResult.getResultString()); // evaluatorResult为原始的xml剖析后果,须要调用解析函数来失去最终后果 lastResult = builder.toString(); if(evaluateListener != null) { evaluateListener.onNewResult(builder.toString()); evaluateListener.onTotalResult(builder.toString(), isLast); } } @Override public void onError(SpeechError speechError) { if(evaluateListener != null) { evaluateListener.onError(speechError); } } @Override public void onEvent(int eventType, int arg1, int arg2, Bundle obj) { Log.d(TAG, "onEvent type " + eventType); } private void setParams(String category) { // 设置评测语种 String language = "zh_cn"; // 设置后果等级(中文仅反对complete) String result_level = "complete"; // 设置语音前端点:静音超时工夫,即用户多长时间不谈话则当做超时解决 String vad_bos = "5000"; // 设置语音后端点:后端点静音检测时间,即用户进行谈话多长时间内即认为不再输出, 主动进行录音 String vad_eos = "1800"; // 语音输入超时工夫,即用户最多能够间断说多长时间; String speech_timeout = "-1"; // 设置流式版本所需参数 : ent sub plev ise.setParameter("ent", "cn_vip"); ise.setParameter(SpeechConstant.SUBJECT, "ise"); ise.setParameter("plev", "0"); // 设置评分百分制 应用 ise_unite rst extra_ability 参数 ise.setParameter("ise_unite", "1"); ise.setParameter("rst", "entirety"); ise.setParameter("extra_ability", "syll_phone_err_msg;pitch;multi_dimension"); ise.setParameter(SpeechConstant.LANGUAGE, language); // 设置须要评测的类型 ise.setParameter(SpeechConstant.ISE_CATEGORY, category); ise.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8"); ise.setParameter(SpeechConstant.VAD_BOS, vad_bos); ise.setParameter(SpeechConstant.VAD_EOS, vad_eos); ise.setParameter(SpeechConstant.KEY_SPEECH_TIMEOUT, speech_timeout); ise.setParameter(SpeechConstant.RESULT_LEVEL, result_level); ise.setParameter(SpeechConstant.AUDIO_FORMAT_AUE, "opus"); // 设置音频保留门路,保留音频格式反对pcm、wav, /* ise.setParameter(SpeechConstant.AUDIO_FORMAT, "wav"); ise.setParameter(SpeechConstant.ISE_AUDIO_PATH, getExternalFilesDir("msc").getAbsolutePath() + "/ise.wav"); */ //通过writeaudio形式间接写入音频时才须要此设置 //mIse.setParameter(SpeechConstant.AUDIO_SOURCE,"-1"); }}
应用:
public class NotificationsFragment extends Fragment implements EvaluateListener { private Unbinder unbinder; private NotificationsViewModel notificationsViewModel; @BindView(R.id.etEva) EditText etEva; public Context mContext; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mContext = this.getContext(); notificationsViewModel = ViewModelProviders.of(this).get(NotificationsViewModel.class); View root = inflater.inflate(R.layout.fragment_notifications, container, false); // fragment绑定butterKnife unbinder = ButterKnife.bind(this,root); final TextView textView = root.findViewById(R.id.text_notifications); notificationsViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() { @Override public void onChanged(@Nullable String s) { textView.setText(s); } }); notificationsViewModel.getEvaluateText().observe(getViewLifecycleOwner(), new Observer<String>() { @Override public void onChanged(String s) { etEva.setText(s); } }); //初始化讯飞音频评测治理类 EvaluateSpeechManager.instance().init(mContext); EvaluateSpeechManager.instance().setEvaluateListener(this); return root; } @OnClick({R.id.btStart, R.id.btCancel, R.id.btStop, R.id.btParse}) public void onClick(View v) { switch (v.getId()){ case R.id.btStart: EvaluateSpeechManager.instance().startEvaluate("read_word",notificationsViewModel.getEvaluateText().getValue()); break; case R.id.btCancel: EvaluateSpeechManager.instance().cancelEvaluate(); break; case R.id.btStop: EvaluateSpeechManager.instance().stopEvaluate(); break; case R.id.btParse: notificationsViewModel.setText(EvaluateSpeechManager.instance().parseResult().toString()); } } @Override public void onDestroy() { super.onDestroy(); if(unbinder != null) { unbinder.unbind();//视图销毁时必须解绑 } EvaluateSpeechManager.instance().release(); } @Override public void onNewResult(String result) { notificationsViewModel.setText(result); } @Override public void onTotalResult(String result, boolean isLast) { // Toast.makeText(mContext, result, Toast.LENGTH_SHORT).show(); } @Override public void onError(SpeechError speechError) { Toast.makeText(mContext, "出错了 " + speechError, Toast.LENGTH_SHORT).show(); }}
public class NotificationsViewModel extends ViewModel { private MutableLiveData<String> mText; private MutableLiveData<String> evaluateText; public NotificationsViewModel() { mText = new MutableLiveData<>(); evaluateText = new MutableLiveData<>(); mText.setValue("This is notifications fragment"); evaluateText.setValue("西瓜"); } public LiveData<String> getText() { return mText; } public LiveData<String> getEvaluateText() { return evaluateText; } public void setText(String mText) { this.mText.setValue(mText); } public void setEvaluateText(String evaluateText) { this.evaluateText.setValue(evaluateText); }}
布局文件res/layout (有应用腾讯的QMUI, 如果你没有接入QMUI, 只须要将QMUIRoundButton替换为一般button即可):
fragment_home.xml
<?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="match_parent" tools:context=".ui.home.HomeFragment"> <TextView android:id="@+id/tvContent" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="84dp" android:padding="20dp" app:layout_constraintBottom_toTopOf="@+id/text_home" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/btStart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="32dp" android:text="开始辨认" app:layout_constraintStart_toStartOf="@+id/tvContent" app:layout_constraintTop_toBottomOf="@+id/tvContent" /> <Button android:id="@+id/btCancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="32dp" android:text="勾销" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/tvContent" /> <Button android:id="@+id/btStop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="进行" app:layout_constraintEnd_toStartOf="@+id/btCancel" app:layout_constraintStart_toEndOf="@+id/btStart" app:layout_constraintTop_toBottomOf="@+id/tvContent" /> <TextView android:id="@+id/text_home" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:textAlignment="center" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:paddingLeft="16dp" android:paddingTop="10dp" android:paddingRight="16dp" android:paddingBottom="10dp" android:text="圆角为短边的一半" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@+id/text_home" app:layout_constraintStart_toStartOf="@+id/text_home" app:layout_constraintTop_toBottomOf="@+id/text_home" app:layout_constraintVertical_bias="0.26" app:qmui_isRadiusAdjustBounds="true" /></androidx.constraintlayout.widget.ConstraintLayout>
fragment_dashboard.xml
<?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="match_parent" tools:context=".ui.dashboard.DashboardFragment"> <EditText android:id="@+id/etEva" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:textAlignment="center" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton android:id="@+id/btParse" android:layout_width="93dp" android:layout_height="40dp" android:layout_centerInParent="true" android:paddingLeft="16dp" android:paddingTop="10dp" android:paddingRight="16dp" android:paddingBottom="10dp" android:text="开始播放" app:layout_constraintStart_toStartOf="@+id/etEva" app:layout_constraintTop_toBottomOf="@+id/etEva" app:qmui_isRadiusAdjustBounds="true" /> <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton android:id="@+id/btResumed" android:layout_width="93dp" android:layout_height="40dp" android:layout_centerInParent="true" android:paddingLeft="16dp" android:paddingTop="10dp" android:paddingRight="16dp" android:paddingBottom="10dp" android:text="持续播放" app:layout_constraintEnd_toEndOf="@+id/etEva" app:layout_constraintTop_toBottomOf="@+id/etEva" app:qmui_isRadiusAdjustBounds="true" /> <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton android:id="@+id/btPaused" android:layout_width="93dp" android:layout_height="40dp" android:layout_centerInParent="true" android:paddingLeft="16dp" android:paddingTop="10dp" android:paddingRight="16dp" android:paddingBottom="10dp" android:text="暂停播放" app:layout_constraintEnd_toStartOf="@+id/btResumed" app:layout_constraintHorizontal_bias="0.52" app:layout_constraintStart_toEndOf="@+id/btParse" app:layout_constraintTop_toBottomOf="@+id/etEva" app:qmui_isRadiusAdjustBounds="true" /></androidx.constraintlayout.widget.ConstraintLayout>
fragment_notifications.xml
<?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="match_parent" tools:context=".ui.notifications.NotificationsFragment"> <EditText android:id="@+id/etEva" android:layout_width="match_parent" android:layout_height="100dp" android:layout_marginStart="8dp" android:layout_marginTop="96dp" android:layout_marginEnd="8dp" android:textAlignment="center" android:textSize="20sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton android:id="@+id/btStart" android:layout_width="93dp" android:layout_height="40dp" android:layout_centerInParent="true" android:layout_marginTop="20dp" android:text="开始评测" app:layout_constraintEnd_toEndOf="@+id/btParse" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/btParse" app:layout_constraintTop_toBottomOf="@+id/btParse" app:qmui_isRadiusAdjustBounds="true" /> <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton android:id="@+id/btStop" android:layout_width="93dp" android:layout_height="40dp" android:layout_centerInParent="true" android:layout_marginTop="20dp" android:text="进行评测" app:layout_constraintEnd_toEndOf="@+id/btCancel" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/btCancel" app:layout_constraintTop_toBottomOf="@+id/btCancel" app:qmui_isRadiusAdjustBounds="true" /> <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton android:id="@+id/btCancel" android:layout_width="93dp" android:layout_height="40dp" android:layout_centerInParent="true" android:layout_marginEnd="52dp" android:text="勾销评测" app:layout_constraintEnd_toEndOf="@+id/etEva" app:layout_constraintTop_toBottomOf="@+id/etEva" app:qmui_isRadiusAdjustBounds="true" /> <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton android:id="@+id/btParse" android:layout_width="93dp" android:layout_height="40dp" android:layout_centerInParent="true" android:layout_marginStart="56dp" android:text="后果解析" app:layout_constraintStart_toStartOf="@+id/etEva" app:layout_constraintTop_toBottomOf="@+id/etEva" app:qmui_isRadiusAdjustBounds="true" /> <TextView android:id="@+id/text_notifications" android:layout_width="364dp" android:layout_height="285dp" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:textAlignment="center" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/btStart" app:layout_constraintVertical_bias="0.060000002" /></androidx.constraintlayout.widget.ConstraintLayout>