一个叫GUN的有趣的APP源码
来源:互联网 发布:zara抄袭知乎 编辑:程序博客网 时间:2024/05/19 08:01
这个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的提示
说明:
- 点中时中间圆圈颜色变淡,使用Material动效:触控涟漪-按压/释放,参考http://www.ui.cn/project.php?id=18846
- 随机选择30分钟,33分33秒,35分钟这三种锁定时间
操作【开始】进入倒计时,从30:00(或33:33,35:00)开始倒计时计数,开始计数后,白色圆圈逐渐减少白色区域大小
计时结束后,白色圆圈又恢复到整圆,并显示连续使用记录 - 计时正常结束后显示统计连续使用并未强制停止的次数,次数用画#正#统计
如果是重新加载启动app,不显示统计结果
连续使用次数1-5 (^0^)/ 已连续使用 正 次
连续使用次数6-10 o(^^o) 已连续使用 正正 次
连续使用次数11-15 ( ^)o(^ )已连续使用 正正正 次
连续使用次数 16-20 ヽ(^。^)丿连续使用 正正正正 次
连续使用次数>21 (^_^;) 可以卸载了 - 未解锁状态下,操作应用内任意区域显示提示,1s后消失
- 操作安装的其他app和系统自带应用(电话功能除外)均显示此界面
<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" > <TextView android: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" /> <TextView android: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.DonutProgress xmlns: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
canvas.drawArc(finishedOuterRect, 360 - getProgressAngle(), 270, false,finishedPaint); // blackcanvas.drawArc(unfinishedOuterRect, 270, 360 - getProgressAngle(),false, unfinishedPaint); // white
canvas.drawArc(finishedOuterRect, -90,getProgressAngle(), false, finishedPaint); //black canvas.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" /> <stroke android:width="@dimen/stroke_size" android:color="@color/ivory" /> <size android:height="@dimen/center_start_size" android:width="@dimen/center_start_size" /></shape>
<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,并重置一些参数。
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)。
- 一个叫GUN的有趣的APP源码
- 有趣的c++源码
- 记录: 看thinkphp源码,发现一个有趣的除法 计算
- GUN的__attribute__使用
- 一个有趣的游戏
- 一个有趣的故事
- 看到一个有趣的
- 一个有趣的故事
- 一个有趣的现象
- 一个有趣的数学公式
- 一个有趣的javascript
- 一个有趣的程序
- 一个有趣的学习方法
- 一个有趣的框架
- 一个有趣的问题
- 一个有趣的查找
- 一个有趣的广告
- 一个有趣的bug
- [Rx86OS-XVI] 实现多任务
- 设计模式学习笔记---备忘录模式memento pattern(Java版)
- 第8周项目--分数类
- 关于 android 中 postDelayed方法的讲解
- 欢迎使用CSDN-markdown编辑器
- 一个叫GUN的有趣的APP源码
- 分数类中的运算符重载
- Android实现形形色色的进度条 很简单的实现
- 15款值得学习的小型开源项目,带你快速步入开源世界
- 【网络课】VB.NET程序设计 作业答案
- Modelsim
- 第八周项目一——实现复数类中的运算符重载(2)
- 第八周项目一实现复数类中的运算符重载(3)
- 第八周 项目一 实现复数类中的运算符重载