Як зафіксувати подію "віртуальна клавіатура показати / приховати" в Android?


227

Я хотів би змінити макет залежно від того, відображається віртуальна клавіатура чи ні. Я шукав API та різні блоги, але не можу знайти нічого корисного.

Це можливо?

Дякую!


Відповіді:


69

Примітка

Це рішення не працюватиме для м'яких клавіатур і onConfigurationChangedне буде вимагатися для м'яких (віртуальних) клавіатур.


Ви повинні самостійно впоратися із змінами конфігурації.

http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange

Зразок:

// from the link above
@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);


    // Checks whether a hardware keyboard is available
    if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "keyboard visible", Toast.LENGTH_SHORT).show();
    } else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "keyboard hidden", Toast.LENGTH_SHORT).show();
    }
}

Потім просто змініть видимість деяких представлень, оновіть поле та змініть файл макета.


4
@shiami try newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO~ Chris
cimnine

3
Це працює лише в тому випадку, якщо ви зареєстрували активність для прослуховування потрібних конфігураційних змін в AndroidManifest.
Рауль Аграйт

65
оновіть свою відповідь і скажіть, що вона не працює для м'якої клавіатури. Я витрачав свою половину дня, пробуючи ваш код. А потім побачив ці коментарі.
Shirish Herwade

17
Це не працює для "віртуальних" клавіатур, що було початковим питанням.
brummfondel

18
Що ж, питання стосувалося МЕЧНОГО КЛЮЧА, чому прийнята відповідь про апаратну клавіатуру? -1!
Denys Vitali

56

Це може бути не найефективнішим рішенням. Але це працювало для мене кожен раз ... Я називаю цю функцію там, де мені потрібно слухати softKeyboard.

boolean isOpened = false;

public void setListenerToRootView() {
    final View activityRootView = getWindow().getDecorView().findViewById(android.R.id.content);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {

            int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
            if (heightDiff > 100) { // 99% of the time the height diff will be due to a keyboard.
                Toast.makeText(getApplicationContext(), "Gotcha!!! softKeyboardup", 0).show();

                if (isOpened == false) {
                    //Do two things, make the view top visible and the editText smaller
                }
                isOpened = true;
            } else if (isOpened == true) {
                Toast.makeText(getApplicationContext(), "softkeyborad Down!!!", 0).show();
                isOpened = false;
            }
        }
    });
}

Примітка. Цей підхід спричинить проблеми, якщо користувач використовує плаваючу клавіатуру.


1
addOnGlobalLayoutListener?
coolcool1994

7
Це пахне витоком пам’яті. Ви додаєте слухача до глобального об’єкта, який буде триматися за вас і ніколи не відпускатиме вас.
flexicious.com

9
Цей також не буде працювати для дій, встановлених за допомогою вікна android:windowSoftInputMode="adjustPan"або adjustResizeна повноекранному екрані, оскільки макет ніколи не змінюється.
Іонокласт Брігхем,

1
Це працює лише з коригуваннямReResize. Для настройкиPan, висотаDiff ніколи не змінюється.
alexhilton

2
чому ти порівнюєш булеву?
Ксерус

37

Якщо ви хочете обробляти показ / приховування вікна IMM (віртуальної) клавіатури зі своєї діяльності, вам потрібно буде підкласифікувати ваш макет та замінити метод onMesure (щоб ви могли визначити вимірювану ширину та виміряну висоту вашого макета). Після цього встановіть підкласичний макет як основний вигляд вашої діяльності by setContentView (). Тепер ви зможете обробляти покази / приховування вікон IMM. Якщо це звучить складно, це не так насправді. Ось код:

main.xml

   <?xml version="1.0" encoding="utf-8"?>
   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal" >
        <EditText
             android:id="@+id/SearchText" 
             android:text="" 
             android:inputType="text"
             android:layout_width="fill_parent"
             android:layout_height="34dip"
             android:singleLine="True"
             />
        <Button
             android:id="@+id/Search" 
             android:layout_width="60dip"
             android:layout_height="34dip"
             android:gravity = "center"
             />
    </LinearLayout>

Тепер усередині вашої активності оголосіть підклас для вашого макета (main.xml)

    public class MainSearchLayout extends LinearLayout {

    public MainSearchLayout(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d("Search Layout", "Handling Keyboard Window shown");

        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();

        if (actualHeight > proposedheight){
            // Keyboard is shown

        } else {
            // Keyboard is hidden
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

З коду ви бачите, що ми завищуємо макет нашої діяльності в конструкторі підкласу

inflater.inflate(R.layout.main, this);

А тепер просто встановіть перегляд вмісту підкласового макета для нашої діяльності.

public class MainActivity extends Activity {

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        MainSearchLayout searchLayout = new MainSearchLayout(this, null);

        setContentView(searchLayout);
    }

    // rest of the Activity code and subclassed layout...

}

3
Мені потрібно далі дослідити, але я сумніваюся, чи не спрацює це в моєму випадку для невеликого діалогового вікна на великому екрані, для якого на вимірювання макета не вплине наявність клавіатури.
PJL

4
Він не працює для android: windowSoftInputMode = "коригуватиPan". Я хотів, щоб мій екран не стискався після появи м’якої клавіатури. Чи можете ви скажіть будь-яке виправлення, щоб воно працювало навіть для налаштуванняPan
Shirish Herwade

Це не працює, він завжди переходить до іншої частини тут, якщо (фактичнаВисота> запропонована висота) {// Клавіатура показана} ще {{Клавіатура прихована}
Аамірхан

Ви також можете скористатися власною програмою
Хуліо Родрігес,

1
Не буде працювати для заходів, встановлених за допомогою вікна android:windowSoftInputMode="adjustPan"або adjustResizeна повноекранному екрані, оскільки макет ніколи не змінюється.
Іонокласт Брігхем

35

Я зробив так:

Додати OnKeyboardVisibilityListenerінтерфейс.

public interface OnKeyboardVisibilityListener {
    void onVisibilityChanged(boolean visible);
}

HomeActivity.java :

public class HomeActivity extends Activity implements OnKeyboardVisibilityListener {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_sign_up);
    // Other stuff...
    setKeyboardVisibilityListener(this);
}

private void setKeyboardVisibilityListener(final OnKeyboardVisibilityListener onKeyboardVisibilityListener) {
    final View parentView = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
    parentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        private boolean alreadyOpen;
        private final int defaultKeyboardHeightDP = 100;
        private final int EstimatedKeyboardDP = defaultKeyboardHeightDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);
        private final Rect rect = new Rect();

        @Override
        public void onGlobalLayout() {
            int estimatedKeyboardHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, parentView.getResources().getDisplayMetrics());
            parentView.getWindowVisibleDisplayFrame(rect);
            int heightDiff = parentView.getRootView().getHeight() - (rect.bottom - rect.top);
            boolean isShown = heightDiff >= estimatedKeyboardHeight;

            if (isShown == alreadyOpen) {
                Log.i("Keyboard state", "Ignoring global layout change...");
                return;
            }
            alreadyOpen = isShown;
            onKeyboardVisibilityListener.onVisibilityChanged(isShown);
        }
    });
}


@Override
public void onVisibilityChanged(boolean visible) {
    Toast.makeText(HomeActivity.this, visible ? "Keyboard is active" : "Keyboard is Inactive", Toast.LENGTH_SHORT).show();
  }
}

Сподіваюся, це допоможе вам.


2
Дякую Хірен. Це ідеальне рішення +1
Харін Каклотар

1
Дякую, працювали для мене! Якщо ви хочете просто налаштувати RecyclerView см рішення тут: stackoverflow.com/a/43204258/373106
David ПАПІР

1
Ідеальна реалізація для багаторазового використання, опрацьована в «Діяльність» або «Фрагмент», дякую
Пеланес

1
дійсно приємний ти.
ZaoTaoBao

@DavidPapirov, ви вставили посилання на RecyclerView, але тут не згадали про нього.
CoolMind

22

На основі кодексу Небойши Томчича я розробив наступний підклас відносного макета:

import java.util.ArrayList;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;

public class KeyboardDetectorRelativeLayout extends RelativeLayout {

    public interface IKeyboardChanged {
        void onKeyboardShown();
        void onKeyboardHidden();
    }

    private ArrayList<IKeyboardChanged> keyboardListener = new ArrayList<IKeyboardChanged>();

    public KeyboardDetectorRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

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

    public void addKeyboardStateChangedListener(IKeyboardChanged listener) {
        keyboardListener.add(listener);
    }

    public void removeKeyboardStateChangedListener(IKeyboardChanged listener) {
        keyboardListener.remove(listener);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();

        if (actualHeight > proposedheight) {
            notifyKeyboardShown();
        } else if (actualHeight < proposedheight) {
            notifyKeyboardHidden();
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private void notifyKeyboardHidden() {
        for (IKeyboardChanged listener : keyboardListener) {
            listener.onKeyboardHidden();
        }
    }

    private void notifyKeyboardShown() {
        for (IKeyboardChanged listener : keyboardListener) {
            listener.onKeyboardShown();
        }
    }

}

Це працює чудово ... Позначте, що це рішення працюватиме лише тоді, коли для режиму м'якого введення вашої активності встановлено значення "WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE"


3
Він не працює для android: windowSoftInputMode = "коригуватиPan". Я хотів, щоб мій екран не стискався після появи м’якої клавіатури. Чи можете ви скажіть будь-яке виправлення, щоб воно працювало навіть для налаштуванняPan
Shirish Herwade

1
Цей також не буде працювати для дій, встановлених за допомогою вікна android:windowSoftInputMode="adjustPan"або adjustResizeна повноекранному екрані, оскільки макет ніколи не змінюється.
Іонокласт Брігхем

він тригери досить багато разів.
zionpi

22

Як і у відповідь @ amalBit, зареєструйте слухача глобального макета та обчисліть різницю видимого дна dectorView та запропонованого дна, якщо різниця більша за якесь значення (здогадалися висоту IME), ми вважаємо, що IME збільшився:

    final EditText edit = (EditText) findViewById(R.id.edittext);
    edit.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (keyboardShown(edit.getRootView())) {
                Log.d("keyboard", "keyboard UP");
            } else {
                Log.d("keyboard", "keyboard Down");
            }
        }
    });

private boolean keyboardShown(View rootView) {

    final int softKeyboardHeight = 100;
    Rect r = new Rect();
    rootView.getWindowVisibleDisplayFrame(r);
    DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
    int heightDiff = rootView.getBottom() - r.bottom;
    return heightDiff > softKeyboardHeight * dm.density;
}

Поріг висоти 100 - здогадана мінімальна висота IME.

Це працює як для prilagodPan, так і для prilagodResize.


2
Я ось-ось забираю волосся !! Ти врятував мені волосся;)
Vijay Singh Chouhan

1
Тут єдина хороша відповідь, вона працює на м'якій клавіатурі ідеально, дякую
Z3nk

12

Рішення Небойси майже для мене спрацювало. Коли я натиснув всередині рядкового EditText, він знав, що відображається клавіатура, але коли я почав вводити всередину EditText, фактична висота та запропонована висота все ще були однаковими, тому він не знав, що клавіатура все ще відображається. Я зробив невелику модифікацію, щоб зберігати максимальну висоту, і вона працює чудово. Ось переглянений підклас:

public class CheckinLayout extends RelativeLayout {

    private int largestHeight;

    public CheckinLayout(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.checkin, this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        largestHeight = Math.max(largestHeight, getHeight());

        if (largestHeight > proposedheight)
            // Keyboard is shown
        else
            // Keyboard is hidden

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

10

Не впевнений, чи хтось допише це. Знайшов це рішення простим у використанні! . Клас SoftKeyboard знаходиться на gist.github.com . Але в той час, коли спливаюче або приховане виклик клавіатури подій, нам потрібен обробник, щоб правильно виконувати дії в інтерфейсі:

/*
Somewhere else in your code
*/
RelativeLayout mainLayout = findViewById(R.layout.main_layout); // You must use your root layout
InputMethodManager im = (InputMethodManager) getSystemService(Service.INPUT_METHOD_SERVICE);

/*
Instantiate and pass a callback
*/
SoftKeyboard softKeyboard;
softKeyboard = new SoftKeyboard(mainLayout, im);
softKeyboard.setSoftKeyboardCallback(new SoftKeyboard.SoftKeyboardChanged()
{

    @Override
    public void onSoftKeyboardHide() 
    {
        // Code here
        new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    // Code here will run in UI thread
                    ...
                }
            });
    }

    @Override
    public void onSoftKeyboardShow() 
    {
        // Code here
        new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    // Code here will run in UI thread
                    ...
                }
            });

    }   
});

ось Git, щоб отримати SoftkeyBoard " gist.github.com/felHR85/… "
douarbou

9

Я вирішую це шляхом переосмислення onKeyPreIme (int keyCode, подія KeyEvent) у моєму користувальницькому редакторі EditText.

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
        //keyboard will be hidden
    }
}

Як використовувати його у фрагменті чи діяльності? @Qbait
Maulik Dodia

Це не працює, його можна викликати лише тоді, коли я залишаю сторінку у своєму випадку.
DysaniazzZ

це метод з EditText см ця відповідь: stackoverflow.com/a/5993196/2093236
Dmide

4

У мене є такий собі злом, щоб це зробити. Хоча, здається, не існує способу виявити, коли м'яка клавіатура показала або приховала, ви насправді можете визначити, коли вона збираєтьсяOnFocusChangeListener відобразити або приховати, встановивши на те, EditTextщо ви слухаєте.

EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
    {
        @Override
        public void onFocusChange(View view, boolean hasFocus)
        {
            //hasFocus tells us whether soft keyboard is about to show
        }
    });

ПРИМІТКА. Одне з цих хак необхідно пам’ятати про те, що цей зворотний виклик запускається негайно, коли ви EditTextотримуєте або втрачаєте фокус. Це насправді буде спрацьовувати безпосередньо перед тим, як м'яка клавіатура відображається або ховається. Найкращий спосіб, коли я виявив щось робити після того, як клавіатура показує або приховує, - це використовувати Handlerта затримати щось ~ 400 мс, як-от так:

EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
    {
        @Override
        public void onFocusChange(View view, boolean hasFocus)
        {
            new Handler().postDelayed(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        //do work here
                    }
                }, 400);
        }
    });

1
Це не працює, інакше. OnFocusChangeListenerговорить лише, чи EditTextмає фокус після зміни стану. Але, IMEможливо, приховано, коли EditTextє фокус, як виявити цей випадок?
DysaniazzZ

3

Сандер, я вважаю, ви намагаєтесь показати вид, заблокований м'якою клавіатурою. Спробуйте це http://android-developers.blogspot.com/2009/04/updating-applications-for-on-screen.html .


Перший трекбек цієї URL-адреси вказує у веб-журналі RussenReaktor, який згадує про додавання андроїда: windowSoftInputMode = "коригуватиPan" до маніфесту Діяльності. Це спрацювало для мене чудово.
JohnnyLambada

2

Я вирішив проблему з однорядковим кодуванням зворотного перегляду тексту.

package com.helpingdoc;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;

public class MainSearchLayout extends LinearLayout {
    int hieght = 0;
    public MainSearchLayout(Context context, AttributeSet attributeSet) {

        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);


    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d("Search Layout", "Handling Keyboard Window shown");
       if(getHeight()>hieght){
           hieght = getHeight();
       }
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();
        System.out.println("....hieght = "+ hieght);
        System.out.println("....actualhieght = "+ actualHeight);
        System.out.println("....proposedheight = "+ proposedheight);
        if (actualHeight > proposedheight){
            // Keyboard is shown


        } else if(actualHeight<proposedheight){
            // Keyboard is hidden

        }

        if(proposedheight == hieght){
             // Keyboard is hidden
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

2
Він не працює для android: windowSoftInputMode = "коригуватиPan". Я хотів, щоб мій екран не стискався після появи м’якої клавіатури. Чи можете ви скажіть будь-яке виправлення, щоб воно працювало навіть для налаштуванняPan
Shirish Herwade

Коли функція приховати / показати, цей метод слухача викликає двічі чи тричі. Я не знаю, в чому саме проблема.
Jagveer Singh Rajput

2

Ви також можете перевірити наявність першої нижньої підкладки DecorView для дітей. Коли буде показано клавіатуру, вона буде встановлена ​​на нульове значення.

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    View view = getRootView();
    if (view != null && (view = ((ViewGroup) view).getChildAt(0)) != null) {
        setKeyboardVisible(view.getPaddingBottom() > 0);
    }
    super.onLayout(changed, left, top, right, bottom);
}

1

Приховати | Показати події для клавіатури можна просто за допомогою простого хаку в OnGlobalLayoutListener:

 final View activityRootView = findViewById(R.id.top_root);
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();

                if (heightDiff > 100) {
                    // keyboard is up
                } else {
                    // keyboard is down
                }
            }
        });

Тут ActivityRootView - це кореневий вигляд вашої діяльності.


мій висотаDiff становить 160 на старті і 742 з kbd, тому мені довелося представити і встановити початковийHeightDiff на старті
djdance

0

за допомогою viewTreeObserver легко отримати подію клавіатури.

layout_parent.viewTreeObserver.addOnGlobalLayoutListener {
            val r = Rect()
            layout_parent.getWindowVisibleDisplayFrame(r)
            if (layout_parent.rootView.height - (r.bottom - r.top) > 100) { // if more than 100 pixels, its probably a keyboard...
                Log.e("TAG:", "keyboard open")
            } else {
                Log.e("TAG:", "keyboard close")
            }
        }

** layout_parent - це ваш поглядedit_text.parent


-2

Відповідь Небойші Томчича мені не була корисною. Я маю RelativeLayoutз TextViewі AutoCompleteTextViewвсередині нього. Мені потрібно прокрутити TextViewдонизу, коли клавіатура відображається і коли вона прихована. Для цього я відміняю onLayoutметод, і він прекрасно працює для мене.

public class ExtendedLayout extends RelativeLayout
{
    public ExtendedLayout(Context context, AttributeSet attributeSet)
    {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        super.onLayout(changed, l, t, r, b);

        if (changed)
        {
            int scrollEnd = (textView.getLineCount() - textView.getHeight() /
                textView.getLineHeight()) * textView.getLineHeight();
            textView.scrollTo(0, scrollEnd);
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.