本文主要是介绍一个叫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的提示
说明:
- 点中时中间圆圈颜色变淡,使用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" ><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 canvas.drawArc(finishedOuterRect, 360 - getProgressAngle(), 270, false,finishedPaint); // blackcanvas.drawArc(unfinishedOuterRect, 270, 360 - getProgressAngle(),false, unfinishedPaint); // white
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>
<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源码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!