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 authored and hippy-actions[bot] committed Dec 6, 2023
1 parent 94b4c54 commit 49d8a58
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,48 @@

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

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

import android.graphics.Paint;
import android.graphics.Typeface;
import androidx.annotation.Nullable;
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.NativeRender;
import com.tencent.renderer.NativeRendererManager;
import com.tencent.renderer.component.text.FontAdapter;
import com.tencent.renderer.component.text.TypeFaceUtil;
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.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
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;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatEditText;

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.mtt.hippy.utils.LogUtils;
import com.tencent.mtt.hippy.utils.PixelUtil;
import com.tencent.renderer.NativeRender;
import com.tencent.renderer.NativeRendererManager;
import com.tencent.renderer.component.Component;
import com.tencent.renderer.component.FlatViewGroup;
import com.tencent.renderer.component.text.FontAdapter;
import com.tencent.renderer.component.text.TypeFaceUtil;
import com.tencent.renderer.node.RenderNode;
import com.tencent.renderer.utils.EventUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
Expand All @@ -71,17 +67,23 @@ public class HippyTextInput extends AppCompatEditText implements HippyViewBase,
TextView.OnEditorActionListener, View.OnFocusChangeListener {

private static final String TAG = "HippyTextInput";
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;
private boolean mItalic = false;
private int mFontWeight = TypeFaceUtil.WEIGHT_NORMAL;
Expand Down Expand Up @@ -148,15 +150,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 @@ -274,97 +276,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(TAG, "Failed to get visible insets. (Reflection error). " + e.getMessage(), e);
}

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(TAG, "Failed to get visible insets from AttachInfo " + e.getMessage());
}

} catch (SecurityException e) {
LogUtils.d(TAG, "getScreenHeight: " + 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;
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;
}

final View rootView = getRootView();
int systemWindowBottom = 0;
int rootStableBottom = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (insets == null) {
return 0;
}
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;
}
}
} catch (IllegalAccessException e) {
LogUtils.w(TAG, "Failed to get insets from AttachInfo. " + e.getMessage());
}
}
}
// 问题ID: 106874510 某些奇葩手机ROM调用此方法会报错,做下捕获吧
try {
rootView.getWindowVisibleDisplayFrame(mRect);
} catch (Throwable e) {
LogUtils.d("InputMethodStatusMonitor:", "getWindowVisibleDisplayFrame failed !" + e);
e.printStackTrace();
if (systemWindowBottom > rootStableBottom) {
return systemWindowBottom;
}

int visibleHeight = mRect.bottom - mRect.top;
if (visibleHeight < 0) {
return -1;
if (!sVisibleRectReflectionFetched) {
loadReflectionField();
}
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;
}
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);
}
mIsKeyBoardShow = true; //键盘显示 ----s首次需要通知
}
} 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 (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(TAG,
"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 @@ -510,23 +557,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 Expand Up @@ -632,7 +685,7 @@ public void setCursorColor(int color) {
}
}
}

public void refreshSoftInput() {
InputMethodManager imm = getInputMethodManager();
if (imm.isActive(this)) { // refresh the showing soft keyboard
Expand Down
Loading

0 comments on commit 49d8a58

Please sign in to comment.