1. 程式人生 > >Android沉浸式瀏覽圖片

Android沉浸式瀏覽圖片

Android沉浸式瀏覽圖片

一、目標

沉浸式瀏覽圖片

二、體驗地址

神馬筆記最新版本:【神馬筆記Version1.1.0_beta.apk

三、需求分析

圖片瀏覽分為2個層次

  1. 瀏覽層

  2. 操作層

  • 瀏覽層

圖片瀏覽層用於檢視圖片,使用者通過雙擊,多點操作,移動檢視圖片。瀏覽層為全屏模式,圖片的頂部和底部可能被操作層所遮擋。

  • 操作層

操作層提供了圖片相關或其他操作,目前需要實現的是返回前一介面,檢視圖片列表。可以隱藏操作層從而提供使用者沉浸式檢視圖片的體驗。

四、準備工作

1. 實現全屏

有2種方式可以實現全屏。

  1. windowFullscreen
  2. SystemUI

windowFullscreen將Window設定為全屏,從而實現全屏模式。

Android提供了一種更好的方式來實現全屏,使用SystemUI。

對於圖片檢視,相關的SystemUI有2個,頂部的狀態列Status,底部的導航欄Navigation。SystemUI提供了使用Status及Navigation背後控制元件的功能,及將應用的控制元件區域延伸到系統控制元件之下。

SystemUI提供了3個Flag來實現這個功能。

  • SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

    使控制元件區域延伸到Status之下。

  • SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    使控制元件區域延伸到Navigation之下。

  • SYSTEM_UI_FLAG_LAYOUT_STABLE

    使介面保持穩定,不會因切換SystemUI可見性或者Activity切換導致佈局變化。

設定這3個Flag只是延伸了控制元件的空間,Status和Navigation依然是可見的。

2. 控制可見性

SystemUI提供了另外2個Flag使用者控制Status和Navigation的可見性。

  • SYSTEM_UI_FLAG_FULLSCREEN

隱藏Status,預設為顯示

  • SYSTEM_UI_FLAG_HIDE_NAVIGATION

隱藏Navigation,預設為顯示

3. 設定樣式風格

SystemUI提供了2個Flag用於設定Status和Navigation的風格。

  • SYSTEM_UI_FLAG_LIGHT_STATUS_BAR

將Status設定為明亮風格,通常顯示為白底黑色按鈕。預設為深色,黑底白色按鈕。

  • SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR

將Navigation設定為明亮風格,通常顯示為白底黑色按鈕。預設為深色,黑底白色按鈕。

注意,這2個Flag控制的是Status和Navigation按鈕的樣式,背景可以獨立設定。

4. 設定背景顏色

在styles.xml資源中可以設定以下的樣式風格。

<item name="android:windowDrawsSystemBarBackgrounds">true</item>

<item name="android:windowTranslucentStatus">false</item>
<item name="android:statusBarColor">@android:color/black</item>

<item name="android:windowTranslucentNavigation">false</item>
<item name="android:navigationBarColor">@android:color/black</item>
  • windowDrawsSystemBarBackgrounds

繪製Status及Navigation背景

  • windowTranslucentStatus

設定Status為半透明狀態,預設為true

  • statusBarColor

設定Status的背景顏色

  • windowTranslucentNavigation

設定Navigation為半透明狀態,預設為true

  • navigationBarColor

設定Navigation的背景顏色

5. 實現沉浸式全屏

SystemUI提供另外3個Flag來完成沉浸模式。

  • SYSTEM_UI_FLAG_IMMERSIVE

設定為沉浸模式,使用者從底部或底部向螢幕內滑動,或者Window焦點發生變化時,會顯示Status及Navigation,並且一直顯示,直到重新被隱藏。顯示Status及Navigation不會影響佈局。

預設狀態下,會進行重新佈局

  • SYSTEM_UI_FLAG_IMMERSIVE_STICKY

設定為粘性沉浸模式,與SYSTEM_UI_FLAG_IMMERSIVE的區別在於,Status或Navigation在一段時間後會自動隱藏。

  • SYSTEM_UI_FLAG_LOW_PROFILE

降低Status的明亮度,弱化Status顯示。具體機型實現有差異,視具體機型而定。

6. SystemUI FLAG

  1. 預設為可見
  • SYSTEM_UI_FLAG_VISIBLE
  1. 控制佈局,能否延伸到SystemUI的空間
  • SYSTEM_UI_FLAG_LAYOUT_STABLE
  • SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
  • SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  1. SystemUI的樣式
  • SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
  • SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
  1. 控制SystemUI可見性
  • SYSTEM_UI_FLAG_HIDE_NAVIGATION
  • SYSTEM_UI_FLAG_FULLSCREEN
  1. 實現沉浸式全屏
  • SYSTEM_UI_FLAG_IMMERSIVE
  • SYSTEM_UI_FLAG_IMMERSIVE_STICKY
  • SYSTEM_UI_FLAG_LOW_PROFILE

7. 處理SystemUI空間

當我們設定SYSTEM_UI_FLAG_LAYOUT_FULLSCREENSYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATIONSYSTEM_UI_FLAG_LAYOUT_STABLE將控制元件延伸到Status及Navigation的區域後,控制元件中的內容會被Status及Navigation遮擋,如同使用FrameLayout一樣。

View提供了2個方法來處理SystemUI控制元件問題。

public WindowInsets dispatchApplyWindowInsets(WindowInsets insets); 
public WindowInsets onApplyWindowInsets(WindowInsets insets); 

並且提供了預設處理方式,也可以在佈局檔案中設定fitSystemWindows屬性。

public void setFitsSystemWindows(boolean fitSystemWindows) {
    setFlags(fitSystemWindows ? FITS_SYSTEM_WINDOWS : 0, FITS_SYSTEM_WINDOWS);
}
private boolean fitSystemWindowsInt(Rect insets) {
    if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
        mUserPaddingStart = UNDEFINED_PADDING;
        mUserPaddingEnd = UNDEFINED_PADDING;
        Rect localInsets = sThreadLocal.get();
        if (localInsets == null) {
            localInsets = new Rect();
            sThreadLocal.set(localInsets);
        }
        boolean res = computeFitSystemWindows(insets, localInsets);
        mUserPaddingLeftInitial = localInsets.left;
        mUserPaddingRightInitial = localInsets.right;
        internalSetPadding(localInsets.left, localInsets.top,
                           localInsets.right, localInsets.bottom);
        return res;
    }
    return false;
}

ViewGroup過載了其中一個方法。

一旦WindowInsets被標誌為Consumed,將不再傳遞給下一個控制元件。

@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
    insets = super.dispatchApplyWindowInsets(insets);
    if (!insets.isConsumed()) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            insets = getChildAt(i).dispatchApplyWindowInsets(insets);
            if (insets.isConsumed()) {
                break;
            }
        }
    }
    return insets;
}

五、組合起來

1. 設定初始狀態

int flags = View.SYSTEM_UI_FLAG_VISIBLE;
flags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
flags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
flags |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;

view.setSystemUiVisibility(flags);

2. 處理SystemUI控制元件

getView().setOnApplyWindowInsetsListener((v, insets) -> {
    titleBar.setPadding(0, insets.getSystemWindowInsetTop(), 0, 0);
    bottomBar.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());

    return insets.consumeSystemWindowInsets();
});

3. 過渡效果

使用TransitionDrawable來切換全屏/非全屏下的背景。

<transition xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/anc_shape_texture_paper"></item>

    <item>
        <color android:color="@android:color/black"/>
    </item>

</transition>

預設為非全屏狀態,顯示為紙張紋理的背景。

全屏狀態下,切換到黑色背景。

4. 切換SystemUI可見性

void setActionVisible(boolean value) {

    if (!(isActionVisible() ^ value)) {
        return;
    }

    this.actionVisible = value;

    {
        if (value) {
            titleBar.animate().translationY(0);
            bottomBar.animate().translationY(0);

            TransitionDrawable d = (TransitionDrawable) (getView().getBackground());
            d.reverseTransition(getResources().getInteger(android.R.integer.config_shortAnimTime));

        } else {
            titleBar.animate().translationY(-titleBar.getHeight());
            bottomBar.animate().translationY(bottomBar.getHeight());

            TransitionDrawable d = (TransitionDrawable) (getView().getBackground());
            d.startTransition(getResources().getInteger(android.R.integer.config_shortAnimTime));
        }
    }

    {
        View view = this.getView();
        int flags = view.getSystemUiVisibility();

        if (value) {
            flags &= (~View.SYSTEM_UI_FLAG_FULLSCREEN);
            flags &= (~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
        } else {
            flags |= (View.SYSTEM_UI_FLAG_FULLSCREEN);
            flags |= (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
        }

        view.setSystemUiVisibility(flags);
    }

}

六、Final

完成以上所有步驟之後,完美的圖片沉浸式檢視功能終於大功告成。

BUT,Android碎片化問題總能給我們帶來問題。

Android 9.0開始支援劉海屏,但國產手機廠商如小米在Android 8.1已經開始出現劉海屏。

我手上的測試機紅米6 Pro就是一臺有著劉海屏的Android 8.1手機。

在紅米6 Pro上切換全屏/非全屏時會出現圖片跳躍的問題,除小米自帶的圖片應用外,快圖瀏覽、相簿管家都會出現跳躍的問題。

臨時的處理辦法,關閉MIUI的劉海屏功能。

<meta-data
           android:name="notch.config"
           android:value="none"/>