Bagaimana cara memeriksa visibilitas keyboard perangkat lunak di Android?

516

Saya perlu melakukan hal yang sangat sederhana - cari tahu apakah keyboard perangkat lunak ditampilkan. Apakah ini mungkin di Android?

fhucho
sumber
9
Meskipun jawaban Reuben Scratton baik, tampaknya rusak di tablet. Saya mengganti cek diff> 128 dengan diff> screenHeight / 3.
kingston
2
Jawaban Reuben Scratton baik tetapi saya meminta penyesuaian KaChi untuk benar-benar menggunakannya.
Cullan
1
Mengapa Google tidak membuat metode bawaan standar berfungsi untuk semua aplikasi papan ketik?
Buah
4
Masih bersaudara saya, bahwa ini bukan fungsi sistem ...
longi
1
Ini benar-benar gila bahwa API ini masih hilang 10 tahun kemudian . Sangat senang saya pindah dari Android.
Reuben Scratton

Jawaban:

674

NEW ANSWER ditambahkan 25 Jan 2012

Sejak menulis jawaban di bawah ini, seseorang memberi petunjuk kepada saya tentang keberadaan ViewTreeObserver dan teman-teman, API yang telah mengintai di SDK sejak versi 1.

Daripada memerlukan jenis Tata Letak khusus, solusi yang jauh lebih sederhana adalah memberikan tampilan root aktivitas Anda ID yang dikenal, katakan @+id/activityRoot, kaitkan GlobalLayoutListener ke ViewTreeObserver, dan dari sana hitung perbedaan ukuran antara root tampilan aktivitas Anda dan ukuran jendela:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
        if (heightDiff > dpToPx(this, 200)) { // if more than 200 dp, it's probably a keyboard...
            // ... do something here
        }
     }
});

Menggunakan utilitas seperti:

public static float dpToPx(Context context, float valueInDp) {
    DisplayMetrics metrics = context.getResources().getDisplayMetrics();
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
}

Mudah!

Catatan: Aplikasi Anda harus mengatur tanda ini di Android Manifest android:windowSoftInputMode="adjustResize"jika tidak, solusi di atas tidak akan berfungsi.

JAWABAN ASLI

Ya itu mungkin, tapi itu jauh lebih sulit dari yang seharusnya.

Jika saya perlu peduli kapan keyboard muncul dan menghilang (yang cukup sering) maka apa yang saya lakukan adalah menyesuaikan kelas tata letak tingkat atas saya menjadi yang ditimpa onMeasure(). Logika dasar adalah bahwa jika tata letak menemukan dirinya mengisi secara signifikan kurang dari total luas jendela, maka keyboard lunak mungkin ditampilkan.

import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.LinearLayout;

/*
 * LinearLayoutThatDetectsSoftKeyboard - a variant of LinearLayout that can detect when 
 * the soft keyboard is shown and hidden (something Android can't tell you, weirdly). 
 */

public class LinearLayoutThatDetectsSoftKeyboard extends LinearLayout {

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

    public interface Listener {
        public void onSoftKeyboardShown(boolean isShowing);
    }
    private Listener listener;
    public void setListener(Listener listener) {
        this.listener = listener;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = MeasureSpec.getSize(heightMeasureSpec);
        Activity activity = (Activity)getContext();
        Rect rect = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        int statusBarHeight = rect.top;
        int screenHeight = activity.getWindowManager().getDefaultDisplay().getHeight();
        int diff = (screenHeight - statusBarHeight) - height;
        if (listener != null) {
            listener.onSoftKeyboardShown(diff>128); // assume all soft keyboards are at least 128 pixels high
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);       
    }

    }

Kemudian di kelas Aktivitas Anda ...

public class MyActivity extends Activity implements LinearLayoutThatDetectsSoftKeyboard.Listener {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        LinearLayoutThatDetectsSoftKeyboard mainLayout = (LinearLayoutThatDetectsSoftKeyboard)findViewById(R.id.main);
        mainLayout.setListener(this);
        ...
    }


    @Override
    public void onSoftKeyboardShown(boolean isShowing) {
        // do whatever you need to do here
    }

    ...
}
Ruben Scratton
sumber
62
Ini tidak berfungsi untuk saya sampai saya menyadari bahwa Anda harus menetapkan atribut berikut pada aktivitas Anda: android: windowSoftInputMode = "AdjustResize"
ajh158
9
Tampaknya melakukan trik. Juga, jika Anda tidak tahu ID tampilan root, berikut adalah cara Anda bisa mendapatkan pandangan:((ViewGroup) findViewById(android.R.id.content)).getChildAt(0)
Goldsmith
8
Jika Anda mencoba ini menggunakan tampilan root yang sebenarnya ( android.R.id.content) Anda akan dapat lebih percaya diri mengatakan bahwa Systemalih - alih aplikasi Anda adalah entitas yang mengubah tingginya. Akan jauh lebih aman bagi tim Android untuk memberi kami istirahat dan beri tahu kami setidaknya hal-hal dasar tentang input SoftKeyboard.
Graeme
14
Waspadalah yang heightDiffakan selalu mencakup ketinggian bilah tindakan. Di jawaban baru yang telah diabaikan oleh pengujian apakah ketinggian itu lebih besar dari beberapa konstanta, tetapi 100 piksel tidak cukup untuk perangkat xxhdpi seperti Nexus 4. Pertimbangkan untuk mengubah nilai itu ke DP jika Anda benar-benar ingin menggunakan pekerjaan yang peretasan ini- sekitar.
Paul Lammertsma
8
Perhatikan: tidak bekerja dengan WindowManager.LayoutParams.FLAG_FULLSCREEN dan dengan tema layar penuh.
VAV
303

Jadi semoga ini membantu seseorang keluar.

Jawaban baru yang diberikan Reuben Scratton sangat bagus dan sangat efisien, tetapi itu benar-benar hanya berfungsi jika Anda mengatur windowSoftInputMode Anda untuk menyesuaikanResize. Jika Anda mengaturnya ke AdjustPan, masih tidak mungkin mendeteksi apakah keyboard terlihat menggunakan potongan kode-nya atau tidak. Untuk mengatasinya, saya membuat modifikasi kecil pada kode di atas.

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
    Rect r = new Rect();
    //r will be populated with the coordinates of your view that area still visible.
    activityRootView.getWindowVisibleDisplayFrame(r);

    int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
    if (heightDiff > 0.25*activityRootView.getRootView().getHeight()) { // if more than 25% of the screen, its probably a keyboard...
        ... do something here
    }
 }
}); 
Kachi
sumber
1
Ini yang bekerja untuk saya. Saya mencoba untuk mendeteksi keadaan keyboard dari kebiasaan yang TwoDScrollerViewmirip dengan stackoverflow.com/a/5224088/530513 meskipun dengan zooming juga. Anak itu tidak sederhana ImageViewtetapi tata letak kustom (meluas RelativeLayout) tetapi tidak dapat mendeteksi keyboard menggunakan solusi yang disarankan meskipun pengaturan android:windowSoftInputMode="adjustResize". Terima kasih!
David O'Meara
1
Terima kasih terima kasih terima kasih! AdjustResize tidak layak untuk aplikasi saya dan solusi Anda bekerja dengan sempurna.
dwemthy
1
Bekerja dengan ActionBardan ActionBarSherlock. Terima kasih banyak! Omong-omong, ada metode r.height():)
Dmitry Zaytsev
1
Saya akan melampirkan hadiah di sini dalam waktu 23 jam untuk menandai jawaban ini.
Dmitry Zaytsev
9
heightDiff > root.getRootView().getHeight() / 4adalah nilai yang baik untuk bekerja dengan perangkat resolusi tinggi. 100px adalah singkatnya. di Nexus 5 dengan 1080x1920 res, 1920 - (996-75)>? 100 = 999 1920 - (1776-75)>? 100 = 219 // keyboard menyala di galaxy s2 dengan 480x800 res, 800 - (800-38)>? 100 = 38 800 - (410-38)>? 100 = 428 // keyboard naik jadi, angka ajaib 100px tidak cukup baik.
Flask_KR
55

Sudah selamanya dalam hal komputer tetapi pertanyaan ini masih sangat relevan!

Jadi saya telah mengambil jawaban di atas dan telah menggabungkan dan memperbaikinya sedikit ...

public interface OnKeyboardVisibilityListener {


    void onVisibilityChanged(boolean visible);
}

public final void setKeyboardListener(final OnKeyboardVisibilityListener listener) {
    final View activityRootView = ((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0);

    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        private boolean wasOpened;

        private final int DefaultKeyboardDP = 100;

        // From @nathanielwolf answer...  Lollipop includes button bar in the root. Add height of button bar (48dp) to maxDiff
        private final int EstimatedKeyboardDP = DefaultKeyboardDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);

        private final Rect r = new Rect();

        @Override
        public void onGlobalLayout() {
            // Convert the dp to pixels.
            int estimatedKeyboardHeight = (int) TypedValue
                    .applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, activityRootView.getResources().getDisplayMetrics());

            // Conclude whether the keyboard is shown or not.
            activityRootView.getWindowVisibleDisplayFrame(r);
            int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
            boolean isShown = heightDiff >= estimatedKeyboardHeight;

            if (isShown == wasOpened) {
                Log.d("Keyboard state", "Ignoring global layout change...");
                return;
            }

            wasOpened = isShown;
            listener.onVisibilityChanged(isShown);
        }
    });
}

Bekerja untuk saya :)

CATATAN: Jika Anda melihat bahwa DefaultKeyboardDP tidak sesuai dengan perangkat Anda bermain dengan nilai dan memposting komentar untuk semua orang untuk mengetahui apa yang seharusnya menjadi nilai ... akhirnya kami akan mendapatkan nilai yang benar untuk semua perangkat!

Untuk detail lebih lanjut, lihat implementasi di Cyborg

TacB0sS
sumber
2
+1 Terima kasih banyak !! Saya mencoba jawaban yang lain, tetapi tidak berhasil. Lalu aku menemukan milikmu dan itu berfungsi seperti pesona. Kode yang luar biasa! : D
Kevin van Mierlo
ini hanya berfungsi jika Anda menambahkan: android: windowSoftInputMode = "stateHidden | adjustPan" atau android: windowSoftInputMode = "stateHidden | adjustResize" Terima kasih !!!!
Lena Bru
Apakah kamu yakin Jika server memori, saya mendapatkan acara dengan benar juga ketika android: windowSoftInputMode memiliki nilai default ... satu-satunya hal yang tidak berhasil adalah perilaku layar, jadi saya secara manual menyusutnya ...
TacB0sS
2
koreksi kecil: int final pribadi EstimatedKeyboardDP = DefaultKeyboardDP + (Build.VERSION.SDK_INT> = Build.VERSION_CODES.LOLLIPOP? 48: 0);
binaryKarmic
2
Luar biasa !! Tidak peduli "windowSoftInputMode" disetel ke "AdjustPan" / "AdjustResize" / "AdjustPan | stateHidden" / "AdjustResize | stateHidden", atau bahkan tanpa opsi ini, ia selalu berfungsi! Diuji pada XiaoMi 8.
Zhou Hongbo
52

Maaf atas jawaban yang terlambat, tetapi saya telah membuat kelas pembantu kecil untuk menangani acara buka / tutup dengan memberi tahu pendengar dan hal-hal bermanfaat lainnya, mungkin seseorang akan merasa terbantu:

import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver;

import java.util.LinkedList;
import java.util.List;

public class SoftKeyboardStateWatcher implements ViewTreeObserver.OnGlobalLayoutListener {

    public interface SoftKeyboardStateListener {
        void onSoftKeyboardOpened(int keyboardHeightInPx);
        void onSoftKeyboardClosed();
    }

    private final List<SoftKeyboardStateListener> listeners = new LinkedList<SoftKeyboardStateListener>();
    private final View activityRootView;
    private int        lastSoftKeyboardHeightInPx;
    private boolean    isSoftKeyboardOpened;

    public SoftKeyboardStateWatcher(View activityRootView) {
        this(activityRootView, false);
    }

    public SoftKeyboardStateWatcher(View activityRootView, boolean isSoftKeyboardOpened) {
        this.activityRootView     = activityRootView;
        this.isSoftKeyboardOpened = isSoftKeyboardOpened;
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    public void onGlobalLayout() {
        final Rect r = new Rect();
        //r will be populated with the coordinates of your view that area still visible.
        activityRootView.getWindowVisibleDisplayFrame(r);

        final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
        if (!isSoftKeyboardOpened && heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
            isSoftKeyboardOpened = true;
            notifyOnSoftKeyboardOpened(heightDiff);
        } else if (isSoftKeyboardOpened && heightDiff < 100) {
            isSoftKeyboardOpened = false;
            notifyOnSoftKeyboardClosed();
        }
    }

    public void setIsSoftKeyboardOpened(boolean isSoftKeyboardOpened) {
        this.isSoftKeyboardOpened = isSoftKeyboardOpened;
    }

    public boolean isSoftKeyboardOpened() {
        return isSoftKeyboardOpened;
    }

    /**
     * Default value is zero {@code 0}.
     *
     * @return last saved keyboard height in px
     */
    public int getLastSoftKeyboardHeightInPx() {
        return lastSoftKeyboardHeightInPx;
    }

    public void addSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
        listeners.add(listener);
    }

    public void removeSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
        listeners.remove(listener);
    }

    private void notifyOnSoftKeyboardOpened(int keyboardHeightInPx) {
        this.lastSoftKeyboardHeightInPx = keyboardHeightInPx;

        for (SoftKeyboardStateListener listener : listeners) {
            if (listener != null) {
                listener.onSoftKeyboardOpened(keyboardHeightInPx);
            }
        }
    }

    private void notifyOnSoftKeyboardClosed() {
        for (SoftKeyboardStateListener listener : listeners) {
            if (listener != null) {
                listener.onSoftKeyboardClosed();
            }
        }
    }
}

Contoh penggunaan:

final SoftKeyboardStateWatcher softKeyboardStateWatcher 
    = new SoftKeyboardStateWatcher(findViewById(R.id.activity_main_layout);

// Add listener
softKeyboardStateWatcher.addSoftKeyboardStateListener(...);
// then just handle callbacks
Artem Zinnatullin
sumber
2
Kelas tidak sedikit tetapi implementasinya pasti :). Terima kasih, saya akan coba ini :)
Atul O Holic
1
Ping saya jika Anda punya masalah dengan itu :) Saya berhasil menggunakannya dalam 2 proyek
Artem Zinnatullin
Setelah mencoba banyak contoh di atas dan mengalami masalah kecil, yang ini adalah yang paling cocok untuk saya di banyak perangkat yang berbeda (termasuk xxhdpi). Plus itu di kelasnya sendiri yang dapat digunakan kembali. Saya mengkonversikannya untuk digunakan dalam mono droid.
kheit
Beberapa keyboard, kadang-kadang, memiliki baris tambahan tombol khusus di atas (misalnya, kata-kata yang diprediksi). Ini tampaknya bukan bagian dari keyboard itu sendiri, karena menggunakan getLastKeyboardHeightInPx()tidak termasuk ketinggian baris itu. Apakah Anda tahu cara untuk memperhitungkannya juga?
Ygesher
Ini hanya berfungsi jika Anda siap berkompromi dengan perubahan tinggi Layout saat keyboard muncul. Baik?
M. Usman Khan
34

Beberapa peningkatan untuk menghindari kesalahan mendeteksi visibilitas keyboard lunak pada perangkat kepadatan tinggi:

  1. Ambang batas perbedaan ketinggian harus didefinisikan sebagai 128 dp , bukan 128 piksel .
    Lihat dokumen desain Google tentang Metrik dan Kotak , 48 dp adalah ukuran yang nyaman untuk objek sentuh dan 32 dp minimum untuk tombol. Keyboard lunak generik harus menyertakan 4 baris tombol, jadi tinggi minimum keyboard harus: 32 dp * 4 = 128 dp , itu berarti ukuran ambang batas harus ditransfer ke piksel dengan mengalikan kepadatan perangkat. Untuk perangkat xxxhdpi (kepadatan 4), ambang tinggi keyboard lunak harus 128 * 4 = 512 piksel.

  2. Perbedaan tinggi antara tampilan root dan area yang terlihat:
    tinggi tampilan root - tinggi status bar - tinggi bingkai terlihat = tampilan dasar root - bawah bingkai terlihat, karena tinggi status bar sama dengan bagian atas bingkai terlihat pandangan root.

    private final String TAG = "TextEditor";
    private TextView mTextEditor;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_editor);
        mTextEditor = (TextView) findViewById(R.id.text_editor);
        mTextEditor.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                isKeyboardShown(mTextEditor.getRootView());
            }
        });
    }
    
    private boolean isKeyboardShown(View rootView) {
        /* 128dp = 32dp * 4, minimum button height 32dp and generic 4 rows soft keyboard */
        final int SOFT_KEYBOARD_HEIGHT_DP_THRESHOLD = 128;
    
        Rect r = new Rect();
        rootView.getWindowVisibleDisplayFrame(r);
        DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
        /* heightDiff = rootView height - status bar height (r.top) - visible frame height (r.bottom - r.top) */
        int heightDiff = rootView.getBottom() - r.bottom;
        /* Threshold size: dp to pixels, multiply with display density */
        boolean isKeyboardShown = heightDiff > SOFT_KEYBOARD_HEIGHT_DP_THRESHOLD * dm.density;
    
        Log.d(TAG, "isKeyboardShown ? " + isKeyboardShown + ", heightDiff:" + heightDiff + ", density:" + dm.density
                + "root view height:" + rootView.getHeight() + ", rect:" + r);
    
        return isKeyboardShown;
    }
Orchard Cafe
sumber
4
Ini layak menjadi jawaban yang diterima. Mengabaikan kepadatan memberi saya hasil yang sangat berbeda pada perangkat dengan faktor bentuk berbeda tetapi ukuran piksel yang sama. Terima kasih!
Ginger McMurray
2
Terima kasih ... ini adalah kondisi yang lebih baik!
TacB0sS
1
Luar biasa. Metode isKeyboardShown () adalah yang kita butuhkan. Terima kasih
Danylo Volokh
Ini bekerja pada tahun 2020 juga. Jawaban sempurna. Saya mencoba semua kode tetapi ini bekerja dengan sempurna.
Mitesh Jain
8

Saya menggunakan sedikit waktu untuk mencari tahu ini ... Saya menjalankannya beberapa CastExceptions, tetapi menemukan bahwa Anda dapat menggantikan Anda LinearLayout di layout.xml dengan nama kelas.

Seperti ini:

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/llMaster">

<com.ourshoppingnote.RelativeLayoutThatDetectsSoftKeyboard android:background="@drawable/metal_background"
    android:layout_width="fill_parent" android:layout_height="fill_parent"
    android:id="@+id/rlMaster" >
    <LinearLayout android:layout_width="fill_parent"
        android:layout_height="1dip" android:background="@drawable/line"></LinearLayout>

          ....

</com.ourshoppingnote.RelativeLayoutThatDetectsSoftKeyboard>    


</LinearLayout>

Dengan begitu Anda tidak mengalami masalah pemeran.

... dan jika Anda tidak ingin melakukan ini di setiap halaman, saya sarankan Anda menggunakan "MasterPage di Android". Lihat tautan di sini: http://jnastase.alner.net/archive/2011/01/08/ldquomaster-pagesrdquo-in-android.aspx

Janus Kamp Hansen
sumber
Wow hati-hati tentang menempelkan ini ke XML Anda jika Anda tidak memiliki paket / nama kelas yang sama. Eclipse hanya memutuskan untuk membekukan dan Anda harus mematikannya. Produk yang sangat profesional. / s
Spencer Ruport
5
@SpencerRuport, itu sebabnya gratis.
Cody
@DoctorOreo - dapatkan IntelliJ. Ini gratis dan tidak payah.
Markus
@ Mark - beberapa bulan setelah saya memposting ini saya memang mencoba IntelliJ. Jauh lebih baik, IMO, daripada Eclipse. Semua produk mereka (sebagian besar) saya pikir sangat bagus. Saya bahkan sudah membeli beberapa.
Cody
Maaf telah menghidupkan kembali utas komentar yang lama. Saya senang Anda menggunakannya dan menikmatinya. Saya suka menggunakan IntelliJ serta AppCode untuk iOS dan PyCharm untuk kerja Python. Bersulang!
Markus
5

Idenya adalah, jika Anda perlu menyembunyikan keyboard Anda dan memeriksa status input lunak pada saat yang sama, gunakan solusi berikut:

public boolean hideSoftInput() {
    InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
    return imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0);
}

Metode ini mengembalikan true jika keyboard ditampilkan sebelum bersembunyi.

George Maisuradze
sumber
Ini yang berfungsi tanpa menggunakan ketinggian dan semuanya ... Terima kasih ... kamu menghemat waktuku ...
Vji
4

Saya menemukan bahwa kombinasi metode @ Reuben_Scratton bersama dengan metode @ Yogesh tampaknya bekerja paling baik. Menggabungkan metode mereka akan menghasilkan sesuatu seperti ini:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
  @Override
  public void onGlobalLayout() {
    if (getResources().getConfiguration().keyboardHidden == Configuration.KEYBOARDHIDDEN_NO) { // Check if keyboard is not hidden
       // ... do something here
    }
  }
});
cbradley
sumber
selalu Konfigurasi.KEYBOARDHIDDEN_NO.
fantouch
4

Anda dapat mengamati persembunyian papan tombol dengan menggunakan decorView aktivitas.

public final class SoftKeyboardUtil {
    public static final String TAG = "SoftKeyboardUtil";
    public static void observeSoftKeyBoard(Activity activity , final OnSoftKeyBoardHideListener listener){
        final View decorView = activity.getWindow().getDecorView();
        decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Rect rect = new Rect();
                decorView.getWindowVisibleDisplayFrame(rect);
                int displayHight = rect.bottom - rect.top;
                int hight = decorView.getHeight();
                boolean hide = (double)displayHight / hight > 0.8 ;
                if(Log.isLoggable(TAG, Log.DEBUG)){
                    Log.d(TAG ,"DecorView display hight = "+displayHight);
                    Log.d(TAG ,"DecorView hight = "+ hight);
                    Log.d(TAG, "softkeyboard visible = " + !hide);
                }

                listener.onSoftKeyBoardVisible(!hide);

            }
        });
    }



    public interface OnSoftKeyBoardHideListener{
        void onSoftKeyBoardVisible(boolean visible);
    }
}
Zebulon Li
sumber
4

Alih-alih mengasumsikan perbedaan coding saya melakukan sesuatu seperti ini, karena saya tidak punya opsi menu di aplikasi saya.

final View root= findViewById(R.id.myrootview); 
root.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
    public void onGlobalLayout() {
        int heightDiff = root.getRootView().getHeight() - root.getHeight();

        Rect rectgle= new Rect();
        Window window= getWindow();
        window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
        int contentViewTop=                     
          window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
        if(heightDiff <= contentViewTop){
            //Soft KeyBoard Hidden
        }else{
            //Soft KeyBoard Shown
        }
     }
});
Santhosh Shettigar
sumber
Ini tidak berfungsi untuk android: windowSoftInputMode = "AdjustPan". Saya ingin agar layar saya tidak menyusut setelah keyboard lunak muncul. Bisakah Anda memberi tahu perbaikan apa pun agar itu berfungsi bahkan untuk AdjustPan
Shirish Herwade
4

Ada juga solusi dengan insets sistem, tetapi hanya bekerja dengan API >= 21( Android L). Katakan sudah BottomNavigationView, yang merupakan anak dari LinearLayoutdan Anda perlu menyembunyikannya saat keyboard ditampilkan:

> LinearLayout
  > ContentView
  > BottomNavigationView

Yang perlu Anda lakukan adalah memperluas LinearLayoutsedemikian rupa:

public class KeyboardAwareLinearLayout extends LinearLayout {
    public KeyboardAwareLinearLayout(Context context) {
        super(context);
    }

    public KeyboardAwareLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public KeyboardAwareLinearLayout(Context context,
                                     @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

    @Override
    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        int childCount = getChildCount();
        for (int index = 0; index < childCount; index++) {
            View view = getChildAt(index);
            if (view instanceof BottomNavigationView) {
                int bottom = insets.getSystemWindowInsetBottom();
                if (bottom >= ViewUtils.dpToPx(200)) {
                    // keyboard is shown
                    view.setVisibility(GONE);
                } else {
                    // keyboard is hidden
                    view.setVisibility(VISIBLE);
                }
            }
        }
        return insets;
    }
}

Idenya adalah ketika keyboard ditampilkan, insets sistem diubah dengan .bottomnilai yang cukup besar .

nikis
sumber
4

Ada metode tersembunyi yang bisa membantu untuk ini InputMethodManager.getInputMethodWindowVisibleHeight,. Tapi saya tidak tahu mengapa itu disembunyikan.

import android.content.Context
import android.os.Handler
import android.view.inputmethod.InputMethodManager

class SoftKeyboardStateWatcher(private val ctx: Context) {
  companion object {
    private const val DELAY = 10L
  }

  private val handler = Handler()
  private var isSoftKeyboardOpened: Boolean = false

  private val height: Int
    get() {
      val imm = ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
      val method = imm.javaClass.getMethod("getInputMethodWindowVisibleHeight")
      method.isAccessible = true
      return method.invoke(imm) as Int
    }

  private val task: Runnable by lazy {
    Runnable {
      start()
      if (!isSoftKeyboardOpened && height > 0) {
        isSoftKeyboardOpened = true
        notifyOnSoftKeyboardOpened(height)
      } else if (isSoftKeyboardOpened && height == 0) {
        isSoftKeyboardOpened = false
        notifyOnSoftKeyboardClosed()
      }
    }
  }

  var listener: SoftKeyboardStateListener? = null

  interface SoftKeyboardStateListener {
    fun onSoftKeyboardOpened(keyboardHeightInPx: Int)
    fun onSoftKeyboardClosed()
  }

  fun start() {
    handler.postDelayed(task, DELAY)
  }

  fun stop() {
    handler.postDelayed({
      if (!isSoftKeyboardOpened) handler.removeCallbacks(task)
    }, DELAY * 10)
  }

  private fun notifyOnSoftKeyboardOpened(keyboardHeightInPx: Int) {
    listener?.onSoftKeyboardOpened(keyboardHeightInPx)
  }

  private fun notifyOnSoftKeyboardClosed() {
    listener?.onSoftKeyboardClosed()
  }
}
Kevin Du
sumber
Jika seseorang membutuhkan ini - ini berfungsi di Xamarin juga, nama metode ini persis sama dan perlu diakses dengan cara yang sama - melalui properti Class pada InputMethodManager.
Konstantin Severy
Berhati-hatilah dalam menggunakan ini, ini adalah API yang tidak didukung (tersembunyi karena suatu alasan) dan untuk pemula itu tidak bekerja pada KitKat.
Daniele Ricci
3

Tak satu pun dari solusi ini yang akan berfungsi untuk Lollipop seperti apa adanya. Di LollipopactivityRootView.getRootView().getHeight() termasuk ketinggian bilah tombol, sedangkan mengukur tampilan tidak. Saya telah mengadaptasi solusi terbaik / paling sederhana di atas untuk bekerja dengan Lollipop.

    final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
  @Override
  public void onGlobalLayout() {
    Rect r = new Rect();
    //r will be populated with the coordinates of your view that area still visible.
    activityRootView.getWindowVisibleDisplayFrame(r);

    int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
    Resources res = getResources();
    // The status bar is 25dp, use 50dp for assurance
    float maxDiff =
        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, res.getDisplayMetrics());

    //Lollipop includes button bar in the root. Add height of button bar (48dp) to maxDiff
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      float buttonBarHeight =
          TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, res.getDisplayMetrics());
      maxDiff += buttonBarHeight;
    }
    if (heightDiff > maxDiff) { // if more than 100 pixels, its probably a keyboard...
      ...do something here
    }
  }
});
nathanielwolf
sumber
Mengapa solusi serupa dari stackoverflow.com/a/18992807/2914140 bekerja untuk Anda dan bagaimana perbedaan solusi Anda?
CoolMind
3

Saya baru saja menemukan bug saat menggunakan sebagian besar solusi di atas yang menyarankan menambahkan nomor tetap.

S4 memiliki dpi tinggi yang mengakibatkan ketinggian bilah navigasi menjadi 100px sehingga aplikasi saya berpikir bahwa keyboard terbuka sepanjang waktu.

Jadi dengan semua ponsel beresolusi tinggi yang baru dirilis saya percaya menggunakan nilai kode keras bukan ide yang baik untuk jangka panjang.

Pendekatan yang lebih baik yang saya temukan setelah beberapa pengujian pada berbagai layar dan perangkat adalah menggunakan persentase. Dapatkan perbedaan antara decorView dan konten aplikasi Anda dan kemudian periksa berapa persentase perbedaan itu. Dari statistik yang saya dapatkan, sebagian besar nav bar (terlepas dari ukuran, resolusi dll.) Akan mengambil antara 3% hingga 5% dari layar. Di mana seolah-olah keyboard terbuka itu mengambil antara 47% hingga 55% dari layar.

Sebagai kesimpulan solusi saya adalah untuk memeriksa apakah diff lebih dari 10% maka saya menganggap itu keyboard terbuka.

N Jay
sumber
3

Saya menggunakan sedikit varian dari jawaban Reuban, yang terbukti lebih membantu dalam keadaan tertentu, terutama dengan perangkat resolusi tinggi.

final View activityRootView = findViewById(android.R.id.content);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(
        new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int heightView = activityRootView.getHeight();
                int widthView = activityRootView.getWidth();
                if (1.0 * widthView / heightView > 3) {
                    //Make changes for Keyboard not visible
                } else {
                    //Make changes for keyboard visible
                }
            }
        });
PearsonArtPhoto
sumber
apa R.id.activityRoot ini
Ranjith
2
alih-alih membuat dan menggunakan R.id.activityRoot, Anda cukup menggunakan android.R.id.contentyang persis Anda butuhkan.
Marcin Orlowski
3

Sudah selamanya dalam hal komputer tetapi pertanyaan ini masih sangat relevan! Jadi saya telah mengambil jawaban di atas dan telah menggabungkan dan memperbaikinya sedikit ...

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

public final void setKeyboardListener(final OnKeyboardVisibilityListener listener) {
    final View activityRootView = ((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        private boolean wasOpened;

    private final Rect r = new Rect();

        @Override
        public void onGlobalLayout() {
            activityRootView.getWindowVisibleDisplayFrame(r);

            int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
            boolean isOpen = heightDiff > 100;
            if (isOpen == wasOpened) {
                logDebug("Ignoring global layout change...");
                return;
            }

            wasOpened = isOpen;
            listener.onVisibilityChanged(isOpen);
        }
    });
}

Ini bekerja untuk saya.

Roselyn Soffer
sumber
3

Coba ini:

final View activityRootView = getWindow().getDecorView().getRootView();
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        Rect r = new Rect();
        //r will be populated with the coordinates of your view that area still visible.
        activityRootView.getWindowVisibleDisplayFrame(r);

        int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
        if (heightDiff < activityRootView.getRootView().getHeight() / 4 ) { // if more than 100 pixels, its probably a keyboard...
             // ... do something here ... \\
        }
    }
});
Ofek Ashery
sumber
2

Jawaban saya pada dasarnya sama dengan jawaban Kachi, tetapi saya membungkusnya menjadi kelas pembantu yang bagus untuk membersihkan cara penggunaannya di seluruh aplikasi saya.

import android.app.Activity;
import android.app.Fragment;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;

/**
 * Detects Keyboard Status changes and fires events only once for each change
 */
public class KeyboardStatusDetector {
    KeyboardVisibilityListener visibilityListener;

    boolean keyboardVisible = false;

    public void registerFragment(Fragment f) {
        registerView(f.getView());
    }

    public void registerActivity(Activity a) {
        registerView(a.getWindow().getDecorView().findViewById(android.R.id.content));
    }

    public KeyboardStatusDetector registerView(final View v) {
        v.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Rect r = new Rect();
                v.getWindowVisibleDisplayFrame(r);

                int heightDiff = v.getRootView().getHeight() - (r.bottom - r.top);
                if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
                    /** Check this variable to debounce layout events */
                    if(!keyboardVisible) {
                        keyboardVisible = true;
                        if(visibilityListener != null) visibilityListener.onVisibilityChanged(true);
                    }
                } else {
                    if(keyboardVisible) {
                        keyboardVisible = false;
                        if(visibilityListener != null) visibilityListener.onVisibilityChanged(false);
                    }
                }
            }
        });

        return this;
    }

    public KeyboardStatusDetector setVisibilityListener(KeyboardVisibilityListener listener) {
        visibilityListener = listener;
        return this;
    }

    public static interface KeyboardVisibilityListener {
        public void onVisibilityChanged(boolean keyboardVisible);
    }
}

Anda dapat menggunakan ini untuk mendeteksi perubahan keyboard di mana saja di seluruh aplikasi seperti ini:

    new KeyboardStatusDetector()
            .registerFragment(fragment)  //register to a fragment 
            .registerActivity(activity)  //or register to an activity
            .registerView(view)          //or register to a view
            .setVisibilityListener(new KeyboardVisibilityListener() {
                @Override
                public void onVisibilityChanged(boolean keyboardVisible) {
                    if(keyboardVisible) {
                       //Do stuff for keyboard visible
                    }else {
                       //Do stuff for keyboard hidden
                    }
                }
            });

Catatan: hanya gunakan salah satu panggilan "daftar". Mereka semua bekerja sama dan hanya ada untuk kenyamanan

pembuat jendela
sumber
2

Anda dapat mencoba ini, bekerja sangat baik untuk saya:

InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);

if (imm.isAcceptingText()) {
    //Software Keyboard was shown..
} else {
    //Software Keyboard was not shown..
}
IRvanFauziE
sumber
1
Jangan gunakan ini, ini mengembalikan true jika keyboard disembunyikan menggunakan kembali tetapi tampilan memiliki fokus, setidaknya pada Marshmallow
Maragues
2
selalu merespons yang terlihat
jose920405
2

Saya mengalami kesulitan mempertahankan keadaan keyboard saat mengubah orientasi fragmen dalam viewpager. Saya tidak yakin mengapa, tetapi sepertinya tidak stabil dan bertindak berbeda dari Kegiatan standar.

Untuk mempertahankan keadaan keyboard dalam hal ini, pertama Anda harus menambahkan android:windowSoftInputMode = "stateUnchanged"keAndroidManifest.xml . Anda mungkin memperhatikan, bahwa ini tidak benar-benar menyelesaikan seluruh masalah - keyboard tidak terbuka untuk saya jika sebelumnya dibuka sebelum perubahan orientasi. Dalam semua kasus lain, perilaku itu tampaknya benar.

Kemudian, kita perlu mengimplementasikan salah satu solusi yang disebutkan di sini. Yang terbersih yang saya temukan adalah milik George Maisuradze - gunakan panggilan balik boolean dari hideSoftInputFromWindow:

InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
return imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0);

Saya menyimpan nilai ini dalam onSaveInstanceStatemetode Fragmen saya dan mengambilnya onCreate. Kemudian, saya secara paksa menunjukkan keyboard onCreateViewjika memiliki nilai true(mengembalikan benar jika keyboard terlihat sebelum benar-benar menyembunyikannya sebelum penghancuran Fragmen).

Quantum Dot
sumber
1

Inilah solusi saya, dan itu berhasil. Alih-alih mencari ukuran piksel cukup periksa bahwa ketinggian tampilan konten telah berubah atau tidak:

// Scroll to the latest comment whenever the keyboard is shown
commentsContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        private int oldHeight;

        @Override
        public void onGlobalLayout() {
            int newHeight = commentsContent.getMeasuredHeight();
            if (newHeight < oldHeight) {
                // Check for the keyboard showing in case the height difference
                // is a result of orientation change
                if (isSoftKeyboardShowing(CommentsActivity.this)) {
                    // Keyboard is showing so scroll to the latest comment
                    scrollToLatestComment();
                }
            }
            oldHeight = newHeight;
        }

    });


public static boolean isSoftKeyboardShowing(Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    return inputMethodManager.isActive();
}
Meanman
sumber
inputMethodManager.isActive () selalu mengembalikan nilai true untuk saya, terlepas dari apakah keyboardnya atau tidak
EionRobb
1

Jangan membuat kode keras. Cara terbaik adalah Anda harus mengubah ukuran tampilan saat di Dapatkan Fokus pada EditText dengan KeyBord Show. Anda dapat melakukan ini menambahkan properti ukuran pada aktivitas ke dalam file Manifest menggunakan kode di bawah ini.

android:windowSoftInputMode="adjustResize"

Rahul Mandaliya
sumber
1

Ada metode langsung untuk mengetahuinya. Dan, itu tidak memerlukan perubahan Tata Letak.
Jadi, ia bekerja dalam mode layar penuh imersif juga.

Kuncinya adalah Anda mencoba menyembunyikan atau menampilkan keyboard lunak dan menangkap hasil percobaan itu.
Jangan panik, ini tidak benar-benar menunjukkan atau menyembunyikan keyboard. Kami hanya meminta negara.

Agar tetap terkini, Anda cukup mengulangi operasi, misalnya setiap 200 milidetik, menggunakan Handler.

Anda menemukan implementasi di sini: https://stackoverflow.com/a/27567074/2525452

Fies
sumber
1

Saya pikir metode ini akan membantu Anda untuk mengetahui apakah keybord terlihat atau tidak.

 public Boolean isSoftKeyBoardVisible(){
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);

    if (imm.isAcceptingText()) {
        Log.d(TAG,"Software Keyboard was shown");
        return true;
    } else {
        Log.d(TAG,"Software Keyboard was not shown");
        return false;
    }

}
John Smith
sumber
selalu merespons yang terlihat
jose920405
0

Jawaban baru Reuben Scratton (menghitung HeightDiff int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();) tidak akan berfungsi dalam aktivitas jika Anda mengatur mode status bar transparan.

jika Anda menggunakan bilah status transparan, activityRootView.getHeight()tidak akan pernah mengubah cuaca keyboard lunak terlihat. itu akan selalu mengembalikan ketinggian aktivitas dan status bar.

Sebagai contoh, Nexus 4, Android 5.0.1, disetel android:windowTranslucentStatuske true, itu akan mengembalikan 1184 selamanya, bahkan saat sedang opend. Jika Anda mengaturandroid:windowTranslucentStatus false, itu akan mengembalikan Tinggi dengan benar, jika saya tidak terlihat, itu mengembalikan 1134 (tidak termasuk status bar)。 tutup ime, itu akan mengembalikan 5xx mungkin (tergantung pada tinggi ime)

Saya tidak tahu apakah ini bug, saya sudah mencoba 4.4.4 dan 5.0.1, hasilnya sama.

Jadi, hingga sekarang, jawaban kedua yang paling disepakati, solusi Kachi akan menjadi cara paling aman untuk menghitung ketinggian waktu. Ini salinannya:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new        OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
activityRootView.getWindowVisibleDisplayFrame(r);

int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
    ... do something here
    }
 }
}); 
Loyea
sumber
0

Metode yang tidak memerlukan LayoutListener

Dalam kasus saya, saya ingin menyimpan keadaan keyboard sebelum mengganti Fragmen saya. Saya memanggil metode hideSoftInputFromWindow dari onSaveInstanceState, yang menutup keyboard dan mengembalikan saya apakah keyboard itu terlihat atau tidak.

Metode ini mudah tetapi dapat mengubah kondisi keyboard Anda.

Gordak
sumber
0

Saya tahu bahwa ini adalah posting lama tetapi saya pikir ini adalah pendekatan paling sederhana yang saya tahu dan perangkat pengujian saya adalah Nexus 5. Saya belum mencobanya di perangkat lain. Berharap bahwa orang lain akan membagikan pendekatan mereka jika mereka menemukan kode saya tidak baik :)

public static boolean isKeyboardShown(Context context, View view) {
        if (context == null || view == null) {
            return false;
        }
        InputMethodManager imm = (InputMethodManager) context
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        return imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 
}

imm.hideSoftInputFromWindow mengembalikan boolean.

Terima kasih,

Fran Ceriu
sumber
0
if (keyopen())
{
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY,0);            
}

Fungsi di atas adalah apa yang saya gunakan untuk memeriksa apakah Keyboard terlihat. Jika ya, maka saya akan menutupnya.

Di bawah ini menunjukkan dua metode yang diperlukan.

Pertama, tentukan ketinggian Window yang bisa diterapkan di onCreate.

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

//  add to onCreate method
    Rect rectgle= new Rect();
    Window window= getWindow();
    window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
    sheight= rectgle.bottom;
//

} 

Kemudian, tambahkan metode boolean yang mendapatkan tinggi Window pada saat itu. Jika tidak cocok dengan aslinya (dengan asumsi Anda tidak mengubahnya sepanjang jalan ...) maka, keyboard terbuka

public boolean keyopen()
{
    Rect rectgle= new Rect();
    Window window= getWindow();
    window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
    int curheight= rectgle.bottom;

    if (curheight!=sheight)
    {
        return true;
    }
    else
    {
        return false;
    }
}

Frotz!

Belboz
sumber
0

Saya tahu seberapa tepat Anda dapat menentukan apakah keyboard disembunyikan atau tidak.

public int getStatusBarHeight() {
    int result = 0;
    int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

public int getNavigationBarHeight() {
    int result = 0;
    int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

public boolean isKeyboardHidden() {
    int delta = mRootView.getRootView().getHeight() - mRootView.getHeight() - getNavigationBarHeight() - getStatusBarHeight()
            - getSupportActionBar().getHeight();
    return delta <= 0;
}

Ini berfungsi untuk tablet. Ketika bilah navigasi ditampilkan secara horizontal.

Valentin Baryshev
sumber