注:以下均为android源码Framework层修改。
设计实现在任意界面从屏幕边缘上滑弹出快捷操作栏,包括亮度调节、正在后台运行的程序显示、一键加速、关闭后台进程、开关控制(含wifi、双卡数据网络、飞行模式、位置信息、蓝牙、闪光灯、屏幕旋转锁定、快捷振动模式)。点击快捷栏其他地方收回快捷栏。
效果如下(动图效果后面加):
主要设计事项有以下几点:
1)总开关:将功能的开关设置写入Settings中,每次弹出快捷栏读取设置信息
2)屏幕上滑操作:监听屏幕坐标,根据按下时的坐标是否在规定范围内以及上滑的距离来判断是否弹出快捷栏
3)自定义ShortcutDialog快捷栏
4)开关控制及状态更新:自定义开关的控制和系统原生的开关需要保持同步状态及更新,所以开关控制的逻辑应嵌入系统的开关中,不是单纯的控制
5)亮度调节:使用系统的亮度控件
6)一键加速:获取正在运行程序,过滤掉特定项后根据包名杀掉相关进程并更新UI界面
其中,关键点有任意界面上滑调出快捷栏、开关状态更新、获取正在运行程序和kill。
下面只讲着三个关键点:
【任意界面上滑调出快捷栏】
由于在处于ListView界面时phonewindow的焦点会被抢掉,容易误操作,所以根据屏幕分辨率设定上滑操作的区域仅限在边缘(参考魅族、苹果等实现方式也是如此),监听拦截在屏幕上的touch操作。根据屏幕分辨率大小设定预设区域范围,如ACTION_DOWN时的坐标满足预设区域内则记录为初始坐标,当ACTION_MOVE满足预设Y轴滑动距离后检查Setting开关后发送弹出自定义对话框的广播,接收器在同目录PhoneWindowManager下。
要实现在任意界面都可以滑出快捷栏,首先,该项功能的入口只能是系统根View,这样才能拦截并监听到所有窗口的手势操作,在PhoneWindow
(alps\framework\base\policy\src\com\android\internal\policy\impl\phonewindow.java)
里面的onInterceptTouchEvent()方法中添加坐标和手势的判断操作。
if (action == MotionEvent.ACTION_MOVE) {
if(SHORTCUT_INFO == 1){//SHORTCUT_INFO判断按下操作是否在预设的区域内
if ((mY-y1)>30) {
//y1是当前手指位置坐标,mY是当次操作按下时的坐标,当Y垂直距离大于30时判断setting的值并发送弹出快捷栏的广播
try{
boolean pullupPreference = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_PULLUP_ENABLED) != 0;
Log.i(TAG_TEST,"pullupPreference = " + pullupPreference + "homedown_info = " + HOMEDOWN_INFO);
if(pullupPreference){
shortcutintent.setAction(UPDATE_SHORTCUT_ACTION);
mContext.sendBroadcast(shortcutintent);
}
}catch(SettingNotFoundException e){
Log.e(TAG, "get PullupPreference Exception", e);
}finally{
mX = 0;
mY = 0;
SHORTCUT_INFO = 0;
HOMEDOWN_INFO = false;
}
}
return true;
}
} else if (action == MotionEvent.ACTION_UP) {
//手势抬起时将各状态值重置
Log.i(TAG_TEST, "PhoneWindow_onInterceptTouchEvent_action == MotionEvent.ACTION_UP");
mX = 0;
mY = 0;
SHORTCUT_INFO = 0;
}
【开关状态更新】
开关的状态更新最重要的一点是必须和系统原生status bar里面的控制开关状态一致,所以不单是要做到控制,而是要在原生的控制开关里加入我们自己的状态和控制逻辑而不影响原生开关。
系统statusbar下的控制开关是以QSPanel为容器,分别载入XXXTile以及Controller实现的。自定义开关控制时需要和系统自身的开关状态保持一致,所以不能在自定义开关点击处写对系统功能的控制(如wifi、Bluetooth、location等)。需要以广播的形式通知对应的Tile,并在Tile内部调用原生开关的控制器来实现。
在每次上滑弹出快捷栏和点击操作时需要根据Setting的各值或方法来刷新状态。以Location为例(alps\framework\base\package\systemui\src\com\android\systemui\qs\tiles\LocationTile.java),控制操作自定义广播接收器控制。状态更新在原来的updatestate方法里加入自己的操作就行:
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
final boolean locationEnabled = mController.isLocationEnabled();
// Work around for bug 15916487: don't show location tile on top of lock screen. After the
// bug is fixed, this should be reverted to only hiding it on secure lock screens:
// state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing());
state.visible = !mKeyguard.isShowing();
state.value = locationEnabled;
if (locationEnabled) {
state.icon = mEnable;
state.label = mContext.getString(R.string.quick_settings_location_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_location_on);
} else {
state.icon = mDisable;
state.label = mContext.getString(R.string.quick_settings_location_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_location_off);
}
// kth add for shorcut bar test at 20150820 start 加入自己的操作
updateIntent.setAction(CONTROLACTION_LOCATION_UPDATE_STATE);
TILE_STATE = state.value ? 1 : 0;
updateIntent.putExtra("msg", TILE_STATE);
mContext.sendBroadcast(updateIntent);
// kth add for shorcut bar test at 20150820 end
}
【正在运行程序获取和一键加速】
这块区域是HorizontalScrollView和GridView组成的横向List,宽度自适应item数量(横向list布局技巧网上多查多试) 。
获取正在运行程序:
final PackageManager pm = mContext.getPackageManager();
am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
runningTasks = am.getRunningTasks(16);//The maximum number of running list
Log.i(TAG, "runningTasks = " + runningTasks.size());
for (ActivityManager.RunningTaskInfo rt : runningTasks) {
HashMap runningAppInfo = new HashMap();
ComponentName component = rt.baseActivity;
String pkgName = component.getPackageName();//包名,一键加速时是根据包名kill
String className = component.getClassName();
String shortclassName = component.getShortClassName();
ApplicationInfo applicationInfo = null;
String applicationName = (String) pm.getApplicationLabel(applicationInfo);
Drawable icon = pm.getApplicationIcon(applicationInfo);
int id = rt.id;
ActivityManager.TaskThumbnail taskThumbnail = am.getTaskThumbnail(id);
Bitmap bitmap = taskThumbnail.mainThumbnail;//正在运行程序的略缩截图
if (pkgName.equals("com.android.launcher3") || pkgName.equals("com.android.systemui")) {
// skip the launcher and systemui
continue;
}
try {
applicationInfo = pm.getApplicationInfo(pkgName, 0);
// skip the system app跳过系统内置应用
// if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
// continue;
// }
} catch (NameNotFoundException e) {
}
//省略代码若干
}
一键加速:kill进程的方法可以用killBackgroundProcesses(kill_pkgName);这种方法进程被kill了会立马被重启am.forceStopPackage(kill_pkgName);这种方法是调用底层jni来kill进程,不会被重启,除某某顽固卫士。但这种方法被hide,在上层调用时需要使用反射,底层可直接调用。
/**
* kill process by package name
*
* @param kill_pkgName
* selected item's packege name
* @param item
* position of the origin data list
*/
private void killProcessByPkgname(String kill_pkgName, int position) {
if (kill_pkgName != null) {
Log.i(TAG, "kill_pkgName =" + kill_pkgName + " position =" + position);
try {
am.killBackgroundProcesses(kill_pkgName);
am.forceStopPackage(kill_pkgName);//关键
appInfos.remove(position);
Log.i(TAG, "killProcessByPkgname appInfos.tostring = " + appInfos.toString());
if (adapter != null) {
adapter.notifyDataSetChanged();
if (appInfos.size() == 0) {//设置无后台程序时的UI
no_runningapp_tx.setVisibility(View.VISIBLE);
no_runningapp_tx.setText(R.string.no_runningapp_str);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
先讲这么多,后续会完善=。=