【移动设备交互及应用】我的校园安卓APP设计

源码:https://github.com/Alex-Shen1121/SZU_Learning_Resource/tree/main/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%8E%E8%BD%AF%E4%BB%B6%E5%AD%A6%E9%99%A2/%E7%A7%BB%E5%8A%A8%E8%AE%BE%E5%A4%87%E4%BA%A4%E4%BA%92%E5%BA%94%E7%94%A8/%E5%AE%9E%E9%AA%8C/%E5%AE%9E%E9%AA%8C3-%E6%88%91%E7%9A%84%E6%A0%A1%E5%9B%AD

一、实验目的与内容:

目的:掌握安卓中活动的编写、自定义用户界面的开发、碎片开发、广播机制以及数据持久化技术等;并能通过对课堂知识进行扩展来完善该界面,并使界面尽量美观。
内容要求:

  1. 请尽量模拟如下深大校园主页的功能,参考:
    https://www1.szu.edu.cn/
    在这里插入图片描述

  2. 具体要求:

    1. 该实现的界面在某些地方应体现出如下功能:
      a. 界面能对平板与手机平台进行自适应(参考第4章碎片);
      b. 能对用户身份有强制下线的功能,比如网络中断,登录界面强行退出并显示提示错误的界面;
      c. 界面某些地方体现数据持久化的技术,如文件数据的读取、存储的多种实现方式,并简单阐述几种实现方式具体的适用场景;
      d. 界面要比较工整,没必要实现参考界面上的所有子项,能保证自己的界面实现能有扩展到参考界面的能力即可。
    2. 功能并不局限于上面的要求,可以根据自己的理解设计一些新的功能,并在报告文档中进行详细的阐述,作为报告的亮点;
      3)APP的布局尽快模仿参考界面,如果有较大的困难,可以只实现出右半边部分的界面,并尽量按上面要求进行完善;
    3. 对于某一种功能,可以在不同的子项处采用多种实现方式,并比较这些实现方式的不同及优劣势。
  3. 参考:尽量多的应用参考书《第一行代码 Android》第二版第2章活动、第3章UI开发第4章碎片、第5章广播机制与第6章数据持久化技术的各个知识点。

注意:

  1. 实验报告中需要有功能的描述、实验结果的截屏图像及详细说明;
  2. 也欢迎采用其它章节的知识点完成本次实验报告,如果实现的功能言之合理,会考虑酌情加分。

二、实验过程和代码与结果

“我的校园”APP的构建过程及结果

注:测试环境为华为nova5 pro 6.39寸手机与虚拟机Nexus 9 8.86寸平板。
界面展示:
主页:
(平板)在这里插入图片描述(手机) 在这里插入图片描述
学生界面:
在这里插入图片描述 在这里插入图片描述 在这里插入图片描述
在这里插入图片描述 在这里插入图片描述
管理员界面:
在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述
构建过程:
具体项目构建过程可以参考github
URL:https://github.com/Alex-Shen1121/SZU_Website_Android

  1. 编写Activity基类与ActivityCollector为实现强制下线功能打下基础
  2. 设计登陆界面布局,编写账号登录逻辑
  3. 设计编写管理员主界面,完成用户信息展示
  4. 实现强制下线功能
  5. 设计管理员修改界面,完成文件的修改逻辑
  6. 设计学生界面,并完成左右Fragment的平板手机自适应功能
  7. 编写学生界面各个布局的内容填充
  8. 实现网页的跳转
  9. 修复部分bug

具体各个部分的关键代码会在下一个部分进行展示。

请详细说明“我的校园”APP的功能、出现的关键问题及解决方案

“我的校园”APP的功能亮点介绍及代码分析:
(以下截图将以手机页面进行展示,平板上有基本一致的效果)
本次APP提供两个默认账号用于登录。
学生账户用户名:student 学生账户密码:123456
管理员账户用户名:admin 管理员账户密码:123456

①账号登陆匹配

主要技术参考章节:7.2文件存储,3 Activity
思路:

  1. 通过文件读写的方式获取APP的默认账户,后期可以通过连接云端服务器。
  2. 将正确的用户名密码以键值对的方式保存。
  3. 与用户输入的用户名密码进行匹配,如果完全匹配则进入相对应界面,否则弹出错误提示,并删去密码,重新输入(更加符合使用习惯)。

具体核心代码展示:

  1. 添加默认账号文件
private fun addDefaultAccount() {
//判断文件是否存在
        val file = File("/data/data/com.example.experiment3/files/account_password.txt")
        if (!file.exists()) {
            val output = openFileOutput("account_password.txt", MODE_APPEND)
            val writer = BufferedWriter(OutputStreamWriter(output))
            writer.use {
                it.write("admin\n")
                it.write("123456\n")
                it.write("student\n")
                it.write("123456\n")
            }
        }
    }

在这里插入图片描述

  1. 读写默认账号文件,键值对的方式保存。
//设置正确账号map表
val accountList = mutableMapOf<String, String>()
private fun setAccountList() {
        val input = openFileInput("account_password.txt")
        val reader = BufferedReader(InputStreamReader(input))
        var line = 0
        val account = ArrayList<String>()
        val password = ArrayList<String>()
        reader.use {
            reader.forEachLine {
                line += 1
                if (line % 2 == 1)
                    account.add(it)
                else if (line % 2 == 0)
                    password.add(it)
            }
        }
        for (i in account.indices) {
            accountList[account[i]] = password[i]
        }
  1. 获得控件Text信息,用户名密码匹配。
    匹配成功:
if (accountList[account] == password) {
                Toast.makeText(this, "登陆成功", Toast.LENGTH_SHORT).show()
                ......
                //进入管理员界面
                //有且仅有一个管理员账号
                if (account == "admin") {
                    ......
                    val intent = Intent(this, AdminMenu::class.java)
                    startActivity(intent)
                    finish()
                }
                //其他全部进入学生界面
                else {
                    ......
                    val intent = Intent(this, StudentMenu::class.java)
                    startActivity(intent)
                    finish()
                }
            } 

匹配失败:

else {
                AlertDialog.Builder(this).apply {
                    setTitle("登陆失败")
                    setMessage("请重新检查用户名与密码。\n或者联系管理员。")
                    setCancelable(false)
                    setPositiveButton("OK") { _, _ -> }
                    show()
                }
                passwordEdit.text = null
            }

其他:
1.在布局文件中的EditText中添加属性android:singleLine=“true”,防止用户输入回车导致形成多行文字输入。
2.EditText中添加属性android:inputType=“textPassword”,输入的文字会以···显示,防止密码泄露。

②记住密码功能

主要技术参考章节:7.3SharePreferences存储
思路:

  1. 进入登录页面时,检查prefs中“remember_password”是否为true,true则将保存的用户名密码直接显示在输入框内,否则不做显示。
  2. 登录账号时将用户名密码以及是否记住密码选项存入SharePreferences为下次登录做准备。

具体核心代码展示:

  1. 登陆时存入SharePreferences
val editor=prefs.edit()
if(rememberPass.isChecked){
  editor.putBoolean("remember_password",true)
  editor.putString("account",account)
  editor.putString("password",password)
}else{
  editor.clear()
}

在这里插入图片描述

  1. 登录时检查上次是否保记住密码
//记住密码功能
        val prefs=getPreferences(Context.MODE_PRIVATE)
        val isRemember=prefs.getBoolean("remember_password",false)
        if(isRemember){
            val account=prefs.getString("account","")
            val password=prefs.getString("password","")
            accountEdit.setText(account)
            passwordEdit.setText(password)
            rememberPass.isChecked=true
        }

③设置个人信息

主要技术参考章节:7.3SharePreferences存储
思路:

  1. 从SharePreferences读取登录用户的信息
    具体核心代码展示:
var user_name = "用户名:"
var user_identity = "身份:"
val prefs=getSharedPreferences("LoginUI.LoginActivity", MODE_PRIVATE)
user_name+=prefs.getString("account","null")
user_identity+=prefs.getString("identity","null")
userName.text = user_name
userIdentity.text = user_identity

在这里插入图片描述

④强制下线

主要技术参考章节:6全局大喇叭,广播机制
思路:

  1. 设计BaseActicvity作为Activity的基类,每创建一个新的活动就加入ActivityCollector。每当接收到强制下线的广播通知时,就调用ActivityCollector回收所有活动,回到登陆界面。
  2. ForceOfflineReceiver继承自BroadcastReceiver,当收到对应广播时,弹出提示,并返回界面。
  3. 强制下线按钮被点击时发出广播信息。

具体核心代码展示:

  1. ActivityCollector对象
object 1.ActivityCollector {
    private val activities = ArrayList<Activity>()

    fun addActivity(activity: Activity) {
        activities.add(activity)
    }

    fun removeActivity(activity: Activity) {
        activities.remove(activity)
    }

    fun finishAll() {
        for (activity in activities) {
            if (!activity.isFinishing) {
                activity.finish()
            }
        }
        activities.clear()
    }
}
  1. BaseActivity基类与ForceOfflineReceiver接收器
open class BaseActivity : AppCompatActivity() {
    lateinit var receiver: ForceOfflineReceiver
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ActivityCollector.addActivity(this)
    }
    override fun onResume() {
        super.onResume()
        val intentFilter = IntentFilter()
        intentFilter.addAction("com.example.experiment3.FORCE_OFFLINE")
        receiver = ForceOfflineReceiver()
        registerReceiver(receiver, intentFilter)
                registerReceiver(receiver, intentFilter)
    }

    override fun onPause() {
        super.onPause()
        unregisterReceiver(receiver)
    }

    override fun onDestroy() {
        super.onDestroy()
        ActivityCollector.removeActivity(this)
    }

    inner class ForceOfflineReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            android.app.AlertDialog.Builder(context).apply {
                setTitle("Warning")
                setMessage("强制下线。请重新登录。")
                setCancelable(false)
                setPositiveButton("OK") { _, _ ->
                    ActivityCollector.finishAll()
                    val i = Intent(context, LoginActivity::class.java)
                    context.startActivity(i)
                }
                show()
            }
        }

在这里插入图片描述

  1. 强制下线按钮
ForceOffline.setOnClickListener() {
val intent = Intent("com.example.experiment3.FORCE_OFFLINE")
intent.setPackage(packageName)
sendBroadcast(intent)
}

其他:1. 必须将需要的Activity继承于BaseActivity,否则无法接收到广播信息。

⑤管理员菜单下拉框选择

主要技术参考章节:3 Activity跳转,其他
思路:

  1. 使用Spinner控件实现下拉框。
  2. 利用intent实现页面跳转,并将选择信息传递到下一个Activity。

具体核心代码展示:

  1. 初始化Spinner,并设置被选中时的文字样式。
val mItems = arrayOf("重要通知", "学术讲座", "深大新闻")
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, mItems)
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter

spinner.onItemSelectedListener = object : OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View, pos: Int, id: Long) {
val tv = view as TextView
tv.setTextColor(Color.BLUE)
tv.textSize = 20f
tv.gravity = Gravity.CENTER
}
}

在这里插入图片描述 在这里插入图片描述

  1. 点击事件
addInform.setOnClickListener() {
    val intent = Intent(this, AdminAddInform::class.java)
    intent.putExtra("column", spinner.selectedItem.toString())
    startActivity(intent)
}

⑥ 管理员添加通知

主要技术参考章节:7.2文件存储
思路:

  1. 通过intent获取到用户选择选项,一开始将部分页面的布局属性设置成android:visibility=“invisible”,根据选择将部分页面布局进行展示。
  2. 将用户输入的内容追加读写入文件,当读到空串时返回报错信息。

具体核心代码展示:

  1. 页面布局(下拉框省略,大致同上)
//设置栏目
var column_title = "修改栏目:"
column_title += intent.getStringExtra("column")
columnTitle.text = column_title

//编辑布局
when (intent.getStringExtra("column")) {
    "重要通知" -> {
        informType.visibility = View.VISIBLE
        informTitle.visibility = View.VISIBLE
        blank3.visibility = View.VISIBLE
......
    }
    "学术讲座" -> {
        dateTime.visibility = View.VISIBLE
        place.visibility = View.VISIBLE
        blank.visibility = View.VISIBLE
        blank2.visibility = View.VISIBLE
        informTitle.visibility = View.VISIBLE
    }
    "深大新闻" -> {
        informTitle.visibility = View.VISIBLE
    }
}

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

  1. 文档修改,以学术讲座为例
"重要通知" -> {
val output = openFileOutput("important_information.txt", MODE_APPEND)
    val writer = BufferedWriter(OutputStreamWriter(output))
    val type = informType.selectedItem.toString()
    val content = informTitle.text.toString()
    //如果未填写,做出反馈
    if (content == "") {
    	Toast.makeText(this, "请正确输入内容", Toast.LENGTH_SHORT).show()
        return@setOnClickListener
    }
    writer.use {
        it.write(type)
        it.newLine()
        it.write(content)
        t.newLine()
    }
    val intent = Intent(this, AdminMenu::class.java)
    startActivity(intent)
    finish()
}

在这里插入图片描述

⑦平板手机自适应

主要技术参考章节:5 探究fragment
思路:

  1. 将左右界面设计为fragment,并且将右fragment的可见性设置为invisible。
  2. 判断当前设备的屏幕大小,如果为平板则将右fragment可见性设置为visible,形成双栏展示的效果。

具体核心代码展示:

  1. 判断手机或平板,是否要双栏展示。
private var isTwoPane = false
isTwoPane = activity?.findViewById<View>(R.id.StudentRightLayout) != null
  1. 刷新左右布局
class RightMainActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_right_main)
        val fragment = RightMainFrag as RightFragment
        supportActionBar?.hide()
        fragment.refresh()
    }
}

fun refresh() {
    contentLayout.visibility = View.VISIBLE
}
  1. 平板/手机xml布局
    手机:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    tools:context=".Student.StudentMenu">

    <fragment
        android:id="@+id/StudentLeftFrag"
        android:name="com.example.experiment3.Student.LeftFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

在这里插入图片描述
平板:左右1.65:3平分宽度

<?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="horizontal">

    <fragment
        android:id="@+id/StudentLeftFrag"
        android:name="com.example.experiment3.Student.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1.65" />

    <FrameLayout
        android:id="@+id/StudentRightLayout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3">

        <fragment
            android:id="@+id/StudentRightFrag"
            android:name="com.example.experiment3.Student.RightFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </FrameLayout>

</LinearLayout>

在这里插入图片描述

⑧新闻列表刷新

主要技术参考章节:7.2 文件存储,4.6 RecyclerView,3 活动
思路:

  1. 按行读取对应文件内容,插入RecyclerView中实现新闻刷新。
  2. 点击新闻头时,触发点击事件刷新按钮颜色,以及不同新闻栏的切换。
  3. 点击其他–内部网时,根据平板或手机选择是刷新界面或者活动跳转。
    展示的效果。

具体核心代码展示:
1.颜色刷新,新闻栏切换

important_inform_button.setOnClickListener() {
            important_inform_button.setBackgroundColor(Color.BLUE)
            important_inform_button.setTextColor(Color.WHITE)
            academic_lecture_button.setBackgroundColor(Color.TRANSPARENT)
            academic_lecture_button.setTextColor(Color.RED)
            szu_news_button.setBackgroundColor(Color.TRANSPARENT)
            szu_news_button.setTextColor(Color.RED)
            importantInformRecyclerView.visibility = View.VISIBLE
            academicLectureRecyclerView.visibility = View.GONE
            szuNewsRecyclerView.visibility = View.GONE
        }
  1. 读取文件(以学术讲座为例)(与前面介绍的管理员添加通知形成呼应,如添加会有显示)
    private fun getInform2(): ArrayList<academic_lecture> {
        val informList = ArrayList<academic_lecture>()
        val input = activity?.openFileInput("academic_lecture.txt")
        val reader = BufferedReader(InputStreamReader(input))
        var line = 0
        val date = ArrayList<String>()
        val title = ArrayList<String>()
        val place = ArrayList<String>()
        reader.use {
            reader.forEachLine {
                line += 1
                if (line % 3 == 1)
                    date.add(it)
                else if (line % 3 == 0) {
                    place.add(it)
                } else if (line % 3 == 2)
                    title.add(it)
            }
        }
        for (i in date.indices) {
            informList.add(academic_lecture(date[i], title[i], place[i]))
            }
        return informList
    }
  1. recyclerView设置(以学术讲座为例)
//设置学术讲座
val layoutManager3 = LinearLayoutManager(activity)
szuNewsRecyclerView.layoutManager = layoutManager3
val adapter3 = StudentMenu.SzuNewsAdapter(getInform3())
szuNewsRecyclerView.adapter = adapter3

//学术讲座RecyclerViewAdapter
    class AcademicLectureAdapter(private val informList: List<academic_lecture>) :
        RecyclerView.Adapter<AcademicLectureAdapter.ViewHolder>() {

        inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            val AcademicLectureDate: TextView = view.findViewById(R.id.lecturedate)
            val AcademicLectureTitle: TextView = view.findViewById(R.id.lecturetitle)
            val AcademicLecturePlace: TextView = view.findViewById(R.id.lectureplace)
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.academic_lecture_item, parent, false)
            return ViewHolder(view)
        }

        override fun getItemCount() = informList.size

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            var inform = informList[position]
            holder.AcademicLectureTitle.text = inform.title
            holder.AcademicLectureDate.text = inform.date
            holder.AcademicLecturePlace.text = inform.place
        }
    }

注:visibility属性中Gone与Invisible的区别是Gone不保留原控件所占位置,而invisible保存原控件所占位置。

  1. 根据平板或手机选择是刷新界面或者活动跳转
szu_website_button.setOnClickListener() {
    //手机版
    if (!isTwoPane) {
        val intent = Intent(this, RightMainActivity::class.java)
        startActivity(intent)
    }
    //平板版
    else {
        contentLayout.visibility=View.VISIBLE
        right1.textSize = 15F
        right2.textSize = 15F
        right3.textSize = 15F
        right4.textSize = 15F
    }
}

⑨网页活动跳转

主要技术参考章节:3 活动
思路:

  1. 点击办事大厅实现网页跳转(其他按钮功能类似,没有做实现)

具体核心代码展示:

task1_1.setOnClickListener(){
    val intent = Intent(Intent.ACTION_VIEW)
    intent.data = Uri.parse("http://ehall.szu.edu.cn/new/index.html")
    startActivity(intent)
}

在这里插入图片描述

三、实验总结

本次实验在完成基本要求的基础上,加入了自己的一些理解与创新。
实验过程中遇到了两个比较大的问题是

  1. 添加默认账号列表时会有多次重复添加的问题
    一开始时的思路时在打开应用时,每次都向指定文件中加入默认账号信息。由于提取信息是通过map键值对的方式,所以并没有影响,也没有做修改。但是当用户使用次数逐渐增多时,文件内容越积越多,显然不合理。
    所以经过查询,发现可以通过查询文件是否已经存在,来判断是否要加入新信息。即通过if (!file.exists())来判断,从而提高效率。

  2. 利用Intent传输活动间信息间信息丢失
    一开始时通过intent.putExtraString()的方式向下一个活动传递用户名,身份信息。但是随着开发的进行,发现当活动通过其他方式被唤醒时会出来没有intent传递信息的情况,导致获取到空串。
    所以我选择将用户信息通过Share Preference的方式进行存储,这样就可以随时随地获取账号信息了。

总体而言,本次实验结合了各种技术,比较好的完成了深大内部网的复现任务。

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
总结高校目前信息化发展的技术趋势,已经逐步向资源共享、服务导向架构、社交网络、移动互联网、物联网和云计算等技术手段上进行信息化建设的转变, 1、资源共享 通过信息化技术手段实现各类信息资源的快速传递与共享,加快资源的积累、促进资源的高效利用,更好的服务于学院、师生和社会。 2、服务导向架构 基于SOA的理念组织和展现面向师生的一体化服务,通过服务之间定义良好的接口和契约连接应用程序的不同功能单元(成为服务)联系起来,使得构建在各种系统中的服务可以以一种统一和通用的方式进行交互。 3、社交网络 基于SNS的设计思想,以教科研、社区活动中的“人”为中心,以教科研活动“过程”为主线,提供面向人际交互与协同应用。通过人际协作过程的信息化,为广大师生提供一个服务教学、科研活动、社区生活全过程中的虚拟网络交互环境。 4、云计算 云计算不仅带来了新技术,如:虚拟化、自动化、标准化和海量数据处理能力,大大降低信息化建设成本、提高效率;更带来了一种新的服务交付模式、一种服务化的理念,对提升用户体验、改进教学手段和创新管理文化都带来了新思路。 5、移动互联网 移动互联网的快速发展已经昭示了不远的将来是真正的后PC时代,移动终端将是人们访问信息和获取服务的主要渠道,将移动计算纳入到信息化的基础架构和服务范畴这对提升用户体验、提高效率、流程重组和实现价值最大化都会有巨大作用。 6、物联网 物联网是实现智慧校园的有力途径,它强调感知,即透过对真实世界物体的“感”应取得的数据信息到后台的“知”处理,获得知识和洞察力,辅助决策和规划。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex_SCY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值