Android Как настроить макет в полноэкранном режиме, когда программная клавиша видна - PullRequest
163 голосов
/ 14 сентября 2011

Я много исследовал, чтобы настроить макет, когда софт-клавиатура активна, и я успешно его реализовал, но проблема возникает, когда я использую android:theme="@android:style/Theme.NoTitleBar.Fullscreen" в теге активности в файле манифеста.

Для этого я использовал android:windowSoftInputMode="adjustPan|adjustResize|stateHidden" с разными вариантами, но не повезло.

После этого я программно внедрил FullScreen и попробовал различные макеты для работы с FullScreen, но все тщетно.

Я ссылался на эти ссылки и просмотрел здесь много постов, связанных с этой проблемой:

http://android -developers.blogspot.com / 2009/04 / ОБНОВЛЕНИЕ-приложения-для-на-screen.html

http://davidwparker.com/2011/08/30/android-how-to-float-a-row-above-keyboard/

Вот код XML:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:id="@+id/masterContainerView"
    android:layout_width="fill_parent" android:layout_height="fill_parent"
    android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="#ffffff">

    <ScrollView android:id="@+id/parentScrollView"
        android:layout_width="fill_parent" android:layout_height="wrap_content">

        <LinearLayout android:layout_width="fill_parent"
            android:layout_height="fill_parent" android:orientation="vertical">

            <TextView android:id="@+id/setup_txt" android:layout_width="wrap_content"
                android:layout_height="wrap_content" android:text="Setup - Step 1 of 3"
                android:textColor="@color/top_header_txt_color" android:textSize="20dp"
                android:padding="8dp" android:gravity="center_horizontal" />

            <TextView android:id="@+id/txt_header" android:layout_width="fill_parent"
                android:layout_height="40dp" android:text="AutoReply:"
                android:textColor="@color/top_header_txt_color" android:textSize="14dp"
                android:textStyle="bold" android:padding="10dp"
                android:layout_below="@+id/setup_txt" />

            <EditText android:id="@+id/edit_message"
                android:layout_width="fill_parent" android:layout_height="wrap_content"
                android:text="Some text here." android:textSize="16dp"
                android:textColor="@color/setting_editmsg_color" android:padding="10dp"
                android:minLines="5" android:maxLines="6" android:layout_below="@+id/txt_header"
                android:gravity="top" android:scrollbars="vertical"
                android:maxLength="132" />

            <ImageView android:id="@+id/image_bottom"
                android:layout_width="fill_parent" android:layout_height="wrap_content"
                android:layout_below="@+id/edit_message" />

        </LinearLayout>
    </ScrollView>

    <RelativeLayout android:id="@+id/scoringContainerView"
        android:layout_width="fill_parent" android:layout_height="50px"
        android:orientation="vertical" android:layout_alignParentBottom="true"
        android:background="#535254">

        <Button android:id="@+id/btn_save" android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:layout_alignParentRight="true"
            android:layout_marginTop="7dp" android:layout_marginRight="15dp"
            android:layout_below="@+id/edit_message"
            android:text = "Save" />

        <Button android:id="@+id/btn_cancel" android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:layout_marginTop="7dp"
            android:layout_marginRight="10dp" android:layout_below="@+id/edit_message"
            android:layout_toLeftOf="@+id/btn_save" android:text = "Cancel" />

    </RelativeLayout>
</RelativeLayout>

enter image description here

Я хочу, чтобы нижние 2 кнопки были направлены вверх, когда на экранной клавиатуре появляется картинка.

enter image description here

Ответы [ 26 ]

245 голосов
/ 21 октября 2013

Основываясь на обходном пути yghm, я кодировал удобный класс, который позволяет мне решить проблему с помощью однострочного (конечно, после добавления нового класса в мой исходный код).Одна строка:

     AndroidBug5497Workaround.assistActivity(this);

И класс реализации:


public class AndroidBug5497Workaround {

    // For more information, see https://issuetracker.google.com/issues/36911528
    // To use this class, simply invoke assistActivity() on an Activity that already has its content view set.

    public static void assistActivity (Activity activity) {
        new AndroidBug5497Workaround(activity);
    }

    private View mChildOfContent;
    private int usableHeightPrevious;
    private FrameLayout.LayoutParams frameLayoutParams;

    private AndroidBug5497Workaround(Activity activity) {
        FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
        mChildOfContent = content.getChildAt(0);
        mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                possiblyResizeChildOfContent();
            }
        });
        frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
    }

    private void possiblyResizeChildOfContent() {
        int usableHeightNow = computeUsableHeight();
        if (usableHeightNow != usableHeightPrevious) {
            int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
            int heightDifference = usableHeightSansKeyboard - usableHeightNow;
            if (heightDifference > (usableHeightSansKeyboard/4)) {
                // keyboard probably just became visible
                frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
            } else {
                // keyboard probably just became hidden
                frameLayoutParams.height = usableHeightSansKeyboard;
            }
            mChildOfContent.requestLayout();
            usableHeightPrevious = usableHeightNow;
        }
    }

    private int computeUsableHeight() {
        Rect r = new Rect();
        mChildOfContent.getWindowVisibleDisplayFrame(r);
        return (r.bottom - r.top);
    }
}

Надеюсь, это кому-нибудь поможет.

34 голосов
/ 08 июня 2012

Поскольку ответ уже был выбран и проблема известна как ошибка, я подумал, что добавлю «Возможное решение».

Вы можете переключать полноэкранный режим, когда отображается программная клавиатура.Это позволяет корректно работать «AdjustPan».

Другими словами, я все еще использую @ android: style / Theme.Black.NoTitleBar.Fullscreen как частьтема приложения и stateVisible | AdjustResize как часть режима мягкого ввода окна активности, но чтобы заставить их работать вместе, я должен переключить полноэкранный режим, прежде чем клавиатура появится.

Используйте следующий код:

Отключение полноэкранного режима

getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

Включение полноэкранного режима

getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);

Примечание - вдохновение пришло: Скрытие заголовка в полноэкранном режиме

20 голосов
/ 16 февраля 2017

Я попробовал решение от Джозефа Джонсона , но, как и другие, я столкнулся с проблемой разрыва между контентом и клавиатурой.Проблема возникает потому, что при использовании полноэкранного режима режим мягкого ввода всегда равен pan .Это панорамирование мешает решению Джозефа, когда вы активируете поле ввода, которое будет скрыто программным вводом.

Когда появляется программный ввод, содержимое сначала панорамируется на основе его первоначальной высоты, а затем изменяется с помощью макета.запрошенный решением Иосифа.Изменение размера и последующее расположение не отменяют панорамирование, что приводит к разрыву.Полный порядок событий:

  1. Глобальный слушатель макета
  2. Панорамирование
  3. Макет контента (= фактическое изменение размера контента)

Невозможно отключить панорамирование, но можно принудительно установить смещение панорамирования равным 0, изменив высоту содержимого.Это может быть сделано в слушателе, потому что он запускается перед панорамированием.Установка высоты содержимого на доступную высоту приводит к плавному взаимодействию с пользователем, то есть к отсутствию мерцания.

Я также внес эти изменения.Если что-то из этого вызывает проблемы, дайте мне знать:

  • Переключено определение доступной высоты для использования getWindowVisibleDisplayFrame.Rect кэшируется для предотвращения ненужного мусора.
  • Разрешить удаление слушателя тоже.Это полезно при повторном использовании действия для разных фрагментов, имеющих разные требования к полноэкранному режиму.
  • Не различайте отображаемую или скрытую клавиатуру, но всегда устанавливайте высоту содержимого равной видимой высоте рамки дисплея.

Он был протестирован на Nexus 5, и эмуляторы работают с уровнями API 16-24 с размерами экрана от крошечного до большого.

Код был перенесен в Kotlin, но переносил мои изменения обратнона Java это просто.Дайте мне знать, если вам нужна помощь:

class AndroidBug5497Workaround constructor(activity: Activity) {
    private val contentContainer = activity.findViewById(android.R.id.content) as ViewGroup
    private val rootView = contentContainer.getChildAt(0)
    private val rootViewLayout = rootView.layoutParams as FrameLayout.LayoutParams
    private val viewTreeObserver = rootView.viewTreeObserver
    private val listener = ViewTreeObserver.OnGlobalLayoutListener { possiblyResizeChildOfContent() }

    private val contentAreaOfWindowBounds = Rect()
    private var usableHeightPrevious = 0

    // I call this in "onResume()" of my fragment
    fun addListener() {
        viewTreeObserver.addOnGlobalLayoutListener(listener)
    }

    // I call this in "onPause()" of my fragment
    fun removeListener() {
        viewTreeObserver.removeOnGlobalLayoutListener(listener)
    }

    private fun possiblyResizeChildOfContent() {
        contentContainer.getWindowVisibleDisplayFrame(contentAreaOfWindowBounds)
        val usableHeightNow = contentAreaOfWindowBounds.height()
        if (usableHeightNow != usableHeightPrevious) {
            rootViewLayout.height = usableHeightNow
            // Change the bounds of the root view to prevent gap between keyboard and content, and top of content positioned above top screen edge.
            rootView.layout(contentAreaOfWindowBounds.left, contentAreaOfWindowBounds.top, contentAreaOfWindowBounds.right, contentAreaOfWindowBounds.bottom)
            rootView.requestLayout()

            usableHeightPrevious = usableHeightNow
        }
    }
}
10 голосов
/ 19 февраля 2017

Я только что нашел простое и надежное решение, если вы используете системный интерфейс (https://developer.android.com/training/system-ui/immersive.html).

Это работает в случае, когда вы используете View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN, например. если вы используете CoordinatorLayout.

Он не будет работать для WindowManager.LayoutParams.FLAG_FULLSCREEN (тот, который вы также можете установить в теме с помощью android:windowFullscreen), но вы можете добиться аналогичного эффекта с SYSTEM_UI_FLAG_LAYOUT_STABLE (который "имеет тот же визуальный эффект" в соответствии с в документы ) и это решение должно снова работать.

getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION /* If you want to hide navigation */
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE)

Я проверил это на своем устройстве под управлением Marshmallow.

Ключ в том, что программные клавиатуры также являются одним из системных окон (таких как строка состояния и панель навигации), поэтому отправляемая системой WindowInsets содержит точную и надежную информацию о ней.

Для случая использования, такого как DrawerLayout, где мы пытаемся рисовать за строкой состояния, мы можем создать макет, который игнорирует только верхнюю вставку и применяет нижнюю вставку, которая отвечает за программную клавиатуру.

Вот мой заказ FrameLayout:

/**
 * Implements an effect similar to {@code android:fitsSystemWindows="true"} on Lollipop or higher,
 * except ignoring the top system window inset. {@code android:fitsSystemWindows="true"} does not
 * and should not be set on this layout.
 */
public class FitsSystemWindowsExceptTopFrameLayout extends FrameLayout {

    public FitsSystemWindowsExceptTopFrameLayout(Context context) {
        super(context);
    }

    public FitsSystemWindowsExceptTopFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FitsSystemWindowsExceptTopFrameLayout(Context context, AttributeSet attrs,
                                                 int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    public FitsSystemWindowsExceptTopFrameLayout(Context context, AttributeSet attrs,
                                                 int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setPadding(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(),
                    insets.getSystemWindowInsetBottom());
            return insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
        } else {
            return super.onApplyWindowInsets(insets);
        }
    }
}

и использовать его:

<com.example.yourapplication.FitsSystemWindowsExceptTopFrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Your original layout here -->
</com.example.yourapplication.FitsSystemWindowsExceptTopFrameLayout>

Теоретически это должно работать для любого устройства без безумной модификации, гораздо лучше, чем любой взлом, который пытается использовать в качестве эталона случайный 1/3 или 1/4 размера экрана.

(для него требуется API 16+, но я использую полноэкранный режим только на Lollipop + для рисования за строкой состояния, поэтому в этом случае это лучшее решение.)

9 голосов
/ 15 августа 2013

Мне тоже пришлось столкнуться с этой проблемой, и у меня была работа, которую я проверил на HTC one, galaxy s1, s2, s3, note и HTC Sensation.

Поместите прослушиватель глобального макета в корневой вид вашего макета

mRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){
            public void onGlobalLayout() {
                checkHeightDifference();
            }
    });

и там я проверил разницу высот, и если разница высот экрана больше трети высоты экрана, мы можем предположить, что клавиатура открыта. взял от этот ответ .

private void checkHeightDifference(){
    // get screen frame rectangle 
    Rect r = new Rect();
    mRootView.getWindowVisibleDisplayFrame(r);
    // get screen height
    int screenHeight = mRootView.getRootView().getHeight();
    // calculate the height difference
    int heightDifference = screenHeight - (r.bottom - r.top);

    // if height difference is different then the last height difference and
    // is bigger then a third of the screen we can assume the keyboard is open
    if (heightDifference > screenHeight/3 && heightDifference != mLastHeightDifferece) {
        // keyboard visiblevisible
        // get root view layout params
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mRootView.getLayoutParams();
        // set the root view height to screen height minus the height difference
        lp.height = screenHeight - heightDifference;
        // call request layout so the changes will take affect
        .requestLayout();
        // save the height difference so we will run this code only when a change occurs.
        mLastHeightDifferece = heightDifference;
    } else if (heightDifference != mLastHeightDifferece) {
        // keyboard hidden
        PFLog.d("[ChatroomActivity] checkHeightDifference keyboard hidden");
        // get root view layout params and reset all the changes we have made when the keyboard opened.
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mRootView.getLayoutParams();
        lp.height = screenHeight;
        // call request layout so the changes will take affect
        mRootView.requestLayout();
        // save the height difference so we will run this code only when a change occurs.
        mLastHeightDifferece = heightDifference;
    }
}

это, вероятно, не пуленепробиваемый и, возможно, на некоторых устройствах это не будет работать, но это сработало для меня, и надеюсь, что это поможет вам тоже.

7 голосов
/ 06 марта 2015

Обратите внимание, что android:windowSoftInputMode="adjustResize" не работает, если для активности установлено WindowManager.LayoutParams.FLAG_FULLSCREEN. У вас есть два варианта.

  1. Либо отключите полноэкранный режим для вашей деятельности. Активность не изменяется в полноэкранном режиме. Вы можете сделать это либо в XML (путем изменения темы упражнения), либо в коде Java. Добавьте следующие строки в ваш метод onCreate ().

    getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);   
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);`
    

OR

  1. Используйте альтернативный способ для достижения полноэкранного режима. Добавьте следующий код в метод onCreate ().

    getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    View decorView = getWindow().getDecorView();
    // Hide the status bar.
    int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
    decorView.setSystemUiVisibility(uiOptions);`
    

Обратите внимание, что метод-2 работает только в Android 4.1 и выше.

6 голосов
/ 13 апреля 2016

Я реализовал решение Джозефа Джонсона, и оно работало хорошо, я заметил, что после использования этого решения иногда ящик приложения не закрывался должным образом.Я добавил функциональность для удаления слушателя removeOnGlobalLayoutListener, когда пользователь закрывает фрагмент, в котором находятся edittexts.

    //when the application uses full screen theme and the keyboard is shown the content not scrollable! 
//with this util it will be scrollable once again
///4833547/android-kak-nastroit-maket-v-polnoekrannom-rezhime-kogda-programmnaya-klavisha-vidna
public class AndroidBug5497Workaround {


    private static AndroidBug5497Workaround mInstance = null;
    private View mChildOfContent;
    private int usableHeightPrevious;
    private FrameLayout.LayoutParams frameLayoutParams;
    private ViewTreeObserver.OnGlobalLayoutListener _globalListener;

    // For more information, see https://code.google.com/p/android/issues/detail?id=5497
    // To use this class, simply invoke assistActivity() on an Activity that already has its content view set.

    public static AndroidBug5497Workaround getInstance (Activity activity) {
        if(mInstance==null)
        {
            synchronized (AndroidBug5497Workaround.class)
            {
                mInstance = new AndroidBug5497Workaround(activity);
            }
        }
        return mInstance;
    }

    private AndroidBug5497Workaround(Activity activity) {
        FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
        mChildOfContent = content.getChildAt(0);
        frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();

        _globalListener = new ViewTreeObserver.OnGlobalLayoutListener()
        {

            @Override
            public void onGlobalLayout()
            {
                 possiblyResizeChildOfContent();
            }
        };
    }

    public void setListener()
    {
         mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(_globalListener);
    }

    public void removeListener()
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            mChildOfContent.getViewTreeObserver().removeOnGlobalLayoutListener(_globalListener);
        } else {
            mChildOfContent.getViewTreeObserver().removeGlobalOnLayoutListener(_globalListener);
        }
    }

    private void possiblyResizeChildOfContent() {
        int usableHeightNow = computeUsableHeight();
        if (usableHeightNow != usableHeightPrevious) {
            int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
            int heightDifference = usableHeightSansKeyboard - usableHeightNow;
            if (heightDifference > (usableHeightSansKeyboard/4)) {
                // keyboard probably just became visible
                frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
            } else {
                // keyboard probably just became hidden
                frameLayoutParams.height = usableHeightSansKeyboard;
            }
            mChildOfContent.requestLayout();
            usableHeightPrevious = usableHeightNow;
        }
    }

    private int computeUsableHeight() {
        Rect r = new Rect();
        mChildOfContent.getWindowVisibleDisplayFrame(r);
        return (r.bottom - r.top);
    } 
}

использует класс, в котором мои edittexts расположены

@Override
public void onStart()
{
    super.onStart();
    AndroidBug5497Workaround.getInstance(getActivity()).setListener();
}

@Override
public void onStop()
{
    super.onStop();
    AndroidBug5497Workaround.getInstance(getActivity()).removeListener();
}
5 голосов
/ 11 сентября 2015

Чтобы заставить его работать с FullScreen:

Используйте плагин для ионной клавиатуры. Это позволяет вам слушать, когда клавиатура появляется и исчезает.

OnDeviceReady добавить эти прослушиватели событий:

// Allow Screen to Move Up when Keyboard is Present
window.addEventListener('native.keyboardshow', onKeyboardShow);
// Reset Screen after Keyboard hides
window.addEventListener('native.keyboardhide', onKeyboardHide);

Логика:

function onKeyboardShow(e) {
    // Get Focused Element
    var thisElement = $(':focus');
    // Get input size
    var i = thisElement.height();
    // Get Window Height
    var h = $(window).height()
    // Get Keyboard Height
    var kH = e.keyboardHeight
    // Get Focused Element Top Offset
    var eH = thisElement.offset().top;
    // Top of Input should still be visible (30 = Fixed Header)
    var vS = h - kH;
    i = i > vS ? (vS - 30) : i;
    // Get Difference
    var diff = (vS - eH - i);
    if (diff < 0) {
        var parent = $('.myOuter-xs.myOuter-md');
        // Add Padding
        var marginTop = parseInt(parent.css('marginTop')) + diff - 25;
        parent.css('marginTop', marginTop + 'px');
    }
}

function onKeyboardHide(e) {
  // Remove All Style Attributes from Parent Div
  $('.myOuter-xs.myOuter-md').removeAttr('style');
}

В основном, если разница между ними равна минус, то это количество пикселей, которое клавиатура покрывает вашим вводом. Так что если вы подстроите свой родительский div этим, это должно противодействовать ему.

Добавление тайм-аутов к логике, скажем, 300 мс должны также оптимизировать производительность (так как это позволит отображать время клавиатуры.

5 голосов
/ 20 октября 2017

Добавьте android:fitsSystemWindows="true" к макету, и размер этого макета будет изменен.

...