《移动项目实践》实验报告——Android自定义控件
實(shí)驗(yàn)?zāi)康?/h1>
1、熟悉App開發(fā)經(jīng)常涉及的自定義控件相關(guān)技術(shù),主要包括自定義視圖的過程與步驟、自定義動(dòng)畫的原理與實(shí)現(xiàn)、自定義對話框的概念與示例、自定義通知欄的用法與定制;
2、熟悉四大組件之一的服務(wù)Service的生命周期與啟停方式;
實(shí)驗(yàn)內(nèi)容
“手機(jī)安全助手”的設(shè)計(jì)與實(shí)現(xiàn)。
開發(fā)思路請參考:課件《第6章 自定義控件.pptx》
該項(xiàng)目采用多種自定義控件的相關(guān)技術(shù),并同時(shí)運(yùn)用多種存儲(chǔ)技術(shù)。通過該實(shí)戰(zhàn)項(xiàng)目的練習(xí)能夠加深自定義控件的用法理解,還能復(fù)習(xí)鞏固前兩章的存儲(chǔ)技術(shù)知識(shí)。
界面效果如下:
手機(jī)安全助手的流量頁面
上拉應(yīng)用列表的流量頁面
實(shí)驗(yàn)過程(實(shí)驗(yàn)的設(shè)計(jì)思路、關(guān)鍵源代碼等)
源代碼: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 // 聲明一個(gè)列表視圖對象: ListView? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_app_info)// 從布局文件中獲取名叫l(wèi)v_appinfo的列表視圖lv_appinfo = findViewById(R.id.lv_appinfo)initTypeSpinner()}// 初始化應(yīng)用類型的下拉框private fun initTypeSpinner() {val typeAdapter = ArrayAdapter(this,R.layout.item_select, typeArray)val sp_list = findViewById<Spinner>(R.id.sp_type)sp_list.prompt = "請選擇應(yīng)用類型"sp_list.adapter = typeAdaptersp_list.onItemSelectedListener = TypeSelectedListener()sp_list.setSelection(0)}private val typeArray = arrayOf("所有應(yīng)用", "聯(lián)網(wǎng)應(yīng)用")internal inner class TypeSelectedListener : AdapterView.OnItemSelectedListener {override fun onItemSelected(arg0: AdapterView<*>?, arg1: View, arg2: Int, arg3: Long) {// 獲取已安裝的應(yīng)用信息隊(duì)列val appinfoList = AppUtil.getAppInfo(this@AppInfoActivity, arg2)// 構(gòu)建一個(gè)應(yīng)用信息的列表適配器val adapter = AppInfoAdapter(this@AppInfoActivity, appinfoList)// 給lv_appinfo設(shè)置應(yīng)用信息列表適配器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 // 聲明一個(gè)列表視圖對象: TextView? = nullprivate var lv_traffic: ListView? = nullprivate val mHandler = Handler() // 聲明一個(gè)處理器對象override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_traffic_info)tv_traffic = findViewById(R.id.tv_traffic)// 從布局文件中獲取名叫l(wèi)v_traffic的列表視圖lv_traffic = findViewById(R.id.lv_traffic)// 延遲50毫秒后開始刷新應(yīng)用流量數(shù)據(jù)mHandler.postDelayed(mRefresh, 50)}// 定義一個(gè)刷新任務(wù)private val mRefresh = Runnable {val desc = String.format("""當(dāng)前總共接收流量:%s其中接收數(shù)據(jù)流量:%s當(dāng)前總共發(fā)送流量:%s其中發(fā)送數(shù)據(jù)流量:%s""".trimIndent(),StringUtil.formatData(TrafficStats.getTotalRxBytes()), // 獲取總共接收的流量數(shù)據(jù)StringUtil.formatData(TrafficStats.getMobileRxBytes()), // 獲取數(shù)據(jù)流量的接收數(shù)據(jù)StringUtil.formatData(TrafficStats.getTotalTxBytes()), // 獲取總共發(fā)送的流量數(shù)據(jù)StringUtil.formatData(TrafficStats.getMobileTxBytes())) // 獲取數(shù)據(jù)流量的發(fā)送數(shù)據(jù)tv_traffic!!.text = desc// 獲取已安裝的應(yīng)用信息隊(duì)列val appinfoList = AppUtil.getAppInfo(this@TrafficInfoActivity, 1)for (i in appinfoList!!.indices) {val item = appinfoList[i]// 根據(jù)應(yīng)用編號(hào)獲取該應(yīng)用的接收流量數(shù)據(jù)// Android7之后,TrafficStats類的getUidRxBytes和getUidTxBytes只能查自身的流量。只有當(dāng)前應(yīng)用為系統(tǒng)應(yīng)用之時(shí),才能查其他應(yīng)用的流量item!!.traffic = TrafficStats.getUidRxBytes(item.uid)appinfoList[i] = item}// 構(gòu)建一個(gè)流量信息的列表適配器val adapter = TrafficInfoAdapter(this@TrafficInfoActivity, appinfoList)// 給lv_traffic設(shè)置流量信息列表適配器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 // 聲明一個(gè)不滾動(dòng)列表視圖: NoScrollListView? = nullprivate var mDay // 選擇的日期= 0private var mNowDay // 今天的日期= 0private val traffic_month: Long = 0 // 月流量數(shù)據(jù)private var traffic_day: Long = 0 // 日流量數(shù)據(jù)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)// 創(chuàng)建一個(gè)通往流量服務(wù)的意圖val intent = Intent(this, TrafficService::class.java)// 啟動(dòng)指定意圖的服務(wù)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的不滾動(dòng)列表視圖nslv_traffic = findViewById(R.id.nslv_traffic)findViewById<View>(R.id.iv_menu).setOnClickListener(this)findViewById<View>(R.id.iv_refresh).setOnClickListener(this)// 從共享參數(shù)中讀取月流量限額limit_month = SharedUtil.Companion.getIntance(this)!!.readInt("limit_month", 1024)// 從共享參數(shù)中讀取日流量限額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毫秒后開始刷新日流量數(shù)據(jù)mHandler.postDelayed(mDayRefresh, 500)}private val mHandler = Handler() // 聲明一個(gè)處理器對象// 定義一個(gè)日流量的刷新任務(wù)private val mDayRefresh = Runnable { refreshTraffic(mDay) }override fun onClick(v: View) {if (v.id == R.id.tv_day) { // 點(diǎn)擊了日期文本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) { // 點(diǎn)擊了三點(diǎn)菜單圖標(biāo)// 跳轉(zhuǎn)到流量限額配置頁面val intent = Intent(this, MobileConfigActivity::class.java)startActivity(intent)} else if (v.id == R.id.iv_refresh) { // 點(diǎn)擊了轉(zhuǎn)圈刷新圖標(biāo)mDay = mNowDay// 立即啟動(dòng)今天的流量刷新任務(wù)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// 選擇完日期,立即啟動(dòng)流量刷新任務(wù)mHandler.post(mDayRefresh)}// 刷新指定日期的流量數(shù)據(jù)private fun refreshTraffic(day: Int) {val last_date = DateUtil.getAddDate("" + day, -1)// 查詢數(shù)據(jù)庫獲得截止到昨日的應(yīng)用流量val lastArray: ArrayList<AppInfo>? =MainApplication.instance?.mTrafficHelper?.query("day=$last_date")// 查詢數(shù)據(jù)庫獲得截止到今日的應(yīng)用流量val thisArray: ArrayList<AppInfo>? =MainApplication.instance?.mTrafficHelper?.query("day=$day")val newArray = ArrayList<AppInfo?>()traffic_day = 0// 截止到今日的應(yīng)用流量減去截止到昨日的應(yīng)用流量,二者之差便是今日的流量數(shù)據(jù)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)}}// 給流量信息隊(duì)列補(bǔ)充每個(gè)應(yīng)用的圖標(biāo)val fullArray = AppUtil.fillAppInfo(this, newArray)// 構(gòu)建一個(gè)流量信息的列表適配器val adapter = TrafficInfoAdapter(this@MobileAssistantActivity, fullArray)// 給nslv_traffic設(shè)置流量信息列表適配器nslv_traffic!!.adapter = adaptershowDayAnimation() // 顯示日流量動(dòng)畫showMonthAnimation() // 顯示月流量動(dòng)畫}// 顯示日流量的圓弧動(dòng)畫private fun showDayAnimation() {rl_day!!.removeAllViews()val diameter = Math.min(rl_day!!.width, rl_day!!.height) - line_width * 2var desc = "今日已用流量" + StringUtil.formatData(traffic_day)// 創(chuàng)建日流量的圓弧動(dòng)畫val dayAnimation = CircleAnimation(this@MobileAssistantActivity)// 設(shè)置日流量動(dòng)畫的四周邊界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) { // 超出兩倍限額,則展示紅色圓弧進(jìn)度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) { // 超出一倍限額,則展示橙色圓弧進(jìn)度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 { // 未超出限額,則展示綠色圓弧進(jìn)度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)// 渲染日流量的圓弧動(dòng)畫dayAnimation.render()tv_day_traffic!!.text = desc}// 顯示月流量的圓弧動(dòng)畫。未實(shí)現(xiàn),讀者可實(shí)踐之private fun showMonthAnimation() {rl_month!!.removeAllViews()val diameter = Math.min(rl_month!!.width, rl_month!!.height) - line_width * 2tv_month_traffic!!.text = "本月已用流量待統(tǒng)計(jì)"// 創(chuàng)建月流量的圓弧動(dòng)畫val monthAnimation = CircleAnimation(this@MobileAssistantActivity)// 設(shè)置月流量動(dòng)畫的四周邊界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)// 渲染月流量的圓弧動(dòng)畫monthAnimation.render()}companion object {private const val TAG = "MobileAssistantActivity"} }實(shí)驗(yàn)結(jié)果(實(shí)驗(yàn)最終作品截圖說明)
實(shí)驗(yàn)心得
1、熟悉App開發(fā)經(jīng)常涉及的自定義控件相關(guān)技術(shù),主要包括自定義視圖的過程與步驟、自定義動(dòng)畫的原理與實(shí)現(xiàn)、自定義對話框的概念與示例、自定義通知欄的用法與定制;
2、熟悉四大組件之一的服務(wù)Service的生命周期與啟停方式;
參考文章
總結(jié)
以上是生活随笔為你收集整理的《移动项目实践》实验报告——Android自定义控件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vuepress 2.x 集成 elem
- 下一篇: 《移动项目实践》实验报告——Androi