337 lines
14 KiB
Java
337 lines
14 KiB
Java
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.example.android.themednavbarkeyboard;
|
|
|
|
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.Color;
|
|
import android.graphics.drawable.GradientDrawable;
|
|
import android.inputmethodservice.InputMethodService;
|
|
import android.os.Build;
|
|
import android.util.TypedValue;
|
|
import android.view.Gravity;
|
|
import android.view.View;
|
|
import android.view.Window;
|
|
import android.view.WindowInsets;
|
|
import android.widget.Button;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.TextView;
|
|
|
|
/**
|
|
* A sample {@link InputMethodService} to demonstrates how to integrate the software keyboard with
|
|
* custom themed navigation bar.
|
|
*/
|
|
public class ThemedNavBarKeyboard extends InputMethodService {
|
|
|
|
private final int MINT_COLOR = 0xff98fb98;
|
|
private final int LIGHT_RED = 0xff98fb98;
|
|
|
|
private static final class BuildCompat {
|
|
private static final boolean IS_RELEASE_BUILD = Build.VERSION.CODENAME.equals("REL");
|
|
|
|
/**
|
|
* The "effective" API version.
|
|
* {@link android.os.Build.VERSION#SDK_INT} if the platform is a release build.
|
|
* {@link android.os.Build.VERSION#SDK_INT} plus 1 if the platform is a development build.
|
|
*/
|
|
private static final int EFFECTIVE_SDK_INT = IS_RELEASE_BUILD
|
|
? Build.VERSION.SDK_INT
|
|
: Build.VERSION.SDK_INT + 1;
|
|
}
|
|
|
|
private KeyboardLayoutView mLayout;
|
|
|
|
@Override
|
|
public void onCreate() {
|
|
super.onCreate();
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
// Disable contrast for extended navbar gradient.
|
|
getWindow().getWindow().setNavigationBarContrastEnforced(false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public View onCreateInputView() {
|
|
mLayout = new KeyboardLayoutView(this, getWindow().getWindow());
|
|
return mLayout;
|
|
}
|
|
|
|
@Override
|
|
public void onComputeInsets(Insets outInsets) {
|
|
super.onComputeInsets(outInsets);
|
|
|
|
// For floating mode, tweak Insets to avoid relayout in the target app.
|
|
if (mLayout != null && mLayout.isFloatingMode()) {
|
|
// Lying that the visible keyboard height is 0.
|
|
outInsets.visibleTopInsets = getWindow().getWindow().getDecorView().getHeight();
|
|
outInsets.contentTopInsets = getWindow().getWindow().getDecorView().getHeight();
|
|
|
|
// But make sure that touch events are still sent to the IME.
|
|
final int[] location = new int[2];
|
|
mLayout.getLocationInWindow(location);
|
|
final int x = location[0];
|
|
final int y = location[1];
|
|
outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_REGION;
|
|
outInsets.touchableRegion.set(x, y, x + mLayout.getWidth(), y + mLayout.getHeight());
|
|
}
|
|
}
|
|
|
|
private enum InputViewMode {
|
|
/**
|
|
* The input view is adjacent to the bottom Navigation Bar (if present). In this mode the
|
|
* IME is expected to control Navigation Bar appearance, including button color.
|
|
*
|
|
* <p>Call {@link Window#setNavigationBarColor(int)} to change the navigation bar color.</p>
|
|
*
|
|
* <p>Call {@link View#setSystemUiVisibility(int)} with
|
|
* {@link View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} to optimize the navigation bar for
|
|
* light color.</p>
|
|
*/
|
|
SYSTEM_OWNED_NAV_BAR_LAYOUT,
|
|
/**
|
|
* The input view is extended to the bottom Navigation Bar (if present). In this mode the
|
|
* IME is expected to control Navigation Bar appearance, including button color.
|
|
*
|
|
* <p>In this state, the system does not automatically place the input view above the
|
|
* navigation bar. You need to take care of the inset manually.</p>
|
|
*
|
|
* <p>Call {@link View#setSystemUiVisibility(int)} with
|
|
* {@link View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} to optimize the navigation bar for
|
|
* light color.</p>
|
|
|
|
* @see View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
* @see View#SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
*/
|
|
IME_OWNED_NAV_BAR_LAYOUT,
|
|
/**
|
|
* The input view is floating off of the bottom Navigation Bar region (if present). In this
|
|
* mode the target application is expected to control Navigation Bar appearance, including
|
|
* button color.
|
|
*/
|
|
FLOATING_LAYOUT,
|
|
}
|
|
|
|
private final class KeyboardLayoutView extends LinearLayout {
|
|
|
|
private final Window mWindow;
|
|
private InputViewMode mMode = InputViewMode.SYSTEM_OWNED_NAV_BAR_LAYOUT;
|
|
|
|
private void updateBottomPaddingIfNecessary(int newPaddingBottom) {
|
|
if (getPaddingBottom() != newPaddingBottom) {
|
|
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), newPaddingBottom);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
|
|
if (insets.isConsumed()
|
|
|| (getSystemUiVisibility() & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0) {
|
|
// In this case we are not interested in consuming NavBar region.
|
|
// Make sure that the bottom padding is empty.
|
|
updateBottomPaddingIfNecessary(0);
|
|
return insets;
|
|
}
|
|
|
|
// In some cases the bottom system window inset is not a navigation bar. Wear devices
|
|
// that have bottom chin are examples. For now, assume that it's a navigation bar if it
|
|
// has the same height as the root window's stable bottom inset.
|
|
final WindowInsets rootWindowInsets = getRootWindowInsets();
|
|
if (rootWindowInsets != null && (rootWindowInsets.getStableInsetBottom() !=
|
|
insets.getSystemWindowInsetBottom())) {
|
|
// This is probably not a NavBar.
|
|
updateBottomPaddingIfNecessary(0);
|
|
return insets;
|
|
}
|
|
|
|
final int possibleNavBarHeight = insets.getSystemWindowInsetBottom();
|
|
updateBottomPaddingIfNecessary(possibleNavBarHeight);
|
|
return possibleNavBarHeight <= 0
|
|
? insets
|
|
: insets.replaceSystemWindowInsets(
|
|
insets.getSystemWindowInsetLeft(),
|
|
insets.getSystemWindowInsetTop(),
|
|
insets.getSystemWindowInsetRight(),
|
|
0 /* bottom */);
|
|
}
|
|
|
|
public KeyboardLayoutView(Context context, final Window window) {
|
|
super(context);
|
|
mWindow = window;
|
|
setOrientation(VERTICAL);
|
|
|
|
if (BuildCompat.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.O_MR1) {
|
|
final TextView textView = new TextView(context);
|
|
textView.setText("ThemedNavBarKeyboard works only on API 28 and higher devices");
|
|
textView.setGravity(Gravity.CENTER);
|
|
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
|
textView.setPadding(20, 10, 20, 20);
|
|
addView(textView);
|
|
setBackgroundColor(LIGHT_RED);
|
|
return;
|
|
}
|
|
|
|
// By default use "SeparateNavBarMode" mode.
|
|
switchToSeparateNavBarMode(Color.DKGRAY, false /* lightNavBar */);
|
|
setBackgroundColor(MINT_COLOR);
|
|
|
|
{
|
|
final LinearLayout subLayout = new LinearLayout(context);
|
|
{
|
|
final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
|
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
LinearLayout.LayoutParams.WRAP_CONTENT);
|
|
lp.weight = 50;
|
|
subLayout.addView(createButton("BACK_DISPOSITION\nDEFAULT", () -> {
|
|
setBackDisposition(BACK_DISPOSITION_DEFAULT);
|
|
}), lp);
|
|
}
|
|
{
|
|
final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
|
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
LinearLayout.LayoutParams.WRAP_CONTENT);
|
|
lp.weight = 50;
|
|
subLayout.addView(createButton("BACK_DISPOSITION\nADJUST_NOTHING", () -> {
|
|
setBackDisposition(BACK_DISPOSITION_ADJUST_NOTHING);
|
|
}), lp);
|
|
}
|
|
addView(subLayout);
|
|
}
|
|
|
|
addView(createButton("Floating Mode", () -> {
|
|
switchToFloatingMode();
|
|
setBackgroundColor(Color.TRANSPARENT);
|
|
}));
|
|
addView(createButton("Extended Dark Navigation Bar", () -> {
|
|
switchToExtendedNavBarMode(false /* lightNavBar */);
|
|
final GradientDrawable drawable = new GradientDrawable(
|
|
GradientDrawable.Orientation.TOP_BOTTOM,
|
|
new int[] {MINT_COLOR, Color.DKGRAY});
|
|
setBackground(drawable);
|
|
}));
|
|
addView(createButton("Extended Light Navigation Bar", () -> {
|
|
switchToExtendedNavBarMode(true /* lightNavBar */);
|
|
final GradientDrawable drawable = new GradientDrawable(
|
|
GradientDrawable.Orientation.TOP_BOTTOM,
|
|
new int[] {MINT_COLOR, Color.WHITE});
|
|
setBackground(drawable);
|
|
}));
|
|
addView(createButton("Separate Dark Navigation Bar", () -> {
|
|
switchToSeparateNavBarMode(Color.DKGRAY, false /* lightNavBar */);
|
|
setBackgroundColor(MINT_COLOR);
|
|
}));
|
|
addView(createButton("Separate Light Navigation Bar", () -> {
|
|
switchToSeparateNavBarMode(Color.GRAY, true /* lightNavBar */);
|
|
setBackgroundColor(MINT_COLOR);
|
|
}));
|
|
|
|
// Spacer
|
|
addView(new View(getContext()), 0, 40);
|
|
}
|
|
|
|
public boolean isFloatingMode() {
|
|
return mMode == InputViewMode.FLOATING_LAYOUT;
|
|
}
|
|
|
|
private View createButton(String text, final Runnable onClickCallback) {
|
|
final Button button = new Button(getContext());
|
|
button.setText(text);
|
|
button.setOnClickListener(view -> onClickCallback.run());
|
|
return button;
|
|
}
|
|
|
|
private void updateSystemUiFlag(int flags) {
|
|
final int maskFlags = SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
| SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
| SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
|
final int visFlags = getSystemUiVisibility();
|
|
setSystemUiVisibility((visFlags & ~maskFlags) | (flags & maskFlags));
|
|
}
|
|
|
|
/**
|
|
* Updates the current input view mode to {@link InputViewMode#FLOATING_LAYOUT}.
|
|
*/
|
|
private void switchToFloatingMode() {
|
|
mMode = InputViewMode.FLOATING_LAYOUT;
|
|
|
|
final int prevFlags = mWindow.getAttributes().flags;
|
|
|
|
// This allows us to keep the navigation bar appearance based on the target application,
|
|
// rather than the IME itself.
|
|
mWindow.setFlags(0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
|
|
|
updateSystemUiFlag(0);
|
|
|
|
// View#onApplyWindowInsets() will not be called if direct or indirect parent View
|
|
// consumes all the insets. Hence we need to make sure that the bottom padding is
|
|
// cleared here.
|
|
updateBottomPaddingIfNecessary(0);
|
|
|
|
// For some reasons, seems that we need to post another requestLayout() when
|
|
// FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is changed.
|
|
// TODO: Investigate the reason.
|
|
if ((prevFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
|
|
post(() -> requestLayout());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the current input view mode to {@link InputViewMode#SYSTEM_OWNED_NAV_BAR_LAYOUT}.
|
|
*
|
|
* @param navBarColor color to be passed to {@link Window#setNavigationBarColor(int)}.
|
|
* {@link Color#TRANSPARENT} cannot be used here because it hides the
|
|
* color view itself. Consider floating mode for that use case.
|
|
* @param isLightNavBar {@code true} when the navigation bar should be optimized for light
|
|
* color
|
|
*/
|
|
private void switchToSeparateNavBarMode(int navBarColor, boolean isLightNavBar) {
|
|
mMode = InputViewMode.SYSTEM_OWNED_NAV_BAR_LAYOUT;
|
|
mWindow.setNavigationBarColor(navBarColor);
|
|
|
|
// This allows us to use setNavigationBarColor() + SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.
|
|
mWindow.setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
|
|
|
updateSystemUiFlag(isLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0);
|
|
|
|
// View#onApplyWindowInsets() will not be called if direct or indirect parent View
|
|
// consumes all the insets. Hence we need to make sure that the bottom padding is
|
|
// cleared here.
|
|
updateBottomPaddingIfNecessary(0);
|
|
}
|
|
|
|
/**
|
|
* Updates the current input view mode to {@link InputViewMode#IME_OWNED_NAV_BAR_LAYOUT}.
|
|
*
|
|
* @param isLightNavBar {@code true} when the navigation bar should be optimized for light
|
|
* color
|
|
*/
|
|
private void switchToExtendedNavBarMode(boolean isLightNavBar) {
|
|
mMode = InputViewMode.IME_OWNED_NAV_BAR_LAYOUT;
|
|
|
|
// This hides the ColorView.
|
|
mWindow.setNavigationBarColor(Color.TRANSPARENT);
|
|
|
|
// This allows us to use setNavigationBarColor() + SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.
|
|
mWindow.setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
|
|
|
updateSystemUiFlag(SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
| SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
| (isLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0));
|
|
}
|
|
}
|
|
}
|