(3)主界面的实现
首先分析下样例的主界面。1标题栏可以使用Toolbar来实现;2可以使用一张图片;这里使用ImageView实现;3是一个横向的LinearLayout,分布着四对Button和TextView;4、5同在一个横向的LinearLayout中,每个控件都可以使用LinearLayout来排列,也是由Button1和TextView组成的;6是一个搜索框,可以使用EditText来实现;7是一个Button,用于识别语音;8则是底部导航栏Bottom navigation。
首先实现底部导航栏Bottom navigation。需要先添加依赖:
implementation 'com.google.android.material:material:1.0.0'
然后编写activity_main.xml。同样的,这里使用线性布局,使控件垂直排列。这里还是有个标题栏Toolbar。然后使用ViewPager容器占满其余屏幕,用于显示碎片。底下则是BottomNavigationView的底部导航栏。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
android:layout_gravity="bottom"
app:menu="@menu/bottom_nav_menu" />
</LinearLayout>
接着我们需要编写底部导航栏所需要的菜单bottom_nav_menu.xml,给底部导航栏添加合适的图标和相应的标题。
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/home"
android:title="首页" />
<item
android:id="@+id/navigation_guide"
android:icon="@drawable/menu"
android:title="指南" />
<item
android:id="@+id/navigation_setting"
android:icon="@drawable/setting"
android:title="设置" />
</menu>
前面提到了BottomNavigationView用ViewPager容器存放Fragment,所以我们需要一个Adapter适配器。编写PagerAdapter.java,可以初始化适配器,这边getItem()方法你需要返回你当前List中和当前position对应的元素(也就是Fragment页面),getCount()就比较简单了,直接返回List的大小就行了。
public class PagerAdapter extends FragmentPagerAdapter {
Context context;
private List<Fragment> fragmentList;
public PagerAdapter(@NonNull FragmentManager fragmentManager, Context context, List<Fragment> list) {
super(fragmentManager);
fragmentList = list;
this.context = context;
}
public PagerAdapter(@NonNull FragmentManager fragmentManager, GuideFragment guideFragment, List<Fragment> list) {
super(fragmentManager);
fragmentList = list;
}
@NonNull
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return fragmentList.size();
}
}
最后编写MainActivity.java。在主类中,首先定义ViewPager和BottomNavigationView对象,以及Fragment列表,用于初始化。然后在onCreate方法中初始化主界面,然后调用initView方法。
public class MainActivity extends BaseActivity {
private ViewPager viewPager;
private BottomNavigationView navigation;
private List<Fragment> fragmentList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
在initView方法中首先获得布局文件中的ViewPager和BottomNavigationView实例。然后在列表中加入三个对应菜单的fragment,然后实例化adapter,最后ViewPager调用 .setAdapter() 方法传入PagerAdapter即可实现一个可以左右侧滑切换界面的效果。
private void initView() {
viewPager = (ViewPager) findViewById(R.id.viewPager);
navigation = (BottomNavigationView) findViewById(R.id.nav_view);
//添加Fragment
fragmentList.add(new HomeFragment());
fragmentList.add(new GuideFragment());
fragmentList.add(new SettingFragment());
PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager(), this, fragmentList);
//ViewPager设置adpater
viewPager.setAdapter(adapter);
接着实现底部按钮带动界面的功能。我们需要给BottomNavigationView加上一个ItemSelectedListener(子项选择监听器),然后根据子项的改变,然后ViewPager调用.setCurrentItem()方法对当前显示的页面进行更改;点击按钮以后,页面跟着动的效果也就实现了。
//导航栏点击事件和ViewPager滑动事件,让两个控件相互关联
navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
//这里设置为:当点击到某子项,ViewPager就滑动到对应位置
switch (item.getItemId()) {
case R.id.navigation_home:
viewPager.setCurrentItem(0);
return true;
case R.id.navigation_guide:
viewPager.setCurrentItem(1);
return true;
case R.id.navigation_setting:
viewPager.setCurrentItem(2);
return true;
default:
break;
}
return false;
}
});
最后实现ViewPager侧滑改变底部BottomNavigationView的当前选项的功能。只需给你的ViewPager加上一个PageChangeListener(页面改变监听器),然后在页面改变以后,BottomNavigationView调用.setChecked ()方法,手动对当前选项作出对应的改变,就实现了。
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
//该方法只在滑动停止时调用,position滑动停止所在页面位置
// 当滑动到某一位置,导航栏对应位置被按下
navigation.getMenu().getItem(position).setChecked(true);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
}
然后我们按照刚才对主界面元素的分析,编写frag_home.xml,实现主界面(菜单第一个选项)的内容。该界面主要还是采用LinearLayout布局,里面所有的界面都是垂直排列。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
最开始排列到的是一张图片,这里使用了scaleType进行了拉伸。
<ImageView
android:id="@+id/home_image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:scaleType="fitXY"
android:src="@drawable/home_image" />
接着就是存放了四个按钮的横向的线性布局。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingBottom="5dp"
android:orientation="horizontal">
在该布局中,存放着如下四个纵向的现场布局,每个布局包含一个ImageButton和一个TextView,其中ImageButton也使用了scaleType进行了拉伸,防止图片变形。两个元素都使用layout_gravity来保证居中。
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageButton
android:id="@+id/recyclable_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="fitCenter"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:src="@drawable/recyclable"
android:background="#00000000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="可回收"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageButton
android:id="@+id/harmful_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="fitCenter"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:src="@drawable/harmful"
android:background="#00000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="有害垃圾"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageButton
android:id="@+id/wet_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="fitCenter"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:src="@drawable/wet"
android:background="#00000000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="湿垃圾"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageButton
android:id="@+id/dry_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="fitCenter"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:src="@drawable/dry"
android:background="#00000000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="干垃圾"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout>
在该布局下面有这一段布局,用以设置分割线,使界面更加美观。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#A3DD53" />
接着又是一段横向的线性布局,用来存放两块线性布局、五个按钮。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:orientation="horizontal">
第一块以一个线性布局来存放一个ImageButton和TextView,和之前差不多,不同的是padding的值比较大,这样子将控件缩放在中央位置。
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:padding="40dp">
<ImageButton
android:id="@+id/test_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:src="@drawable/test"
android:background="#00000000"
android:layout_gravity="center"
android:padding="5dp"
android:scaleType="fitCenter" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="模拟考试"
android:textSize="15sp" />
</LinearLayout>
第二块又分成两块以纵向排列的线性布局。
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
而每一块又分成两块横向排列的线性布局。这样子,我们就将一大块区域分成四块。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
然后就是设置ImageView和TextView,这里不再赘述。
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageButton
android:id="@+id/exercise_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="fitCenter"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:src="@drawable/exercise"
android:background="#00000000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="练习"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageButton
android:id="@+id/errorProne_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="fitCenter"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:src="@drawable/errorprone"
android:background="#00000000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="易错"
android:textSize="15sp"
android:background="#00000000"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageButton
android:id="@+id/common_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="fitCenter"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:src="@drawable/common"
android:background="#00000000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="常见"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageButton
android:id="@+id/special_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="fitCenter"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:src="@drawable/special"
android:background="#00000000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="专项"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
这里同样是加了分割线。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#A3DD53" />
最后这里设置了一个SearchView搜索框和一个ImageButton。其中设置了搜索框的内容,并在background中引入编写的circle文件使搜索框的边缘呈现圆形。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:orientation="vertical">
<EditText
android:id="@+id/searchHome"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint=" 🔍 请输入搜索内容"
android:paddingLeft="20dp"
android:background="@drawable/searchview_circle"/>
<ImageButton
android:id="@+id/recording_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@drawable/recording"
android:background="#00000000"
android:scaleType="fitCenter"
android:padding="20dp"/>
</LinearLayout>
</LinearLayout>
circle文件如下,设置了形状以及颜色,利用color设置绿色,用radius设置边缘为圆形。
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#57A81C" />
<corners android:radius="20dp" />
</shape>
最后home界面效果如下:
(4)首页界面逻辑编写
首先,我们给app内置一个数据库,用以储存垃圾以及对应的分类。这里使用LitePal数据库来储存数据。 配置litepal.xml文件(在app/src/main目录下新建一个litepal.xml文件),并编辑内容为:
<?xml version="1.0" encoding="UTF-8" ?>
<litepal>
<dbname value="KnowledgeStore"></dbname>
<version value="1"></version>
<list>
<mapping class="com.example.refuseclassification.Database.Knowledge"></mapping>
</list>
</litepal>
然后配置LitePalApplication,修改Manifest.xml文件中的代码,如下所示:
<application
android:name="org.litepal.LitePalApplication"
定义一个类Knowledge类,继承自LitePalSupport,声明属性id、name、kind和answer,并实现其getter和setter方法。然后在上面的litepal.xml文件添加该类的映射模型。
public class Knowledge extends LitePalSupport implements Serializable {
private int id;
private String name;
private String kind;
private String answer;
public Knowledge() {
}
public Knowledge(int id, String name, String kind, String answer) {
this.id = id;
this.name = name;
this.kind = kind;
this.answer = answer;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setKind(String kind) {
this.kind = kind;
}
public void setAnswer(String answer) {
this.answer = answer;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getKind() {
return kind;
}
public String getAnswer() {
return answer;
}
}
配置工作完成了,开始进行数据库的创建的插入。首先用两个数组储存要插入的name和kind。
public class KnowledgeDatabase {
String[] name = {"菠萝", "鸭脖", "鸭脖子", "萝卜", "胡萝卜", "萝卜干", "草", "草莓",
"红肠", "香肠", "鱼肠", "过期食品", "剩菜剩饭", "死老鼠", "麦丽素", "果粒",
"巧克力", "芦荟", "落叶", "乳制品", "鲜肉月饼", "炸鸡腿", "药渣", "毛毛虫", "蜗牛",
"安全套", "按摩棒", "肮脏塑料袋", "旧扫把", "旧拖把", "搅拌棒", "棒骨", "蚌壳",
"保龄球", "爆竹", "纸杯", "扇贝", "鼻毛", "鼻屎", "笔", "冥币",
"尿不湿", "餐巾", "餐纸", "一次性叉子", "掉下来的牙齿", "丁字裤", "耳屎", "飞机杯", "碱性无汞电池",
"安全帽", "棉袄", "白纸", "手办", "包包", "保温杯", "报纸", "电脑设备",
"被单", "本子", "手表", "玻璃", "尺子", "充电宝", "充电器", "空调",
"耳机", "衣服", "乐高", "公仔", "可降解塑料", "酒瓶", "篮球", "红领巾", "泡沫箱",
"阿司匹林", "浴霸灯泡", "避孕药", "温度计", "杀毒剂", "感冒药", "药瓶", "止咳糖浆",
"胶囊", "灯泡", "农药", "油漆", "维生素", "酒精", "指甲油", "铅蓄电池",
"废电池", "打火机", "医用纱布", "医用棉签", "相片", "干电池", "钙片", "针管", "针筒"};
String[] kind = {"湿垃圾", "干垃圾", "可回收物", "有害垃圾"};
接着开始数据库的配置。首先获取数据库的实例,然后循环插入数据,先用where子句来查询是否已经有此数据id,若无则用类方法insert插入该数据,否则跳过插入,以防止数据的重复插入。
public void setKnowledgeDatabase() {
LitePal.getDatabase();
for (int i = 0; i < 100; i++) {
// 获取数据表数据,查询是否有相同数据,防止重复插入
List<Knowledge> knowledges = LitePal.where("id = ?", String.valueOf(i + 1))
.find(Knowledge.class);
if (knowledges == null || knowledges.size()== 0)
if (i < 25)
insert(i + 1, name[i], kind[0]);
else if (i < 50)
insert(i + 1, name[i], kind[1]);
else if (i < 75)
insert(i + 1, name[i], kind[2]);
else
insert(i + 1, name[i], kind[3]);
else
continue;
}
}
这里是insert方法。这里先创造出一个Knowledge实例,然后调用set方法进行设置,最后用save将数据保存到数据库中。
public void insert(int id, String name, String kind) {
Knowledge knowledge = new Knowledge();
knowledge.setId(id);
knowledge.setName(name);
knowledge.setKind(kind);
knowledge.save();
}
}
这样,app中重要的垃圾数据我们就成功嵌入到app的数据库中。
该界面中,这四个按钮的界面和功能类似,我们用其中的“可回收”按钮进行分析。
新建活动RecyclableActivity和布局activity_recyclable,首先编写布局。该布局是一个线性布局,首先包含的是一个用于显示标题的toolbar。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/recyclable_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#64E269" />
接下来是个横向排列的线性布局,先放置一张ImageView,使用scaleType来保证图片不被拉伸。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="fitCenter"
android:layout_margin="15dp"
android:src="@drawable/recyclable"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="5"
android:orientation="vertical"
android:layout_margin="5dp">
该图片的右侧,是一个纵向排列的线性布局,该布局由两个TextView组成,内容是标题已经可回垃圾的定义。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#D8000000"
android:text="可回收垃圾"
android:textSize="15sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#45000000"
android:text="可回收物指适宜回收利用和资源化利用的生活废弃物。主要包括:废纸、废弃塑料瓶、废金属、废包装物、废旧纺织物、废弃电器电子产品、废玻璃、废纸塑铝复合包装等。" />
</LinearLayout>
</LinearLayout>
在这个线性布局下面,又是一个纵向排列的线性布局,也是由两个TextView组成内容是投放要求和投放要求的具体内容。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:layout_margin="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#D8000000"
android:text="投放要求"
android:textSize="15sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#45000000"
android:text="鼓励居民直接将可回收物纳入再生资源回收系统,如需分类投放应尽量保持清洁干燥,避免污染,轻投轻放。其中:1、废纸应保持平整,立体包装物应清空内容物,清洁后压扁投放。2、废玻璃有尖锐边角的,应包裹后投放。" />
</LinearLayout>
最后还是一个纵向排列的线性布局,该布局由一个标题和一个RecyclerView组成,RecyclerView用于储存常见的可回收垃圾。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#D8000000"
android:text="常见可回收垃圾"
android:textSize="15sp"
android:layout_margin="10dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclable_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"/>
</LinearLayout>
</LinearLayout>
RecyclerView由子项item组成,所以需要对子项布局进行编写,这里采用相对布局,将垃圾名放置在子项左边,种类放在子项右边,最下边是一条分割线。
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:textSize="20sp"
android:layout_margin="10dp"/>
<TextView
android:id="@+id/kind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:textSize="20sp"
android:layout_margin="10dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#1B000000"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
接下来编写RecyclableActivity。首先我们定义一些需要用到的控件以及RecyclerView的适配器,然后在主方法中设置标题栏内容,用litepal调用where查询语句获取数据库内容,得到knowledges列表。然后我们实例化适配器,给RecyclerView适配适配器和布局管理器。
public class RecyclableActivity extends BaseActivity {
private Toolbar toolbar;
private RecyclerView recyclerView;
private List<Knowledge> knowledges = new ArrayList<>();
private MyAdapter myAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recyclable);
toolbar = (Toolbar) findViewById(R.id.recyclable_toolbar);
toolbar.setTitle("可回收垃圾");
new setTitleCenter().setTitleCenter(toolbar);
// 编写列表内容
recyclerView = findViewById(R.id.recyclable_recyclerView);
knowledges = LitePal.where("kind = ?", "可回收物").find(Knowledge.class);
myAdapter = new MyAdapter();
recyclerView.setAdapter(myAdapter);
LinearLayoutManager manager = new LinearLayoutManager(RecyclableActivity.this);
recyclerView.setLayoutManager(manager);
}
如图,我们在该活动的主类中创建适配器内部类,在onCreateViewHolder方法中将item布局加载进来,然后创建一个ViewHolder实例并返回;onBindViewHolder方法中,我们对子项进行赋值;最后在getItemCount中返回有多少子项。
private class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = View.inflate(RecyclableActivity.this, R.layout.item_recyclerview, null);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
Knowledge knowledge = knowledges.get(position);
holder.name.setText(knowledge.getName());
//holder.kind.setText((knowledge.getKind()));
}
@Override
public int getItemCount() {
return knowledges.size();
}
}
这个类是用于适配ViewHolder,实例化name和kind。
private class MyViewHolder extends RecyclerView.ViewHolder {
TextView name;
TextView kind;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
name = itemView.findViewById(R.id.name);
kind = itemView.findViewById(R.id.kind);
}
}
}
最后效果如下:
首页中,这五个按钮对应的活动的功能也是相似的,这里以模拟考试来分析。
新建活动activity_test.xml,编写界面。该界面首先是一个纵向排列的线性布局,第一个控件时toolbar,这个已经提到很多次了,用于设置标题栏。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/test_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#64E269" />
接下来是一个横向排列的线性布局,该布局由两个TextView组成,用于显示题目的数量,其中question_num会在活动的方法中动态改变值,达到答题然后题目数改变的效果。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="10dp">
<TextView
android:id="@+id/question_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="/10"
android:textColor="#88000000" />
</LinearLayout>
接下来的布局由一个大的纵向排列的线性布局组成。 首先是一个醒目的TextView,用于显示当前的题目,它也会在方法进行刷新,以显示不同的题目。然后是一个RadioGroup,它包含四个RadioButton,分别表示四个选项:可回收物、有害垃圾、湿垃圾、干垃圾。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/question"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="30dp"
android:textSize="35sp" />
<RadioGroup
android:id="@+id/radioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp">
<RadioButton
android:id="@+id/answer1"
android:text="可回收物"
android:textSize="30sp"
android:layout_margin="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<RadioButton
android:id="@+id/answer2"
android:text="有害垃圾"
android:textSize="30sp"
android:layout_margin="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"/>
<RadioButton
android:id="@+id/answer3"
android:text="湿垃圾 "
android:textSize="30sp"
android:layout_margin="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<RadioButton
android:id="@+id/answer4"
android:text="干垃圾 "
android:textSize="30sp"
android:layout_margin="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RadioGroup>
最后是一个Button,用于提示用户开始答题,当用户点击按钮之后,会变成“提交答案”,以提示用户作答。
<Button
android:id="@+id/submit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始答题"
android:textSize="30sp"
android:layout_margin="20dp"
android:background="@drawable/button_circle"
android:padding="20dp"
android:layout_gravity="center_horizontal" />
</LinearLayout>
</LinearLayout>
接下来编写TextActivity的代码。这里还是先定义一些控件和垃圾知识列表,以及分数和计数器。
public class TestActivity extends BaseActivity{
private Toolbar toolbar;
private TextView question_num;
private TextView question;
private Button submit;
private RadioGroup radiogroup;
private RadioButton answer1;
private RadioButton answer2;
private RadioButton answer3;
private RadioButton answer4;
private List<Knowledge> knowledges = new ArrayList<>();
private String answer = "";
private int score = 0;
private int count;
这里是引入布局和初始化toolbar以及计数器然后使用Math.random()方法来获取随机数,并加入到一个集合中。之所以使用集合来储存随机数,是因为集合中的元素不重复,这样子我们就可以得到不重复的10个0~99的数字,以此从数据库中随机选择题目。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
toolbar = (Toolbar) findViewById(R.id.test_toolbar);
toolbar.setTitle("模拟考试");
count = -1;
new setTitleCenter().setTitleCenter(toolbar);// 初始化ToolBar
// 初始化随机数列表,10个1~100的数
Set<Integer> hashSet = new HashSet<Integer>();
while (hashSet.size() != 10) {
int number = (int) (Math.random() * 100);
hashSet.add(number);
}
这里使用到了前面所说的随机数集合hashSet,我们使用迭代器,将里面的数据转化为int型的id,按照这个随机的id,使用LitePal的查询语句获取数据库中相应id的knowledge对象,并把这个对象加入到该活动的knowledge对象列表中。然后就是对控件的实例化。
// 初始化问题列表
Iterator it = hashSet.iterator();
while (it.hasNext()) {
int id = Integer.parseInt(it.next().toString());
Knowledge knowledge = LitePal.find(Knowledge.class, id);
knowledges.add(knowledge);
}
// 设置题目
question = findViewById(R.id.question);
question_num = findViewById(R.id.question_num);
radiogroup = findViewById(R.id.radioGroup);
answer1 = findViewById(R.id.answer1);
answer2 = findViewById(R.id.answer2);
answer3 = findViewById(R.id.answer3);
answer4 = findViewById(R.id.answer4);
submit = findViewById(R.id.submit);
这里我们对radioGroup设置一个监听器,监听用户的选择,选择相应的答案,answer会赋值为相应的答案,用以存储用户的答案,并设置用户选择选项时,选项的颜色为红色。
radiogroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
// 选中文字显示红色,没有选中显示黑色
if(answer1.isChecked()) {
answer = "可回收物";
answer1.setTextColor(Color.parseColor("#FF0033"));
}else{
answer1.setTextColor(Color.parseColor("#000000"));
}
if(answer2.isChecked()) {
answer = "有害垃圾";
answer2.setTextColor(Color.parseColor("#FF0033"));
}else{
answer2.setTextColor(Color.parseColor("#000000"));
}
if(answer3.isChecked()) {
answer = "湿垃圾";
answer3.setTextColor(Color.parseColor("#FF0033"));
}else{
answer3.setTextColor(Color.parseColor("#000000"));
}
if(answer4.isChecked()) {
answer = "干垃圾";
answer4.setTextColor(Color.parseColor("#FF0033"));
}else{
answer4.setTextColor(Color.parseColor("#000000"));
}
}
});
接下来设置按钮的监听器。当用户还未点击时,count为-1,此时按钮文字未改变,只有当点击之后,才会变成“提交答案”。用户每点击一次按钮,count++,并将answer与正确答案做对比,记录分数并储存用户答案。
submit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
radiogroup.clearCheck();
if (count == -1) {
count++;
question_num.setText(Integer.toString(count + 1));
question.setText(knowledges.get(count).getName());
submit.setText("提交答案");
}
else if (count < 10) {
if (!answer.equals("")) {
if (answer.equals(knowledges.get(count).getKind())) {
score += 10;
}
Knowledge knowledge = knowledges.get(count);
knowledge.setAnswer(answer);
knowledges.set(count, knowledge);
}
当用户答到最后一题时,按钮变成查看结果。
count = count + 1;
if (count != 10)
{
question_num.setText(Integer.toString(count + 1));
question.setText(knowledges.get(count).getName());
}
else {
submit.setText("查看结果");
}
}
最后点击按钮时,我们跳转到一个新的活动,使用intent和bundle传递数据knowledges列表和分数score。由于我们需要将answer储存到列表中,所以修改Knowledge类,新增answer元素以及相应的set和get方法。因为使用bundle传递对象列表与传递一般的数据不一样,需要对列表进行序列,只要让类继承接口Serializable就行了。然后就是启动新活动并结束该活动。
else {
Intent intent = new Intent(TestActivity.this,
AnswerActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("knowledges", (Serializable) knowledges);
bundle.putInt("score", score);
intent.putExtra("message", bundle);
startActivity(intent);
finish();// 销毁活动
}
}
});
}
}
我们来编写答题完成之后跳转的活动AnswerActivity。这里首先依然是纵向排列的线性布局以及toolbar。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/test_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#64E269" />
然后就是一个横向排列的线性布局,包含一个ImageView和一个TextView,用于提示用户和显示最后的得分。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal"
android:layout_margin="40dp">
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@drawable/score"
android:layout_margin="10dp"
android:scaleType="fitCenter" />
<TextView
android:id="@+id/score"
android:layout_width="0dp"
android:textSize="70sp"
android:textColor="#F73C2E"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_margin="10dp" />
</LinearLayout>
接下来是一个纵向的布局,用于显示用户答题的结果。包含的第一个横向的线性布局是答题的表头,里面就是使用相对布局居中的TextView和分割线。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="题目"
android:textSize="20sp"
android:layout_centerInParent="true" />
</RelativeLayout>
<LinearLayout
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="#1B000000" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我的答案"
android:textSize="20sp"
android:layout_centerInParent="true" />
</RelativeLayout>
<LinearLayout
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="#1B000000" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="正确答案"
android:textSize="20sp"
android:layout_centerInParent="true" />
</RelativeLayout>
</LinearLayout>
最后就是显示最终答题情况的RecyclerView。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#1B000000" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/answer_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout>
既然有了RecyclerView,那我们就需要编写子项布局。编写item_answer.xml,该布局和上一个布局中显示答案的表头差不多。主要是分割线和利用RelativeLayout居中显示的TextView。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3">
<TextView
android:id="@+id/question_done"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="20sp"
android:layout_centerInParent="true" />
</RelativeLayout>
<LinearLayout
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="#1B000000" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2">
<TextView
android:id="@+id/my_answer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="20sp"
android:layout_centerInParent="true" />
</RelativeLayout>
<LinearLayout
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="#1B000000" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2">
<TextView
android:id="@+id/right_answer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="20sp"
android:layout_centerInParent="true" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#1B000000" />
</LinearLayout>
最后我们来编写AnswerActivity。这里依然还是定义控件。
public class AnswerActivity extends BaseActivity {
private Toolbar toolbar;
private TextView score_view;
private List<Knowledge> knowledges = new ArrayList<>();
private int score;
private RecyclerView recyclerView;
private MyAdapter myAdapter;
主方法这里我们先初始化控件,然后使用Intent和Bundle获取来自上个活动用户的答题情况以及分数,然后显示、适配到控件中。给RecyclerView适配数据以及适配器,这里就不在赘述了。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_answer);
toolbar = (Toolbar) findViewById(R.id.test_toolbar);
toolbar.setTitle("考试结果");
new setTitleCenter().setTitleCenter(toolbar);// 初始化ToolBar
score_view = findViewById(R.id.score);
// 获取数据
Intent intent = getIntent();
Bundle bundle = intent.getBundleExtra("message");
knowledges = (List<Knowledge>) bundle.getSerializable("knowledges");
score = bundle.getInt("score");
score_view.setText(String.valueOf(score));
// 适配
recyclerView = findViewById(R.id.answer_recyclerView);
myAdapter = new MyAdapter();
recyclerView.setAdapter(myAdapter);
LinearLayoutManager manager = new LinearLayoutManager(AnswerActivity.this);
recyclerView.setLayoutManager(manager);
}
private class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = View.inflate(AnswerActivity.this, R.layout.item_answer, null);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
Knowledge knowledge = knowledges.get(position);
holder.question_done.setText(knowledge.getName());
holder.right_answer.setText(knowledge.getKind());
holder.my_answer.setText(knowledge.getAnswer());
}
@Override
public int getItemCount() {
return knowledges.size();
}
}
private class MyViewHolder extends RecyclerView.ViewHolder {
TextView question_done;
TextView my_answer;
TextView right_answer;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
question_done = itemView.findViewById(R.id.question_done);
my_answer = itemView.findViewById(R.id.my_answer);
right_answer = itemView.findViewById(R.id.right_answer);
}
}
}
结果如图所示:
最后,我们对这首页中剩下的两个功能:搜索框和语音识别,进行实现。
首先,我们在主页界面设置搜索框EditText失去焦点并设置点击事件,使得用户点击搜索框就可以跳转到搜索活动SearchActivity中。
search = (EditText) view.findViewById(R.id.searchHome);
search.setFocusable(false);//失去焦点
search.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), SearchActivity.class);
startActivity(intent);
}
});
首先编写搜索活动的布局activity_search.xml。该布局是线性排列,顶部是标题栏Toolbar,然后就是搜索栏EditText,搜索栏下方则是展现搜索结果的RecyclerView。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/search_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#64E269" />
<EditText
android:id="@+id/search"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint=" 🔍 请输入搜索内容"
android:paddingLeft="20dp"
android:background="@drawable/searchview_circle"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"/>
</LinearLayout>
然后我们编写搜索活动SearchActivity。首先在主函数定义需要使用到的控件、知识列表以及一个适配器。
public class SearchActivity extends BaseActivity {
private Toolbar toolbar;
private EditText editText;
private RecyclerView recyclerView;
List<Knowledge> knowledges = new ArrayList<>();
private MyAdapter myAdapter;
接着在主函数onCreate中加载刚才写好的布局,并且设置标题栏为搜索然后设置其居中显示;然后初始化数据列表,利用SQLite语句将数据库中的所有数据加载到knowledges列表当中,并初始化recyclerView和适配器myAdapter,最后再用LinearLayoutManager将布局指定为线性布局。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
toolbar = (Toolbar) findViewById(R.id.search_toolbar);
toolbar.setTitle("搜索");
new setTitleCenter().setTitleCenter(toolbar);
// 初始化数据列表
knowledges = LitePal.findAll(Knowledge.class);
recyclerView = findViewById(R.id.search_recyclerView);
myAdapter = new SearchActivity.MyAdapter();
recyclerView.setAdapter(myAdapter);
LinearLayoutManager manager = new LinearLayoutManager(SearchActivity.this);
recyclerView.setLayoutManager(manager);
然后我们实例化EditText,由于我们要使用语音识别功能,这里要将语音识别的结果利用intent传到当前的活动中。先用if语句判断是否有传入识别结果,如果是,则将识别到的结果显示在EditText中;然后清空列表,重新利用SQLite语句获取数据库中包含识别结果的内容,然后更新适配器,从而刷新recyclerView显示的搜索结果。
// 实例化EditText
editText = findViewById(R.id.search);
Intent intent = getIntent();
String record = intent.getStringExtra("record");
if (record != null) {
editText.setText(record);
knowledges.clear();
knowledges = LitePal.where("name like ?", "%" + record + "%").
find(Knowledge.class);
myAdapter = new SearchActivity.MyAdapter();
recyclerView.setAdapter(myAdapter);
}
我们给EditText设置一个监听器,监听用户的输入。在用户输入文本前,设置好适配器;当用户开始输入的时候,EditText获取当前输入框中的内容,然后使用SQLite语句,在数据库中获取含有输入框内容的搜索结果,以此来实现动态的模糊搜索。
editText.addTextChangedListener(new TextWatcher() {
// 输入文本前的状态
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if(myAdapter != null){
recyclerView.setAdapter(myAdapter);
}
}
// 输入文本时的状态
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String str = s.toString();
knowledges.clear();
knowledges = LitePal.where("name like ?", "%" + str + "%").
find(Knowledge.class);
myAdapter = new SearchActivity.MyAdapter();
recyclerView.setAdapter(myAdapter);
}
// 输入文本之后的状态
@Override
public void afterTextChanged(Editable s) {
}
});
}
剩下的就是适配器类和ViewHolder的实现,这个在前面已经介绍过作用了,这里不予赘述。
private class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = View.inflate(SearchActivity.this, R.layout.item_recyclerview, null);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
Knowledge knowledge = knowledges.get(position);
holder.name.setText(knowledge.getName());
holder.kind.setText((knowledge.getKind()));
}
@Override
public int getItemCount() {
return knowledges.size();
}
}
private class MyViewHolder extends RecyclerView.ViewHolder {
TextView name;
TextView kind;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
name = itemView.findViewById(R.id.name);
kind = itemView.findViewById(R.id.kind);
}
}
}
结果如下,用户输入一个字就会进行刷新,展示搜索内容,实现了动态搜索;内容是包含用户搜索内容的所有内容,以此实现模糊搜索。
编写主角界面的最后一个按钮——语音识别。如何导入api我是参考了这篇博客blog.csdn.net/qq_38436214…
按照上面的博客将百度语音识别库api导入完成之后,首先在HomeFragment中定义语音识别核心库。
private EventManager asr;//语音识别核心库
private String result;
接下来开始编写语音识别功能。首先在Manifest中添加以下权限。
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_TASKS" /> <!-- 蓝牙录音使用,不需要可以去除 -->
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
然后在core文件夹中的Manifest文件中添加id和密钥,value对应的值是自己注册之后生成的,每个人都不一样,这样才可以使用该api。
<application>
<meta-data
android:name="com.baidu.speech.APP_ID"
android:value="25415468"/>
<meta-data
android:name="com.baidu.speech.API_KEY"
android:value="iCYsEmwScqNGAlzDbZcl7vh4"/>
<meta-data
android:name="com.baidu.speech.SECRET_KEY"
android:value="WakD4dM6hykhHreHjQ5uNayIXnP3BSSp"/>
</application>
首先在HomeFragment的主函数中,初始化待会儿要用到的权限。
// 初始化权限
initPermission();
语音识别自然要用到这个麦克风,这个权限是需要动态申请的,initPermission方法如下。
private void initPermission() {
String permissions[] = {Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.INTERNET,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
ArrayList<String> toApplyList = new ArrayList<String>();
for (String perm : permissions) {
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(getContext(), perm)) {
toApplyList.add(perm);
}
}
String tmpList[] = new String[toApplyList.size()];
if (!toApplyList.isEmpty()) {
ActivityCompat.requestPermissions(getActivity(), toApplyList.toArray(tmpList), 123);
}
}
接下来给录音按钮绑定按下和松开事件,当按下按钮时开始录音,松开按钮时录音结束。
recording_button = (ImageButton) view.findViewById(R.id.recording_button);
recording_button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
// 按下 处理相关逻辑
asr.send(SpeechConstant.ASR_START, "{}", null, 0, 0);
} else if (action == MotionEvent.ACTION_UP) {
// 松开 处理相关逻辑
asr.send(SpeechConstant.ASR_STOP, "{}", null, 0, 0);
}
return false;
}
});
然后还要初始化语音识别核心库对象,并设置监听器。
//初始化EventManager对象
asr = EventManagerFactory.create(getContext(), "asr");
//注册自己的输出事件类
asr.registerListener(this); // EventListener 中 onEvent方法
由于解析之后得到的语音识别结果是JSON字符串,无法直接对其进行搜索,我们需要对结果进行处理,转化成可以直接搜索的文字识别结果。定义ASRresponse实体bean,如下。
package com.example.refuseclassification;
import java.util.List;
public class ASRresponse {
/**
* results_recognition : ["你好,"]
* result_type : final_result
* best_result : 你好,
* origin_result : {"asr_align_begin":80,"asr_align_end":130,"corpus_no":6835867007181645805,"err_no":0,"raf":133,"result":{"word":["你好,"]},"sn":"82d975e0-6eb4-43ac-a0e7-850bb149f28e"}
* error : 0
*/
private String result_type;
private String best_result;
private OriginResultBean origin_result;
private int error;
private List<String> results_recognition;
public String getResult_type() {
return result_type;
}
public void setResult_type(String result_type) {
this.result_type = result_type;
}
public String getBest_result() {
return best_result;
}
public void setBest_result(String best_result) {
this.best_result = best_result;
}
public OriginResultBean getOrigin_result() {
return origin_result;
}
public void setOrigin_result(OriginResultBean origin_result) {
this.origin_result = origin_result;
}
public int getError() {
return error;
}
public void setError(int error) {
this.error = error;
}
public List<String> getResults_recognition() {
return results_recognition;
}
public void setResults_recognition(List<String> results_recognition) {
this.results_recognition = results_recognition;
}
public static class OriginResultBean {
/**
* asr_align_begin : 80
* asr_align_end : 130
* corpus_no : 6835867007181645805
* err_no : 0
* raf : 133
* result : {"word":["你好,"]}
* sn : 82d975e0-6eb4-43ac-a0e7-850bb149f28e
*/
private int asr_align_begin;
private int asr_align_end;
private long corpus_no;
private int err_no;
private int raf;
private ResultBean result;
private String sn;
public int getAsr_align_begin() {
return asr_align_begin;
}
public void setAsr_align_begin(int asr_align_begin) {
this.asr_align_begin = asr_align_begin;
}
public int getAsr_align_end() {
return asr_align_end;
}
public void setAsr_align_end(int asr_align_end) {
this.asr_align_end = asr_align_end;
}
public long getCorpus_no() {
return corpus_no;
}
public void setCorpus_no(long corpus_no) {
this.corpus_no = corpus_no;
}
public int getErr_no() {
return err_no;
}
public void setErr_no(int err_no) {
this.err_no = err_no;
}
public int getRaf() {
return raf;
}
public void setRaf(int raf) {
this.raf = raf;
}
public ResultBean getResult() {
return result;
}
public void setResult(ResultBean result) {
this.result = result;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public static class ResultBean {
private List<String> word;
public List<String> getWord() {
return word;
}
public void setWord(List<String> word) {
this.word = word;
}
}
}
}
然后在gradle中加入GSON依赖,我们使用GSON对JSON数据进行处理。
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
后面我们在处理之后的结果中就可以直接使用GSON来进行解析了。
接下来我们实现核心库接口EventListener。
public class HomeFragment extends Fragment implements EventListener {...}
我们重写接口的回调方法。参数params是识别之后未经处理的json字符串,如果params为空,表示未识别,我们跳过处理;否则,我们使用GSON解析参数,并去掉其中的“,”,保证字符串的连续;最后使用intent将结果传递到下一个活动SearchActivity。
@Override
public void onEvent(String name, String params, byte[] data, int offset, int length) {
if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_PARTIAL)) {
// 识别相关的结果都在这里
if (params == null || params.isEmpty()) {
return;
}
if (params.contains("\"final_result\"")) {
// 一句话的最终识别结果
Gson gson = new Gson();
ASRresponse asRresponse = gson.fromJson(params, ASRresponse.class);//数据解析转实体bean
if(asRresponse.getBest_result().contains(",")){
// 包含逗号 则将逗号替换为空格
// 替换为空格之后,通过trim去掉字符串的首尾空格
setResult(asRresponse.getBest_result().replace(',',' ').trim());
}else {// 不包含
setResult(asRresponse.getBest_result().trim());
}
Intent intent = new Intent(getActivity(), SearchActivity.class);
if (result.contains("。")) {
setResult(result.replaceAll("。", ""));
}
intent.putExtra("record", result);
startActivity(intent);
}
}
}
最后,我们还要重写结束事件,退出监听。
@Override
public void onDestroy() {
super.onDestroy();
//发送取消事件
asr.send(SpeechConstant.ASR_CANCEL, "{}", null, 0, 0);
//退出事件管理器
// 必须与registerListener成对出现,否则可能造成内存泄露
asr.unregisterListener(this);
}
结果如下,按下录音键开始语音识别,松开录音键则跳转到搜索界面,显示搜索结果。