欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 运维知识 > Android >内容正文

Android

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

发布时间:2024/10/5 Android 42 豆豆
生活随笔 收集整理的这篇文章主要介绍了 《移动项目实践》实验报告——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自定义控件的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。