深圳大学移动互联网应用期末大作业——垃圾分类app(二)

354 阅读31分钟

(3)主界面的实现

首先分析下样例的主界面。1标题栏可以使用Toolbar来实现;2可以使用一张图片;这里使用ImageView实现;3是一个横向的LinearLayout,分布着四对Button和TextView;4、5同在一个横向的LinearLayout中,每个控件都可以使用LinearLayout来排列,也是由Button1和TextView组成的;6是一个搜索框,可以使用EditText来实现;7是一个Button,用于识别语音;8则是底部导航栏Bottom navigation。 image.png 首先实现底部导航栏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);
        }
    }
}

最后效果如下: 这四个按钮对应界面的效果图 首页中,这五个按钮对应的活动的功能也是相似的,这里以模拟考试来分析。 image.png 新建活动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);
    }

结果如下,按下录音键开始语音识别,松开录音键则跳转到搜索界面,显示搜索结果。 点击录音按钮开始录音搜索结果如下