Jak uchwycić zdarzenie „pokaż / ukryj wirtualną klawiaturę” w Androidzie?


Odpowiedzi:


69

Uwaga

To rozwiązanie nie będzie działać w przypadku klawiatur miękkich i onConfigurationChangednie będzie wywoływane w przypadku klawiatur miękkich (wirtualnych).


Musisz sam poradzić sobie ze zmianami konfiguracji.

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

Próba:

// 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();
    }
}

Następnie wystarczy zmienić widoczność niektórych widoków, zaktualizować pole i zmienić plik układu.


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

3
Działa to tylko wtedy, gdy zarejestrowałeś działanie w celu odsłuchania zmian konfiguracji, które chcesz w AndroidManifest.
Raul Agrait

65
zaktualizuj swoją odpowiedź i powiedz, że to nie działa na miękkiej klawiaturze. Zmarnowałem swój pół dnia, próbując kodu. A potem zobaczyłem te komentarze.
Shirish Herwade,

17
To nie działa na „wirtualnych” klawiaturach, co było pierwotnym pytaniem.
brummfondel

18
Pytanie dotyczyło SOFT KEYBOARD, dlaczego jest akceptowana odpowiedź na temat klawiatury sprzętowej? -1!
Denys Vitali

56

To może nie być najskuteczniejsze rozwiązanie. Ale działało to dla mnie za każdym razem ... Wywołuję tę funkcję tam, gdzie muszę słuchać 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;
            }
        }
    });
}

Uwaga: Takie podejście spowoduje problemy, jeśli użytkownik użyje pływającej klawiatury.


1
addOnGlobalLayoutListener?
coolcool1994

7
Pachnie jak wyciek pamięci. Dodajesz słuchacza do obiektu globalnego, który będzie cię trzymał i nigdy nie pozwoli ci odejść.
flexicious.com,

9
To również nie będzie działać dla działań ustawionych z oknem pełnoekranowym android:windowSoftInputMode="adjustPan"lub adjustResizez nim, ponieważ układ nigdy nie zmienia rozmiaru.
Ionoclast Brigham

1
Działa to tylko z adjustResize. W przypadku AdjustPan wysokośćDiff nigdy się nie zmienia.
alexhilton

2
dlaczego porównujesz wartość logiczną?
Xerus,

37

Jeśli chcesz obsługiwać wyświetlanie / ukrywanie okna klawiatury IMM (wirtualnej) z działania, musisz podklasować swój układ i zastąpić metodę onMesure (abyś mógł określić zmierzoną szerokość i zmierzoną wysokość swojego układu). Następnie ustaw układ podklas jako główny widok działania za pomocą setContentView (). Teraz będziesz mógł obsługiwać pokazy / ukrywanie okien IMM. Jeśli to brzmi skomplikowanie, to nie jest tak naprawdę. Oto kod:

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>

Teraz w swojej działalności zadeklaruj podklasę dla swojego układu (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);
    }
}

Z kodu wynika, że ​​nadmuchujemy układ dla naszej aktywności w konstruktorze podklasy

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

A teraz po prostu ustaw widok zawartości układu podklas dla naszej działalności.

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
Muszę zbadać dalej, ale mam wątpliwości, czy to zadziała w moim przypadku w przypadku małego okna dialogowego na urządzeniu z dużym ekranem, dla którego obecność klawiatury nie miałaby wpływu na pomiary układu.
PJL,

4
Nie działa na Androidzie: windowSoftInputMode = "adjustPan". Chciałem, żeby mój ekran nie kurczył się po pojawieniu się miękkiej klawiatury. Czy możesz podać jakąkolwiek poprawkę, aby działała nawet dla AdjustPan
Shirish Herwade

To nie działa, zawsze przechodzi do części else tutaj, jeśli (actualHeight> proponheight) {// Klawiatura jest pokazana} else {// Klawiatura jest ukryta}
Aamirkhan

Możesz również użyć widoku niestandardowego z tym samym pomysłem, podążając za przykładem gist.github.com/juliomarcos/8ca307cd7eca607c8547
Julio Rodrigues

1
Nie będzie działać w przypadku działań ustawionych w oknie pełnoekranowym android:windowSoftInputMode="adjustPan"lub adjustResizez nim, ponieważ układ nigdy nie zmienia rozmiaru.
Ionoclast Brigham

35

Zrobiłem w ten sposób:

Dodaj OnKeyboardVisibilityListenerinterfejs.

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();
  }
}

Mam nadzieję, że to ci pomoże.


2
Dzięki Hiren. To idealne rozwiązanie +1
Harin Kaklotar

1
Dzięki, pracowałeś dla mnie! Jeśli chcesz tylko wyregulować swój RecyclerView, zobacz rozwiązanie tutaj: stackoverflow.com/a/43204258/373106
David Papirov

1
Idealna implementacja wielokrotnego użytku, działająca w Aktywność lub Fragment, dzięki
Pelanes,

1
naprawdę fajny ty.
ZaoTaoBao

@DavidPapirov, wkleiłeś link do RecyclerView, ale nie wspomniałeś o tym tutaj.
CoolMind

22

W oparciu o kod z Nebojsa Tomcic opracowałem następującą podklasę RelativeLayout:

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();
        }
    }

}

Działa to całkiem dobrze ... Zaznacz, że to rozwiązanie będzie działać, gdy Tryb Miękkiego Wprowadzania Twojej Aktywności jest ustawiony na „WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE”


3
Nie działa na Androidzie: windowSoftInputMode = "adjustPan". Chciałem, żeby mój ekran nie kurczył się po pojawieniu się miękkiej klawiatury. Czy możesz podać jakąkolwiek poprawkę, aby działała nawet dla AdjustPan
Shirish Herwade

1
To również nie będzie działać dla działań ustawionych z oknem pełnoekranowym android:windowSoftInputMode="adjustPan"lub adjustResizez nim, ponieważ układ nigdy nie zmienia rozmiaru.
Ionoclast Brigham

tryga całkiem sporo razy.
zionpi

22

Podobnie jak odpowiedź @ amalBit, zarejestruj nasłuchiwanie w układzie globalnym i oblicz różnicę widocznego dna dectorView i proponowanego dna, jeśli różnica jest większa niż pewna wartość (domyślna wysokość IME), uważamy, że IME jest wyższy:

    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;
}

próg wysokości 100 to odgadnięta minimalna wysokość edytora IME.

Działa to zarówno dla adjustPan, jak i adjustResize.


2
Już mam pociągnąć włosy !! Uratowałeś mi włosy;)
Vijay Singh Chouhan

1
To jedyna dobra odpowiedź tutaj, działa idealnie na miękkiej klawiaturze, dziękuję
Z3nk

12

Rozwiązanie Nebojsa prawie dla mnie zadziałało. Kiedy kliknąłem wewnątrz wielowierszowego tekstu EditText, wiedziałem, że klawiatura jest wyświetlana, ale kiedy zacząłem pisać wewnątrz EditText, faktyczna wysokość i proponowana wysokość były nadal takie same, więc nie wiedziałam, że klawiatura jest nadal wyświetlana. Wprowadziłem niewielką modyfikację, aby zapisać maksymalną wysokość i działa dobrze. Oto poprawiona podklasa:

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

Nie jestem pewien, czy ktoś to opublikuje. Znaleziono rozwiązanie to proste w użyciu! . SoftKeyboard klasa jest na gist.github.com . Ale podczas gdy wywołanie zwrotne / ukrywanie klawiatury na klawiaturze wymaga obsługi, aby poprawnie wykonywać czynności w interfejsie użytkownika:

/*
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
                    ...
                }
            });

    }   
});

oto Git, aby zdobyć SoftkeyBoard „ gist.github.com/felHR85/…
douarbou

9

Rozwiązuję ten problem, nadpisując onKeyPreIme (int keyCode, KeyEvent event) w moim niestandardowym EditText.

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

Jak używać go we fragmencie lub aktywności? @Qbait
Maulik Dodia

To nie działa, można go wywołać tylko wtedy, gdy opuszczę stronę w moim przypadku.
DysaniazzZ

jest to metoda z EditText, zobacz tę odpowiedź: stackoverflow.com/a/5993196/2093236
Dmide

4

Mam do tego jakiś hack. Chociaż nie wydaje się być sposób, aby wykryć, kiedy klawiatura wykazało lub ukryty, to może faktycznie wykryć, kiedy jest o być pokazane lub ukryte poprzez ustawienie OnFocusChangeListenerna EditTexttym, że słuchasz.

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
        }
    });

UWAGA: Jedną z rzeczy, o których należy pamiętać podczas tego włamania, jest to, że oddzwanianie jest uruchamiane natychmiast po EditTextuzyskaniu lub utracie ostrości. To faktycznie uruchomi się tuż przed wyświetleniem lub ukrywaniem się miękkiej klawiatury. Najlepszym sposobem, w jaki znalazłem coś po pokazaniu lub ukryciu klawiatury, jest użycie a Handleri opóźnienie czegoś ~ 400 ms, na przykład:

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
W przeciwnym razie to nie działa. OnFocusChangeListenermówi tylko, czy EditTextma fokus po zmianie stanu. Ale IMEmoże być ukryty, gdy EditTextjest skupiony, jak wykryć tę sprawę?
DysaniazzZ


2

Rozwiązałem problem z kodowaniem wstecznym jednowierszowego tekstu.

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
Nie działa na Androidzie: windowSoftInputMode = "adjustPan". Chciałem, żeby mój ekran nie kurczył się po pojawieniu się miękkiej klawiatury. Czy możesz podać jakąkolwiek poprawkę, aby działała nawet dla AdjustPan
Shirish Herwade

Kiedy funkcja ukryj / pokaż, wówczas ta metoda nasłuchiwania wywołuje się dwa lub trzy razy. Nie wiem, na czym dokładnie polega problem.
Jagveer Singh Rajput

2

Możesz także sprawdzić, czy pierwsze wyściółki dla dzieci DecorView są dolne. Zostanie ustawiona na wartość niezerową, gdy zostanie wyświetlona klawiatura.

@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

Ukryj | Pokaż zdarzenia na klawiaturze można odsłuchiwać poprzez prosty hack w 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
                }
            }
        });

Tutaj ActivityRootView to widok główny Twojej aktywności.


mój wzrostDiff to 160 na starcie i 742 z kbd, więc musiałem przedstawić i ustawić initialHeightDiff na starcie
djdance

0

za pomocą viewTreeObserver, aby łatwo uzyskać zdarzenie z klawiatury.

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 to twój widokedit_text.parent


-2

Odpowiedź Nebojsa Tomcic nie była dla mnie pomocna. Mam RelativeLayoutz nim TextViewi AutoCompleteTextViewwewnątrz. Muszę przewinąć w TextViewdół, gdy klawiatura jest wyświetlana i gdy jest ukryta. Aby to osiągnąć, przesłoniłem onLayoutmetodę i działa ona dla mnie dobrze.

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);
        }
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.