一个叫GUN的有趣的APP源码

2023-10-10 05:30
文章标签 源码 app 有趣 gun

本文主要是介绍一个叫GUN的有趣的APP源码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

      这个APP是帮一个小伙伴开发的,功能和UI都超级简单,代码量很少,目前算是alpha版本吧,因为是开发着玩的(非公司项目),所以把目前的代码放送出来。

      这是在开发过程中注意的几个知识点:

  • 使用Material Design中的Ripple Effect;
  • 使用自定义进度条,中间显示倒计时时间,外圆白色逐渐减少,黑圈逐渐增加(两个Paint,一个绘制未完成的白圈,一个绘制完成的黑圈)
  • 在Service中开启TimerTask每隔100ms监听一次当前使用的APP是否为本APP,否则自动跳转到指定界面
  • 注意监听Activity设置 android:launchMode="singleTask"
  • 使用android:excludeFromRecents="true"把最近使用的应用程序的列表中排出此APP

      这是源代码目录:


      先看一下APP设计原型(有木有很渣渣的赶脚),就是按照这个设计敲的代码…… 

这是一个比较有恶趣味的 app
产品名称:「gun」,「鲧」的拼音,鲧是禹的爹,《大禹治水》的典故里有讲,鲧治水靠「堵」,禹治水靠「疏导」。不玩手机这事儿,大家都知道影响不好,道理都懂,但还是停不下来,能做到的毕竟是少数。除了靠「自律」,还可以用「堵」这个方式,直接不让用
功能定位:定时番茄钟
面向对象:手机依赖症,离不开手机,有强迫症的人,随时都要看看玩玩手机
产品特色:设定后不能中途暂停或结束,得等到设定的时间到了自动解禁,或者关机重启解禁,以更强迫的方式治疗强迫症
产品劣势:目前就我们俩,一个产品汪+一个攻城狮的配置,没有UI视觉设计,所以界面采用极简风格,黑白配
必备功能:彻底做到控制手机,不给用户机会暂停。原生的电话功能可使用
魅族和nexus5可通过删除曾经打开过的应用记录,退出应用,看下有没有办法能绝对控制权限
后续迭代版本功能:1.设置白名单功能,根据反馈添加一些不能强制禁掉的应用,2.数据统计功能,统计连续使用次数和总的成功使用的次数,写代码时注意下
产品辅助:已经注册了一个微博帐号「一个叫gun的app」,主要用来收集使用反馈,当然,可能粉丝不多
字体字号:雅痞-繁(如果没有可以采用微软雅黑)
【开始】和倒计时数字字号72,提示文字和颜文字字号24,启动页字号60 和 24
【不用不能活】字号72,颜文字字号36

 

应用启动页                                        进入软件页面显示内容

 

点击【开始】圆圈区域颜色变浅           显示倒计时,圆圈区域颜色恢复

 

未解锁状态,操作应用内任意区域显示文字提示      倒计时完成,提示使用记录                                                  


未解锁状态操作其他app的提示

说明:

  1. 点中时中间圆圈颜色变淡,使用Material动效:触控涟漪-按压/释放,参考http://www.ui.cn/project.php?id=18846
  2. 随机选择30分钟,33分33秒,35分钟这三种锁定时间
    操作【开始】进入倒计时,从30:00(或33:33,35:00)开始倒计时计数,开始计数后,白色圆圈逐渐减少白色区域大小

    计时结束后,白色圆圈又恢复到整圆,并显示连续使用记录
  3. 计时正常结束后显示统计连续使用并未强制停止的次数,次数用画#正#统计
    如果是重新加载启动app,不显示统计结果
    连续使用次数1-5   (^0^)/ 已连续使用 正 次
    连续使用次数6-10 o(^^o)  已连续使用 正正 次
    连续使用次数11-15  ( ^)o(^ )已连续使用 正正正 次
    连续使用次数 16-20 ヽ(^。^)丿连续使用 正正正正 次
    连续使用次数>21  (^_^;) 可以卸载了
  4. 未解锁状态下,操作应用内任意区域显示提示,1s后消失
  5. 操作安装的其他app和系统自带应用(电话功能除外)均显示此界面
设计原型看完了,就开始Coding吧:
那些BaseActivity,AppManager之类的项目中常用的就不贴出来了,为了控制篇幅,只贴核心功能代码。
先看一下activity_main.xml,UI要注意的地方都在这里了
<com.wen.gun.ripplelibrary.RippleBackground xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/content"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/black"app:rb_color="@color/center_bg_color"app:rb_duration="1000"app:rb_radius="60dp"app:rb_rippleAmount="1"app:rb_scale="8" ><TextViewandroid:id="@+id/tv_notes"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_above="@+id/tv_start"android:layout_centerHorizontal="true"android:layout_marginBottom="@dimen/mar_bo_main_note"android:textColor="@color/ivory"android:textSize="@dimen/text_main_note" /><TextViewandroid:id="@+id/tv_start"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:background="@drawable/bg_tv"android:gravity="center"android:lineSpacingMultiplier="1.2"android:text="@string/main_mid_start"android:textColor="@color/ivory"android:textSize="@dimen/text_splash_mid" />  <com.wen.gun.circleprogress.DonutProgressxmlns:custom="http://schemas.android.com/apk/res-auto"android:id="@+id/donut_progress"android:layout_width="@dimen/center_monitor_size"android:layout_height="@dimen/center_monitor_size"android:layout_centerInParent="true"android:background="@color/black"android:visibility="invisible"custom:donut_finished_color="@color/black"custom:donut_finished_stroke_width="10dp"custom:donut_inner_bottom_text=""custom:donut_progress="0"custom:donut_text_size="@dimen/text_monitor_time"custom:donut_unfinished_color="@color/ivory"custom:donut_unfinished_stroke_width="@dimen/stroke_size" />
</com.wen.gun.ripplelibrary.RippleBackground>
com.wen.gun.ripplelibrary.RippleBackground设置Ripple Effect
com.wen.gun.circleprogress.DonutProgress自定义进度条,外面是圆圈,中间是数字
		canvas.drawArc(finishedOuterRect, 360 - getProgressAngle(), 270, false,finishedPaint); // blackcanvas.drawArc(unfinishedOuterRect, 270, 360 - getProgressAngle(),false, unfinishedPaint); // white
在drawArc的时候,注意右边中间是0°,正上方是270°(-90°),我这里是逆时针,如果想要圆圈进度效果为顺时针,修改startAngle和sweepAngle就可以了:
		 canvas.drawArc(finishedOuterRect, -90,getProgressAngle(), false,finishedPaint); //blackcanvas.drawArc(unfinishedOuterRect, -90+getProgressAngle(),360-getProgressAngle(), false, unfinishedPaint); //white
至于bg_tv.xml,是为文字设置一个oval的背景,加上边框,填充就可以
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="oval"android:useLevel="false" ><solid android:color="@color/center_bg_color" /><strokeandroid:width="@dimen/stroke_size"android:color="@color/ivory" /><sizeandroid:height="@dimen/center_start_size"android:width="@dimen/center_start_size" /></shape>

再看看MainActivity
<pre name="code" class="java">public class MainActivity extends BaseActivity {private static String TAG = MainActivity.class.getName();private TextView tv_start, tv_notes;private RippleBackground rippleBackground;private DonutProgress donutProgress;private boolean isStart;private MyCount mc;private AlphaAnimation mHideAnimation = null;private AlphaAnimation mShowAnimation = null;private int animationTime;private String mDTime;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();initData();setLinstener();fillData();}@Overrideprotected void initData() {// int times = (Integer) SPUtils.get(MainActivity.this, "use_times", 0);tv_notes.setText(StringNotsUtils.setText(0));setMonitorDurationTime();L.i(TAG, "这次随机生成的时间为=" + Constant.TIME_DURATION);animationTime = 1000;mc = new MyCount(Constant.TIME_DURATION * 1000 + 1000, 1000); // 加上1秒donutProgress.setMax(Constant.TIME_DURATION);isStart = false;}@Overrideprotected void initView() {tv_start = (TextView) this.findViewById(R.id.tv_start);tv_notes = (TextView) this.findViewById(R.id.tv_notes);rippleBackground = (RippleBackground) findViewById(R.id.content);donutProgress = (DonutProgress) this.findViewById(R.id.donut_progress);}@Overrideprotected void setLinstener() {tv_start.setOnClickListener(this);donutProgress.setOnClickListener(this);}@Overrideprotected void fillData() {// TODO Auto-generated method stub}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.tv_start:if (!isStart) {isStart = true;donutProgress.setProgress(0);setRippleEffect();stratService();} else {L.i(TAG, "已经开始了额");}break;case R.id.donut_progress:T.showLong(MainActivity.this, "你将在  " + mDTime + " 后恢复手机使用权");break;default:break;}}/*** TODO<点击开始的时候出现涟漪效果,3s后停止涟漪效果,并开启倒计时>* * @throw* @return void*/private void setRippleEffect() {rippleBackground.startRippleAnimation();new Handler().postDelayed(new Runnable() {@Overridepublic void run() {rippleBackground.stopRippleAnimation();tv_start.setVisibility(View.INVISIBLE);donutProgress.setVisibility(View.VISIBLE);donutProgress.setClickable(true);setHideAnimation(tv_start, animationTime);setShowAnimation(donutProgress, animationTime);Countdown();}}, 3000);}/*** TODO<开启倒计时>* * @throw* @return void*/private void Countdown() {tv_notes.setText("再等等 或 关机重启");mc.start();// 开启倒计时}/*** TODO<结束一次治疗后准备下一次的治疗数据准备>* * @throw* @return void*/public void prepareNewTask() {L.i(TAG, "这次结束了");isStart = false;tv_start.setVisibility(View.VISIBLE);donutProgress.setProgress(0);donutProgress.setVisibility(View.INVISIBLE);donutProgress.setClickable(false);setHideAnimation(donutProgress, animationTime);setShowAnimation(tv_start, animationTime);stopService();int times = (Integer) SPUtils.get(MainActivity.this, "use_times", 0);tv_notes.setText(StringNotsUtils.setText(times));}/*** TODO<保存治疗次数>* * @throw* @return void*/public void saveUseTimes() {int times = (Integer) SPUtils.get(MainActivity.this, "use_times", 0);SPUtils.put(MainActivity.this, "use_times", times + 1);}/*** TODO<设置开始界面与倒计时界面切换时的动画效果>* * @param view* @param duration* @throw* @return void*/private void setHideAnimation(View view, int duration) {if (null == view || duration < 0) {return;}if (null != mHideAnimation) {mHideAnimation.cancel();}mHideAnimation = new AlphaAnimation(1.0f, 0.0f);mHideAnimation.setDuration(duration);mHideAnimation.setFillAfter(true);view.startAnimation(mHideAnimation);}private void setShowAnimation(View view, int duration) {if (null == view || duration < 0) {return;}if (null != mShowAnimation) {mShowAnimation.cancel();}mShowAnimation = new AlphaAnimation(0.0f, 1.0f);mShowAnimation.setDuration(duration);mShowAnimation.setFillAfter(true);view.startAnimation(mShowAnimation);}/*** TODO<开始监听服务>* * @throw* @return void*/private void stratService() {Intent intent = new Intent(MainActivity.this, MonitorService.class);startService(intent);}/*** TODO<停止服务>* * @throw* @return void*/private void stopService() {Intent intent = new Intent(MainActivity.this, MonitorService.class);stopService(intent);}/* 定义一个倒计时的内部类 */class MyCount extends CountDownTimer {public MyCount(long millisInFuture, long countDownInterval) {super(millisInFuture, countDownInterval);}// 最后一次不会再调用@Overridepublic void onTick(long millisUntilFinished) {L.i(TAG, "我在倒计时");if (donutProgress.getProgress() != Constant.TIME_DURATION) {donutProgress.setProgress(donutProgress.getProgress() + 1);mDTime = TimeConvert.secondsToMinute1(Constant.TIME_DURATION- donutProgress.getProgress());}}/*** 重载方法,如果有需要可以在这里关闭APP*/@Overridepublic void onFinish() {saveUseTimes();prepareNewTask();L.i(TAG, "倒计时结束");new Handler().postDelayed(new Runnable() {@Overridepublic void run() {// AppManager.getInstance().AppExit(getApplicationContext());}}, 3000);}}@Overrideprotected void onDestroy() {super.onDestroy();// mc.cancel(); 保持运行if (mShowAnimation != null) {mShowAnimation.cancel();}if (mHideAnimation != null) {mHideAnimation.cancel();}}// 随机选择30分钟,33分33秒,35分钟这三种锁定时间public void setMonitorDurationTime() {switch (StringRandomUtils.getRandomNumber(1, 3)) {case 1:Constant.TIME_DURATION = 1800; // 30min// Constant.TIME_DURATION = 60;break;case 2:Constant.TIME_DURATION = 2013; // 33分33秒// Constant.TIME_DURATION = 60;break;case 3:Constant.TIME_DURATION = 2100; // 35min// Constant.TIME_DURATION = 60;break;default:break;}}
}
 
说明:
  • 在进入APP的时候,调用setMonitorDurationTime()随机选择30分钟,33分33秒,35分钟这三种锁定时间;
  • 点击开始的时候,setRippleEffect()产生Ripple Effect效果,并开启MonitorService,3s后开启倒计时(倒计时的方式太多种了,我这里用的是CountDownTimer),在onTick中每隔一秒更新一次UI(圆圈进度和中间的倒计时文字)。
  • 倒计时结束onFinish中停止MonitorService,并重置一些参数。
再来看看MonitorService:
public class MonitorService extends Service {boolean flag = true;// 用于停止线程private ActivityManager activityManager;private Timer timer;private TimerTask task = new TimerTask() {@Overridepublic void run() {// TODO Auto-generated method stubif (activityManager == null) {activityManager = (ActivityManager) MonitorService.this.getSystemService(ACTIVITY_SERVICE);}List<RecentTaskInfo> recentTasks = activityManager.getRecentTasks(2, ActivityManager.RECENT_WITH_EXCLUDED);RecentTaskInfo recentInfo = recentTasks.get(0);Intent intent = recentInfo.baseIntent;String recentTaskName = intent.getComponent().getPackageName();//			
//			||!recentTaskName.equals("com.android.contacts")
//			||!recentTaskName.equals("com.android.phone")
//			||!recentTaskName.equals("com.android.launcher")
//			||!recentTaskName.equals("com.miui.home")if (!recentTaskName.equals("com.wen.gun")) {L.i("MonitorService", "Yes--recentTaskName=" + recentTaskName);Intent intentNewActivity = new Intent(MonitorService.this,MonitorActivity.class);intentNewActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intentNewActivity);}else{L.i("MonitorService", "No--recentTaskName="+recentTaskName);}}};@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {if (flag == true) {timer = new Timer();timer.schedule(task, 0, 100);		flag = false;}return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {// TODO Auto-generated method stubsuper.onDestroy();timer.cancel();}@Overridepublic IBinder onBind(Intent intent) {// TODO Auto-generated method stubreturn null;}
}

原理很简单,就是开启一个TimerTask,不断的轮循取出当前的recentInfo,如果不是本APP,就跳转到MonitorActivity.class,注意,这里要设置setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)。
还有要注意AndroidManifest.xml配置。
代码很少,思路很简单!
最后,加上友盟或者百度统计就可以了……

DEMO下载地址http://download.csdn.net/detail/yalinfendou/8645295

这篇关于一个叫GUN的有趣的APP源码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/178292

相关文章

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

nudepy,一个有趣的 Python 库!

更多资料获取 📚 个人网站:ipengtao.com 大家好,今天为大家分享一个有趣的 Python 库 - nudepy。 Github地址:https://github.com/hhatto/nude.py 在图像处理和计算机视觉应用中,检测图像中的不适当内容(例如裸露图像)是一个重要的任务。nudepy 是一个基于 Python 的库,专门用于检测图像中的不适当内容。该

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除

red5-server源码

red5-server源码:https://github.com/Red5/red5-server

TL-Tomcat中长连接的底层源码原理实现

长连接:浏览器告诉tomcat不要将请求关掉。  如果不是长连接,tomcat响应后会告诉浏览器把这个连接关掉。    tomcat中有一个缓冲区  如果发送大批量数据后 又不处理  那么会堆积缓冲区 后面的请求会越来越慢。