《移动项目实践》实验报告——Android自定义控件
实验目的
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自定义控件相关推荐
- 20155220 实验三 敏捷开发与XP实践 实验报告
20155220 实验三 敏捷开发与XP实践 实验报告 实验内容 XP基础 XP核心实践 相关工具 实验要求 没有Linux基础的同学建议先学习<Linux基础入门(新版)><Vim ...
- 20155226 实验三 敏捷开发与XP实践 实验报告
20155226 实验三 敏捷开发与XP实践 实验报告 实验内容 XP基础 XP核心实践 相关工具 实验要求 没有Linux基础的同学建议先学习<Linux基础入门(新版)><Vim ...
- 《Python程序设计》实验四 Python综合实践实验报告
<Python程序设计>实验四 Python综合实践实验报告 1.实验内容 Python综合应用:爬虫.数据处理.可视化.机器学习.神经网络.游戏.网络安全等. 在华为ECS服务器(Ope ...
- 软件测试与维护实验报告,软件测试技术与实践实验报告
软件测试技术与实践实验报告 (11页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 9.90 积分 北华大学计算机科学技术学院实 验 报 告课程名称 软件测 ...
- android消息响应实验报告,android实验一实验报告-20210401011015.docx-原创力文档
Last revision on 21 December 2020 Last revision on 21 December 2020 Android实验一实验报告 Android实验报告一 姓名:丁 ...
- 20175212童皓桢 实验三敏捷开发与XP实践实验报告
20175212童皓桢 实验三敏捷开发与XP实践实验报告 实验内容 XP基础 XP核心实践 相关工具 实验步骤 一.Code菜单功能的研究 Move Line/statement Down/Up:将某 ...
- 20175312 2018-2019-2 实验三 敏捷开发与XP实践 实验报告
20175312 2018-2019-2 实验三 敏捷开发与XP实践 实验报告 码云博客链接 https://gitee.com/dky20175312/dky_20175312_warehouse_ ...
- 北邮计算机网络dns实验报告,北邮计网实践实验报告范文
计算机网络技术是一门需要动手实践才能真正掌握知识的学科,多参加实践,多动手,可以学到更多知识.下面是爱汇网小编为大家整理的北邮计网实践实验报告范文,供大家阅读! 北邮计网实践实验报告范文篇1 开学第一 ...
- 仿真软件与应用实践 实验报告
仿真软件与应用实践 实验报告 https://download.csdn.net/download/weixin_45859534/1323187图直接粘贴传不上去 实验名称 Matlab简单介绍和操 ...
最新文章
- java的知识点运用_Java--知识点运用
- java中方法不调用会执行_java[新手]类里的方法没有调用为什么实现了?
- Boost:循环缓冲区总和的测试程序
- 处理JS中数据失真问题-随笔
- php上传png_PHP支持多种格式图片上传(支持jpg、png、gif)
- html5 dzzxjbd cn,UEditor实现单张图片上传至腾讯云(对象存储服务)功能(html5
- tensorflow:图(Graph)的核心数据结构与通用函数(Utility function)
- 关于sizeof(struct student)的问题
- html 椭圆特效,HTML帖图常用到的特效《椭圆形》(国外英语资料).doc
- 在淘宝,我如何做好一个项目的启动?
- Wox - 开源免费强大的快捷启动器辅助工具,快速高效率打开软件/搜索文件!
- 华为云HCS解决方案笔记HUAWEI CLOUD Stack【面试篇】
- 滴滴 NewSQL 演进之 Fusion 实践
- 中国象棋将帅问题的另类解法
- Visual Studio 历史简介
- 111. 二叉树的最小深度
- Android中添加和识别手势操作
- 想接私活?那你得来看看程序员接私活经验总结吧
- php 无法添加ico 图标,PHP 保存到桌面的代码,怎么加ICO图标跟乱码问题呢
- 京津冀地区限行算法PHP
热门文章
- Opera 发布新技术 Opera Unite
- 基于matlab的智能天线波束方向图仿真,基于MATLAB的智能天线波束方向图仿真
- flask html下拉列表,如何使用Flask和HTML从python列表创建下拉菜单
- linux 查询内存和核心数,Linux下查看操作系统信息、内存情况及cpu信息:cpu个数、核心数、线程数...
- tf rnn layer
- 计算机分组教学,中职计算机教学分组协作式学习论文
- python文件操作解码_python基础3之文件操作、字符编码解码、函数介绍
- java paint 怎么用_java如何使用paint方法画图
- 【推荐系统】基于MovieLens数据集实现的协同过滤算法
- 十七、去年jQuery的笔记