在android的状态栏(statusbar)中增加menu,home和back快捷键的方法 改进
;nbsp;
需要说明的是:刚入手android没几天,对系统还不算很熟悉,这篇文章是基于前一篇转帖
做的。只是觉得他的方法有些麻烦,而且改出来的效果也不是我想要的。
由于完全改了 status bar ,建议先做几张 png 图片,加到
Frameworks/base/core/res/res/drawable
下。最好做一张背景图,替换 statusbar_background.png
另外我又加了几张 icon ,分别是 home menu 和 back 的正常和按下状态。
这些图片为:
stat_home.png
stat_home_pressed.png
stat_back.png
stat_back_pressed.png
stat_menu.png
stat_menu_pressed.png
修改步骤为:
一( 修改 xml 界面
1. 增加图标
当然,更改整个 status bar 避免不要要对源码大刀修一下。我的该法是:
修改 status bar 的 layerout 文件:
Frameworks/base/core/res/res/layout/status_bar.xml 在原来的 linearlayout 中新增三个 image view
view plain
copy to clipboard
print
?
<; LinearLayout android:id = ";@+id/icons";
android:layout_width = ";fill_parent";
android:layout_height = ";fill_parent";
android:orientation = ";horizontal"; >;
<; ImageView
android:id = ";@+id/status_home";
&n
bsp; android:layout_width = ";wrap_content";
android:layout_height = ";wrap_content";
android:layout_gravity = ";top";
android:paddingTop = ";6dip";
android:paddingRight = ";10dip";
android:paddingLeft = ";10dip";
android:src = ";@drawable/stat_home"; />;
<; com.android.server.status.IconMerger android:id = ";@+id/notificationIcons";
android:layout_width = ";0dip";
android:layout_weight = ";1";
android:layout_height = ";fill_parent";
android:layout_alignParentLeft = ";true";
android:paddingLeft = ";6dip";
android:gravity = ";center_vertical";
android:orientation = ";horizontal"; />;
<; LinearLayout android:id = ";@+id/statusIcons";
android:layout_width = ";wrap_content";
android:layout_height =
";fill_parent";
android:layout_alignParentRight = ";true";
android:paddingRight = ";6dip";
android:gravity = ";center_vertical";
android:orientation = ";horizontal"; />;
<; ImageView android:id = ";@+id/status_menu";
android:layout_width = ";wrap_content";
android:layout_height = ";wrap_content";
android:layout_gravity = ";top";
android:paddingTop = ";6dip";
android:paddingLeft = ";10dip";
android:paddingRight = ";10dip";
android:src = ";@drawable/stat_menu"; />;
<; ImageView android:id = ";@+id/status_back";
android:layout_width = ";wrap_content";
android:layout_height = ";wrap_content";
android:layout_gravity = ";top";
android:paddingTop = ";6dip";
android:paddingRight = ";10dip";
android:paddingLeft = ";10dip";
android:src = ";@drawable/stat_back"; />;
/LinearLayout>;
<;LinearLayout android:id=";@+id/icons"; android:layout_width=";fill_parent"; android:layout_height=";fill_parent"; android:orientation=";horizontal";>; <;ImageView android:id=";@+id/status_home"; android:layout_width=";wrap_content"; android:layout_height=";wrap_content"; android:layout_gravity=";top"; android:paddingTop=";6dip"; android:paddingRight=";10dip";
android:paddingLeft=";10dip"; android:src=";@drawable/stat_home"; />; <;com.android.server.status.IconMerger android:id=";@+id/notificationIcons"; android:layout_width=";0dip"; android:layout_weight=";1";
android:layout_height=";fill_parent"; android:layout_alignParentLeft=";true"; android:paddingLeft=";6dip"; android:gravity=";center_vertical"; android:orientation=";horizontal";/>; <;LinearLayout
android:id=";@+id/statusIcons"; android:layout_width=";wrap_content"; android:layout_height=";fill_parent"; android:layout_alignParentRight=";true"; android:paddingRight=";6dip"; android:gravity=";center_vertical"; android:orientation=";horizontal";/>; <;ImageView android:id=";@+id/status_menu"; android:layout_width=";wrap_content"; android:layout_height=";wrap_content"; android:layout_gravity=";top"; android:paddingTop=";6dip";
android:paddingLeft=";10dip"; android:paddingRight=";10dip";
android:src=";@drawable/stat_menu"; />; <;ImageView
android:id=";@+id/status_back"; android:layout_width=";wrap_content"; android:layout_height=";wrap_content"; android:layout_gravity=";top"; android:paddingTop=";6dip"; android:paddingRight=";10dip";
android:paddingLeft=";10dip"; android:src=";@drawable/stat_back"; />; <;/LinearLayout>;
这样做的好处就是简单。同时保证最右端是 home 按钮,最左端是 back 按钮,不受它本来的约束。这样 status bar 上即可看到这些按钮了。
图标的位置,可通过修改 paddingRight , paddingLeft 和 paddingTop 的值达到最佳视觉效果。
2. 修改 status bar 的高度。
既然要在 status bar 上增加那么几个按钮,当然是想要使用触摸操作的, android 自带的 status bar 高度太小,不适用。对于 7 寸屏的话, 50pixel 的高度应该是差不多了。 修改高度很简单,如我转的 shinning mm 的博文。
修改 frameworks/base/core/res/res/values/dimens.xml 的 status_bar_height 属性 <;!-- Height of the status bar -->;
<;dimen name=";status_bar_height";>;50dip<;/dimen>;
当然,如果相改 title 的高度,可以修改 Frameworks /base/core/res/res/values/themes.xml 中的 Window attributes 的 windowTitleSize 值,不过我觉得没必要,改了反倒不好看了 :)
编译运行一下:
view plain
copy to clipboard
print
?
~/donut$ source ./env.sh
~/donut$ make –j8
~/donut$ emulator –skin WVGA800
~/donut$ source ./env.sh ~/donut$ make –j8 ~/donut$ emulator –skin WVGA800
, 看状态栏是不是改变了,
二 为按钮添加动态效果
添加动态效果,就是触摸按下 hilight ,松开或者移出后恢复的动作。这一块,我是通过修改 frameworks/base/services/java/com/android/server/status/StatusBarView.java
实现的。
1. 获取 statusbar 中新增加的 icon 的 handler 。
在类中新增加三个成员(这需要 import android.widget.ImageView; ):
view plain
copy to clipboard
print
?
ImageView mHomeIcon;
ImageView mBackIcon;
ImageView mMenuIcon;
ImageView mHomeIcon; ImageView mBackIcon; ImageView mMenuIcon;
同时增加三个常量,表示这些 icon 对应的键值 ( 这需要 import android.view.KeyEvent;)
view plain
copy to clipboard
print
?
public static final int RESV_KEY_HOME = KeyEvent.KEYCODE_HOME;
public static final int RESV_KEY_BACK = KeyEvent.KEYCODE_BACK;
public static final int RESV_KEY_MENU = KeyEvent.KEYCODE_MENU;;
public static final int RESV_KEY_HOME = KeyEvent.KEYCODE_HOME; public static final int RESV_KEY_BACK = KeyEvent.KEYCODE_BACK; public static final int RESV_KEY_MENU = KeyEvent.KEYCODE_MENU;;
在 onFinishInflate() 中,获得实际的对象:
view plain
copy to clipboard
print
?
mHomeIcon = (ImageView)findViewById(R.id.status_home);
mBackIcon = (ImageView)findViewById(R.id.status_back);
mMenuIcon = (ImageView)findViewById(R.id.status_menu);
mHomeIcon = (ImageView)findViewById(R.id.status_home); mBackIcon = (ImageView)findViewById(R.id.status_back); mMenuIcon =
(ImageView)findViewById(R.id.status_menu);
这三个对象就是我们在 status_bar.xml 中添加的。
2. 添加触摸处理。
首先,应该判断是那个图标被按下,这个我们在 StatusBarView.Java 的 onTouchEvent 中
来判断。
这里,我做了一个小的按键状态,已方便处理按下、弹起和移出的动作。
首先增加两个状态成员:
view plain
copy to clipboard
print
?
int mResvKeyState = -1; //记住的上次按键状
态, -1为无状态。
int mResvKeyCode = -1; //记住的上次按键值,
-1为无状态。
int mResvKeyState = -1; //记住的上次按键状态, -1为无状态。 int mResvKeyCode = -1; //记住的上次按键值,-1为无状态。
这样我的 onTouchEvent 就变成这样了:
view plain
copy to clipboard
print
?
@Override
public boolean
onTouchEvent(MotionEvent event) {
if (mService.mExpanded== true || mService.mTracking== true ){
if
(event.getAction() != MotionEvent.ACTION_DOWN) {  
;
mService.inter
ceptTouchEvent(event);
}
return true ;
}
if (mResvKeyState == - 1 ) // remembered key state, no reserve
{
switch
(getResvKeyArea(event)){
case RESV_KEY_HOME:
case RESV_KEY_BACK:
case RESV_KEY_MENU:
{
mResvKeyState = event.getAction();
mResvKeyCode = getResvKeyArea(event);
updateResvKeyIcon(mResvKeyState, mResvKeyCode);
}
break ;
default :
if (event.getAction() != MotionEvent.ACTION_DOWN) {
mService.interceptTouchEvent(event);
}
}
}else {
mResvKeyState = event.getAction(); // new state
if
(mResvKeyState == MotionEvent.ACTION_MOVE){
if (mResvKeyCode != getResvKeyArea(event)){
/* out of bound, resume the icon */
updateResvKeyIcon(MotionEvent.ACTION_UP, mResvKeyCode);
mResvKeyCode = -1 ;
mResvKeyState = -1 ;
}
}else if (mResvKeyState == MotionEvent.ACTION_UP){
updateResvKeyIcon(mResvKeyState, mResvKeyCode);
mResvKeyCode = -1 ;
mResvKeyState = -1 ;
}else {
Log.d(TAG, ";state machine error! Never be here!"; ); 
;
}
}
return true ;
}
@Override public boolean onTouchEvent(MotionEvent event)
{ if(mService.mExpanded==true || mService.mTracking==true){ if
(event.getAction() != MotionEvent.ACTION_DOWN)
{ mService.interceptTouchEvent(event); } return true; } if(mResvKeyState == -1) // remembered key state, no reserve { switch(getResvKeyArea(event)){ case RESV_KEY_HOME: case RESV_KEY_BACK: case RESV_KEY_MENU: { mResvKeyState = event.getAction(); mResvKeyCode = getResvKeyArea(event); updateResvKeyIcon(mResvKeyState, mResvKeyCode); } break; default: if (event.getAction() != MotionEvent.ACTION_DOWN) { mService.interceptTouchEvent(event); } } }else{ mResvKeyState = event.getAction(); // new state if(mResvKeyState == MotionEvent.ACTION_MOVE){ if(mResvKeyCode != getResvKeyArea(event)){ /* out of bound, resume the icon */
updateResvKeyIcon(MotionEvent.ACTION_UP, mResvKeyCode); mResvKeyCode = -1; mResvKeyState = -1; } }else if(mResvKeyState ==
MotionEvent.ACTION_UP){ updateResvKeyIcon(mResvKeyState, mResvKeyCode); mResvKeyCode = -1; mResvKeyState = -1; }else{ Log.d(TAG, ";state machine error! Never be here!";); } } return true; }
里面用到的两个private方法简单实现如下:
view plain
copy to clipboard
print
?
private int getResvKeyArea(MotionEvent event )
{
if
( ( event .getX() <;= mHomeIcon.getRight())
&;&; (event .getY() <;= this .getHeight()) ){
return
RESV_KEY_HOME;
}
else if
( ( event .getX() >;= mBackIcon.getLeft())
&;&; (event .getY() <;= this .getHeight()) ){
return
RESV_KEY_BACK;
}
else if
( ( event .getX() >;= mMenuIcon.getLeft())
&;&; (event .getY() <;= this .getHeight()) ){
return
RESV_KEY_MENU;
}else
return -1;
}
private int updateResvKeyIcon( int state, int key)
{
if (key == RESV_KEY_BACK){
if
(state == MotionEvent.ACTION_UP){
mBackIcon.setImageResource(com.android.internal .R.drawable.stat_back);
}else if (state == MotionEvent.ACTION_DOWN){
mBackIcon.setImageResource(com.android.internal .R.drawable.stat_back_pressed);
}
}else if
(key == RESV_KEY_HOME){
if
(state == MotionEvent.ACTION_UP){
mHomeIcon.setImageResource(com.android.internal .R.drawable.stat_home);
}else if (state == MotionEvent.ACTION_DOWN){
mHomeIcon.setImageResource(com.android.internal .R.drawable.stat_home_pressed);
}
}else if
(key == RESV_KEY_MENU){
if
(state == MotionEvent.ACTION_UP){
mMenuIcon.setImageResource(com.android.internal .R.drawable.stat_menu);
}else if (state == MotionEvent.ACTION_DOWN){
mMenuIcon.setImageResource(com.android.internal .R.drawable.stat_menu_pressed);
}
}
return 0;
}
private int getResvKeyArea(MotionEvent event) { if( (event.getX() <;= mHomeIcon.getRight()) &;&; (event.getY() <;= this.getHeight()) ){ return RESV_KEY_HOME; } else if( (event.getX() >;= mBackIcon.getLeft()) &;&; (event.getY() <;= this.getHeight()) ){ return RESV_KEY_BACK; } else if( (event.getX() >;= mMenuIcon.getLeft()) &;&; (event.getY() <;= this.getHeight()) ){ return RESV_KEY_MENU; }else return -1; } private int updateResvKeyIcon(int state, int key) { if(key == RESV_KEY_BACK){ if(state ==
MotionEvent.ACTION_UP){ mBackIcon.setImageResource(com.android.internal.R.drawable.stat_back); }else if(state ==
MotionEvent.ACTION_DOWN){ mBackIcon.setImageResource(com.android.internal.R.drawable.stat_back_pressed); } }else if(key == RESV_KEY_HOME){ if(state == MotionEvent.ACTION_UP){ mHomeIcon.setImageResource(com.android.internal.R.drawable.stat_home); }else if(state ==
MotionEvent.ACTION_DOWN){ mHomeIcon.setImageResource(com.android.internal.R.drawable.stat_home_pressed); } }else if(key == RESV_KEY_MENU){ if(state == MotionEvent.ACTION_UP){ mMenuIcon.setImageResource(com.android.internal.R.drawable.stat_menu); }else if(state ==
MotionEvent.ACTION_DOWN){ mMenuIcon.setImageResource(com.android.internal.R.drawable.stat_menu_pressed); } } return 0; }
同时,我不想再在按下这些icon的时候,触发下拉动作,我也改了onInterceptTouchEvent
函数:
view plain
copy to clipboard
print
?
@Override
public boolean
onInterceptTouchEvent(MotionEvent event) {
if
( (event.getX() >; mHomeIcon.getRight())
&;&; (event.getX() <; mMenuIcon.getLeft())){
return
mService.interceptTouchEvent(event)
? true : super .onInterceptTouchEvent(event);
}
return false ;
}
@Override public boolean onInterceptTouchEvent(MotionEvent event) { if( (event.getX() >; mHomeIcon.getRight()) &;&; (event.getX() <; mMenuIcon.getLeft())){ return mService.interceptTouchEvent(event) ? true : super.onInterceptTouchEvent(event); } return false; }
再编译一下,看一下结果 : ) 是不是能动了,
三,添加相应事件
1. 添加新的intent
首先是新增一条 intent , 在
framework/base/core/java/android/content/intent.java 中增加
view plain
copy to clipboard
print
?
@SdkConstant (SdkConstantType.BROADCAST_INTENT_ACTION)
public static final
String ACTION_ICONKEY_CHANGED = ";android.intent.action.ICONKEY_CHANGED"; ;
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
ACTION_ICONKEY_CHANGED = ";android.intent.action.ICONKEY_CHANGED";;
2. 发送intent
在StatusBarView.java的OnKeyEvent中,松开按键的分支else if(mResvKeyState == MotionEvent.ACTION_UP)操作中加入发送intent的动作:
view plain
copy to clipboard
print
?
Intent intent = new
Intent(Intent.ACTION_ICONKEY_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtra(";keycode"; , mResvKeyCode);
mService.sendIntent(intent);
Intent intent = new Intent(Intent.ACTION_ICONKEY_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtra(";keycode";, mResvKeyCode); mService.sendIntent(intent);
这个intent是只有注册的接收者才能接收。
这里,我们是通过StatusBarService来发送这个intent的。
在StatusBarService.java中新增一个方法:
view plain
copy to clipboard
print
?
void sendIntent(Intent intent)
{
mContext.sendBroadcast(intent);
}
void sendIntent(Intent intent) { mContext.sendBroadcast(intent); } 3.接收并处理intent
这个就要修改StatusBarPolicy.java了
首先,在构造函数中加入Intent的filter,注册号这个intent的receiver。
filter.addAction(Intent.ACTION_ICONKEY_CHANGED); 然后再private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() 加入
Intent的receiver动作;
view plain
copy to clipboard
print
?
else if
(action.equals(Intent.ACTION_ICONKEY_CHANGED)) {
G, ";Received ACTION_ICONKEY_CHANGED"; );
updateIconKeyAction(intent);
}
else if (action.equals(Intent.ACTION_ICONKEY_CHANGED)) { Log.d(TAG, ";Received ACTION_ICONKEY_CHANGED";); updateIconKeyAction(intent); }
方法updateIconKeyAction的定义如下:
view plain
copy to clipboard
print
?
private final void
updateIconKeyAction(Intent intent){
int
keycode = intent.getIntExtra( ";keycode"; , - 1 );
if (keycode != - 1 ){
long
now = SystemClock.uptimeMillis();
try {
KeyEvent down = new
KeyEvent(now, now, KeyEvent.ACTION_DOWN, keycode, 0 );
KeyEvent up = new
KeyEvent(now, now, KeyEvent.ACTION_UP, keycode, 0 );
(IWindowManager.Stub
.asInterface(ServiceManager.getService(";window"; )))
.injectKeyEvent(down, false );
(IWindowManager.Stub
.asInterface(ServiceManager.getService(";window"; )))
.injectKeyEvent(up, false );
} catch (RemoteException e) {
Log.i(";Input"; , ";DeadOjbectException"; );
}
}
}
private final void updateIconKeyAction(Intent intent){ int keycode = intent.getIntExtra(";keycode";, -1); if(keycode != -1){ long now = SystemClock.uptimeMillis(); try { KeyEvent down = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keycode, 0); KeyEvent up = new KeyEvent(now, now, KeyEvent.ACTION_UP, keycode, 0);
(IWindowManager.Stub .asInterface(ServiceManager.getService(";window";))) .injectKeyEvent(down, false);
(IWindowManager.Stub .asInterface(ServiceManager.getService(";window";))) .injectKeyEvent(up, false); } catch (RemoteException e) { Log.i(";Input";, ";DeadOjbectException";); } } }
这样,基本上就完成了。
编译一下, 由于新增了一个intent,因此要先make update-api,
view plain
copy to clipboard
print
?
~/donut$ source ./env.sh
~/donut$ make update-api
~/donut$ make –j8
~/donut$ emulator –skin WVGA800
~/donut$ source ./env.sh ~/donut$ make update-api ~/donut$ make –j8 ~/donut$ emulator –skin WVGA800
另外,如果不是做phone,也可以在StatusBarPolicy.java中将所有phone相关的处理都删
掉。