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>