Android NavigationBar中虚拟键调查
来源:互联网 发布:最优化例题 编辑:程序博客网 时间:2024/06/05 16:42
NavigationBar调查
NavigationBar是Android4.0以后出现的新特性,下图2就是NavigationBar,其中包括Back, Home, Recent键。并且对于基于2.3或者更早的版本的app,会在右下角显示一个Menu。这几个键都是虚拟的按键,对于没有实体键的手机或者tablet相当的方便。
NavigationBar的显示与隐藏
NavigationBar在App层是由SystemUI控制显示与隐藏的,并且布局文件也在SystemUI中。SystemUI在路径frameworks/base/packages/SystemUI/。
SystemUI就是StatusBar的界面部分,随系统启动而启动。SystemUI启动后会加载SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
之后调用makeStatusBarView构建视图,其中就包括了NavigationBar的创建。代码如下:
try {boolean showNav = mWindowManagerService.hasNavigationBar();if (showNav) {mNavigationBarView =(NavigationBarView) View.inflate(context, R.layout.navigation_bar, null); mNavigationBarView.setDisabledFlags(mDisabled);mNavigationBarView.setBar(this); }} catch (RemoteException ex) { // no window manager? good luck with that}
可见NavigationBarView其实是NavigationBar的视图,对应的layout文件为navigation_bar.xml。NavigationBar是否构建显示是通过mWindowManagerService的hasNavigationBar()接口确定的。WindowManagerService会向下调用Policy的接口hasNavigationBar决定NavigationBar显示与否。
public interface WindowManagerPolicy { public boolean hasNavigationBar();}
而Policy调用的实际上是PhoneWindowManager的接口hasNavigationBar,获取变量mHasNavigationBar的布尔值,这个变量是setInitialDisplaySize的时候初始化的。
public class PhoneWindowManager implements WindowManagerPolicy {... ...// Use this instead of checking config_showNavigationBar so that it can be consistently // overridden by qemu.hw.mainkeys in the emulator. public boolean hasNavigationBar() { return mHasNavigationBar; }... ...public void setInitialDisplaySize(Display display, int width, int height, int density) {... ...// SystemUI (status bar) layout policy int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / density; String deviceType = SystemProperties.get("sys.device.type"); if (! "".equals(deviceType) && deviceType.equals("tablet")) { // if indicate device type is tablet skip the judge for "phone" UI } else if (shortSizeDp < 600) { // 0-599dp: "phone" UI with a separate status & navigation bar mHasSystemNavBar = false; mNavigationBarCanMove = true; } else if (shortSizeDp < 720) { // 600+dp: "phone" UI with modifications for larger screens mHasSystemNavBar = false; mNavigationBarCanMove = false; }if (!mHasSystemNavBar) { mHasNavigationBar = mContext.getResources().getBoolean( com.android.internal.R.bool.config_showNavigationBar); // Allow a system property to override this. Used by the emulator. String navBarOverride = SystemProperties.get("qemu.hw.mainkeys"); if (! "".equals(navBarOverride)) { if (navBarOverride.equals("1")) mHasNavigationBar = false; else if (navBarOverride.equals("0")) mHasNavigationBar = true; } } else { mHasNavigationBar = false; }}}
com.android.internal.R.bool.config_showNavigationBar这个才是navigationbar显示与否的配置变量,这个变量其实是在Android编译过程中由
frameworks/base/core/res/res/values/config.xml配置的
<!-- Whether a software navigation bar should be shown. NOTE: in the future this may be autodetected from the Configuration. --> <bool name="config_showNavigationBar">false</bool>
WindowManagerPolicy -- frameworks/base/core/java/android/view/WindowManagerPolicy.java
PhoneWindowManager -- frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
另一方面,通过调用SystemProperties.get("qemu.hw.mainkeys");获取到NavigationBar是否被自定义,系统默认是不会设置qemu.hw.mainkeys的值的,所以获取到的是“0”。因此,就算不设定config.xml中config_showNavigationBar的值为true,屏幕够大的话,NavigationBar默认还是会显示出来的。
External/qemu/vl-android.c中如下代码会设定qemu.hw.mainkeys的值,下层代码没有继续调查。
/* Initialize presence of hardware nav button */ boot_property_add("qemu.hw.mainkeys", android_hw->hw_mainKeys ? "1" : "0");
总的来说NavigationBar的显示与隐藏主要依赖3个方面
1.config.xml的配置config_showNavigationBar为true则显示,false不显示
2.Density,WXGA720/WXGA800/WXGA800-7in三种分辨率显示
3.Qemu.hw.mainkeys的设置,0显示,1不显示
NavigationBar Click事件处理
NavigationBar的layout文件是frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml
视图类是Frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
从layout文件看navigation_bar.xml是按方向配置的,rot0/rot90/rot270
每个方向都会包含KeyButtonView类型的4个View,
id分别是back/home/recent_apps/menu。
视图类NavigationBarView.java中会包含获取4个button的接口,代码如下:
public View getRecentsButton() { return mCurrentView.findViewById(R.id.recent_apps); }public View getMenuButton() { return mCurrentView.findViewById(R.id.menu); }public View getBackButton() { return mCurrentView.findViewById(R.id.back); }public View getHomeButton() { return mCurrentView.findViewById(R.id.home); }
SystemUI启动后会调用PhoneStatusBar.java的start->addNavigationBar()->
prepareNavigationBarView(),并在prepare中设定了recent和Home的处理事件。
@Overridepublic void start() {... ...addNavigationBar();}// For small-screen devices (read: phones) that lack hardware navigation buttonsprivate void addNavigationBar() {if (DEBUG) Slog.v(TAG, "addNavigationBar: about to add " + mNavigationBarView);if (mNavigationBarView == null) return;prepareNavigationBarView(); mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());}private void prepareNavigationBarView() {mNavigationBarView.reorient(); mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener); mNavigationBarView.getHomeButton().setOnTouchListener(mHomeSearchActionListener); mNavigationBarView.getSearchLight().setOnTouchListener(mHomeSearchActionListener); updateSearchPanel();}
接下来先看最简单的Recent Button。
Recent Button Click事件处理
private View.OnClickListener mRecentsClickListener = new View.OnClickListener() { public void onClick(View v) { toggleRecentApps(); } };
Recent button设定了onClickListener,处理函数是toggleRecentApps()。toggleRecentApps在PhoneStatusBar的父类BaseStatusBar中实现。
@Overridepublic void toggleRecentApps() {int msg = MSG_TOGGLE_RECENTS_PANEL;mHandler.removeMessages(msg); mHandler.sendEmptyMessage(msg);}
从函数实现来看,发送了MSG_TOGGLE_RECENTS_PANEL msg给线程。
protected class H extends Handler {public void handleMessage(Message m) {switch (m.what) { case MSG_TOGGLE_RECENTS_PANEL: if (DEBUG) Slog.d(TAG, "toggle recents panel"); toggleRecentsActivity(); break; ... ... }}}
BaseStatusBar.java的内部线程会调用toggleRecentsActivity()处理接收到的消息。
protected void toggleRecentsActivity() { try {TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask(); Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT);intent.setClassName("com.android.systemui","com.android.systemui.recent.RecentsActivity");intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); if (firstTask == null) {if (RecentsActivity.forceOpaqueBackground(mContext)) {ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, R.anim.recents_launch_from_launcher_enter, R.anim.recents_launch_from_launcher_exit);mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( UserHandle.USER_CURRENT)); } else {// The correct window animation will be applied via the activity's stylemContext.startActivityAsUser(intent, new UserHandle( UserHandle.USER_CURRENT));} } else {Bitmap first = firstTask.getThumbnail(); ... ... ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(getStatusBarView(), first, x, y, new ActivityOptions.OnAnimationStartedListener() { public void onAnimationStarted() {Intent intent = new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT);intent.setPackage("com.android.systemui");mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));}}); intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true);mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( UserHandle.USER_CURRENT));}return;} catch (ActivityNotFoundException e) {Log.e(TAG, "Failed to launch RecentAppsIntent", e);}
处理消息的本质其实是通过context启动一个Activity,显示RecentApp。下层调用没有继续调查。从Log中看也确实是这样
D/PhoneStatusBar( 360): mRecentsClickListener onClicked call toggleRecentApps. I/ActivityManager( 279): START u0 {act=com.android.systemui.recent.action.TOGGLE_RECENTS flg=0x10800000 cmp=com.android.systemui/.recent.RecentsActivity} from pid 360
Home/Back/Menu Button Click事件处理
其实home/back/recent/menu都是KeyButtonView类型的View,而Home/Back Button的onTouch事件其实也是在KeyButtonView中做的处理。
onTouch会判断发送过来的key_code是否合法,合法就调用sendEvent将事件发送出去。事件是通过InputManager的injectInputEvent强制插入一个事件
public boolean onTouchEvent(MotionEvent ev) {final int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: ... ... case MotionEvent.ACTION_MOVE: ... ... case MotionEvent.ACTION_CANCEL: ... ... case MotionEvent.ACTION_UP: final boolean doIt = isPressed(); setPressed(false); if (mCode != 0) { if (doIt) { sendEvent(KeyEvent.ACTION_UP, 0); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); playSoundEffect(SoundEffectConstants.CLICK); } else { sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); } } else { // no key code, just a regular ImageView if (doIt) { performClick(); } } }}void sendEvent(int action, int flags) {sendEvent(action, flags, SystemClock.uptimeMillis());}void sendEvent(int action, int flags, long when) {final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);}
从log看也确实是这样处理的,其中action 0代表ACTION_DOWN,1代表ACTION_UP。mCode是键值,82代表KEYCODE_MENU,3代表KEYCODE_HOME,4代表KEYCODE_BACK。具体的KeyCode和Action可以参考
http://developer.android.com/intl/zh-cn/reference/android/view/KeyEvent.html
D/KeyButtonView( 351): onTouchEvent MotionEvent = MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=40.0, y[0]=52.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=170128, downTime=170128, deviceId=0, source=0x1002 }D/KeyButtonView( 351): sendEvent mCode = 82D/KeyButtonView( 351): sendEvent, action = 0D/KeyButtonView( 351): onTouchEvent MotionEvent = MotionEvent { action=ACTION_UP, id[0]=0, x[0]=40.0, y[0]=52.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=170195, downTime=170128, deviceId=0, source=0x1002 }D/KeyButtonView( 351): sendEvent mCode = 82D/KeyButtonView( 351): sendEvent, action = 1E/StrictMode( 360): at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)D/KeyButtonView( 360): onTouchEvent MotionEvent = MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=95.0, y[0]=47.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=160035, downTime=160035, deviceId=0, source=0x1002 }D/KeyButtonView( 360): sendEvent mCode = 3D/KeyButtonView( 360): sendEvent, action = 0D/KeyButtonView( 360): onTouchEvent MotionEvent = MotionEvent { action=ACTION_UP, id[0]=0, x[0]=95.0, y[0]=47.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=160137, downTime=160035, deviceId=0, source=0x1002 }D/KeyButtonView( 360): sendEvent mCode = 3D/KeyButtonView( 360): sendEvent, action = 1D/KeyButtonView( 360): onTouchEvent MotionEvent = MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=74.0, y[0]=61.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=177529, downTime=177529, deviceId=0, source=0x1002 }D/KeyButtonView( 360): sendEvent mCode = 4D/KeyButtonView( 360): sendEvent, action = 0D/KeyButtonView( 360): onTouchEvent MotionEvent = MotionEvent { action=ACTION_UP, id[0]=0, x[0]=74.0, y[0]=61.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=177649, downTime=177529, deviceId=0, source=0x1002 }D/KeyButtonView( 360): sendEvent mCode = 4D/KeyButtonView( 360): sendEvent, action = 1
- Android NavigationBar中虚拟键调查
- Android NavigationBar中虚拟键调查
- [Android][SystemUI]navigationbar 3个虚拟键隐藏与显示
- android4.4 navigationbar中去除虚拟menu键
- android虚拟按键NavigationBar的判断
- 隐藏底部虚拟键NavigationBar实现全屏
- 如何Android中自定义Navigationbar
- 如何Android中自定义Navigationbar
- 如何Android中自定义Navigationbar
- Android 状态栏(StatusBar)和虚拟键(NavigationBar)的一些设置
- Android 状态栏(StatusBar)和虚拟键(NavigationBar)的高度获取
- Android 删除隐藏NavigationBar (虚拟导航栏)
- android 虚拟导航按钮(NavigationBar)可手动隐藏开发
- Android隐藏和沉浸式虚拟按键NavigationBar的实现
- 隐藏虚拟按键 NavigationBar
- Android NavigationBar
- android系统定制开发动态显示隐藏虚拟按键虚拟导航Navigationbar
- Android 7.0 虚拟按键(NavigationBar)源码分析 之 View的创建流程
- 人心本无染,心静自然清 ——赞“落梅”
- 巩俐挽老总现身捞金 与人打招呼无架子
- 不使用引擎,如何开发游戏
- 杭电 1008 Elevator
- OpenGL: 平面阴影投射矩阵的推导
- Android NavigationBar中虚拟键调查
- linux grep命令
- mysql存储过程
- 利用观察者模式实现通知
- [IOS]TableView Cell重用机制及TableView方法说明
- css div的水平、垂直同时居中
- Struts2 使用annotation从action跳转到action并传递参数
- 第六周作业
- 同步中,为什么要wait,又notify谁?