Как запечатлеть событие «показать / скрыть виртуальную клавиатуру» в 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 попробуй newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO~ Крис
cimnine
3
Это работает, только если вы зарегистрировали активность для прослушивания изменений config, которые вы хотите в AndroidManifest.
Рауль Аграйт
66
пожалуйста, обновите свой ответ и скажите, что он не работает для программной клавиатуры. Я потратил полдня на то, чтобы попробовать твой код. А потом увидел эти комментарии.
Шириш Хервейд
17
Это не работает для "виртуальных" клавиатур, что было первоначальным вопросом.
brummfondel
18
Ну, вопрос был о ПРОГРАММНОЙ КЛАВИАТУРЕ, почему принят ответ об аппаратной клавиатуре? -1!
Денис Виталий
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;
            }
        }
    });
}

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

amalBit
источник
1
addOnGlobalLayoutListener?
coolcool1994
7
Это пахнет как утечка памяти. Вы добавляете слушателя к глобальному объекту, который будет удерживать вас и никогда не отпустит вас.
flexicious.com
9
Это также не будет работать для действий, установленных с android:windowSoftInputMode="adjustPan"или adjustResizeс полноэкранным окном, так как макет никогда не изменяется.
Ионокласт Бригам
1
Это работает только с AdjustResize. Для AdjustPan высотаDiff никогда не меняется.
alexhilton
2
почему ты сравниваешь логическое значение?
Xerus
37

Если вы хотите обработать отображение / скрытие окна виртуальной клавиатуры IMM из вашей деятельности, вам нужно создать подкласс своего макета и переопределить метод onMesure (чтобы вы могли определить измеренную ширину и измеренную высоту вашего макета). После этого установите подклассный макет в качестве основного вида для вашей деятельности с помощью 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>

Теперь внутри вашей Activity объявите подкласс для вашего макета (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 = "AdjustPan". Я хотел, чтобы мой экран не уменьшался после появления мягкой клавиатуры. Можете ли вы сказать какое-либо исправление, чтобы оно работало даже для AdjustPan
Shirish Herwade
Это не работает, здесь всегда идет другая часть, если (actualHeight> предлагаемый высота) {// Клавиатура показана} else {// Клавиатура скрыта}
Aamirkhan
Вы также можете использовать пользовательский вид с той же идеей, следуя примеру gist.github.com/juliomarcos/8ca307cd7eca607c8547
Хулио Родригес
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
Давид Папиров
1
Идеальная реализация,
пригодная для
1
действительно хороший ты.
ZaoTaoBao
@DavidPapirov, вы вставили ссылку на RecyclerView, но не упомянули об этом здесь.
CoolMind
22

На основе кода от Nebojsa Tomcic я разработал следующий RelativeLayout-Subclass:

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 = "AdjustPan". Я хотел, чтобы мой экран не уменьшался после появления мягкой клавиатуры. Можете ли вы сказать какое-либо исправление, чтобы оно работало даже для AdjustPan
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.

Это работает как для AdjustPan, так и AdjustResize.

alexhilton
источник
2
Я просто собираюсь вырвать мои волосы! Ты спас мне волосы;)
Виджай Сингх Чухан
1
Это единственный хороший ответ здесь, он отлично работает на мягкой клавиатуре, спасибо
Z3nk
12

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

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
источник
Как использовать это во Фрагменте или Деятельности? @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усиливается или теряет фокус. Это на самом деле срабатывает прямо перед тем, как программная клавиатура показывает или скрывается. Лучший способ сделать что-то после того, как клавиатура показывает или скрывается, это использовать a 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);
        }
    });
poisondminds
источник
1
Это не работает, в противном случае. OnFocusChangeListenerтолько сообщает, EditTextимеет ли фокус после изменения состояния. Но IMEможет быть скрыто, когда EditTextесть фокус, как обнаружить этот случай?
DysaniazzZ
3

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

100rabh
источник
Первый трекбек на этом URL указывает на блог RussenReaktor, в котором упоминается добавление android: windowSoftInputMode = "AdjustPan" в манифест действия. Это отлично сработало для меня.
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);
    }
}
user2462737
источник
2
Это не работает для Android: windowSoftInputMode = "AdjustPan". Я хотел, чтобы мой экран не уменьшался после появления мягкой клавиатуры. Можете ли вы сказать какое-либо исправление, чтобы оно работало даже для AdjustPan
Шириш Хервейд
Когда функция скрыть / показать, то этот метод слушателя вызывается дважды или трижды. Я не знаю, в чем именно проблема.
Джагвир Сингх Раджпут
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);
}
MatrixDev
источник
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 - это корневой вид вашей активности.

Варун Верма
источник
у меня heightDiff 160 на старте и 742 с kbd, поэтому мне пришлось ввести и установить initialHeightDiff на старте
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);
        }
    }
}
Режим
источник