如果你有這樣的需求:用戶進入你的app以后,所有的操作都是你的app中設定的,用戶不可以擁有系統設置等行為的能力。然而,Android系統,可以通過從頂部往下拉,從而得到一個通知和快速系統設置的頁面:
因此,現在你想禁止它彈出,怎么辦呢?
我不知道在app中怎么做,但是如果你們的處境像我一樣:android系統是一個針對特殊平台定制的,它一旦啟動就進入特定的功能頁面,並且不允許用戶有進入系統設置的能力,那么您可以像下面這樣,直接在系統代碼中進行修改。
分析如何解決問題
使用Android device monitor工具,我們可以看到Android 狀態欄的布局,我們會發現,平時我們看到的狀態欄(如下圖所示)是由PhoneStatusBarView負責繪制個管理的:
結合我們的操作,當我們點擊狀態欄或者下拉的時候,都會出現通知界面。而點擊和下拉都是觸摸事件,因此,理所當然的,我們會想到在PhoneStatusBarView的onTouchEvent中處理相應的邏輯。onTouchEvent定義在frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\PhoneStatusBarView.Java中:
@Override public boolean onTouchEvent(MotionEvent event) { boolean barConsumedEvent = mBar.interceptTouchEvent(event); if (DEBUG_GESTURES) { if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { EventLog.writeEvent(EventLogTags.SYSUI_PANELBAR_TOUCH, event.getActionMasked(), (int) event.getX(), (int) event.getY(), barConsumedEvent ? 1 : 0); } } return barConsumedEvent || super.onTouchEvent(event); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
它似乎什么都沒有做…然而不要忽視了,它調用了super.onTouchEvent(event)方法。PhoneStatusBarView繼承了PanelBar類,這個類繼承自PanelBar類。因此,super.onTouchEvent就是調用PanelBar中的onTouchEvent方法,PanelBar也在frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\目錄下:
@Override public boolean onTouchEvent(MotionEvent event) { // Allow subclasses to implement enable/disable semantics if (!panelsEnabled()) { if (event.getAction() == MotionEvent.ACTION_DOWN) { Log.v(TAG, String.format("onTouch: all panels disabled, ignoring touch at (%d,%d)", (int) event.getX(), (int) event.getY())); } return false; } // figure out which panel needs to be talked to here if (event.getAction() == MotionEvent.ACTION_DOWN) { final PanelView panel = selectPanelForTouch(event); if (panel == null) { // panel is not there, so we'll eat the gesture Log.v(TAG, String.format("onTouch: no panel for touch at (%d,%d)", (int) event.getX(), (int) event.getY())); mTouchingPanel = null; return true; } boolean enabled = panel.isEnabled(); if (DEBUG) LOG("PanelBar.onTouch: state=%d ACTION_DOWN: panel %s %s", mState, panel, (enabled ? "" : " (disabled)")); if (!enabled) { // panel is disabled, so we'll eat the gesture Log.v(TAG, String.format( "onTouch: panel (%s) is disabled, ignoring touch at (%d,%d)", panel, (int) event.getX(), (int) event.getY())); mTouchingPanel = null; return true; } startOpeningPanel(panel); } final boolean result = mTouchingPanel != null ? mTouchingPanel.onTouchEvent(event) : true; return result; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
從函數的名字猜測,startOpeningPanel方法似乎就是彈出下拉菜單的入口,把它注釋掉,重新編譯SystemUI模塊,然后替換/system/priv-app/SystemUI/SystemUI.apk,重啟系統,就發現無論你是點擊還是下拉屏幕頂部,都不會出現下拉也面了。
我們不妨簡單分析下這里:
startOpeningPanel接收一個panel作為參數,而這個panel則是selectPanelForTouch(event);方法返回的。
PhoneStatusBarView中覆寫了該方法:
@Override public PanelView selectPanelForTouch(MotionEvent touch) { // No double swiping. If either panel is open, nothing else can be pulled down. return mNotificationPanel.getExpandedHeight() > 0 ? null : mNotificationPanel; }
可以看到,這里返回的是mNotificationPanel。是的它就是下面的樣子:
既然我們在這里得到了這個頁面,startOpeningPanel應該就是將這個頁面呈現出來吧。
startOpeningPanel如下:
// called from PanelView when self-expanding, too public void startOpeningPanel(PanelView panel) { if (DEBUG) LOG("startOpeningPanel: " + panel); mTouchingPanel = panel; mPanelHolder.setSelectedPanel(mTouchingPanel); for (PanelView pv : mPanels) { if (pv != panel) { pv.collapse(false /* delayed */); } } }
對所有的PanelView ,調用它的collapse方法,改方法如下:
public void collapse(boolean delayed) { if (DEBUG) logf("collapse: " + this); if (mPeekPending || mPeekAnimator != null) { mCollapseAfterPeek = true; if (mPeekPending) { // We know that the whole gesture is just a peek triggered by a simple click, so // better start it now. removeCallbacks(mPeekRunnable); mPeekRunnable.run(); } } else if (!isFullyCollapsed() && !mTracking && !mClosing) { cancelHeightAnimator(); mClosing = true; notifyExpandingStarted(); if (delayed) { postDelayed(mFlingCollapseRunnable, 120); } else { fling(0, false /* expand */); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
如果我們正在下拉,同時下拉的動畫不為空,那么會調用mPeekRunnable.run();
private Runnable mPeekRunnable = new Runnable() { @Override public void run() { mPeekPending = false; runPeekAnimation(); } };
調用runPeekAnimation:
private void runPeekAnimation() { mPeekHeight = getPeekHeight(); if (DEBUG) logf("peek to height=%.1f", mPeekHeight); if (mHeightAnimator != null) { return; } mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight) .setDuration(250); mPeekAnimator.setInterpolator(mLinearOutSlowInInterpolator); mPeekAnimator.addListener(new AnimatorListenerAdapter() { private boolean mCancelled; @Override public void onAnimationCancel(Animator animation) { mCancelled = true; } @Override public void onAnimationEnd(Animator animation) { mPeekAnimator = null; if (mCollapseAfterPeek && !mCancelled) { postOnAnimation(new Runnable() { @Override public void run() { collapse(false /* delayed */); } }); } mCollapseAfterPeek = false; } }); notifyExpandingStarted(); mPeekAnimator.start(); mJustPeeked = true; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
這里使用了屬性動畫將它移動到指定的高度上。