(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的过程是顺利的,遇到的困难在网上找一找、自己钻研一下就可以顺利解决。由于时间问题,我只花了不到一个星期去完成这个实验,最后可以达到这样的效果,我是很满意的。在这里也感谢老师和同学们,感谢他们也教会了我许多东西,让我基本上手安卓,并做出这样的项目。