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

776 阅读14分钟

(5)指南界面编写

接下来进行第二个界面(指南界面)的编写。打开之前创建的碎片布局frag_guide.xml,编写其中的布局。和前面home碎片一样,需要一个Toolbar作为标题栏;紧接着是一个顶端导航栏,这里使用了TabLayout作为顶部导航栏;最后就是一个ViewPager容器占满其余屏幕,用于显示碎片。

<?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/guide_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="#64E269" />

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/guide_tab"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewPagerGuide"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

和前面提到的BottomNavigationView用ViewPager容器存放Fragment一样,TabLayout也是,两者都需要一个Adapter适配器,由于前面编写了PagerAdapter.java,这里直接重用就行,就不需要再重新编写。 我们直接编写GuideFragmet.java。和前面BottomNavigationView一样,这里先定义一些需要使用的构件。

public class GuideFragment extends Fragment {

    private TabLayout tabLayout;
    private List<Fragment> fragmentList;
    private ViewPager viewPager;
    private PagerAdapter adapter;
    private Toolbar toolbar;

接着,我们初始化碎片界面之后,实例化toolbar并调用方法设置标题栏为“指南”,然后调用类方法来实例化TabLayout和viewPager,并实现点击和滑动事件。

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.frag_guide, container, false);
        toolbar = (Toolbar) view.findViewById(R.id.guide_toolbar);
        toolbar.setTitle("指南");
        new setTitleCenter().setTitleCenter(toolbar);
        // 初始化tabLayout和viewPager并绑定滑动和点击事件
        initMenuTabs(view);
        initViewPager(view);
        bindTabAndPager(view);
        return view;
    }

initMenuTabs方法中,实例化tabLayout并设置了相应的tab标题。

    public void initMenuTabs(View view) {
        tabLayout = (TabLayout) view.findViewById(R.id.guide_tab);
        tabLayout.setSelectedTabIndicatorColor(0);
        tabLayout.addTab(tabLayout.newTab().setText("可回收物"));
        tabLayout.addTab(tabLayout.newTab().setText("有害垃圾"));
        tabLayout.addTab(tabLayout.newTab().setText("湿垃圾"));
        tabLayout.addTab(tabLayout.newTab().setText("干垃圾"));
    }

这个方法和前面的BottomNavigationView一样,在列表中加入四个对应菜单的fragment,然后实例化adapter,最后ViewPager调用 .setAdapter() 方法传入PagerAdapter即可实现一个可以左右侧滑切换界面的效果。

    public void initViewPager(View view) {
        viewPager = (ViewPager) view.findViewById(R.id.viewPagerGuide);
        fragmentList = new ArrayList<>();
        fragmentList.add(new RecyclableFragment());
        fragmentList.add(new HarmfulFragment());
        fragmentList.add(new WetFragment());
        fragmentList.add(new DryFragment());
        adapter = new PagerAdapter(getFragmentManager(), this, fragmentList);
        viewPager.setAdapter(adapter);
    }

这个bindTabAndPager是完成ViewPager和TabLayout的联动。我们给ViewPager设置了监听器,监听TabLayout菜单的改变,当TabLayout改变时,ViewPager也随之改变。最后一个很重要的点,就是设置ViewPager的缓存页面为3,因为我们有四个fragment,而ViewPager的默认缓存页面为1,导致连续跳转fragment要重新加载。

    public void bindTabAndPager(View view) {
        tabLayout = (TabLayout) view.findViewById(R.id.guide_tab);
        viewPager = (ViewPager) view.findViewById(R.id.viewPagerGuide);
        viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
        viewPager.setOffscreenPageLimit(3); // 设置缓存页面为3
        tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                int position = tab.getPosition();
                viewPager.setCurrentItem(position);
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });
    }
}

接下来实现里面的碎片,四个碎片功能都一样,都是用来科普四种垃圾,这里举例可回收垃圾的界面,首先编写frag_recyclable.xml文件,该文件很简单,里面只包含了一个WebView。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <WebView
        android:id="@+id/recyclable_web"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

接着编写RecyclableFragment.java,将刚刚编写的碎片布局加载进来,并实例化WebView,调用getSettings方法和setJavaScriptEnabled(true)来让WebView支持JavaScript脚本,然后使用setWebViewClient方法保证网页在app中显示,最后传入百度百科可回收垃圾的地址url。

public class RecyclableFragment extends Fragment {

    private WebView webView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.frag_recyclable, container, false);
        webView = (WebView) view.findViewById(R.id.recyclable_web);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebViewClient(new WebViewClient());
        // 百度百科
        webView.loadUrl("https://baike.baidu.com/item/%E5%8F%AF%E5%9B%9E%E6%94%B6%E7%89%A9");
        return view;
    }
}

其他文件也一样。 最后在Manifest申请网络权限,才可以访问网络。 结果如下: 可回收物 有害垃圾 湿垃圾 干垃圾

(6)设置界面编写

接下来进行最后一个界面(设置界面)的编写。打开之前创建的碎片布局frag_setting.xml,编写其中的布局。和前面home和guide碎片一样,需要一个Toolbar作为标题栏;然后是一个包含着ImageButton的线性布局,由于更换头像;最后就是六个居中显示的TextView,用于查看通知、联系、退出登录等功能。

<?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.appcompat.widget.Toolbar
        android:id="@+id/setting_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="#64E269" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="140dp"
        android:orientation="horizontal">
        <ImageButton
            android:id="@+id/person_photo"
            android:layout_margin="20dp"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:scaleType="fitCenter"
            android:background="@drawable/image_circle"
            android:src="@drawable/person_default" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#A3DD53" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/text_notification"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:textAlignment="center"
            android:textSize="20sp"
            android:text="查看通知"
            android:paddingTop="15dp"
            android:paddingBottom="15dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#A3DD53" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/text_contact"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:textAlignment="center"
            android:textSize="20sp"
            android:text="联系我们"
            android:paddingTop="15dp"
            android:paddingBottom="15dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#A3DD53" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/text_about"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:textAlignment="center"
            android:textSize="20sp"
            android:text="关于我们"
            android:paddingTop="15dp"
            android:paddingBottom="15dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#A3DD53" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/text_agreement"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:textAlignment="center"
            android:textSize="20sp"
            android:text="用户协议与隐私"
            android:paddingTop="15dp"
            android:paddingBottom="15dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#A3DD53" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/text_version"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:textAlignment="center"
            android:textSize="20sp"
            android:text="当前版本"
            android:paddingTop="15dp"
            android:paddingBottom="15dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#A3DD53" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/text_logout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:textAlignment="center"
            android:textSize="20sp"
            android:text="退出登录"
            android:paddingTop="15dp"
            android:paddingBottom="15dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#A3DD53" />

</LinearLayout>

最后设置界面如下。 设置界面 首先在SettingFragment中定义需要使用的控件。然后在主函数中设置toolbar。

public class SettingFragment extends Fragment {

    private Toolbar toolbar;
    private ImageButton imageButton;
    private TextView notification;
    private TextView contact;
    private TextView about;
    private TextView agreement;
    private TextView version;
    private TextView logout;
    private Bitmap head;// 头像Bitmap
    private static String path = "/sdcard/myHead/";// sd路径
    
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.frag_setting, container, false);
        toolbar = (Toolbar) view.findViewById(R.id.setting_toolbar);
        toolbar.setTitle("设置");
        new setTitleCenter().setTitleCenter(toolbar);

首先,我们来实现更换头像功能。先实例化imageButton,然后将相册中的头像文件转化为bitmap,显示在imageButton上。然后给imageButton设置监听器,点击的时候就调用showTypeDialog()方法。

        //API24以上系统分享支持file:///开头
        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
        StrictMode.setVmPolicy(builder.build());
        builder.detectFileUriExposure();

        imageButton = (ImageButton) view.findViewById(R.id.person_photo);
        Bitmap bt = BitmapFactory.decodeFile(path + "head.jpg");// 从SD卡中找头像,转换成Bitmap
        if (bt != null) {
            @SuppressWarnings("deprecation")
            Drawable drawable = new BitmapDrawable(bt);// 转换成drawable
            imageButton.setImageDrawable(drawable);
        } else {
            /**
             * 如果SD里面没有则需要从服务器取头像,取回来的头像再保存在SD中
             *
             */
        }
        imageButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switch (v.getId()) {
                    case R.id.person_photo:// 更换头像
                        showTypeDialog();
                        break;
                }
            }
        });

showTypeDialog()方法的实现如下。首先显示一个对话框,对话框的布局在dialog_select_photo.xml中实现。然后分别获得对话框TextView的实例,并分别设置点击事件。一个设置打开相册,另外一个则是设置打开相机。

    private void showTypeDialog() {
        //显示对话框
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        final AlertDialog dialog = builder.create();
        View view = View.inflate(getActivity(), R.layout.dialog_select_photo, null);
        TextView tv_select_gallery = (TextView) view.findViewById(R.id.tv_select_gallery);
        TextView tv_select_camera = (TextView) view.findViewById(R.id.tv_select_camera);
        tv_select_gallery.setOnClickListener(new View.OnClickListener() {// 在相册中选取
            @Override
            public void onClick(View v) {
                Intent intent1 = new Intent(Intent.ACTION_PICK, null);
                //打开文件
                intent1.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
                startActivityForResult(intent1, 1);
                dialog.dismiss();
            }
        });
        tv_select_camera.setOnClickListener(new View.OnClickListener() {// 调用照相机
            @Override
            public void onClick(View v) {
                Intent intent2 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                intent2.putExtra(MediaStore.EXTRA_OUTPUT,
                        Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "head.jpg")));
                startActivityForResult(intent2, 2);// 采用ForResult打开
                dialog.dismiss();
            }
        });
        dialog.setView(view);
        dialog.show();
    }

对话框的布局在dialog_select_photo.xml。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="60dp"
    android:paddingRight="60dp">
    <TextView
        android:id="@+id/tv_select_gallery"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="20dp"
        android:padding="20dp"
        android:gravity="center"
        android:text="从相册中选取" />
    <TextView
        android:layout_below="@id/tv_select_gallery"
        android:id="@+id/tv_select_camera"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="20dp"
        android:gravity="center"
        android:text="拍摄照片" />
</RelativeLayout>

接下来我们重写onActivityResult()方法,在选择好图片之后执行cropPhoto()方法。

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case 1:
                if (resultCode == RESULT_OK) {
                    cropPhoto(data.getData());// 裁剪图片
                }
                break;
            case 2:
                if (resultCode == RESULT_OK) {
                    File temp = new File(Environment.getExternalStorageDirectory() + "/head.jpg");
                    cropPhoto(Uri.fromFile(temp));// 裁剪图片
                }
                break;
            case 3:
                if (data != null) {
                    Bundle extras = data.getExtras();
                    head = extras.getParcelable("data");
                    if (head != null) {
                        /**
                         * 上传服务器代码
                         */
                        imageButton.setImageBitmap(head);// 用ImageButton显示出来
                    }
                }
                break;
            default:
                break;

        }
        super.onActivityResult(requestCode, resultCode, data);
    }

这里调用了系统的裁剪功能,指定宽和高,达到裁剪的效果。

    /**
     * 调用系统的裁剪功能
     *
     * @param uri
     */
    public void cropPhoto(Uri uri) {
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop", "true");
        // aspectX aspectY 是宽高的比例
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        // outputX outputY 是裁剪图片宽高
        intent.putExtra("outputX", 250);
        intent.putExtra("outputY", 250);
        intent.putExtra("return-data", true);
        startActivityForResult(intent, 3);
    }
}

效果如下。 在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述 在这里插入图片描述在这里插入图片描述在这里插入图片描述 接着,我们来实现查看通知功能。实例化通知的TextView之后,我们给它绑定了监听事件,首先新建一个活动NotificationActivity,点击通知栏之后可以跳转到该活动;然后用NotificationManager对通知进行管理,最后对通知进行一些设置,设置标题、内容图标以及活动等等就可以了。

        notification = view.findViewById(R.id.text_notification);
        notification.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getActivity(), NotificationActivity.class);
                PendingIntent pi = PendingIntent.getActivities(getContext(),
                        0, new Intent[]{intent}, 0);
                NotificationManager manager = (NotificationManager)
                        getContext().getSystemService(NOTIFICATION_SERVICE);
                //需添加的代码
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
                    String channelId = "default";
                    String channelName = "默认通知";
                    manager.createNotificationChannel(new NotificationChannel
                            (channelId, channelName, NotificationManager.IMPORTANCE_HIGH));
                }
                Notification notification = new NotificationCompat.
                        Builder(getContext(), "default")
                        .setContentTitle("通知")
                        .setContentText("点击查看消息内容")
                        .setWhen(System.currentTimeMillis())
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                        .setContentIntent(pi)
                        .setAutoCancel(true)
                        .build();
                manager.notify(1, notification);
            }
        });

NotificationActivity的活动和布局文件如下,很简单所以不予赘述。

public class NotificationActivity extends BaseActivity {

    private Toolbar toolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_notification);
        toolbar = (Toolbar) findViewById(R.id.notification_toolbar);
        toolbar.setTitle("通知");
        new setTitleCenter().setTitleCenter(toolbar);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/notification_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="#64E269" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textSize="24sp"
        android:text="无通知" />

</RelativeLayout>

效果如下。 在这里插入图片描述在这里插入图片描述 接下来我们来实现“联系我们”、“关于我们”和“用户协议与隐私”。首先实例化contact,当用户点击的时候就会跳转到拨打电话号码的界面,这里传入了一个uri,是一个手机号,用户跳转到拨打页面就可以直接拨打改电话;about则是点击之后跳转到一个AboutActivity;agreement也是跳转到AgreementActivity。

        contact = view.findViewById(R.id.text_contact);
        contact.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_DIAL);
                intent.setData(Uri.parse("tel:15767064234"));
                startActivity(intent);
            }
        });

        about = view.findViewById(R.id.text_about);
        about.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getActivity(), AboutActivity.class);
                startActivity(intent);
            }
        });

        agreement = view.findViewById(R.id.text_agreement);
        agreement.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getActivity(), AgreementActivity.class);
                startActivity(intent);
            }
        });

AboutActivity的活动和布局如下。

public class AboutActivity extends BaseActivity {

    private Toolbar toolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_about);
        toolbar = (Toolbar) findViewById(R.id.about_toolbar);
        toolbar.setTitle("关于我们");
        new setTitleCenter().setTitleCenter(toolbar);
    }
}
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".AboutActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/about_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="#64E269" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="30dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="陈泓龙"
        android:textSize="20sp"
        android:padding="15dp"
        android:layout_gravity="center_horizontal" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="2019151027"
        android:textSize="20sp"
        android:padding="15dp"
        android:layout_gravity="center_horizontal" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="计算机与软件学院软件工程"
        android:textSize="20sp"
        android:padding="15dp"
        android:layout_gravity="center_horizontal" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="2019级软工三班"
        android:textSize="20sp"
        android:padding="15dp"
        android:layout_gravity="center_horizontal" />
</LinearLayout>

AgreementActivity的活动和布局如下。其中隐私与协议的内容直接以TextView的文本内容呈现。

public class AgreementActivity extends BaseActivity {

    private Toolbar toolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_agreement);
        toolbar = (Toolbar) findViewById(R.id.agreement_toolbar);
        toolbar.setTitle("用户协议与隐私");
        new setTitleCenter().setTitleCenter(toolbar);
    }
}
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".AgreementActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/agreement_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="#64E269" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/agreement"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scrollbarAlwaysDrawVerticalTrack="true"
            android:text="用户协议与隐私协议\n... />

    </ScrollView>

</LinearLayout>

效果如下。 在这里插入图片描述在这里插入图片描述在这里插入图片描述 接着,我们来实现查看当前版本的功能。这里给version设置的点击事件是用一个Toast显示“当前已是最新版本”。

        version = view.findViewById(R.id.text_version);
        version.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getContext(), "当前已是最新版本", Toast.LENGTH_SHORT).show();
            }
        });

效果如下。 在这里插入图片描述 最后,我们实现强制下线的功能。给logout设置点击事件,当用户点击的时候,就会发送一条广播。

        logout = view.findViewById(R.id.text_logout);
        logout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.example.refuseclassification.FORCE_OFFLINE");
                getActivity().sendBroadcast(intent);
            }
        });

然后我们编写一个BaseActivity继承自AppCompatActivity,所有的活动都使用重写后的BaseActivity而不是AppCompatActivity。在BaseActivity中注册广播监听器,用于接收用户发送的广播。当用户发送广播之后,就会弹出一个提示框,用户点击之后就会跳转到登录界面。最后我们将广播取消注册。

package com.example.refuseclassification;

import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {

    private ForceOfflineReceiver receiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.refuseclassification.FORCE_OFFLINE");
        receiver = new ForceOfflineReceiver();
        registerReceiver(receiver, intentFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (receiver != null) {
            unregisterReceiver(receiver);
            receiver = null;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }

    class ForceOfflineReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(final Context context, Intent intent) {
            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setTitle("Warning");
            builder.setMessage("您已退出,请重新登录");
            builder.setCancelable(false);
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCollector.finishAll();  //销毁所有活动
                    Intent i = new Intent(context, LoginActivity.class);
                    context.startActivity(i);   //重新启动LoginActivity
                }
            });
            builder.show();
        }
    }
}

结果如下。 在这里插入图片描述在这里插入图片描述

2. 请详细说明“我的垃圾分类APP”的功能、出现的关键问题及解决方案

功能展示

可以点击此处下载,浏览功能。

问题和解决方案

(1)fragment加载问题

如果用BottomNavigation直接加载Fragment,会导致Fragment无法正常显示,需要使用ViewPager来缓存Fragment。

(2)设置按钮背景透明

在缩放按钮图标时,我发现按钮周围呈现灰色,一时不知道怎么解决,后来发现直接设置背景色为白色就可以达到透明效果。 在这里插入图片描述

(3)TabLayout界面的缓存

一开始没有缓存的时候,我发现把界面活动走再滑动回来会导致界面重新加载,刚刚看到的地方就没有了,所以使用设置缓存界面为3,保证不用再重新加载。 在这里插入图片描述

(4)权限的声明

本app需要申请以下权限,包括网络权限、录音权限等等。 在这里插入图片描述

(5)不重复随机数的生成

在随机生成题目的时候,为了保证题目不重复,使用了java类对象Set,它是一个集合,而集合中的元素是不重复的,以此保证题目不重复。 在这里插入图片描述

(6)对象列表的传值

intent传递对象列表的时候,无法像一般地传值,需要先把对象序列化之后才可以正常传值。 在这里插入图片描述 在这里插入图片描述

(7)EditText焦点失去

若不失去焦点,用户在点击首页的输入框的时候,会跳出输入法;失去焦点之后就可以顺利跳转到下个页面。 在这里插入图片描述

(8)百度语音识别api的申请以及调用

参考了blog.csdn.net/qq_38436214…,成功实现了语音识别功能。

三、实验总结

本次实验是我做过的最难的实验,该实验综合运用到了课堂上学习到的很多知识,基本上涵盖了老师所讲的,以及还需要自己去探索学习一些新知识,运用到实验中。虽然本次实验难度较大,但是由于前面的几次实验给我打了些许基础,让我开始这个实验的时候不至于不知所措。构建app的过程是顺利的,遇到的困难在网上找一找、自己钻研一下就可以顺利解决。由于时间问题,我只花了不到一个星期去完成这个实验,最后可以达到这样的效果,我是很满意的。在这里也感谢老师和同学们,感谢他们也教会了我许多东西,让我基本上手安卓,并做出这样的项目。