一、期末大作业的目的与要求:
1. 垃圾分类界面
请尽量模拟如下垃圾分类APP的功能,即参考如下的界面展示形式及功能模块。
2.具体要求
模拟图1所示垃圾分类APP,介绍垃圾分类与回收相关的一些知识点并能提供相应服务: 1) 建议包含的一些功能:活动之间的转换与数据传递;能适应不同的展示界面;有登录功能,强制下线功能;数据有多样化的持久化功能;能跨程序提供与共享数据;有展示一些多媒体的功能; 2) 较好的实现了书本上介绍的一些较成熟的功能,并能较好的把这些功能融合在一个完整且无大bug的APP里; 3) 能在此基础上构建自己的报告亮点,如实现了书本不一样的功能模块,或者为某个知识点找到一些新的应用场景,或者能解决同学们普遍存在的一些问题等; 4) 模拟的APP不局限于所参照APP的功能,即尽量模拟这些功能,不要求将每个功能都实现,如果某个功能不能体现已学知识点,可以不用考虑,当然如果能想办法实现出来,可以作为报告亮点;即不必与这些功能完全一样,可在这些功能基础上进行变通,达到类似的效果就可以;可以设计一些该APP没有的功能,并能清楚说明这些功能的实现方式、潜在的用途等;同时布局的设计也不必与参考APP完全一样,可根据自己需要适当调整; 5) 总体目标是灵活利用所学的知识点,做到每个功能各种实现方式的丰富化(如数据的持久化的三种实现方式都能在APP中有所体现),并且能体现不同实现方式的优劣,如果能在APP上体现会更好;
3.部分参考
1)功能实现参考:图1第3列图尽量参考第6章数据持久化技术的各个知识点;第1,4列尽量参考布局及活动之间的跳转,碎片的实现,多媒体展示功能;第5列可以利用数据持久化技术; 2)潜在的扩展功能:图1第1列尽量参考并利用Android基于位置的服务,比如能根据用户所在位置查找最近的垃圾投放点;添加一个小功能,整合网络技术的应用,即将一个HTML网页文件中的文本与图片网址进行分离,并将文本与图片用不同文件夹分开保持;利用数据后台下载的功能; 3)可以借鉴的部分章节内容,第12章可以让你的APP界面变得更美观;第14章展示了一个大型的工程,可以学习下多个功能怎样在一个工程里体现;
4.其他要求
1)构建的APP要格式工整,美观; 2)实验报告中需要有功能的描述、实验结果的截屏图像及详细说明;结果展示要具体,图文交叉解释;代码与文本重点要突出; 3)也欢迎采用课程后续章节的知识点完成本次大作业,如果实现的功能言之合理,会考虑酌情加分; 4)每位同学在最后一次课都需要上台报告,并且最好能现场演示APP的功能等,没上台报告的同学分数会受一定的影响; 5)报告由个人独立完成。 ###5.评分标准
- APP协议完成度高,与参考APP有一定的相似度,功能完善、丰富。能实现活动的编写、自定义用户界面的开发、碎片开发、广播机制、数据持久化与共享技术、网络技术、后台服务的应用等。-------------(60分)
- 模拟APP结构合理,代码规范,界面美观易用。项目报告撰写规范、美观整齐,内容详实且能准备描述项目内容和设计思想、原理、框架等,项目报告要求5号字、除前两页外A4版面不低于10页的长度。-------------(15分)
- 提供程序源代码和可执行程序(或安装程序);报告文档采用单独的word文档,项目所有代码(不是整个工程文件,应该总共不超过5M)在第17周之前打包作为附件进行上传blackboard系统;纸质版交到任课老师处。-------------(10分)
- 项目报告能够详细,准确的描述项目内容,并在最后一堂课有较好的展示效果。 -------------(15分)
二、实验过程和代码与结果
1.“我的垃圾分类APP”的构建过程及结果
(1)启动页面的实现
首先新建活动StartActivity,编写碎片文件frag_start.xml,在该碎片布局中采用了相对布局RelativeLayout,碎片中只包含一个TextView控件,使用layout_alignParentTop和layout_alignParentRight将其显示在屏幕右上角,并设置文字“跳过 (3s)”使用layout_margin控制边缘的距离,便于美观。
<?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">
<TextView
android:id="@+id/skip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:text="跳过 (3s)"
android:textSize="25dp"
android:layout_margin="15dp" />
</RelativeLayout>
新建一个文件StartFragment作为碎片的适配器,并在onCreateView中将其加载进来。
public class StartFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frag_start, container, false);
return view;
}
}
新建文件夹layout-sw600dp作为平板的碎片文件夹,在layout文件夹和这一文件夹编写activity_start,将碎片包含进来。
<?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:background="@drawable/welcome_small">
<fragment
android:id="@+id/start_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.refuseclassification.StartFragment" />
</LinearLayout>
<?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:background="@drawable/welcome_large">
<fragment
android:id="@+id/start_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.refuseclassification.StartFragment" />
</LinearLayout>
可以看到,手机和平板的activity_start都是直接使用刚刚写的碎片文件,并加载相应大小的图片。因为屏幕尺寸的原因,一张图片无法同时适配平板和手机,会导致图片被压缩或者拉伸,使得欢迎界面很难看,于是对于不同屏幕大小,布局文件加载了不同的照片。
接着编写StartActivity.java。首先定义TextView对象skip,用于后面获取欢迎界面的TextView实例,然后设置倒计时为3s,定义处理信息的handler和线程runnable,定义计时器timer。 首先编写任务类TimerTask,在task中新建一个线程用于计时,之所以这样是为了防止线程堵塞,主线程用于更新UI显示,子线程用来计时,最后更新skip,当倒计时为0的时候,把skip的字体隐藏起来。
public class StartActivity extends BaseActivity implements View.OnClickListener{
private TextView skip;
private int TIME = 3;
private Handler handler;
private Runnable runnable;
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
TIME--;
skip.setText("跳过 " + "(" + TIME + "s)");
if (TIME < 0) {
// 小于0时隐藏字体
timer.cancel();
skip.setVisibility(View.GONE);
}
}
});
}
};
接着编写活动的onCreate方法。首先通过下面这一语句将标题栏隐藏,保证欢迎页面全屏显示。
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
接着获取TextView实例并设置点击事件的监听器。然后使用timer这一计时器工具,执行前面定义的task任务,每隔1s执行一次该任务。我们使用handler来实现计时器,当计时结束时再过2s,跳转到登录界面。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 去掉app标题栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_start);
skip = findViewById(R.id.skip);
skip.setOnClickListener(this);// 设置点击跳过
timer.schedule(task, 1000, 1000);// 等待时间1s,停顿时间1s
// 设置不点击跳过
handler = new Handler();
handler.postDelayed(runnable = new Runnable() {
@Override
public void run() {
//从闪屏界面跳转到首界面
Intent intent = new Intent(StartActivity.this, LoginActivity.class);
startActivity(intent);
finish();
}
}, 5000);//延迟5S后发送handler信息
}
最后实现TextView的点击事件。用switch……case……语句来判断点击的View的id,若是skip,则跳转到登录界面,然后将runnable线程结束。
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.skip:
// 跳转到登录页面
Intent intent = new Intent(StartActivity.this, LoginActivity.class);
startActivity(intent);
finish();
if (runnable != null) {
handler.removeCallbacks(runnable);
}
break;
default:
break;
}
}
}
最后,在Manifest中将启动活动修改为StartActivity。
<activity android:name=".StartActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
结果如图所示。图片在网上随便找一张就可以了。
(2)登陆界面
新建活动LoginActivity,编写碎片活动frag_login.xml。该界面总体是LinearLayout布局,其中包含了四个横向分布的LinearLayout。前两个布局包含了一个TextView和一个EditText,用来设置输入账号和密码;第三个布局设置记住密码的选项;最后一个设置两个按钮,一个用于登录,一个用于注册;最后加一个提示,使用户可以按照正确的方式进行注册。
<?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="60dp"
android:orientation="horizontal">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:text="账户:"
android:padding="10dp"/>
<EditText
android:id="@+id/account"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:text="密码:"
android:padding="10dp"/>
<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:inputType="textPassword" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/remember_pass"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="记住密码" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/login"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:text="登录" />
<Button
android:id="@+id/register"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:text="注册" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="提示:若要进行注册,请填写账户和密码然后点击注册"
android:textSize="16dp" />
</LinearLayout>
新建一个LoginFragment作为碎片的适配器,在onCreateView中将其加载进来。
public class LoginFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frag_login, container, false);
return view;
}
}
和之前的欢迎界面一样,登陆界面也要适应平板和手机这些不同大小屏幕的需求,所以在layout文件夹和layout-sw600dp文件夹中编写activity_login.xml文件,用于适配不同屏幕。
<?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/login_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#64E269" />
<fragment
android:id="@+id/login_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.refuseclassification.LoginFragment" />
</LinearLayout>
小屏幕的布局这里使用了Toolbar来代替系统的ActionBar,设置为自己的颜色。然后直接用fragment将之前的登录碎片包含进来。 而大屏幕的布局同样使用了Toolbar,然后使用一个横向排列的线性布局,将登录的控件控制在屏幕的中央。
<?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/login_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#64E269" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<fragment
android:id="@+id/login_fragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:name="com.example.refuseclassification.LoginFragment" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
</LinearLayout>
实现注册和登录功能,自然要使用数据持久化技术,来储存用户名的账号和密码,这里使用SQLite来实现。首先新建MyDatabaseHelper,使用MYSQL语言建立Account数据表。
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_ACCOUNT = "create table Account (" +
"id integer primary key autoincrement, " +
"account text, " +
"password text)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_ACCOUNT);
Toast.makeText(mContext, "注册成功", Toast.LENGTH_SHORT);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists Account");
onCreate(db);
}
}
最后编写LoginActivity。首先调用findViewById()方法获得账号输入框、密码输入框和登陆按钮的实例。先判断是否有选择“记住密码”这一选项,如果有,就将账号和密码再次设置在输入框内。
public class LoginActivity extends BaseActivity {
private SharedPreferences pref;
private SharedPreferences.Editor editor;
private EditText accountEdit;
private EditText passwordEdit;
private Button login;
private Button register;
private CheckBox rememberPass;
private Toolbar toolbar;
private MyDatabaseHelper dbhelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
pref = PreferenceManager.getDefaultSharedPreferences(this);
toolbar = (Toolbar) findViewById(R.id.login_toolbar);
toolbar.setTitle("登录");
new setTitleCenter().setTitleCenter(toolbar);
accountEdit = (EditText) findViewById(R.id.account);
passwordEdit = (EditText) findViewById(R.id.password);
rememberPass = (CheckBox) findViewById(R.id.remember_pass);
login = (Button) findViewById(R.id.login);
dbhelper = new MyDatabaseHelper(this, "Account password", null, 2);
register = (Button) findViewById(R.id.register);
boolean isRemember = pref.getBoolean("remember_password", false);
if (isRemember) {
// 将账号和密码都设置到文本框中
String account = pref.getString("account", "");
String password = pref.getString("password", "");
accountEdit.setText(account);
passwordEdit.setText(password);
rememberPass.setChecked(true);
}
然后在登陆按钮的点击事件中,获取account和password的内容并获得数据库的实例以便读写数据库,先判断是否有已经创建了数据库,如果没有就先创建。接着调用SQLiteDatabase的query()方法,使用第一个参数指明去查询Account表,后面参数全部为null。查询完后获得一个Cursor对象,接着调用它的moveToFirst()方法将数据的指针移动到第一行的数据,然后进去一个循环,遍历每一行数据,如果有匹配输入的,则成功登陆,进入下一个活动。如果没有,则弹窗显示账号或密码输入错误。
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int flag = 1; //表示账号和密码是否正确
String account = accountEdit.getText().toString();
String password = passwordEdit.getText().toString();
SQLiteDatabase db = dbhelper.getWritableDatabase();
Cursor cursor = db.query("Account", null, null,
null, null, null, null);
if (cursor.moveToFirst()) {
do {
String hadaccount = cursor.getString(cursor.getColumnIndex("account"));
String hadpassword = cursor.getString(cursor.getColumnIndex("password"));
if (account.equals(hadaccount) && password.equals(hadpassword)) {
editor = pref.edit();
if (rememberPass.isChecked()) {
editor.putBoolean("remember_password", true);
editor.putString("account", account);
editor.putString("password", password);
}
else {
editor.clear();
}
editor.apply();
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
flag = 0;
}
} while (cursor.moveToNext());
}
cursor.close();
if (flag == 1) {
Toast.makeText(LoginActivity.this, "账号或密码错误", Toast.LENGTH_SHORT).show();
}
}
});
而在注册的按钮的点击事件中,则是将输入在账号框和密码框中的内容加入到数据库中,完成注册的功能。
register.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbhelper.getWritableDatabase();
String account = accountEdit.getText().toString();
String password = passwordEdit.getText().toString();
ContentValues values = new ContentValues();
values.put("account", account);
values.put("password", password);
db.insert("Account", null, values);
Toast.makeText(LoginActivity.this, "注册成功", Toast.LENGTH_SHORT).show();
}
});
}
}
对于Toolbar,我们需要让它可以显示标题,并且居中,由于后面要写很多活动,因此将标题居中的方法封装到一个类setTitleCenter中。
public class setTitleCenter {
public void setTitleCenter(Toolbar toolbar) {
String title = "title";
final CharSequence originalTitle = toolbar.getTitle();
toolbar.setTitle(title);
for (int i = 0; i < toolbar.getChildCount(); i++) {
View view = toolbar.getChildAt(i);
if (view instanceof TextView) {
TextView textView = (TextView) view;
if (title.equals(textView.getText())) {
textView.setGravity(Gravity.CENTER);
Toolbar.LayoutParams params = new Toolbar.LayoutParams
(Toolbar.LayoutParams.WRAP_CONTENT, Toolbar.LayoutParams.MATCH_PARENT);
params.gravity = Gravity.CENTER;
textView.setLayoutParams(params);
}
}
toolbar.setTitle(originalTitle);
}
}
}
这样子只需要在每个活动主函数中获得Toolbar实例,设置标题并调用这个函数。
toolbar = (Toolbar) findViewById(R.id.login_toolbar);
toolbar.setTitle("登录");
new setTitleCenter().setTitleCenter(toolbar);
最后登录界面结果如下,若为进行注册,登录的时候会显示账号和密码错误。
若已经注册,则可以成功登录