Android監(jiān)聽鍵盤狀態(tài)獲取鍵盤高度的實(shí)現(xiàn)方法
前言
Android暫時還沒有提供一個合適的API來獲取/監(jiān)聽鍵盤的狀態(tài)和高度 , 而我們又經(jīng)常會有這個需求.
最近我的一個項(xiàng)目中,在ugc頁面需要在鍵盤頂部,緊貼著鍵盤顯示一個文字提示,當(dāng)鍵盤消失時就隱藏.
因此,我需要監(jiān)聽軟鍵盤的打開/關(guān)閉 , 以及獲取它的高度.
ViewTreeObserver
A view tree observer is used to register listeners that can be notified of global changes in the view tree. Such global events include, but are not limited to, layout of the whole tree, beginning of the drawing pass, touch mode change…
Android框架提供了一個ViewTreeObserver類,它是一個View視圖樹的觀察者類。ViewTreeObserver類中定義了一系列的公共接口(public interface)。當(dāng)一個View attach到一個窗口上時就會創(chuàng)建一個ViewTreeObserver對象,這樣當(dāng)一個View的視圖樹發(fā)生改變時,就會調(diào)用該對象的某個方法,將事件通知給每個注冊的監(jiān)聽者。
OnGlobalLayoutListener是ViewTreeObserver中定義的眾多接口中的一個,它用來監(jiān)聽一個視圖樹中全局布局的改變或者視圖樹中的某個視圖的可視狀態(tài)的改變。當(dāng)軟鍵盤由隱藏變?yōu)轱@示,或由顯示變?yōu)殡[藏時,都會調(diào)用當(dāng)前布局中所有存在的View中的ViewTreeObserver對象的dispatchOnGlobalLayout()方法,此方法中會遍歷所有已注冊的OnGlobalLayoutListener,執(zhí)行相應(yīng)的回調(diào)方法,將全局布局改變的消息通知給每個注冊的監(jiān)聽者。
view.getViewTreeObserver().addOnGlobalLayoutListener(listener);
getWindowVisibleDisplayFrame
Retrieve the overall visible display size in which the window this view is attached to has been positioned in.
getWindowVisibleDisplayFrame()會返回窗口的可見區(qū)域高度,通過和屏幕高度相減,就可以得到軟鍵盤的高度了。
完整示例代碼
package com.cari.cari.promo.diskon.util;
import android.content.Context;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.TypedValue;
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<>();
private final View activityRootView;
private int lastSoftKeyboardHeightInPx;
private boolean isSoftKeyboardOpened;
private Context mContext;
//使用時用這個構(gòu)造方法
public SoftKeyboardStateWatcher(View activityRootView, Context context) {
this(activityRootView, false);
this.mContext = context;
}
private SoftKeyboardStateWatcher(View activityRootView) {
this(activityRootView, false);
}
private SoftKeyboardStateWatcher(View activityRootView, boolean isSoftKeyboardOpened) {
this.activityRootView = activityRootView;
this.isSoftKeyboardOpened = isSoftKeyboardOpened;
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@Override
public void onGlobalLayout() {
final Rect r = new Rect();
activityRootView.getWindowVisibleDisplayFrame(r);
final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
if (!isSoftKeyboardOpened && heightDiff > dpToPx(mContext, 200)) {
isSoftKeyboardOpened = true;
notifyOnSoftKeyboardOpened(heightDiff);
} else if (isSoftKeyboardOpened && heightDiff < dpToPx(mContext, 200)) {
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);
}
/**
* @param keyboardHeightInPx 可能是包含狀態(tài)欄的高度和底部虛擬按鍵的高度
*/
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();
}
}
}
private static float dpToPx(Context context, float valueInDp) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
}
}
可以看到, 我建了一個自己的一個Listener , 通過這個listener實(shí)現(xiàn)我們想要的監(jiān)聽 , 然后在這里處理一些邏輯問題.
主要代碼還是在onGlobalLayout中:
首先通過activityRootView.getWindowVisibleDisplayFrame(r)檢索此視圖所附加的窗口所在的整個可見顯示大小 ,然后減去,已顯示的視圖的高度 ,(r.bottom - r.top)就是顯示的view的下坐標(biāo)和上坐標(biāo),差即為高度.
至此,我們得到了剩余的高度 . 這個高度可能就是鍵盤高度了, 為什么說可能呢?因?yàn)檫€么有考慮到頂部的狀態(tài)欄和底部的虛擬導(dǎo)航欄. 當(dāng)然也可能不是鍵盤.
然后我們根據(jù)這個高度和之前已知的鍵盤狀態(tài)來判斷是否為鍵盤.
并回調(diào)給監(jiān)聽者.
使用
ScrollView scrollView = findViewById(R.id.ugc_scrollview);
final SoftKeyboardStateWatcher watcher = new SoftKeyboardStateWatcher(scrollView, this);
watcher.addSoftKeyboardStateListener(
new SoftKeyboardStateWatcher.SoftKeyboardStateListener() {
@Override
public void onSoftKeyboardOpened(int keyboardHeightInPx) {
ConstraintLayout.LayoutParams layoutParamsVideo = (ConstraintLayout.LayoutParams) mError1000tv.getLayoutParams();
layoutParamsVideo.setMargins(0,
0,
0,
keyboardHeightInPx
- ScreenUtils.getStatusHeight(UGCEditActivity.this)
- ScreenUtils.getBottomStatusHeight(UGCEditActivity.this));
}
@Override
public void onSoftKeyboardClosed() {
mError1000tv.setVisibility(View.GONE);
}
}
);
Scrollview是整個頁面的根布局, 我通過監(jiān)聽它來實(shí)現(xiàn)對整個布局的監(jiān)聽.
mError1000tv就是我一開始提到的要緊貼鍵盤頂部顯示的一個textview了.
我通過LayoutParams給它設(shè)置邊距 , 只設(shè)置了底部邊距 , 值為返回的"鍵盤高度"- 頂部狀態(tài)欄高度-虛擬導(dǎo)航欄的高度. 得到真實(shí)的鍵盤高度.
在onSoftKeyboardOpened和onSoftKeyboardClosed這兩個回調(diào)中, 處理自己的邏輯就好了.
然后放上我這邊屏幕工具類ScreenUtils的代碼, 需要的可以復(fù)制下來
ScreenUtils
package com.cari.promo.diskon.util;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import java.lang.reflect.Method;
public class ScreenUtils {
private ScreenUtils() {
/* cannot be instantiated */
throw new UnsupportedOperationException("cannot be instantiated");
}
/**
* 獲得屏幕高度
*
* @param context
* @return
*/
public static int getScreenWidth(Context context) {
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.widthPixels;
}
/**
* 獲得屏幕寬度
*
* @param context
* @return
*/
public static int getScreenHeight(Context context) {
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.heightPixels;
}
/**
* 獲得狀態(tài)欄的高度
*
* @param context
* @return
*/
public static int getStatusHeight(Context context) {
int statusHeight = -1;
try {
Class<?> clazz = Class.forName("com.android.internal.R$dimen");
Object object = clazz.newInstance();
int height = Integer.parseInt(clazz.getField("status_bar_height")
.get(object).toString());
statusHeight = context.getResources().getDimensionPixelSize(height);
} catch (Exception e) {
e.printStackTrace();
}
return statusHeight;
}
/**
* 獲取當(dāng)前屏幕截圖,包含狀態(tài)欄
*
* @param activity
* @return
*/
public static Bitmap snapShotWithStatusBar(Activity activity) {
View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bmp = view.getDrawingCache();
int width = getScreenWidth(activity);
int height = getScreenHeight(activity);
Bitmap bp = null;
bp = Bitmap.createBitmap(bmp, 0, 0, width, height);
view.destroyDrawingCache();
return bp;
}
/**
* 獲取當(dāng)前屏幕截圖,不包含狀態(tài)欄
*
* @param activity
* @return
*/
public static Bitmap snapShotWithoutStatusBar(Activity activity) {
View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bmp = view.getDrawingCache();
Rect frame = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
int width = getScreenWidth(activity);
int height = getScreenHeight(activity);
Bitmap bp = null;
bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height
- statusBarHeight);
view.destroyDrawingCache();
return bp;
}
/**
* 獲取 虛擬按鍵的高度
*
* @param context 上下文
* @return 虛擬鍵高度
*/
public static int getBottomStatusHeight(Context context) {
int totalHeight = getAbsoluteHeight(context);
int contentHeight = getScreenHeight(context);
return totalHeight - contentHeight;
}
/**
* 獲取屏幕原始尺寸高度,包括虛擬功能鍵高度
*
* @param context 上下文
* @return The absolute height of the available display size in pixels.
*/
private static int getAbsoluteHeight(Context context) {
int absoluteHeight = 0;
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = null;
if (windowManager != null) {
display = windowManager.getDefaultDisplay();
}
DisplayMetrics displayMetrics = new DisplayMetrics();
@SuppressWarnings("rawtypes")
Class c;
try {
c = Class.forName("android.view.Display");
@SuppressWarnings("unchecked")
Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(display, displayMetrics);
absoluteHeight = displayMetrics.heightPixels;
} catch (Exception e) {
e.printStackTrace();
}
return absoluteHeight;
}
}
全文完.
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。
相關(guān)文章
Android實(shí)現(xiàn)清除單個域名的cookie
這篇文章主要介紹了Android實(shí)現(xiàn)清除單個域名的cookie,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Android編程基于自定義View實(shí)現(xiàn)絢麗的圓形進(jìn)度條功能示例
這篇文章主要介紹了Android編程基于自定義View實(shí)現(xiàn)絢麗的圓形進(jìn)度條功能,結(jié)合實(shí)例形式詳細(xì)分析了Android自定義view實(shí)現(xiàn)圓形進(jìn)度條的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-01-01
Android中Binder詳細(xì)學(xué)習(xí)心得
這篇文章主要介紹了Android中Binder詳細(xì)學(xué)習(xí)心得,并分析了Binder的詳細(xì)用法,需要的朋友參考下吧。2018-01-01
Android實(shí)現(xiàn)從緩存中讀取圖片與異步加載功能類
這篇文章主要介紹了Android實(shí)現(xiàn)從緩存中讀取圖片與異步加載功能類,涉及Android針對緩存的操作及圖片異步加載相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-08-08
Android自定義控件實(shí)現(xiàn)方向盤效果
這篇文章主要為大家詳細(xì)介紹了Android自定義控件實(shí)現(xiàn)方向盤效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04
制作獨(dú)立的Android模擬器實(shí)現(xiàn)方法
本文主要介紹如何制作獨(dú)立的Android模擬器,這里給大家提供詳細(xì)的制作流程,有需要的小伙伴可以參考下2016-08-08
Android JNI c/c++調(diào)用java的實(shí)例
這篇文章主要介紹了Android JNI c/c++調(diào)用java的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-07-07
Android開發(fā)中LayoutInflater用法詳解
這篇文章主要介紹了Android開發(fā)中LayoutInflater用法,結(jié)合實(shí)例形式分析了LayoutInflater類的功能、作用、使用方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-08-08
Android使用OKHttp包處理HTTP相關(guān)操作的基本用法講解
這篇文章主要介紹了Android使用OKHttp包處理HTTP相關(guān)操作的基本用法講解,包括操作如何利用OKHttp操作HTTP請求與處理緩存等內(nèi)容,需要的朋友可以參考下2016-07-07

