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.

Pedro Loureiro
źródło
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.

amalBit
źródło
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...

}
Nebojsa Tomcic
źródło
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.

Hiren Patel
źródło
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”

Stefan
źródło
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.

alexhilton
źródło
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);
    }
}
Gary Foster
źródło
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
                    ...
                }
            });

    }   
});
Robert
źródło
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
    }
}
qbait
źródło
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);
        }
    });
poisondminds
źródło
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);
    }
}
użytkownik2462737
źródło
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);
}
MatrixDev
źródło
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.

Varun Verma
źródło
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

Geet Thakur
źródło
-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);
        }
    }
}
Tryb
źródło