一个叫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的提示

说明:

  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" >    <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
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); //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>

再看看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

0 0
原创粉丝点击