Skip to content

Commit

Permalink
feat(android): onKeyboardWillShow only triggers on focused component
Browse files Browse the repository at this point in the history
  • Loading branch information
iPel committed Oct 25, 2023
1 parent 61c41d8 commit c95f9d1
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,28 @@

package com.tencent.mtt.hippy.views.textinput;

import android.annotation.SuppressLint;
import android.graphics.BlendMode;
import android.graphics.BlendModeColorFilter;

import android.graphics.Rect;
import android.view.WindowInsets;
import com.tencent.mtt.hippy.common.HippyMap;
import com.tencent.mtt.hippy.uimanager.HippyViewBase;
import com.tencent.mtt.hippy.uimanager.NativeGestureDispatcher;
import com.tencent.mtt.hippy.uimanager.RenderManager;
import com.tencent.renderer.node.RenderNode;
import com.tencent.mtt.hippy.utils.ContextHolder;
import com.tencent.mtt.hippy.utils.LogUtils;
import com.tencent.mtt.hippy.utils.PixelUtil;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.Display;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
Expand All @@ -52,27 +52,33 @@
import androidx.appcompat.widget.AppCompatEditText;

import com.tencent.renderer.component.Component;
import com.tencent.renderer.component.FlatViewGroup;
import com.tencent.renderer.utils.EventUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

@SuppressWarnings({"deprecation", "unused"})
public class HippyTextInput extends AppCompatEditText implements HippyViewBase,
TextView.OnEditorActionListener, View.OnFocusChangeListener {

static final int EVENT_FOCUS = 1;
static final int EVENT_BLUR = 1 << 1;
static final int EVENT_KEYBOARD_SHOW = 1 << 2;
static final int EVENT_KEYBOARD_HIDE = 1 << 3;
static final int MASK_FOCUS = EVENT_FOCUS | EVENT_BLUR | EVENT_KEYBOARD_SHOW;
static final int MASK_KEYBOARD = EVENT_KEYBOARD_SHOW | EVENT_KEYBOARD_HIDE;
boolean mHasAddWatcher = false;
private String mPreviousText = "";
TextWatcher mTextWatcher = null;
boolean mHasSetOnSelectListener = false;

private final int mDefaultGravityHorizontal;
private final int mDefaultGravityVertical;
//输入法键盘的相关方法
private final Rect mRect = new Rect(); //获取当前RootView的大小位置信息
private int mLastRootViewVisibleHeight = -1; //当前RootView的上一次大小
private final ViewTreeObserver.OnGlobalLayoutListener mKeyboardEventObserver = this::checkSendKeyboardEvent;
private boolean mIsKeyBoardShow = false; //键盘是否在显示
private boolean mIsKeyBoardShowBySelf = false;
private int mListenerFlag = 0;
private ReactContentSizeWatcher mReactContentSizeWatcher = null;

public HippyTextInput(Context context) {
Expand Down Expand Up @@ -131,15 +137,15 @@ public void onEditorAction(int actionCode) {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (getRootView() != null) {
getRootView().getViewTreeObserver().addOnGlobalLayoutListener(globaListener);
getRootView().getViewTreeObserver().addOnGlobalLayoutListener(mKeyboardEventObserver);
}
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (getRootView() != null) {
getRootView().getViewTreeObserver().removeOnGlobalLayoutListener(globaListener);
getRootView().getViewTreeObserver().removeOnGlobalLayoutListener(mKeyboardEventObserver);
}
}

Expand Down Expand Up @@ -257,97 +263,142 @@ public void hideInputMethod() {

}

//成功的話返回手機屏幕的高度,失敗返回-1
private int getScreenHeight() {
private static boolean sVisibleRectReflectionFetched = false;
private static Method sGetViewRootImplMethod;
private static Field sVisibleInsetsField;
private static Field sAttachInfoField;

private static Field sViewAttachInfoField;
private static Field sStableInsets;
private static Field sContentInsets;

@SuppressLint({ "PrivateApi", "SoonBlockedPrivateApi" }) // PrivateApi is only accessed below SDK 30
private static void loadReflectionField() {
try {
Context context = ContextHolder.getAppContext();
android.view.WindowManager manager = (android.view.WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();

if (display != null) {
int width = manager.getDefaultDisplay().getWidth();
int height = manager.getDefaultDisplay().getHeight();
return Math.max(width, height);
}
sGetViewRootImplMethod = View.class.getDeclaredMethod("getViewRootImpl");
Class<?> sAttachInfoClass = Class.forName("android.view.View$AttachInfo");
sVisibleInsetsField = sAttachInfoClass.getDeclaredField("mVisibleInsets");
Class<?> viewRootImplClass = Class.forName("android.view.ViewRootImpl");
sAttachInfoField = viewRootImplClass.getDeclaredField("mAttachInfo");
sVisibleInsetsField.setAccessible(true);
sAttachInfoField.setAccessible(true);
} catch (ReflectiveOperationException e) {
LogUtils.e("HippyTextInput", "Failed to get visible insets. (Reflection error). " + e.getMessage(), e);
}

} catch (SecurityException e) {
LogUtils.d("HippyTextInput", "getScreenHeight: " + e.getMessage());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
try {
sViewAttachInfoField = View.class.getDeclaredField("mAttachInfo");
sViewAttachInfoField.setAccessible(true);
Class<?> sAttachInfoClass = Class.forName("android.view.View$AttachInfo");
sStableInsets = sAttachInfoClass.getDeclaredField("mStableInsets");
sStableInsets.setAccessible(true);
sContentInsets = sAttachInfoClass.getDeclaredField("mContentInsets");
sContentInsets.setAccessible(true);
} catch (ReflectiveOperationException e) {
LogUtils.w("HippyTextInput", "Failed to get visible insets from AttachInfo " + e.getMessage());
}
}
return -1;

sVisibleRectReflectionFetched = true;
}

/**
* 返回RootView的高度,要注意即使全屏,他應該也少了一個狀態欄的高度
* Get the software keyboard height. This code is migrated from androidx, in case we are running in a lower version
* androidx library, feel free to use androidx directly in the future.
*
* @see androidx.core.view.ViewCompat#getRootWindowInsets(View)
* @see androidx.core.view.WindowInsetsCompat#isVisible(int)
* @see androidx.core.view.WindowInsetsCompat#getInsets(int)
* @see androidx.core.view.WindowInsetsCompat.Type#ime()
*/
private int getRootViewHeight() {
int height = -1;
View rootView = getRootView();
if (rootView == null) {
return height;
}
// 问题ID: 106874510 某些奇葩手机ROM调用此方法会报错,做下捕获吧
try {
rootView.getWindowVisibleDisplayFrame(mRect);
} catch (Throwable e) {
LogUtils.d("InputMethodStatusMonitor:", "getWindowVisibleDisplayFrame failed !" + e);
e.printStackTrace();
private int getImeHeight(View view) {
final WindowInsets insets = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? getRootWindowInsets() : null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return insets == null ? 0 : insets.getInsets(WindowInsets.Type.ime()).bottom;
}

int visibleHeight = mRect.bottom - mRect.top;
if (visibleHeight < 0) {
return -1;
}
return visibleHeight;
}

//监听RootView布局变化的listener
final ViewTreeObserver.OnGlobalLayoutListener globaListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int rootViewVisibleHeight = getRootViewHeight(); //RootView的高度
int screenHeight = getScreenHeight(); //屏幕高度
if (rootViewVisibleHeight == -1 || screenHeight == -1) //如果有失败直接返回 //TODO...仔细检查下这里的逻辑
{
mLastRootViewVisibleHeight = rootViewVisibleHeight;
return;
final View rootView = getRootView();
int systemWindowBottom = 0;
int rootStableBottom = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (insets == null) {
return 0;
}
if (mLastRootViewVisibleHeight == -1) // 首次
{
//假设输入键盘的高度位屏幕高度20%
if (rootViewVisibleHeight > screenHeight * 0.8f) {

mIsKeyBoardShow = false; //键盘没有显示
} else {
if (!mIsKeyBoardShow) {
Map<String, Object> keyboardHeightMap = new HashMap<>();
int height = Math.abs(screenHeight - rootViewVisibleHeight);
keyboardHeightMap.put("keyboardHeight", Math.round(PixelUtil.px2dp(height)));
EventUtils.sendComponentEvent(HippyTextInput.this, "keyboardWillShow", keyboardHeightMap);
systemWindowBottom = insets.getSystemWindowInsetBottom();
rootStableBottom = insets.getStableInsetBottom();
} else {
if (!rootView.isAttachedToWindow()) {
return 0;
}
if (!sVisibleRectReflectionFetched) {
loadReflectionField();
}
if (sViewAttachInfoField != null && sStableInsets != null && sContentInsets != null) {
try {
Object attachInfo = sViewAttachInfoField.get(rootView);
if (attachInfo != null) {
Rect stableInsets = (Rect) sStableInsets.get(attachInfo);
Rect visibleInsets = (Rect) sContentInsets.get(attachInfo);
if (stableInsets != null && visibleInsets != null) {
systemWindowBottom = visibleInsets.bottom;
rootStableBottom = stableInsets.bottom;
}
}
mIsKeyBoardShow = true; //键盘显示 ----s首次需要通知
} catch (IllegalAccessException e) {
LogUtils.w("HippyTextInput", "Failed to get insets from AttachInfo. " + e.getMessage());
}
} else {
//假设输入键盘的高度位屏幕高度20%
if (rootViewVisibleHeight > screenHeight * 0.8f) {
if (mIsKeyBoardShow) {
EventUtils.sendComponentEvent(HippyTextInput.this, "keyboardWillHide", null);
}
mIsKeyBoardShow = false; //键盘没有显示
} else {
if (!mIsKeyBoardShow) {
HippyMap hippyMap = new HippyMap();
hippyMap.pushInt("keyboardHeight",
Math.abs(mLastRootViewVisibleHeight - rootViewVisibleHeight));
EventUtils.sendComponentEvent(HippyTextInput.this, "keyboardWillShow", hippyMap);
}
}
if (systemWindowBottom > rootStableBottom) {
return systemWindowBottom;
}

if (!sVisibleRectReflectionFetched) {
loadReflectionField();
}
if (sGetViewRootImplMethod != null && sAttachInfoField != null && sVisibleInsetsField != null) {
try {
Object viewRootImpl = sGetViewRootImplMethod.invoke(rootView);
if (viewRootImpl != null) {
Object mAttachInfo = sAttachInfoField.get(viewRootImpl);
Rect visibleRect = (Rect) sVisibleInsetsField.get(mAttachInfo);
int visibleRectBottom = visibleRect != null ? visibleRect.bottom : 0;
if (visibleRectBottom > rootStableBottom) {
return visibleRectBottom;
}
mIsKeyBoardShow = true; //键盘显示 ----s首次需要通知
}
} catch (ReflectiveOperationException e) {
LogUtils.e("HippyTextInput",
"Failed to get visible insets. (Reflection error). " + e.getMessage(), e);
}
}
return 0;
}

mLastRootViewVisibleHeight = rootViewVisibleHeight;
private void checkSendKeyboardEvent() {
if ((mListenerFlag & MASK_KEYBOARD) == 0) {
return;
}
};
final int imeHeight = getImeHeight(this);
if (imeHeight > 0) {
mIsKeyBoardShow = true;
boolean bySelf = hasFocus();
if (bySelf != mIsKeyBoardShowBySelf) {
mIsKeyBoardShowBySelf = bySelf;
if (bySelf && (mListenerFlag & EVENT_KEYBOARD_SHOW) != 0) {
Map<String, Object> keyboardHeightMap = new HashMap<>();
keyboardHeightMap.put("keyboardHeight", Math.round(PixelUtil.px2dp(imeHeight)));
EventUtils.sendComponentEvent(HippyTextInput.this, "keyboardWillShow", keyboardHeightMap);
}
}
} else if (mIsKeyBoardShow) {
mIsKeyBoardShow = mIsKeyBoardShowBySelf = false;
if ((mListenerFlag & EVENT_KEYBOARD_HIDE) != 0) {
EventUtils.sendComponentEvent(HippyTextInput.this, "keyboardWillHide", null);
}
}
}

public void showInputMethodManager() {

Expand Down Expand Up @@ -503,23 +554,29 @@ public Map<String, Object> jsIsFocused() {
return result;
}

public void setBlurOrOnFocus(boolean blur) {
if (blur) {
setOnFocusChangeListener(this);
public void setEventListener(boolean enable, int flag) {
final boolean oldHasFocusListener = (mListenerFlag & MASK_FOCUS) != 0;
if (enable) {
mListenerFlag |= flag;
} else {
setOnFocusChangeListener(null);
mListenerFlag &= ~flag;
}
boolean newHasFocusListener = (mListenerFlag & MASK_FOCUS) != 0;
if (oldHasFocusListener != newHasFocusListener) {
setOnFocusChangeListener(newHasFocusListener ? this : null);
}
}

@Override
public void onFocusChange(View v, boolean hasFocus) {

HippyMap hippyMap = new HippyMap();
hippyMap.pushString("text", getText().toString());
if (hasFocus) {
EventUtils.sendComponentEvent(v, "focus", hippyMap);
checkSendKeyboardEvent();
} else {
EventUtils.sendComponentEvent(v, "blur", hippyMap);
mIsKeyBoardShowBySelf = false;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Looper;
Expand All @@ -37,7 +36,6 @@

import androidx.annotation.NonNull;

import androidx.annotation.Nullable;
import com.tencent.mtt.hippy.annotation.HippyController;
import com.tencent.mtt.hippy.annotation.HippyControllerProps;
import com.tencent.mtt.hippy.common.HippyArray;
Expand All @@ -46,7 +44,6 @@
import com.tencent.mtt.hippy.uimanager.HippyViewController;
import com.tencent.mtt.hippy.utils.LogUtils;
import com.tencent.mtt.hippy.utils.PixelUtil;
import com.tencent.mtt.hippy.views.hippypager.HippyPager;
import com.tencent.renderer.NativeRender;
import com.tencent.renderer.NativeRenderException;
import com.tencent.renderer.NativeRendererManager;
Expand Down Expand Up @@ -394,13 +391,23 @@ public void setEndEditing(HippyTextInput hippyTextInput, boolean change) {
}

@HippyControllerProps(name = "focus", defaultType = HippyControllerProps.BOOLEAN)
public void setOnFocus(HippyTextInput hippyTextInput, boolean change) {
hippyTextInput.setBlurOrOnFocus(change);
public void setOnFocus(HippyTextInput hippyTextInput, boolean enable) {
hippyTextInput.setEventListener(enable, HippyTextInput.EVENT_FOCUS);
}

@HippyControllerProps(name = "blur", defaultType = HippyControllerProps.BOOLEAN)
public void setBlur(HippyTextInput hippyTextInput, boolean change) {
hippyTextInput.setBlurOrOnFocus(change);
public void setBlur(HippyTextInput hippyTextInput, boolean enable) {
hippyTextInput.setEventListener(enable, HippyTextInput.EVENT_BLUR);
}

@HippyControllerProps(name = "keyboardwillshow", defaultType = HippyControllerProps.BOOLEAN)
public void setOnKeyboardWillShow(HippyTextInput hippyTextInput, boolean enable) {
hippyTextInput.setEventListener(enable, HippyTextInput.EVENT_KEYBOARD_SHOW);
}

@HippyControllerProps(name = "keyboardwillhide", defaultType = HippyControllerProps.BOOLEAN)
public void setOnKeyboardWillHide(HippyTextInput hippyTextInput, boolean enable) {
hippyTextInput.setEventListener(enable, HippyTextInput.EVENT_KEYBOARD_HIDE);
}

@HippyControllerProps(name = "contentSizeChange", defaultType = HippyControllerProps.BOOLEAN)
Expand Down

0 comments on commit c95f9d1

Please sign in to comment.