鎖屏頁面實現及原理深刻分析

目錄介紹

  • 1.相似酷狗等鎖屏頁面實現步驟
  • 1.1 什麼是鎖屏聯動媒體播放器
  • 1.2 如何實現鎖屏頁面
  • 1.3 關於自定義鎖屏頁面左右滑動的控件
  • 1.4 注意要點分析
  • 1.5 具體完整代碼的案例
  • 1.6 效果圖展現案例
  • 2.自定義鎖屏頁的基本原理
  • 2.1 基本原理
  • 2.2 原理圖形展現
  • 2.3 討論一些細節
  • 3.鎖屏Activity配置信息說明
  • 3.1 去掉系統鎖屏作法
  • 3.2 權限問題
  • 4.屏蔽物理或者手機返回鍵
  • 4.1 爲何要這樣處理
  • 4.2 如何實現,實現邏輯代碼
  • 5.滑動屏幕解鎖
  • 5.1 滑動解鎖原理
  • 5.2 滑動控件自定義
  • 6.透明欄與沉浸模式
  • 6.1 透明欄與沉浸模式的概念
  • 6.2 如何實現,代碼展現
  • 7.用戶指紋識別,如何鎖屏頁面失效
  • 8.關於其餘
  • 8.1 版本更新狀況
  • 8.2 參考案例
  • 8.3 我的博客

0.備註

1.相似酷狗等鎖屏頁面實現步驟

1.1 什麼是鎖屏聯動媒體播放器

  • 播放器除了播放了音樂以外什麼都沒作,就能夠分別在任務管理、鎖屏、負一屏控制播放器。
  • 也能夠這樣通俗的解釋,這個舉例子說一個應用場景,我使用混沌大學聽音頻,而後我關閉了屏幕(屏幕滅了),當我再次打開的時候,屏幕的鎖屏頁面或者頂層頁面便會出現一層音頻播放器控制的頁面,那麼即便我不用解鎖屏幕,也照樣能夠控制音頻播放器的基本播放操做。若是你細心觀察一下,也會發現有些APP正式這樣操做的。目前我發現QQ音樂,酷狗音樂,混沌大學等是這樣的
  • 如何實現,邏輯思路
  • 第一步:在服務中註冊屏幕熄滅廣播
  • 第二步:處理邏輯,發現屏幕熄滅就開啓鎖屏頁面,再次點亮屏幕時就能夠看到鎖屏頁面
  • 第三步:點擊鎖屏頁面上的按鈕,好比上一首,下一首,播放暫停能夠與主程序同步信息。
  • 第四步:滑動鎖屏頁面,鎖屏頁面被銷燬,進入程序主界面。

1.2 如何實現鎖屏頁面

  • 1.2.1 註冊一個廣播接收者監聽屏幕亮了或者滅了
public class AudioBroadcastReceiver extends BroadcastReceiver {
    
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if(action!=null && action.length()>0){
            switch (action){
                //鎖屏時處理的邏輯
                case Constant.LOCK_SCREEN:
                    PlayService.startCommand(context,Constant.LOCK_SCREEN);
                    break;
                //當屏幕滅了
                case Intent.ACTION_SCREEN_OFF:
                    PlayService.startCommand(context,Intent.ACTION_SCREEN_OFF);
                    break;
                //當屏幕亮了
                case Intent.ACTION_SCREEN_ON:
                    PlayService.startCommand(context,Intent.ACTION_SCREEN_ON);
                    break;
                default:
                    break;
            }
        }
    }
}
  • 1.2.2 在服務中開啓和註銷鎖屏操做
  • 在oncreate方法中註冊廣播接收者
final IntentFilter filter = new IntentFilter();
//鎖屏
filter.addAction(Constant.LOCK_SCREEN);
//當屏幕滅了
filter.addAction(Intent.ACTION_SCREEN_OFF);
//當屏幕亮了
filter.addAction(Intent.ACTION_SCREEN_ON);
registerReceiver(mAudioReceiver, filter);
  • 在ondestory方法中註銷廣播接收者
unregisterReceiver(mAudioReceiver);

1.3 關於自定義鎖屏頁面左右滑動的控件

  • 1.3.1 只有從左向右滑動的自定義控件
public class SlitherFinishLayout extends RelativeLayout implements OnTouchListener {

    /** 
     * SlitherFinishLayout佈局的父佈局 
     */  
    private ViewGroup mParentView;
    /** 
     * 處理滑動邏輯的View 
     */  
    private View touchView;
    /** 
     * 滑動的最小距離 
     */  
    private int mTouchSlop;  
    /** 
     * 按下點的X座標 
     */  
    private int downX;  
    /** 
     * 按下點的Y座標 
     */  
    private int downY;  
    /** 
     * 臨時存儲X座標 
     */  
    private int tempX;  
    /** 
     * 滑動類 
     */  
    private Scroller mScroller;
    /** 
     * SlitherFinishLayout的寬度 
     */  
    private int viewWidth;  
    /** 
     * 記錄是否正在滑動 
     */  
    private boolean isSlither;  
      
    private OnSlitherFinishListener onSlitherFinishListener;  
    private boolean isFinish;  
      
  
    public SlitherFinishLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);  
    }  


    public SlitherFinishLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mScroller = new Scroller(context);
    }  


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {  
        super.onLayout(changed, l, t, r, b);  
        if (changed) {  
            // 獲取SlitherFinishLayout所在佈局的父佈局  
            mParentView = (ViewGroup) this.getParent();
            viewWidth = this.getWidth();  
        }  
    }  


    /** 
     * 設置OnSlitherFinishListener, 在onSlitherFinish()方法中finish Activity 
     * @param onSlitherFinishListener           listener
     */  
    public void setOnSlitherFinishListener(OnSlitherFinishListener onSlitherFinishListener) {
        this.onSlitherFinishListener = onSlitherFinishListener;  
    }  
  
    /** 
     * 設置Touch的View 
     * @param touchView
     */  
    public void setTouchView(View touchView) {
        this.touchView = touchView;  
        touchView.setOnTouchListener(this);  
    }  


    public View getTouchView() {
        return touchView;  
    }

  
    /** 
     * 滾動出界面 
     */  
    private void scrollRight() {  
        final int delta = (viewWidth + mParentView.getScrollX());  
        // 調用startScroll方法來設置一些滾動的參數,咱們在computeScroll()方法中調用scrollTo來滾動item  
        mScroller.startScroll(mParentView.getScrollX(), 0, -delta + 1, 0, Math.abs(delta));
        postInvalidate();  
    }  
  
    /** 
     * 滾動到起始位置 
     */  
    private void scrollOrigin() {  
        int delta = mParentView.getScrollX();  
        mScroller.startScroll(mParentView.getScrollX(), 0, -delta, 0, Math.abs(delta));
        postInvalidate();  
    }  
  
    /** 
     * touch的View是不是AbsListView, 例如ListView, GridView等其子類 
     * @return
     */  
    private boolean isTouchOnAbsListView() {
        return touchView instanceof AbsListView ? true : false;
    }  
  
    /** 
     * touch的view是不是ScrollView或者其子類 
     * @return
     */  
    private boolean isTouchOnScrollView() {  
        return touchView instanceof ScrollView ? true : false;
    }  


    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {  
            case MotionEvent.ACTION_DOWN:
                downX = tempX = (int) event.getRawX();
                downY = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                int moveX = (int) event.getRawX();
                int deltaX = tempX - moveX;
                tempX = moveX;
                if (Math.abs(moveX - downX) > mTouchSlop && Math.abs((int) event.getRawY() - downY) < mTouchSlop) {
                    isSlither = true;
                    // 若touchView是AbsListView,
                    // 則當手指滑動,取消item的點擊事件,否則咱們滑動也伴隨着item點擊事件的發生
                    if (isTouchOnAbsListView()) {
                        MotionEvent cancelEvent = MotionEvent.obtain(event);
                        cancelEvent.setAction(MotionEvent.ACTION_CANCEL
                                        | (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                        v.onTouchEvent(cancelEvent);
                    }
                }
                if (moveX - downX >= 0 && isSlither) {
                    mParentView.scrollBy(deltaX, 0);
                    // 屏蔽在滑動過程當中ListView ScrollView等本身的滑動事件
                    if (isTouchOnScrollView() || isTouchOnAbsListView()) {
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                isSlither = false;
                if (mParentView.getScrollX() <= -viewWidth / 2) {
                    isFinish = true;
                    scrollRight();
                } else {
                    scrollOrigin();
                    isFinish = false;
                }
                break;
            default:
                break;
        }
        // 假如touch的view是AbsListView或者ScrollView 咱們處理完上面本身的邏輯以後  
        // 再交給AbsListView, ScrollView本身處理其本身的邏輯  
        if (isTouchOnScrollView() || isTouchOnAbsListView()) {  
            return v.onTouchEvent(event);  
        }
        // 其餘的狀況直接返回true  
        return true;  
    }  


    @Override
    public void computeScroll() {  
        // 調用startScroll的時候scroller.computeScrollOffset()返回true,  
        if (mScroller.computeScrollOffset()) {  
            mParentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
            postInvalidate();
            if (mScroller.isFinished()) {
                if (onSlitherFinishListener != null && isFinish) {  
                    onSlitherFinishListener.onSlitherFinish();  
                }  
            }  
        }  
    }  
      
  
    public interface OnSlitherFinishListener {  
        void onSlitherFinish();
    }  
  
}
  • 1.3.2 支持向左或者向右滑動的控件,靈活處理
public class SlideFinishLayout extends RelativeLayout {

    private final String TAG = SlideFinishLayout.class.getName();

    /**
     * SlideFinishLayout佈局的父佈局
     */
    private ViewGroup mParentView;

    /**
     * 滑動的最小距離
     */
    private int mTouchSlop;
    /**
     * 按下點的X座標
     */
    private int downX;
    /**
     * 按下點的Y座標
     */
    private int downY;
    /**
     * 臨時存儲X座標
     */
    private int tempX;
    /**
     * 滑動類
     */
    private Scroller mScroller;
    /**
     * SlideFinishLayout的寬度
     */
    private int viewWidth;
    /**
     * 記錄是否正在滑動
     */
    private boolean isSlide;

    private OnSlideFinishListener onSlideFinishListener;

    /**
     * 是否開啓左側切換事件
     */
    private boolean enableLeftSlideEvent = true;
    /**
     * 是否開啓右側切換事件
     */
    private boolean enableRightSlideEvent = true;
    /**
     * 按下時範圍(處於這個範圍內就啓用切換事件,目的是使當用戶從左右邊界點擊時才響應)
     */
    private int size ;
    /**
     * 是否攔截觸摸事件
     */
    private boolean isIntercept = false;
    /**
     * 是否可切換
     */
    private boolean canSwitch;
    /**
     * 左側切換
     */
    private boolean isSwitchFromLeft = false;
    /**
     * 右側側切換
     */
    private boolean isSwitchFromRight = false;


    public SlideFinishLayout(Context context) {
        super(context);
        init(context);
    }
    public SlideFinishLayout(Context context, AttributeSet attrs) {
        super(context, attrs, 0);
        init(context);
    }
    public SlideFinishLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        Log.i(TAG, "設備的最小滑動距離:" + mTouchSlop);
        mScroller = new Scroller(context);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {
            // 獲取SlideFinishLayout所在佈局的父佈局
            mParentView = (ViewGroup) this.getParent();
            viewWidth = this.getWidth();
            size = viewWidth;
        }
        Log.i(TAG, "viewWidth=" + viewWidth);
    }


    public void setEnableLeftSlideEvent(boolean enableLeftSlideEvent) {
        this.enableLeftSlideEvent = enableLeftSlideEvent;
    }


    public void setEnableRightSlideEvent(boolean enableRightSlideEvent) {
        this.enableRightSlideEvent = enableRightSlideEvent;
    }

    /**
     * 設置OnSlideFinishListener, 在onSlideFinish()方法中finish Activity
     * @param onSlideFinishListener         onSlideFinishListener
     */
    public void setOnSlideFinishListener(OnSlideFinishListener onSlideFinishListener) {
        this.onSlideFinishListener = onSlideFinishListener;
    }

    /**
     * 是否攔截事件,若是不攔截事件,對於有滾動的控件的界面將出現問題(相沖突)
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        float downX = ev.getRawX();
        Log.i(TAG, "downX =" + downX + ",viewWidth=" + viewWidth);
        if(enableLeftSlideEvent && downX < size){
            Log.e(TAG, "downX 在左側範圍內 ,攔截事件");
            isIntercept = true;
            isSwitchFromLeft = true;
            isSwitchFromRight = false;
            return false;
        }else if(enableRightSlideEvent && downX > (viewWidth - size)){
            Log.i(TAG, "downX 在右側範圍內 ,攔截事件");
            isIntercept = true;
            isSwitchFromRight = true;
            isSwitchFromLeft = false;
            return true;
        }else{
            Log.i(TAG, "downX 不在範圍內 ,不攔截事件");
            isIntercept = false;
            isSwitchFromLeft = false;
            isSwitchFromRight = false;
        }
        return super.onInterceptTouchEvent(ev);
    }


    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //不攔截事件時 不處理
        if(!isIntercept){
            Log.d(TAG,"false------------");
            return false;
        }
        Log.d(TAG,"true-----------");
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                downX = tempX = (int) event.getRawX();
                downY = (int) event.getRawY();
                Log.d(TAG,"downX---"+downX+"downY---"+downY);
                break;
            case MotionEvent.ACTION_MOVE:
                int moveX = (int) event.getRawX();
                int deltaX = tempX - moveX;
                tempX = moveX;
                if (Math.abs(moveX - downX) > mTouchSlop && Math.abs((int) event.getRawY() - downY) < mTouchSlop) {
                    isSlide = true;
                }
                Log.e(TAG, "scroll deltaX=" + deltaX);
                //左側滑動
                if(enableLeftSlideEvent){
                    if (moveX - downX >= 0 && isSlide) {
                        mParentView.scrollBy(deltaX, 0);
                    }
                }
                //右側滑動
                if(enableRightSlideEvent){
                    if (moveX - downX <= 0 && isSlide) {
                        mParentView.scrollBy(deltaX, 0);
                    }
                }
                Log.i(TAG + "/onTouchEvent", "mParentView.getScrollX()=" + mParentView.getScrollX());
                break;
            case MotionEvent.ACTION_UP:
                isSlide = false;
                //mParentView.getScrollX() <= -viewWidth / 2  ==>指左側滑動
                //mParentView.getScrollX() >= viewWidth / 2   ==>指右側滑動
                if (mParentView.getScrollX() <= -viewWidth / 2 || mParentView.getScrollX() >= viewWidth / 2) {
                    canSwitch = true;
                    if(isSwitchFromLeft){
                        scrollToRight();
                    }

                    if(isSwitchFromRight){
                        scrollToLeft();
                    }
                } else {
                    scrollOrigin();
                    canSwitch = false;
                }
                break;
            default:
                break;
        }
        return true;
    }


    /**
     * 滾動出界面至右側
     */
    private void scrollToRight() {
        final int delta = (viewWidth + mParentView.getScrollX());
        // 調用startScroll方法來設置一些滾動的參數,咱們在computeScroll()方法中調用scrollTo來滾動item
        mScroller.startScroll(mParentView.getScrollX(), 0, -delta + 1, 0, Math.abs(delta));
        postInvalidate();
    }

    /**
     * 滾動出界面至左側
     */
    private void scrollToLeft() {
        final int delta = (viewWidth - mParentView.getScrollX());
        // 調用startScroll方法來設置一些滾動的參數,咱們在computeScroll()方法中調用scrollTo來滾動item
        //此處就不可用+1,也不卡直接用delta
        mScroller.startScroll(mParentView.getScrollX(), 0, delta - 1, 0, Math.abs(delta));
        postInvalidate();
    }

    /**
     * 滾動到起始位置
     */
    private void scrollOrigin() {
        int delta = mParentView.getScrollX();
        mScroller.startScroll(mParentView.getScrollX(), 0, -delta, 0, Math.abs(delta));
        postInvalidate();
    }

    @Override
    public void computeScroll(){
        // 調用startScroll的時候scroller.computeScrollOffset()返回true,
        if (mScroller.computeScrollOffset()) {
            mParentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();

            if (mScroller.isFinished()) {
                if (onSlideFinishListener != null && canSwitch) {
                    //回調,左側切換事件
                    if(isSwitchFromLeft){
                        onSlideFinishListener.onSlideBack();
                    }
                    //右側切換事件
                    if(isSwitchFromRight){
                        onSlideFinishListener.onSlideForward();
                    }
                }
            }
        }
    }


    public interface OnSlideFinishListener {
        void onSlideBack();
        void onSlideForward();
    }

}

1.4 注意要點分析

  • 1.4.1 在清單文件須要註冊屬性
<activity android:name=".ui.lock.LockTestActivity"
            android:noHistory="false"
            android:excludeFromRecents="true"
            android:screenOrientation="portrait"
            android:exported="false"
            android:launchMode="singleInstance"
            android:theme="@style/LockScreenTheme"/>
  • 1.4.2 程序在前臺時,當從鎖屏頁面finish時,會有閃屏效果
  • 若是加上這句話android:launchMode="singleInstance",那麼程序在前臺時會有閃屏效果,若是在後臺時,則直接展示棧頂頁面
  • 若是不加這句話

1.5 具體完整代碼的案例

2.自定義鎖屏頁的基本原理

2.1 基本原理

  • Android系統實現自定義鎖屏頁的思路很簡單,即在App啓動時開啓一個service,在Service中時刻監聽系統SCREEN_OFF的廣播,當屏幕熄滅時,Service監聽到廣播,開啓一個鎖屏頁Activity在屏幕最上層顯示,該Activity建立的同時會去掉系統鎖屏(固然若是有密碼是禁不掉的)。

2.2 原理圖形展現

  • image

2.3 討論一些細節

  • 2.3.1 關於啓動Activity時Intent的Flag問題
  • 若是不添加FLAG_ACTIVITY_NEW_TASK的標誌位,會出現「Calling startActivity() from outside of an Activity」的運行時異常,畢竟咱們是從Service啓動的Activity。Activity要存在於activity的棧中,而Service在啓動activity時必然不存在一個activity的棧,因此要新起一個棧,並裝入啓動的activity。使用該標誌位時,也須要在AndroidManifest中聲明taskAffinity,即新task的名稱,不然鎖屏Activity實質上仍是在創建在原來App的task棧中。
  • 標誌位FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,是爲了不在最近使用程序列表出現Service所啓動的Activity,但這個標誌位不是必須的,其使用依狀況而定。
  • 2.3.2 動態註冊廣播接收者
IntentFilter mScreenOffFilter = new IntentFilter();
mScreenOffFilter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mScreenOffReceiver, mScreenOffFilter);

3.鎖屏Activity配置信息說明

3.1 去掉系統鎖屏作法

  • 在自定義鎖屏Activity的onCreate()方法裏設定如下標誌位就能徹底實現相同的功能:
//注意須要作一下判斷
if (getWindow() != null) {
    Window window = getWindow();
    window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
        WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
    // 鎖屏的activity內部也要作相應的配置,讓activity在鎖屏時也可以顯示,同時去掉系統鎖屏。
    // 固然若是設置了系統鎖屏密碼,系統鎖屏是沒有辦法去掉的
    // FLAG_DISMISS_KEYGUARD用於去掉系統鎖屏頁
    // FLAG_SHOW_WHEN_LOCKED使Activity在鎖屏時仍然可以顯示
    window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
        WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
    window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    window.getDecorView().setSystemUiVisibility(
        View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
            | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE);
    }
}

3.2 權限問題

  • 不要忘記在Manifest中加入適當的權限:
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>

4.屏蔽物理或者手機返回鍵

4.1 爲何要這樣處理

  • 當自定義鎖屏頁最終出如今手機上時,咱們總但願它像系統鎖屏頁那樣屹立不倒,全部的按鍵都不能觸動它,只有經過劃瓶或者指紋才能解鎖,所以有必要對按鍵進行必定程度上的屏蔽。針對只有虛擬按鍵的手機,咱們能夠經過隱藏虛擬按鍵的方式部分解決這個問題,具體方法在後文會介紹。可是當用戶在鎖屏頁底部滑動,隱藏後的虛擬按鍵仍是會滑出,並且若是用戶是物理按鍵的話就必須進行屏蔽了。

4.2 如何實現,實現邏輯代碼

@Override
public void onBackPressed() {
    // 不作任何事,爲了屏蔽back鍵
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    int key = event.getKeyCode();
    switch (key) {
        case KeyEvent.KEYCODE_BACK: {
            return true;
        }
        case KeyEvent.KEYCODE_MENU:{
            return true;
        }
        default:
            break;
    }
    return super.onKeyDown(keyCode, event);
}

5.滑動屏幕解鎖

5.1 滑動解鎖原理

  • 當手指在屏幕上滑動時,攔截並處理滑動事件,使鎖屏頁面隨着手指運動,當運動到達必定的閥值時,用戶手指鬆開手指,鎖屏頁自動滑動到屏幕邊界消失,若是沒有達到運動閥值,就會自動滑動到起始位置,從新覆蓋屏幕。
  • 對滑動的距離與閥值進行一個比較,此處的閥值爲0.5*屏幕寬度,若是低於閥值,則移動到初始位置;若是高於閥值,以一樣的方式移出屏幕右邊界,而後將Activity幹掉

5.2 滑動控件自定義

6.透明欄與沉浸模式

6.1 透明欄與沉浸模式的概念

  • 沉浸模式與透明欄是兩個不一樣的概念,因爲某些緣由,國內一些開發或產品會把這兩個概念混淆。
  • 6.1.1 沉浸模式 什麼是沉浸模式?
  • 從4.4開始,Android 爲 「setSystemUiVisibility()」方法提供了新的標記 「SYSTEM_UI_FLAG_IMMERSIVE」以及」SYSTEM_UI_FLAG_IMMERSIVE_STIKY」,就是咱們所談的沉浸模式,全稱爲 「Immersive Full-Screen Mode」,它可使你的app隱藏狀態欄和導航欄,實現真正意義上的全屏體驗。
  • 以前 Android 也是有全屏模式的,主要經過」setSystemUiVisibility()」添加兩個Flag,即」SYSTEM_UI_FLAG_FULLSCREEN」,」SYSTEM_UI_FLAG_HIDE_NAVIGATION」(僅適用於使用導航欄的設備,即虛擬按鍵)。
  • 這兩個標記都存在一些問題,例如使用第一個標記的時候,除非 App 提供暫時退出全屏模式的功能(例如部分電子書軟件中點擊一次屏幕中央位置),用戶是一直都無法看見狀態欄的。這樣,若是用戶想去看看通知中心有什麼通知,那就必須點擊一次屏幕,顯示狀態欄,而後才能調出通知中心。
  • 而第二個標記的問題在於,Google 認爲導航欄對於用戶來講是十分重要的,因此只會短暫隱藏導航欄。一旦用戶作其餘操做,例如點擊一次屏幕,導航欄就會立刻被從新調出。這樣的設定對於看圖軟件,視頻軟件等等沒什麼大問題,可是對於遊戲之類用戶須要常常點擊屏幕的 App,那就幾乎是悲劇了——這也是爲何你在 Android 4.4 以前找不到什麼全屏模式會自動隱藏導航欄的應用。

6.2 如何實現,代碼展現

  • 6.2.1 在案例中代碼展現
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    window.getDecorView().setSystemUiVisibility(
            // SYSTEM_UI_FLAG_LAYOUT_STABLE保持整個View穩定,使View不會由於SystemUI的變化而作layout
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
            // SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,開發者容易被其中的HIDE_NAVIGATION所迷惑,
            // 其實這個Flag沒有隱藏導航欄的功能,只是控制導航欄浮在屏幕上層,不佔據屏幕布局空間;
            View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
            // SYSTEM_UI_FLAG_HIDE_NAVIGATION,纔是可以隱藏導航欄的Flag;
            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
            // SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,由上面可知,也不能隱藏狀態欄,只是使狀態欄浮在屏幕上層。
            View.SYSTEM_UI_FLAG_FULLSCREEN |
            View.SYSTEM_UI_FLAG_IMMERSIVE);
}
  • 注意的是,這段代碼除了須要加在Activity的OnCreate()方法中,也要加在重寫的onWindowFocusChanged()方法中,在窗口獲取焦點時再將Flag設置一遍,不然在部分手機上可能致使沒法達到預想的效果。通常狀況下沒有問題,最後建議仍是加上
@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if(hasFocus && getWindow()!=null){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            getWindow().getDecorView().setSystemUiVisibility(
                    // SYSTEM_UI_FLAG_LAYOUT_STABLE保持整個View穩定,使View不會由於SystemUI的變化而作layout
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                            // SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,開發者容易被其中的HIDE_NAVIGATION所迷惑,
                            // 其實這個Flag沒有隱藏導航欄的功能,只是控制導航欄浮在屏幕上層,不佔據屏幕布局空間;
                            View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
                            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                            // SYSTEM_UI_FLAG_HIDE_NAVIGATION,纔是可以隱藏導航欄的Flag;
                            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
                            // SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,由上面可知,也不能隱藏狀態欄,只是使狀態欄浮在屏幕上層。
                            View.SYSTEM_UI_FLAG_FULLSCREEN |
                            View.SYSTEM_UI_FLAG_IMMERSIVE);
        }
    }
}

8.關於其餘

8.1 版本更新狀況

8.2 參考案例

8.3 我的博客

相關文章
相關標籤/搜索