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

937 阅读13分钟

一、期末大作业的目的与要求:

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.评分标准

  1. APP协议完成度高,与参考APP有一定的相似度,功能完善、丰富。能实现活动的编写、自定义用户界面的开发、碎片开发、广播机制、数据持久化与共享技术、网络技术、后台服务的应用等。-------------(60分)
  2. 模拟APP结构合理,代码规范,界面美观易用。项目报告撰写规范、美观整齐,内容详实且能准备描述项目内容和设计思想、原理、框架等,项目报告要求5号字、除前两页外A4版面不低于10页的长度。-------------(15分)
  3. 提供程序源代码和可执行程序(或安装程序);报告文档采用单独的word文档,项目所有代码(不是整个工程文件,应该总共不超过5M)在第17周之前打包作为附件进行上传blackboard系统;纸质版交到任课老师处。-------------(10分)
  4. 项目报告能够详细,准确的描述项目内容,并在最后一堂课有较好的展示效果。 -------------(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);

最后登录界面结果如下,若为进行注册,登录的时候会显示账号和密码错误。 手机界面平板界面 若已经注册,则可以成功登录 手机界面平板界面