はじめに
概要
GoogleはAndroid 9.0以降、前髪スクリーンを正式にサポートしました。この記事は、前髪スクリーンの公式ソリューションの紹介に基づいています:
- シミュレーター 前髪画面設定をオン
- 公式適応スキーム宣言xmlとコード
- 3つの前髪のスクリーン化ケースの実現
- 前髪のあるアプリのブートストラップページ
- 前髪エリアとインタラクティブなコントロールがうまく調和
- 没入型小説の朗読ページがスクリーンに登場
前髪画面設定をオンにするシミュレータ
開発用の前髪スクリーンがある実機がない場合は、エミュレータをセットアップして様々なタイプの前髪スクリーンを表示させることができます。以下の手順に従って、指定したタイプの前髪スクリーン用にエミュレータをセットアップしてください。
- English Mode: Developer option --> Display cutout
Google公式適応スキーム
Androidの9.0とAPI28の公式ドキュメントによると、前髪画面の適応を開始するメイントピックにステップだけでなく、2つの方法のxml宣言を達成するためにJavaコードをサポートし、2つの原則は同じですが、ページDecorViewのLayoutParams.layoutInDisplayCutoutMode属性の設定を介しているかどうかを達成するためです。サポート前髪画面は、LayoutParams.layoutInDisplayCutoutModeは、次の3つのモードがあります:
java前髪画面を実現するコード
キーコードは以下の通りです:
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
View decorView = getWindow().getDecorView();
decorView.setLayoutParams(layoutParams);
これは、画面がフルスクリーン表示に設定されていないためで、次のonCreate()メソッドのsetContentView()メソッドでページをフルスクリーン表示に設定します。
//フルスクリーン表示を設定する
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
この時点では、コンテンツ領域を前髪スクリーン領域に表示するように実装されていますが、Androidデバイスによっては、コンテンツ表示がシステムバーと重なることを許可されていないように見えることがあります。この振る舞いを置き換えて、前髪領域にコンテンツを強制的に拡張するには、View.setSystemUiVisibility(int)メソッドを使用して、没入型ディスプレイとしてページを表示します。
int systemUiVisibility = getWindow().getDecorView().getSystemUiVisibility();
int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
systemUiVisibility |= flags;
getWindow().getDecorView().setSystemUiVisibility(systemUiVisibility);
xml宣言ページサポート前髪画面
上記は、コンテンツエリアは、同じを達成するために、すべてのコードの前髪領域に表示されますまた、res /値- 28ファイルの形式でxmlレイアウト縞画面の宣言を介して指定することができます縞画面のスタイルをサポートするためにスタイルを宣言するために
<resources>
<style name="CutoutsActivityTheme" parent="AppTheme">
<!-- default, shortEdges, never -->
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
</resources>
前髪の画面スタイルの宣言は、AndroidManifest.xmlアプリケーションと前髪の画面スタイルの指定されたテーマの下にあるアクティビティノードにすることができます後、アプリケーションの下にあるすべてのページが前髪の画面スタイルに適応されているステートメントの下にあるアプリケーションノード、指定されたページのみ指定された下にあるアクティビティノード、一般的な場合。決定の特定のニーズによると、例として、次のアクティビティノード前髪画面スタイルを指定します。
<activity
android:name=".LeadActivity"
android:theme="@style/CutoutsActivityTheme">
注:アクティビティ・ノードの下で前髪のスクリーン・スタイルを指定した後、対応するActivity.javaのコードでページをフル・スクリーンに設定し、View.setSystemUiVisibility(int)を設定する必要もあります。
前髪スクリーン法の具体的な適応を理解した上で、次の3つの一般的なシナリオの組み合わせを適用します。
シナリオ1:リードページを前髪スクリーンに合わせる
一般的なアプリケーションのスタートページは、アプリ名を表示するために使用されるイメージは、前髪画面適応を行っていないなどの前髪画面のデバイスでは、その後、バーの上に純粋な背景イメージと背景イメージが切断された状態では、ユーザーエクスペリエンスが大幅に低下しているので、前髪画面に適応するためのガイドページであり、その必要性;
この例では、style.xmlで前髪モードを宣言しています。
<style name="CutoutsActivityTheme" parent="splashThem">
<item name="android:windowFullscreen">true</item><!--フルスクリーン>
<item name="android:windowContentOverlay">@null</item><!--コンテンツエリアオーバーレイ>
<item name="android:windowTranslucentStatus">true</item><!--没入型透明ステータスバー>
<item name="android:windowBackground">@drawable/bg</item><!--コールドスタートの背景イメージ>
<!-- default, shortEdges, never -->
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item><!--前髪の適応モード>
</style>
アクティビティノードの下にブートページで同じ前髪画面のスタイルを指定するには、ブートページのコードで開始し、没入型ディスプレイに設定することができます
int systemUiVisibility = getWindow().getDecorView().getSystemUiVisibility();
int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
systemUiVisibility |= flags;
getWindow().getDecorView().setSystemUiVisibility(systemUiVisibility);
シナリオ2:前髪エリアとインタラクティブ・コントロールの相性
上記の場合の適応は、前髪領域にページコンテンツを強制的に表示させることにしか成功していません。その後に問題が生じます。ページ上のボタンやその他のコントロールなどのインタラクティブなコントロールの1つがたまたまフリンジによってブロックされた場合、コントロールをクリックすることができず、ユーザーエクスペリエンスに深刻な影響を及ぼします;
ここでは、View.setOnApplyWindowInsetsListener apiを使用して、ページの変更をリッスンし、前髪領域にページの内容を取得し、動的に指定されたコントロールのsetMarginsを変更し、コントロールと前髪画面が重ならないようにする必要があります!
rootLayout.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
@Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
//前髪エリアを取得する
DisplayCutout displayCutout = insets.getDisplayCutout();
if (displayCutout != null) {
int top = displayCutout.getSafeInsetTop();
int bottom = displayCutout.getSafeInsetBottom();
int left = displayCutout.getSafeInsetLeft();
int right = displayCutout.getSafeInsetRight();
Log.i("displayCutout", "セーフインセット左:" + left);
Log.i("displayCutout", "セーフインセット右:" + right);
Log.i("displayCutout", "画面の上部から安全領域の距離SafeInsetTop:" + top);
Log.i("displayCutout", "セーフインセットボトム:" + bottom);
ConstraintLayout.LayoutParams topLayoutParams = (ConstraintLayout.LayoutParams) btnTop.getLayoutParams();
ConstraintLayout.LayoutParams leftLayoutParams = (ConstraintLayout.LayoutParams) btnLeft.getLayoutParams();
topLayoutParams.setMargins(left, top, right, bottom);
leftLayoutParams.setMargins(left, top, right, bottom);
}
return insets.consumeDisplayCutout();
}
});
- ケースコードの詳細は以下の通りです。
public class CustomLayoutRotateActivity extends AppCompatActivity {
private Button btnTop;
private Button btnLeft;
private ConstraintLayout rootLayout;
public static void start(Context context) {
Intent starter = new Intent(context, CustomLayoutRotateActivity.class);
context.startActivity(starter);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//フルスクリーンを設定する
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_custom_layout_rotate);
initView();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
initCutouts();
setWindowListener();
}
}
private void initView() {
btnTop = findViewById(R.id.btn_top);
btnLeft = findViewById(R.id.btn_left);
rootLayout = findViewById(R.id.root_layout_custom);
}
/***
* ルートレイアウトに耳を傾け、初期化や画面を回転させるときにトリガされ、動的に前髪領域の位置を計算し、対応する子コントロールの位置を設定する。
*/
@RequiresApi(api = Build.VERSION_CODES.P)
private void setWindowListener() {
rootLayout.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
@Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
//前髪エリアを取得する
DisplayCutout displayCutout = insets.getDisplayCutout();
if (displayCutout != null) {
int top = displayCutout.getSafeInsetTop();
int bottom = displayCutout.getSafeInsetBottom();
int left = displayCutout.getSafeInsetLeft();
int right = displayCutout.getSafeInsetRight();
Log.i("displayCutout", "セーフインセットレフト:" + left);
Log.i("displayCutout", "セーフインセット右:" + right);
Log.i("displayCutout", "画面の上部からセーフエリアの距離SafeInsetTop:" + top);
Log.i("displayCutout", "セーフインセットボトム:" + bottom);
ConstraintLayout.LayoutParams topLayoutParams = (ConstraintLayout.LayoutParams) btnTop.getLayoutParams();
ConstraintLayout.LayoutParams leftLayoutParams = (ConstraintLayout.LayoutParams) btnLeft.getLayoutParams();
topLayoutParams.setMargins(left, top, right, bottom);
leftLayoutParams.setMargins(left, top, right, bottom);
}
return insets.consumeDisplayCutout();
}
});
}
/***
* フルスクリーン適応を設定する
*/
@RequiresApi(api = Build.VERSION_CODES.P)
private void initCutouts() {
/**
* 1. WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT デフォルトでは、内部は前髪エリアに表示されない
* 2. WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 前髪エリアへの内部表示
* 3. WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
*/
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
View decorView = getWindow().getDecorView();
decorView.setLayoutParams(layoutParams);
/*ステータスバーの設定 没入型、コンテンツは本当にステータスバーに統合されている
* Android コンテンツ・ビューがシステム・バーと重ならないようにする。この振る舞いを置き換えて、コンテンツが前髪エリアまで伸びるようにするには、Viewを渡す。.setSystemUiVisibility(int) メソッドは、可視性を表示するには、次のいずれかのフラグを適用する:
* View.SYSTEM_UI_FLAG_LAYOUT_STABLE ビューで動作する.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
* ページレイアウトの安定領域を設定し、下部にソフトキーボードがある場合は、コンテンツエリアは、ソフトキーボードの上に表示される。
* */
int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
getWindow().getDecorView().setSystemUiVisibility(flags);
}
}
シナリオ3:没入型小説の朗読ページとスクリーンの融合
小説、ゲームアプリページ没入要件では、一緒に次のケースは、小説の内容が安全な領域に表示されている達成するために、画面の適応を前髪のページを読んで没入感を完了するには、前髪の領域と重複していない、小説のタイトルは、フリンジ領域のステータスバーの左側に表示されます;
ページレイアウトは以下の通り:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://..///id"
xmlns:app="http://..//-to"
xmlns:tools="http://../ls"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#E3EDCD">
<TextView
android:id="@+id/tv_title_reader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:text="@string/text_title_wiki"
android:maxLines="1"
android:padding="4dp"
android:singleLine="true"
android:textColor="@color/colorPrimaryDark"
android:textSize="16sp" />
<ScrollView
android:id="@+id/scrollview_reader"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CutOutsFullScreenActivity">
<TextView
android:id="@+id/tv_content_reader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@string/text_about_wiki"
android:textColor="#8C8C8C"
android:textSize="16sp" />
</ScrollView>
</FrameLayout>
指定されたページのフルスクリーン内の特定のアクティビティページでは、前髪モード、没入型ステータスバーのサポートは、まだ最後のケースView.setOnApplyWindowInsetsListener apiを介して、ページの変更、前髪の位置とサイズへの動的アクセスに耳を傾けるためにコールバック
//前髪情報を取得する
DisplayCutout displayCutout = insets.getDisplayCutout();
//前髪の幅と高さを計算するために、前髪の上下左右の位置を取得する。
displayCutout.getBoundingRects();
アイデアは次のとおりです:前髪の位置とサイズへの動的なアクセスを通じて、コンテンツが前髪と重ならないようにするために、新しいコンテンツ領域のパディングの動的な計算、縦画面モードでは、例えば、前髪の左側のサイズと場所によると、ステータスバーの幅の空白領域の計算で、タイトルの幅として指定されますすることができます。
getWindow().getDecorView().setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
@Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
//前髪情報を取得する
DisplayCutout displayCutout = insets.getDisplayCutout();
int top = displayCutout.getSafeInsetTop();
int bottom = displayCutout.getSafeInsetBottom();
int left = displayCutout.getSafeInsetLeft();
int right = displayCutout.getSafeInsetRight();
//scrollViewのパディングでセーフエリアに表示しないコンテンツを設定する scrollViewがセーフエリアと重ならないようにする。
scrollView.setPadding(left, top, right, bottom);
//前髪の幅と高さを計算するために、前髪の上下左右の位置を取得する。
List<Rect> boundingList = displayCutout.getBoundingRects();
if (!boundingList.isEmpty()) {
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) scrollView.getLayoutParams();
//前髪エリアを取得する
Rect boundRect = boundingList.get(0);
//scrollViewとトップヘッダの間隔を設定することで
Log.i("displayRect", "left:" + boundRect.left + " top:" + boundRect.top + " right:" + boundRect.right + " bottom:" + boundRect.bottom);
if (top > 0) {
//上部の縦画面
//タイトルは安全な前髪エリアに強制的に表示され、幅は上部のフリンジエリアの左側の幅となる。
tvTitle.setWidth(boundRect.left);
tvTitle.setHeight(boundRect.bottom);
tvTitle.setGravity(Gravity.CENTER_VERTICAL);
layoutParams.setMargins(0, 0, 0, 0);
} else if (left > 0 || right > 0) {
//左または右の前髪
int height = tvTitle.getMeasuredHeight();
layoutParams.setMargins(0, height, 0, 0);
}
}
return insets.consumeDisplayCutout();
}
});