在Android众多工具类app中,Last App Switcher绝对算是一个让人用过就不会卸载的工具。LAS这个应用,它的功能很简单,就是通过一个浮动按钮实现在两个应用之间一键切换,但是非常实用,尤其是在边玩边聊天需要频繁切换应用的时候。所以可以看出,想开发一款受欢迎的应用,一定要注重用户体验,只要用户用的爽,功能再再再简单,它也会受欢迎。那么这功能到底有多简单呢?跟我来实现一下就好了。

本文地址:http://www.cnblogs.com/rossoneri/p/4561057.html

项目代码地址:https://github.com/wossoneri/LAS_Demo

  我就不截图了,下面用官方的截图来说明。这里真心推荐读者下载用一下。谷歌商店的下载地址:Last App Switcher 搞开发的应该都会翻墙吧

看下原始程序界面:

  可以看到主界面就是一系列开关选项,同时程序右边有一个浮动的圆形窗口。下面我会按照步骤一步步增加功能。

仿iOS按钮

  写demo不需要多好的界面,但也不能太丑,手里有看起来不错的控件就直接拖进来用了。下面是效果图,这一套按钮有好几种,都是仿iOS的,想要的可以点原作者的这篇博客,源码Github地址

先添加一个开关主功能的按钮:

浮动按钮

  可以看到,这个应用的主要功能就在于那个红色的浮动按钮上面。根据程序功能可以知道,这个浮动按钮是由程序开启的服务中创建的。又因为程序的Activity在离开onStart()状态后就会销毁(这样做的原因后面说),之后按钮仍保持其可用状态。所以可以知道是通过startService()启动的服务。下面我们就需要先写一个服务出来,再在服务中绘一个浮动按钮。具体有关服务的细节参考我上一篇博客:博客传送门

写一个服务FloatButtonService,在AndroidManifest.xml文件添加服务

<service android:name=".FloatButtonService" >
</service>

服务中添加绘制浮动按钮方法,相关说明见注释

private void createFloatView() {

}

方法添加完毕在服务相应的调用位置创建和销毁浮动按钮

@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
createFloatView();
} @Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if (mFloatLayout != null) {
mWindowManager.removeView(mFloatLayout);
}
}

使用浮动按钮还需要增加权限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

这样,我们在MainActivity中就可以为按钮增加响应事件,进行开启和关闭服务了。

将程序从最近任务(last recent tasks)中移除

  按下系统导航栏第三个按钮我们就可以看到最近使用过的任务列表,当然,LAS切换程序也是在这里选择最后使用的两个应用程序切换的。所以在切换的时候,把自己的Activity从最近的任务中删掉是很必要的。

前面提到过,就是在Activity的onPause()状态或者onStop()状态中执行finishAndRemoveTask()方法删除任务。但这个方法在API 21也就是Android 5.0才引入。不过,我们还有一个更方便的方法,就是在配置文件的<activity>标签中增加

android:excludeFromRecents="true"

这样不论你是按下back键还是home键,程序都会从最近使用过的任务列表中删除

获取系统任务列表,进行任务间的切换

  将自身Activity从最近任务列表中删除后,我们就可以考虑获取最后两次的任务,然后互相一键切换了。

在浮动按钮的单击事件中添加

首先需要获得ActivityManager的对象

ActivityManager mActivityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);

要获取任务还需要对应权限

<uses-permission android:name="android.permission.GET_TASKS"/>

有了权限,就可以获取到任务列表了

List<ActivityManager.RecentTaskInfo> mAppList = new ArrayList<ActivityManager.RecentTaskInfo>();
mAppList = mActivityManager.getRecentTasks(3, ActivityManager.RECENT_IGNORE_UNAVAILABLE);

建立一个装有RecentTaskInfo的列表,通过getRecentTasks方法获取系统的最近使用过的应用列表。

关于getRecentTasks方法,第一个参数是一个整型值,是你需要返回的应用数量,但实际上得到的数量可能会比这个值要小。比如

我要得到3个,但后台只开了1个,那么只返回1个。第二个参数是要返回的应用的状态,我选择的是忽略不可用的应用,应该是完全关闭,不在后台的应用。

再说一点,这个方法在Android5.0因为安全问题屏蔽掉了,也就是android5.0以上的版本不能用这个方法。所以我前一阵子在App Store上看到评论都是Android5.0用这个没有效果。现在行不行我倒不知道,闲了再研究吧。(每次我说闲了再做基本都是个坑- -|)

前面的参数我之所以要选择3,是因为我只需要获得最近使用的2个应用,因为每次开新应用,这个应用信息都会存在列表的最上面,所以获取前3个即可。

但为什么是3而不是2呢,因为Android的Home界面也是一个Activity(应该是),我可以选择是否要在切换的时候忽略掉Home界面。所以考虑到Home,就要用3。

Home的包名为com.android.launcher,以此为根据进行判断即可。

private void getAndStartIntent(int i){
ActivityManager.RecentTaskInfo info = mAppList.get(i);
if (null == info)
Toast.makeText(FloatButtonService.this, "No other apps", Toast.LENGTH_SHORT).show();
else if(sp.getBoolean(StringKey.ExcludeHome, false)){ // if set true, do follow func
if(info.baseIntent.getComponent().getPackageName().equals(HOME_PACKAGE)) //exclude HOME
getAndStartIntent(2);
}else
startActivityWithoutAnimation(info);
}

启动一个应用的过程默认是有一个切换动画的,我们的程序就是用来切换程序的,所以取消启动动画是一个比较好的选择。

只用给要启动的intent加一个flag即可(有些情况下不会生效)

intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);

开机启动

  Android开机启动结束会发送一个BOOT_COMPLETED的广播,我们在程序中建立一个广播接收器来接收这个广播,接收成功就直接启动服务来显示浮动按钮即可。

先建立一个广播接收器 BootReceiver

public class BootReceiver extends BroadcastReceiver {

	@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {// on boot
Intent a = new Intent(context, FloatButtonService.class);
context.startService(a);
}
}
}

在配置文件中,<application>标签下注册广播接收器

<receiver android:name=".BootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</receiver>

然后增加权限

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

开机启动就完成了。但怎么用开关来控制其是否开机启动呢?

SharedPreferences

  用开关控制功能的开启状态,这个状态不能保存在程序中,因为程序是要被关闭的。那么就是要用一些方法保存开关的状态到系统中,然后服务从文件读取状态,控制自己的程序行为。Android中最适合保存配置状态的就是用SharedPreferences了。当我查看LAS应用的数据文件的时候,发现输出的结果的确是这样的。

cat /data/data/com.abhi.lastappswitcher/shared_prefs/com.inpen.lastAppSwitcher.APPLICATION_PREFS.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<boolean name="com.inpen.lastAppSwitcher.PREF_SNAP_TO_EDGE" value="true" />
<int name="com.inpen.lastAppSwitcher.PREF_LAND_HEIGHT" value="800" />
<int name="com.inpen.lastAppSwitcher.PREF_LAND_FLOATER_Y" value="485" />
<int name="com.inpen.lastAppSwitcher.PREF_LAND_WIDTH" value="1280" />
<int name="com.inpen.lastAppSwitcher.PREF_PORT_FLOATER_Y" value="776" />
<int name="com.inpen.lastAppSwitcher.PREF_PORT_WIDTH" value="800" />
<boolean name="com.inpen.lastAppSwitcher.PREF_ERROR_MSG" value="true" />
<boolean name="com.inpen.lastAppSwitcher.PREF_STATUS_BAR_OVERLAY" value="false" />
<int name="com.inpen.lastAppSwitcher.PREF_FLOATER_SIZE" value="55" />
<int name="com.inpen.lastAppSwitcher.PREF_PORT_FLOATER_X" value="765" />
<int name="com.inpen.lastAppSwitcher.PREF_PORT_HEIGHT" value="1280" />
<int name="com.inpen.lastAppSwitcher.PREF_FLOATER_TRANSPARENCY" value="75" />
<int name="currentQuote" value="6" />
<int name="com.inpen.lastAppSwitcher.PREF_SWITCHING_METHOD" value="1" />
<boolean name="com.inpen.lastAppSwitcher.PREF_FLOATER_MOVABLE" value="true" />
<boolean name="com.inpen.lastAppSwitcher.PREF_HAPTIC_FEEDBACK" value="false" />
<int name="com.inpen.lastAppSwitcher.PREF_FLOATER_COLOR" value="0" />
</map>

那么,我们就可以根据自己的需求来写sharedPreferences文件了

先获得 SharedPreferences 的实例

SharedPreferences sp = getSharedPreferences("las_demo", Context.MODE_PRIVATE);

参数1是不带后缀的文件名,根据文件名获取实例,同一个名字的SharedPreferences对象只获得同一个实例;

参数2是模式操作模式:

  • Context.MODE_PRIVATE:

    为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容。
  • Context.MODE_APPEND:

    创建的文件是私有数据,该模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。
  • MODE_WORLD_READABLE:

    表示当前文件可以被其他应用读取。
  • MODE_WORLD_WRITEABLE:

    表示当前文件可以被其他应用写入。

获得实例之后要进行初始化,写入一些设定值。这里因为初始化只需要一次,但我没找到判断sharedPreferences文件是否存在的方法(没想用File去查,这个文件存在系统路径,有权限问题,估计不行,有知道的可以告诉我),有一个public abstract boolean contains (String key)方法,但用了感觉没效果,所以我又加了一个key,来保存第一次创建的状态,然后写入其他键-值,保存。

if(!sp.getBoolean(StringKey.FirstCreate, true)){

	Editor editor = sp.edit();
editor.putBoolean(StringKey.FirstCreate, true);
editor.putBoolean(StringKey.RunAtStart, false);
editor.putBoolean(StringKey.SnapToEdge, true);
editor.putBoolean(StringKey.StatusBarOverlay, false);
editor.putBoolean(StringKey.ExcludeHome, true); editor.commit();
}

设置好键-值后就可以根据这些值设置界面里按钮的开关状态和设置程序的一些行为。

if (sp.getBoolean(StringKey.RunAtStart, false))
mBtnRunAtStartup.setChecked(true);
else
mBtnRunAtStartup.setChecked(false);

当然,在手动改变按钮状态的时候也要为某个key重新写入新的value

mBtnRunAtStartup.setOnCheckedChangeListener(new OnCheckedChangeListener() {

	@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// TODO Auto-generated method stub
if (isChecked)
editBoolKey(StringKey.RunAtStart, true);
else
editBoolKey(StringKey.RunAtStart, false);
}
}); private void editBoolKey(String str, boolean b) {
Editor editor = sp.edit();
editor.putBoolean(str, b);
editor.apply();
}

改SharedPreferences的key-value的时候需要获得editor对象实例,设置完成用apply()方法或者commit()方法提交修改。如果有两个editor实例在同时修改,则以最后一次的提交为准。如果不关心返回值,且在应用的主线程里使用,用apply()要比commit()好。

至此,需要开关功能就在功能实现的地方加一层读取SP键值的过程,根据读到的结果决定功能。是否可用。

悬浮按钮显示在status bar上方

  按照下面设置windowManager的属性就好,没什么好解释的,放上文档看吧。

wmParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_LAYOUT_IN_SCREEN;
  • int android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL = 32 [0x20]

    Window flag: Even when this window is focusable (its is not set), allow any pointer events outside of the window to be sent to the windows behind it. Otherwise it will consume all pointer events itself, regardless of whether they are inside of the window.

  • int android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE = 8 [0x8]

    Window flag: this window won't ever get key input focus, so the user can not send key or other button events to it. Those will instead go to whatever focusable window is behind it. This flag will also enable FLAG_NOT_TOUCH_MODAL whether or not that is explicitly set.

    Setting this flag also implies that the window will not need to interact with a soft input method, so it will be Z-ordered and positioned independently of any active input method (typically this means it gets Z-ordered on top of the input method, so it can use the full screen for its content and cover the input method if needed. You can use FLAG_ALT_FOCUSABLE_IM to modify this behavior.

  • int android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN = 256 [0x100]

    Window flag: place the window within the entire screen, ignoring decorations around the border (a.k.a. the status bar). The window must correctly position its contents to take the screen decoration into account. This flag is normally set for you by Window as described in Window.setFlags.

按钮边缘吸附效果

  这个应该是最简单的了,在按钮的touch事件中,当移动结束,手指抬起行为ACTION_UP中对位置进行判断,如果按钮的x坐标在屏幕左半边,x设为0,即贴着屏幕左边缘显示,反之一个道理。

功能就粗略地实现了这么多,原应用中还有很多小功能就不一一实现了,代码我传到github了,地址点这里

[Android] 仿照 Last App Switcher 写的小程序的更多相关文章

  1. android上instant app介绍 类似于微信小程序

    android上instant app介绍 类似于微信小程序instant app 是谷歌推出的类似于微信小程序(或者说小程序类似于instant app)的一项技术,用户无须安装应用,用完就走,同时 ...

  2. 像VUE一样写微信小程序-深入研究wepy框架

    像VUE一样写微信小程序-深入研究wepy框架 微信小程序自发布到如今已经有半年多的时间了,凭借微信平台的强大影响力,越来越多企业加入小程序开发. 小程序于M页比相比,有以下优势: 1.小程序拥有更多 ...

  3. 用android去写一个小程序

    前言: 软工的一个小作业:实现"黄金分割小游戏", 需要结对编程,队友:陈乐云    共用时两天. 早期思路设计: 采用键值对的形式,以Map作为存储结构.优点:能够将数据与用户对 ...

  4. mpvue-编写微信小程序总结

    一.写在前面: .....最近在写一个微信小程序项目,在看完官方微信小程序开发文档后,有一种直接想"放弃"的念头: .....使用微信小程序原生框架可以快速,方便,简洁的搭建项目, ...

  5. 写个小程序01 | 注册微信小程序

    出于兴趣和学习目的,我想自己做一个基于"子弹笔记(Bullet Journal)"的小程序.由于个人开发经验很有限,只在课程作业中写过 web 前端,所以也不知道多久能写出来(逃) ...

  6. 从7点到9点写的小程序(用了模块导入,python终端颜色显示,用了点局部和全局可变和不可变作用域,模块全是自定义)

    未完待续的小程序 要是能做的好看为啥不做的好看 在同目录下生成程序 1.程序文件 run.py from login import login from register import registe ...

  7. Java生鲜电商平台-系统异常状态的设计与架构(APP应用或者生鲜小程序)

    Java生鲜电商平台-系统异常状态的设计与架构 说明:在实际开发Java生鲜电商平台的时候,异常状态的设计关系着整体系统的性能问题,架构设计,以及稳定性方面,对此,我根据实际的业务场景,进行了系统设计 ...

  8. 利用 Makefile 写的小程序

    1.建立一个工程 2.写一个进度条的程序(原理就是在同一位置重复打印某一个字符(变化),达到动态显示的效果) 所以说我们这里只用回车'\r',覆盖这一行以前的输出,重新向缓冲区写数据刷新缓冲区,就能达 ...

  9. 初学Java必写的小程序。

    1.矩形面积,周长封装测试. /** * @author Administrator *封装好的矩形类 *自己私有的长宽属性 *开放 求面积求周长的方法 和设置长宽的方法 */ public clas ...

随机推荐

  1. linux 备忘记录

    杂项记录 Ubuntu 通过/etc/network/interfaces修改IP,重启网络服务貌似也不会生效.可以重启电脑使其生效,或执行: ip addr flush dev ens33 & ...

  2. Redis学习系列二之.Net开发环境搭建及基础数据结构String字符串

    一.简介 Redis有5种基本数据结构,分别是string.list(列表).hash(字典).set(集合).zset(有序集合),这是必须掌握的5种基本数据结构.注意Redis作为一个键值对缓存系 ...

  3. JSPatch动态更新APP

    JSPatch,只需在项目中引入极小的引擎,就可以使用JavaScript调用任何Objective-C的原生接口,获得脚本语言的能力:动态更新APP,替换项目原生代码修复bug. 用途 是否有过这样 ...

  4. js便签笔记(12)——浏览TOM大叔博客的学习笔记 part2

    1. 前言 昨天写了<js便签笔记(11)——浏览TOM大叔博客的学习笔记 part1>,简单记录了几个问题.part1的重点还是在于最后那个循环创建函数的问题,也就是多个子函数公用一个闭 ...

  5. Tomcat笔记:Tomcat的执行流程解析

    Bootstrap的启动 Bootstrap的main方法先new了一个自己的对象(Bootstrap),然后用该对象主要执行了四个方法: init(); setAwait(true); load(a ...

  6. 神经网络优化方法总结:SGD,Momentum,AdaGrad,RMSProp,Adam

    1. SGD Batch Gradient Descent 在每一轮的训练过程中,Batch Gradient Descent算法用整个训练集的数据计算cost fuction的梯度,并用该梯度对模型 ...

  7. Redis开发与运维

    常用命令 redis-server启动redis redis-server /opt/redis/redis.conf    配置启动 redis-server --port 6379 --dir / ...

  8. Java中mongodb使用and和or的复合查询

    在MongoDB的JAVA查询中对应这些问题 and查询 //条件 startsAt< curr and endsAt > curr long curr = new Date().getT ...

  9. WIN32控件使用系统样式

    定义如下即可: #ifdef _M_IX86 #pragma comment(linker,"/manifestdependency:\"type='win32' name='Mi ...

  10. VS2012 编译报错:找不到编译动态表达式所需的一个或多个类型。是否缺少引用?

    今天编译公司项目,原本项目是3.5,由于现在要用到dynamic ,把target 改为4.0 ,编译时 报错误  “找不到编译动态表达式所需的一个或多个类型.是否缺少引用?”,然后根据另一个提示排错 ...