實現(xiàn)一個仿iphone來電狀態(tài)欄功能

  • 先看iphone效果圖


    image.png
  • 如何在android上實現(xiàn),需求分析:
    其實是希望我們和系統(tǒng)之間有個接口,如果我的某個app正在運行中,給系統(tǒng)發(fā)個消息。一但被切到了后臺時,系統(tǒng)就把其它app的顯示區(qū)域整小點兒,并在上面顯示個條兒。高級一點的話,希望可以通過不同的接口參數(shù)來控制顯示條上的文字和背景顏色。點擊藍條得回到app,這樣.可以動態(tài)調(diào)整狀態(tài)欄的高度、顏色狀態(tài)欄下面預留一個區(qū)域用于顯示后臺消息

  • 這個問題難點就是動態(tài)改變狀態(tài)欄高度

  private  void setStatusBarHight(int mHight){//增加這個方法可以設置狀態(tài)欄的高度
     mStatusBarWindowManager.setBarHeight(mHight);
    }
   public void resetStatusBarHight(){//增加這個方法可以恢復狀態(tài)欄的高度
      mNaturalBarHeight = res.getDimensionPixelSize(
                com.android.internal.R.dimen.status_bar_height);
      if (mStatusBarWindowManager != null){
         mStatusBarWindowManager.setBarHeight(mNaturalBarHeight);
      }
   }

增加一個view,動態(tài)隱藏的思路:

super_status_bar.xml  這個是root布局想辦法在底部增加一個布局用來顯示其他信息
   protected StatusBarWindowView mStatusBarWindow;  包括狀態(tài)欄跟下拉 對應的xml super_status_bar.xml
    protected PhoneStatusBarView mStatusBarView;  指的是狀態(tài)欄,初始化步驟如下   對應的xml status_bar.xml
    mStatusBarView = (PhoneStatusBarView) fragment.getView();
                    mStatusBarView.setBar(this);
                    mStatusBarView.setPanel(mNotificationPanel);
                    mStatusBarView.setScrimController(mScrimController);
                    mStatusBarView.setBouncerShowing(mBouncerShowing);
    最后發(fā)現(xiàn)
     status_bar.xml中對狀態(tài)欄的高度進行了定義是寫死的
    android:layout_height="@dimen/status_bar_height"
    
    
    CollapsedStatusBarFragment.java   包含一個view private PhoneStatusBarView mStatusBar;  解析status_bar  return inflater.inflate(R.layout.status_bar, container, false);

在系統(tǒng)的整體布局文件中updateSystemBarsLw這個方法用于布局狀態(tài)欄在系統(tǒng)的位置 、大小
在phonewindowmanager中可以看出來statusbar的高度是固定的,系統(tǒng)應用整體布局也是要根據(jù)這個值來計算空間大小

   @Override
    public void onConfigurationChanged() {
        // TODO(multi-display): Define policy for secondary displays.
        Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
        final Resources res = uiContext.getResources();

        mStatusBarHeight =
                res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);//這里獲取的是系統(tǒng)里面的一個固定的值24dp,是否可以動態(tài)控制
           
 private boolean layoutStatusBar(Rect pf, Rect df, Rect of, Rect vf, Rect dcf, int sysui,
            boolean isKeyguardShowing) {
             mStableTop = mUnrestrictedScreenTop + mStatusBarHeight;
             mDockTop = mUnrestrictedScreenTop + mStatusBarHeight;
             mSystemTop = mUnrestrictedScreenTop + mStatusBarHeight;
想到之前做單手模式的時候,下面這個方法系統(tǒng)會根據(jù)當前狀態(tài)重新布局下
 public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
                              int displayRotation, int uiMode) {
                              
            navVisible |= !canHideNavigationBar();

            boolean updateSysUiVisibility = layoutNavigationBar(displayWidth, displayHeight,
                    displayRotation, uiMode, overscanLeft, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,
                    navAllowedHidden, statusBarExpandedNotKeyguard);
            if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)",
                    mDockLeft, mDockTop, mDockRight, mDockBottom));
            updateSysUiVisibility |= layoutStatusBar(pf, df, of, vf, dcf, sysui, isKeyguardShowing);//在這里調(diào)用了狀態(tài)欄的布局,是否會應用系統(tǒng)的整體布局
            if (updateSysUiVisibility) {
                updateSystemUiVisibilityLw();
            }
        }

下面來看看還有哪些地方用到了這個狀態(tài)欄高度的值,可以看出這個值影響的主要在systemui跟PhoneWindowManager中
要想動態(tài)控制這個值的話,需要使用一個全局的變量比如系統(tǒng)屬性
persist.statusbar.height = 1、2 (代表倍數(shù))
比如外界要求狀態(tài)欄高度翻倍,就用這個值乘以原來的大小

  • 于是有了一個思路

設計一個后臺全局服務,或者用系統(tǒng)已經(jīng)有的服務
功能:
1、對狀態(tài)欄的高度的控制 -------------persist
2、對狀態(tài)欄的顏色控制 -------------BackMessageImp 的成員變量 mStatusBarColor
3、對需要顯示的消息的控制-------------BackMessageImp 的成員變量 mMessage
4、對外界提供一個aidl接口,重寫里面的方法,具體再跟客戶討論(需要傳入 顏色、包名類名 消息等參數(shù))
5、還需要記錄當前的后臺消息,因為客戶有個需求是點擊的時候需要返回當前的應用,所以還需要傳入當前的包名類名
6、包名類名的控制---------------------BackMessageImp 的成員變量 mComponent

首先需要實現(xiàn)第一點:對狀態(tài)欄的高度的控制 -------------persist

systemui有一個系統(tǒng)服務
StatusBarManagerService是運行于SystemServer的一個系統(tǒng)服務。并由ServiceManager管理,它比StatusBar創(chuàng)建的早,繼承IStatusBarService.Stub
它接受用戶操作狀態(tài)欄的請求并將其轉(zhuǎn)發(fā)給BaseStatusBar
如下是一個動態(tài)設置view高度的方

View child = new View(this);  
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) child.getLayoutParams();  
layoutParams.width = 120;  
layoutParams.height = 120;  
child.setLayoutParams(layoutParams); 

FrameLayout layout= new FrameLayout(this);//創(chuàng)建幀布局對象layout 
FrameLayout.LayoutParams frameLayout =new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);//設置幀布局的高寬屬性 
FrameLayout.LayoutParams viewPream =new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTEN);
指定高度:
float height = getResources().getDimension(R.dimens.frmelayout_height);

把這個值設過去就行了
修改后的顯現(xiàn)是狀態(tài)欄整體下移了,apk并沒有向下移動,狀態(tài)欄飄在apk上面

@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
        Bundle savedInstanceState) {
    //return inflater.inflate(R.layout.status_bar, container, false);
    View mView = inflater.inflate(R.layout.status_bar, container, false);
    FrameLayout.LayoutParams frameLayout =new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
 frameLayout.height = 96;
 mView.setLayoutParams(frameLayout); 
 return mView;
}

直接修改frameworks/base/core/res/res/values/dimens.xml: <dimen name="status_bar_height">48dp</dimen> 后顯示是想要的效果

//實現(xiàn)動態(tài)控制
@Override
public void onConfigurationChanged() {
// TODO(multi-display): Define policy for secondary displays.
Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
final Resources res = uiContext.getResources();
if(mForcedStatusBarHight){
mStatusBarHeight =res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height)+res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
}else{
mStatusBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
}

    private boolean mForcedStatusBarHight = false;
    public void setBackRunStatusBarHight(boolean isForceHight){
          mForcedStatusBarHight = isForceHight;
          if(mForcedStatusBarHight){
                        mStatusBarHeight =res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height)+res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
                    }else{
                mStatusBarHeight =
                      res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
            } 
    }
  • 狀態(tài)欄-藍色---參考沉浸式狀態(tài)欄
PhoneWindow.java  DecoderView.java
    @Override
    public void setStatusBarColor(int color) {
        mStatusBarColor = color;
        mForcedStatusBarColor = true;
        if (mDecor != null) {
            mDecor.updateColorViews(null, false /* animate */);
        }
    }


   private int calculateStatusBarColor() {
        return calculateStatusBarColor(mWindow.getAttributes().flags,
                mSemiTransparentStatusBarColor, mWindow.mStatusBarColor);
    }

    public static int calculateStatusBarColor(int flags, int semiTransparentStatusBarColor,
            int statusBarColor) {
        return (flags & FLAG_TRANSLUCENT_STATUS) != 0 ? semiTransparentStatusBarColor
                : (flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 ? statusBarColor
                : Color.BLACK;
    }


于是
DecorView.java

參考iphone的設計,后臺運行程序時候,狀態(tài)欄的顏色是綠色,閃爍的是后臺運行的提示

public void ForceStatusBarColor(boolean isForce,int color){
  mForceColor = isForce;
  mColor = color;
    updateColorViews(null /* insets */, true /* animate */);
 }
 
 public static int calculateStatusBarColor(int flags, int semiTransparentStatusBarColor,
            int statusBarColor) {
            
         if(mForceColor){
              return mColor;//Color.RED
         }else{
             return (flags & FLAG_TRANSLUCENT_STATUS) != 0 ? semiTransparentStatusBarColor
                : (flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 ? statusBarColor
                : Color.BLACK;
         }
   }

PhoneWindow.java中添加調(diào)用方法
  public void setBackRunStatusBarColor(boolean isForce,int color) {
        mForcedStatusBarColor = true;
        if (mDecor != null) {
            mDecor.ForceStatusBarColor(isForce,color);
        }
    }
  • 關(guān)于觸摸返回的功能
public class SystemGesturesPointerEventListener  用于處理是不是觸摸到狀態(tài)欄  以及是不是需要下拉


phonewindowmanager.java中處理
  private void requestTransientBars(WindowState swipeTarget) {
 if (sb) mStatusBarController.showTransient();
 
 
 
 mStatusBarWindow:
     protected void inflateStatusBarWindow(Context context) {
        mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
                R.layout.super_status_bar, null);
    }
    也就是mStatusBarWindowManager  將 mStatusBarWindow 加進來了
   private void addStatusBarWindow() {
        makeStatusBarView();
        mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
        mRemoteInputController = new RemoteInputController(mHeadsUpManager);
        mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
    }

mStatusBarView:    
mStatusBarView = (PhoneStatusBarView) fragment.getView();

于是
狀態(tài)欄的觸摸事件;
 /**
     * Returns the {@link android.view.View.OnTouchListener} that will be invoked when the
     * background window of the status bar is clicked.
     */
    protected View.OnTouchListener getStatusBarWindowTouchListener() {
        return (v, event) -> {
            checkUserAutohide(v, event);
            checkRemoteInputOutside(event);
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                if (mExpandedVisible) {
                    animateCollapsePanels();
                }
            }
            return mStatusBarWindow.onTouchEvent(event);
        };

點擊返回到對應的應用的功能分析:
參考電話通知代碼:StatusBarNotifier.java

  private PendingIntent createLaunchPendingIntent(boolean isFullScreen) {  創(chuàng)建了一個pending intent
    Intent intent =
        InCallActivity.getIntent(
            mContext, false /* showDialpad */, false /* newOutgoingCall */, isFullScreen);

    int requestCode = PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN;
    if (isFullScreen) {
      // Use a unique request code so that the pending intent isn't clobbered by the
      // non-full screen pending intent.
      requestCode = PENDING_INTENT_REQUEST_CODE_FULL_SCREEN;
    }

    // PendingIntent that can be used to launch the InCallActivity.  The
    // system fires off this intent if the user pulls down the windowshade
    // and clicks the notification's expanded view.  It's also used to
    // launch the InCallActivity immediately when when there's an incoming
    // call (see the "fullScreenIntent" field below).
    return PendingIntent.getActivity(mContext, requestCode, intent, 0);
  }
  
  
  顯示與取消顯示都是app的行為
    private void updateInCallNotification(CallList callList) {
    LogUtil.d("StatusBarNotifier.updateInCallNotification", "");

    final DialerCall call = getCallToShow(callList);

    if (call != null) {
      showNotification(callList, call);
    } else {
      cancelNotification();
    }
  }

實現(xiàn)點擊返回到指定的應用:
PanelView.java
狀態(tài)欄肯定要獲取當前通知的狀態(tài),如果是有后臺運行的statusbar,那么他的點擊事件下拉就要改成返回到指定的應用

總結(jié)下就是:
1、如何動態(tài)調(diào)整狀態(tài)欄高度,重繪
2、如何動態(tài)調(diào)整狀態(tài)欄亞瑟
3、狀態(tài)欄觸摸事件處理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容