如果你有這樣的需求:用戶進入你的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);
}
它似乎什麼都沒有做…然而不要忽視了,它調用了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;
}
從函數的名字猜測,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 */);
}
}
}
如果我們正在下拉,同時下拉的動畫不爲空,那麼會調用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;
}
這裏使用了屬性動畫將它移動到指定的高度上。