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>