实验目的

1、熟悉App开发经常涉及的自定义控件相关技术,主要包括自定义视图的过程与步骤、自定义动画的原理与实现、自定义对话框的概念与示例、自定义通知栏的用法与定制;
2、熟悉四大组件之一的服务Service的生命周期与启停方式;

实验内容

“手机安全助手”的设计与实现。
开发思路请参考:课件《第6章 自定义控件.pptx》
该项目采用多种自定义控件的相关技术,并同时运用多种存储技术。通过该实战项目的练习能够加深自定义控件的用法理解,还能复习巩固前两章的存储技术知识。
界面效果如下:

手机安全助手的流量页面


上拉应用列表的流量页面

实验过程(实验的设计思路、关键源代码等)

源代码:https://gitee.com/shentuzhigang/mini-project/tree/master/android-traffic

package io.shentuzhigang.demo.trafficimport android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.ListView
import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
import io.shentuzhigang.demo.traffic.R
import io.shentuzhigang.demo.traffic.adapter.AppInfoAdapter
import io.shentuzhigang.demo.traffic.util.AppUtil//import androidx.appcompat.app.AppCompatActivity;
/*** Created by ouyangshen on 2017/10/14.*/
class AppInfoActivity : AppCompatActivity() {private var lv_appinfo // 声明一个列表视图对象: ListView? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_app_info)// 从布局文件中获取名叫lv_appinfo的列表视图lv_appinfo = findViewById(R.id.lv_appinfo)initTypeSpinner()}// 初始化应用类型的下拉框private fun initTypeSpinner() {val typeAdapter = ArrayAdapter(this,R.layout.item_select, typeArray)val sp_list = findViewById<Spinner>(R.id.sp_type)sp_list.prompt = "请选择应用类型"sp_list.adapter = typeAdaptersp_list.onItemSelectedListener = TypeSelectedListener()sp_list.setSelection(0)}private val typeArray = arrayOf("所有应用", "联网应用")internal inner class TypeSelectedListener : AdapterView.OnItemSelectedListener {override fun onItemSelected(arg0: AdapterView<*>?, arg1: View, arg2: Int, arg3: Long) {// 获取已安装的应用信息队列val appinfoList = AppUtil.getAppInfo(this@AppInfoActivity, arg2)// 构建一个应用信息的列表适配器val adapter = AppInfoAdapter(this@AppInfoActivity, appinfoList)// 给lv_appinfo设置应用信息列表适配器lv_appinfo!!.adapter = adapter}override fun onNothingSelected(arg0: AdapterView<*>?) {}}companion object {private const val TAG = "AppInfoActivity"}
}
package io.shentuzhigang.demo.trafficimport android.net.TrafficStats
import android.os.*
import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import io.shentuzhigang.demo.traffic.R
import io.shentuzhigang.demo.traffic.adapter.TrafficInfoAdapter
import io.shentuzhigang.demo.traffic.util.AppUtil
import io.shentuzhigang.demo.traffic.util.StringUtil/*** Created by ouyangshen on 2017/10/14.*/
class TrafficInfoActivity : AppCompatActivity() {private var tv_traffic // 声明一个列表视图对象: TextView? = nullprivate var lv_traffic: ListView? = nullprivate val mHandler = Handler() // 声明一个处理器对象override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_traffic_info)tv_traffic = findViewById(R.id.tv_traffic)// 从布局文件中获取名叫lv_traffic的列表视图lv_traffic = findViewById(R.id.lv_traffic)// 延迟50毫秒后开始刷新应用流量数据mHandler.postDelayed(mRefresh, 50)}// 定义一个刷新任务private val mRefresh = Runnable {val desc = String.format("""当前总共接收流量:%s其中接收数据流量:%s当前总共发送流量:%s其中发送数据流量:%s""".trimIndent(),StringUtil.formatData(TrafficStats.getTotalRxBytes()),  // 获取总共接收的流量数据StringUtil.formatData(TrafficStats.getMobileRxBytes()),  // 获取数据流量的接收数据StringUtil.formatData(TrafficStats.getTotalTxBytes()),  // 获取总共发送的流量数据StringUtil.formatData(TrafficStats.getMobileTxBytes())) // 获取数据流量的发送数据tv_traffic!!.text = desc// 获取已安装的应用信息队列val appinfoList = AppUtil.getAppInfo(this@TrafficInfoActivity, 1)for (i in appinfoList!!.indices) {val item = appinfoList[i]// 根据应用编号获取该应用的接收流量数据// Android7之后,TrafficStats类的getUidRxBytes和getUidTxBytes只能查自身的流量。只有当前应用为系统应用之时,才能查其他应用的流量item!!.traffic = TrafficStats.getUidRxBytes(item.uid)appinfoList[i] = item}// 构建一个流量信息的列表适配器val adapter = TrafficInfoAdapter(this@TrafficInfoActivity, appinfoList)// 给lv_traffic设置流量信息列表适配器lv_traffic!!.adapter = adapter}companion object {private const val TAG = "TrafficInfoActivity"}
}
package io.shentuzhigang.demo.trafficimport android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.graphics.Paint
import android.os.*
import android.view.View
import android.widget.RelativeLayout
import android.widget.TextView
import io.shentuzhigang.demo.traffic.R
import io.shentuzhigang.demo.traffic.MainApplication
import io.shentuzhigang.demo.traffic.MobileConfigActivity
import io.shentuzhigang.demo.traffic.adapter.TrafficInfoAdapter
import io.shentuzhigang.demo.traffic.bean.AppInfo
import io.shentuzhigang.demo.traffic.service.TrafficService
import io.shentuzhigang.demo.traffic.util.AppUtil
import io.shentuzhigang.demo.traffic.util.DateUtil
import io.shentuzhigang.demo.traffic.util.SharedUtil
import io.shentuzhigang.demo.traffic.util.StringUtil
import io.shentuzhigang.demo.traffic.widget.CircleAnimation
import io.shentuzhigang.demo.traffic.widget.CustomDateDialog
import io.shentuzhigang.demo.traffic.widget.NoScrollListView
import java.util.*/*** Created by ouyangshen on 2017/10/14.*/
@SuppressLint("DefaultLocale")
class MobileAssistantActivity : Activity(), View.OnClickListener,CustomDateDialog.OnDateSetListener {private var tv_day: TextView? = nullprivate var rl_month: RelativeLayout? = nullprivate var tv_month_traffic: TextView? = nullprivate var rl_day: RelativeLayout? = nullprivate var tv_day_traffic: TextView? = nullprivate var nslv_traffic // 声明一个不滚动列表视图: NoScrollListView? = nullprivate var mDay // 选择的日期= 0private var mNowDay // 今天的日期= 0private val traffic_month: Long = 0 // 月流量数据private var traffic_day: Long = 0 // 日流量数据private var limit_month // 月流量限额= 0private var limit_day // 日流量限额= 0private val line_width = 10override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_mobile_assistant)// 创建一个通往流量服务的意图val intent = Intent(this, TrafficService::class.java)// 启动指定意图的服务startService(intent)initView()}// 初始化各视图对象private fun initView() {tv_day = findViewById(R.id.tv_day)rl_month = findViewById(R.id.rl_month)tv_month_traffic = findViewById(R.id.tv_month_traffic)rl_day = findViewById(R.id.rl_day)tv_day_traffic = findViewById(R.id.tv_day_traffic)// 从布局文件中获取名叫nslv_traffic的不滚动列表视图nslv_traffic = findViewById(R.id.nslv_traffic)findViewById<View>(R.id.iv_menu).setOnClickListener(this)findViewById<View>(R.id.iv_refresh).setOnClickListener(this)// 从共享参数中读取月流量限额limit_month = SharedUtil.Companion.getIntance(this)!!.readInt("limit_month", 1024)// 从共享参数中读取日流量限额limit_day = SharedUtil.Companion.getIntance(this)!!.readInt("limit_day", 30)mNowDay = DateUtil.getNowDateTime("yyyyMMdd").toInt()mDay = mNowDayval day = DateUtil.getNowDateTime("yyyy年MM月dd日")tv_day?.setText(day)tv_day?.setOnClickListener(this)// 延迟500毫秒后开始刷新日流量数据mHandler.postDelayed(mDayRefresh, 500)}private val mHandler = Handler() // 声明一个处理器对象// 定义一个日流量的刷新任务private val mDayRefresh = Runnable { refreshTraffic(mDay) }override fun onClick(v: View) {if (v.id == R.id.tv_day) { // 点击了日期文本val calendar = Calendar.getInstance()// 弹出自定义的日期选择对话框val dialog = CustomDateDialog(this)dialog.setDate(calendar[Calendar.YEAR], calendar[Calendar.MONTH],calendar[Calendar.DAY_OF_MONTH], this)dialog.show()} else if (v.id == R.id.iv_menu) { // 点击了三点菜单图标// 跳转到流量限额配置页面val intent = Intent(this, MobileConfigActivity::class.java)startActivity(intent)} else if (v.id == R.id.iv_refresh) { // 点击了转圈刷新图标mDay = mNowDay// 立即启动今天的流量刷新任务mHandler.post(mDayRefresh)}}override fun onDateSet(year: Int, month: Int, day: Int) {val date = String.format("%d年%d月%d日", year, month, day)tv_day!!.text = datemDay = year * 10000 + month * 100 + day// 选择完日期,立即启动流量刷新任务mHandler.post(mDayRefresh)}// 刷新指定日期的流量数据private fun refreshTraffic(day: Int) {val last_date = DateUtil.getAddDate("" + day, -1)// 查询数据库获得截止到昨日的应用流量val lastArray: ArrayList<AppInfo>? =MainApplication.instance?.mTrafficHelper?.query("day=$last_date")// 查询数据库获得截止到今日的应用流量val thisArray: ArrayList<AppInfo>? =MainApplication.instance?.mTrafficHelper?.query("day=$day")val newArray = ArrayList<AppInfo?>()traffic_day = 0// 截止到今日的应用流量减去截止到昨日的应用流量,二者之差便是今日的流量数据if (thisArray != null) {for (i in thisArray.indices) {val item = thisArray[i]if (lastArray != null) {for (j in lastArray.indices) {if (item.uid == lastArray[j].uid) {item.traffic -= lastArray[j].trafficbreak}}}traffic_day += item.trafficnewArray.add(item)}}// 给流量信息队列补充每个应用的图标val fullArray = AppUtil.fillAppInfo(this, newArray)// 构建一个流量信息的列表适配器val adapter = TrafficInfoAdapter(this@MobileAssistantActivity, fullArray)// 给nslv_traffic设置流量信息列表适配器nslv_traffic!!.adapter = adaptershowDayAnimation() // 显示日流量动画showMonthAnimation() // 显示月流量动画}// 显示日流量的圆弧动画private fun showDayAnimation() {rl_day!!.removeAllViews()val diameter = Math.min(rl_day!!.width, rl_day!!.height) - line_width * 2var desc = "今日已用流量" + StringUtil.formatData(traffic_day)// 创建日流量的圆弧动画val dayAnimation = CircleAnimation(this@MobileAssistantActivity)// 设置日流量动画的四周边界dayAnimation.setRect((rl_day!!.width - diameter) / 2 + line_width,(rl_day!!.height - diameter) / 2 + line_width,(rl_day!!.width + diameter) / 2 - line_width,(rl_day!!.height + diameter) / 2 - line_width)val trafficM = traffic_day / 1024.0f / 1024.0fdesc = if (trafficM > limit_day * 2) { // 超出两倍限额,则展示红色圆弧进度val end_angle =(if (trafficM > limit_day * 3) 360 else (trafficM - limit_day * 2) * 360 / limit_day) as IntdayAnimation.setAngle(0, end_angle)dayAnimation.setFront(Color.RED, line_width.toFloat(), Paint.Style.STROKE)String.format("%s\n超出限额%s", desc,StringUtil.formatData(traffic_day - limit_day * 1024 * 1024))} else if (trafficM > limit_day) { // 超出一倍限额,则展示橙色圆弧进度val end_angle =(if (trafficM > limit_day * 2) 360 else (trafficM - limit_day) * 360 / limit_day) as IntdayAnimation.setAngle(0, end_angle)dayAnimation.setFront(-0x6700, line_width.toFloat(), Paint.Style.STROKE)String.format("%s\n超出限额%s", desc,StringUtil.formatData(traffic_day - limit_day * 1024 * 1024))} else { // 未超出限额,则展示绿色圆弧进度val end_angle = (trafficM * 360 / limit_day).toInt()dayAnimation.setAngle(0, end_angle)dayAnimation.setFront(Color.GREEN, line_width.toFloat(), Paint.Style.STROKE)String.format("%s\n剩余流量%s", desc,StringUtil.formatData(limit_day * 1024 * 1024 - traffic_day))}rl_day!!.addView(dayAnimation)// 渲染日流量的圆弧动画dayAnimation.render()tv_day_traffic!!.text = desc}// 显示月流量的圆弧动画。未实现,读者可实践之private fun showMonthAnimation() {rl_month!!.removeAllViews()val diameter = Math.min(rl_month!!.width, rl_month!!.height) - line_width * 2tv_month_traffic!!.text = "本月已用流量待统计"// 创建月流量的圆弧动画val monthAnimation = CircleAnimation(this@MobileAssistantActivity)// 设置月流量动画的四周边界monthAnimation.setRect((rl_month!!.width - diameter) / 2 + line_width,(rl_month!!.height - diameter) / 2 + line_width,(rl_month!!.width + diameter) / 2 - line_width,(rl_month!!.height + diameter) / 2 - line_width)monthAnimation.setAngle(0, 0)monthAnimation.setFront(Color.GREEN, line_width.toFloat(), Paint.Style.STROKE)rl_month!!.addView(monthAnimation)// 渲染月流量的圆弧动画monthAnimation.render()}companion object {private const val TAG = "MobileAssistantActivity"}
}

实验结果(实验最终作品截图说明)



实验心得

1、熟悉App开发经常涉及的自定义控件相关技术,主要包括自定义视图的过程与步骤、自定义动画的原理与实现、自定义对话框的概念与示例、自定义通知栏的用法与定制;
2、熟悉四大组件之一的服务Service的生命周期与启停方式;

参考文章

《移动项目实践》实验报告——Android自定义控件相关推荐

  1. 20155220 实验三 敏捷开发与XP实践 实验报告

    20155220 实验三 敏捷开发与XP实践 实验报告 实验内容 XP基础 XP核心实践 相关工具 实验要求 没有Linux基础的同学建议先学习<Linux基础入门(新版)><Vim ...

  2. 20155226 实验三 敏捷开发与XP实践 实验报告

    20155226 实验三 敏捷开发与XP实践 实验报告 实验内容 XP基础 XP核心实践 相关工具 实验要求 没有Linux基础的同学建议先学习<Linux基础入门(新版)><Vim ...

  3. 《Python程序设计》实验四 Python综合实践实验报告

    <Python程序设计>实验四 Python综合实践实验报告 1.实验内容 Python综合应用:爬虫.数据处理.可视化.机器学习.神经网络.游戏.网络安全等. 在华为ECS服务器(Ope ...

  4. 软件测试与维护实验报告,软件测试技术与实践实验报告

    软件测试技术与实践实验报告 (11页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 9.90 积分 北华大学计算机科学技术学院实 验 报 告课程名称 软件测 ...

  5. android消息响应实验报告,android实验一实验报告-20210401011015.docx-原创力文档

    Last revision on 21 December 2020 Last revision on 21 December 2020 Android实验一实验报告 Android实验报告一 姓名:丁 ...

  6. 20175212童皓桢 实验三敏捷开发与XP实践实验报告

    20175212童皓桢 实验三敏捷开发与XP实践实验报告 实验内容 XP基础 XP核心实践 相关工具 实验步骤 一.Code菜单功能的研究 Move Line/statement Down/Up:将某 ...

  7. 20175312 2018-2019-2 实验三 敏捷开发与XP实践 实验报告

    20175312 2018-2019-2 实验三 敏捷开发与XP实践 实验报告 码云博客链接 https://gitee.com/dky20175312/dky_20175312_warehouse_ ...

  8. 北邮计算机网络dns实验报告,北邮计网实践实验报告范文

    计算机网络技术是一门需要动手实践才能真正掌握知识的学科,多参加实践,多动手,可以学到更多知识.下面是爱汇网小编为大家整理的北邮计网实践实验报告范文,供大家阅读! 北邮计网实践实验报告范文篇1 开学第一 ...

  9. 仿真软件与应用实践 实验报告

    仿真软件与应用实践 实验报告 https://download.csdn.net/download/weixin_45859534/1323187图直接粘贴传不上去 实验名称 Matlab简单介绍和操 ...

最新文章

  1. java的知识点运用_Java--知识点运用
  2. java中方法不调用会执行_java[新手]类里的方法没有调用为什么实现了?
  3. Boost:循环缓冲区总和的测试程序
  4. 处理JS中数据失真问题-随笔
  5. php上传png_PHP支持多种格式图片上传(支持jpg、png、gif)
  6. html5 dzzxjbd cn,UEditor实现单张图片上传至腾讯云(对象存储服务)功能(html5
  7. tensorflow:图(Graph)的核心数据结构与通用函数(Utility function)
  8. 关于sizeof(struct student)的问题
  9. html 椭圆特效,HTML帖图常用到的特效《椭圆形》(国外英语资料).doc
  10. 在淘宝,我如何做好一个项目的启动?
  11. Wox - 开源免费强大的快捷启动器辅助工具,快速高效率打开软件/搜索文件!
  12. 华为云HCS解决方案笔记HUAWEI CLOUD Stack【面试篇】
  13. 滴滴 NewSQL 演进之 Fusion 实践
  14. 中国象棋将帅问题的另类解法
  15. Visual Studio 历史简介
  16. 111. 二叉树的最小深度
  17. Android中添加和识别手势操作
  18. 想接私活?那你得来看看程序员接私活经验总结吧
  19. php 无法添加ico 图标,PHP 保存到桌面的代码,怎么加ICO图标跟乱码问题呢
  20. 京津冀地区限行算法PHP

热门文章

  1. Opera 发布新技术 Opera Unite
  2. 基于matlab的智能天线波束方向图仿真,基于MATLAB的智能天线波束方向图仿真
  3. flask html下拉列表,如何使用Flask和HTML从python列表创建下拉菜单
  4. linux 查询内存和核心数,Linux下查看操作系统信息、内存情况及cpu信息:cpu个数、核心数、线程数...
  5. tf rnn layer
  6. 计算机分组教学,中职计算机教学分组协作式学习论文
  7. python文件操作解码_python基础3之文件操作、字符编码解码、函数介绍
  8. java paint 怎么用_java如何使用paint方法画图
  9. 【推荐系统】基于MovieLens数据集实现的协同过滤算法
  10. 十七、去年jQuery的笔记