From d4bb372747f9a085131c64b2fea42f4080c6ae01 Mon Sep 17 00:00:00 2001 From: maxli Date: Thu, 27 Jun 2024 21:47:19 +0800 Subject: [PATCH] feat(android): text input support lineHeight --- docs/api/hippy-react/components.md | 2 + .../src/components/TextInput/index.jsx | 6 +- .../hippy/views/textinput/HippyTextInput.java | 55 ++++++++++++++++++- .../textinput/HippyTextInputController.java | 37 +++++++++++++ .../renderer/node/TextInputRenderNode.java | 34 ++++++++++++ 5 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 renderer/native/android/src/main/java/com/tencent/renderer/node/TextInputRenderNode.java diff --git a/docs/api/hippy-react/components.md b/docs/api/hippy-react/components.md index a7985c317cd..146f66f9335 100644 --- a/docs/api/hippy-react/components.md +++ b/docs/api/hippy-react/components.md @@ -327,6 +327,8 @@ import icon from './qb_icon_new.png'; | keyboardType | 决定弹出的何种软键盘的。 注意,`password`仅在属性 `multiline=false` 单行文本框时生效。 | `enum (default, numeric, password, email, phone-pad)` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | | maxLength | 限制文本框中最多的字符数。使用这个属性而不用JS 逻辑去实现,可以避免闪烁的现象。 | `number` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | | multiline | 如果为 `true` ,文本框中可以输入多行文字。 由于终端特性。 | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | +| lineSpacingExtra | 多行显示时每行文字的额外行高,如果style里设置的lineHeight属性该属性设置无效 | `number` | `Android` | +| lineSpacingMultiplier | 多行显示时每行文字的行高乘积系数,如果style里设置的lineHeight属性该属性设置无效 | `number` | `Android` | | numberOfLines | 设置 `TextInput` 最大显示行数,如果 `TextInput` 没有显式设置高度,会根据 `numberOfLines` 来计算高度撑开。在使用的时候必需同时设置 `multiline` 参数为 `true`。 | `number` | `Android、hippy-react-web、Web-Renderer、Voltron` | | onBlur | 当文本框失去焦点的时候调用此回调函数。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | | onFocus | 当文本框获得焦点的时候调用此回调函数。 | `Function` | `Android、iOS、Voltron` | diff --git a/driver/js/examples/hippy-react-demo/src/components/TextInput/index.jsx b/driver/js/examples/hippy-react-demo/src/components/TextInput/index.jsx index e5ec555a782..307800cef60 100644 --- a/driver/js/examples/hippy-react-demo/src/components/TextInput/index.jsx +++ b/driver/js/examples/hippy-react-demo/src/components/TextInput/index.jsx @@ -20,12 +20,10 @@ const styles = StyleSheet.create({ fontSize: 16, color: '#242424', height: 30, - // you can use lineHeight/lineSpacing/lineHeightMultiple - // to control the space between lines in multi-line input.(iOS only for now) + // you can use lineHeight + // to control the space between lines in multi-line input. // for example: lineHeight: 30, - // lineSpacing: 50, - // lineHeightMultiple: 1.5, }, input_style_block: { height: 100, diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java index 7ab860a52fb..26219b4e1d5 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java @@ -16,6 +16,8 @@ package com.tencent.mtt.hippy.views.textinput; +import static com.tencent.mtt.hippy.views.textinput.HippyTextInputController.UNSET; + import android.annotation.SuppressLint; import android.content.Context; import android.graphics.BlendMode; @@ -83,10 +85,15 @@ public class HippyTextInput extends AppCompatEditText implements HippyViewBase, private final ViewTreeObserver.OnGlobalLayoutListener mKeyboardEventObserver = this::checkSendKeyboardEvent; private boolean mIsKeyBoardShow = false; //键盘是否在显示 private boolean mIsKeyBoardShowBySelf = false; + private boolean mShouldUpdateTypeface = false; + private boolean mShouldUpdateLineHeight = false; private int mListenerFlag = 0; private ReactContentSizeWatcher mReactContentSizeWatcher = null; private boolean mItalic = false; private int mFontWeight = TypeFaceUtil.WEIGHT_NORMAL; + private float mLineSpacingMultiplier = 1.0f; + private float mLineSpacingExtra = 0.0f; + private int mLineHeight = 0; @Nullable private String mFontFamily; private Paint mTextPaint; @@ -177,6 +184,48 @@ void setGravityVertical(int gravityVertical) { setGravity((getGravity() & ~Gravity.VERTICAL_GRAVITY_MASK) | gravityVertical); } + public void setLineSpacingMultiplier(float spacingMultiplier) { + if (Float.compare(spacingMultiplier, mLineSpacingMultiplier) != 0) { + mLineSpacingMultiplier = spacingMultiplier; + mShouldUpdateLineHeight = true; + } + } + + public void setLineSpacingExtra(float spacingExtra) { + if (Float.compare(spacingExtra, mLineSpacingExtra) != 0) { + mLineSpacingExtra = spacingExtra; + mShouldUpdateLineHeight = true; + } + } + + public void setTextLineHeight(int lineHeight) { + if (Float.compare(lineHeight, mLineHeight) != 0) { + mLineHeight = lineHeight; + mShouldUpdateLineHeight = true; + } + } + + public void onBatchComplete() { + if (mShouldUpdateTypeface) { + updateTypeface(); + mShouldUpdateTypeface = false; + } + if (!mShouldUpdateLineHeight) { + return; + } + if (mLineHeight > 0) { + final int fontHeight = getPaint().getFontMetricsInt(null); + // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. + if (mLineHeight != fontHeight) { + // Set lineSpacingExtra by the difference of lineSpacing with lineHeight + setLineSpacing(mLineHeight - fontHeight, 1f); + } + } else { + setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier); + } + mShouldUpdateLineHeight = false; + } + @Override protected void onDraw(Canvas canvas) { RenderNode node = RenderManager.getRenderNode(this); @@ -702,14 +751,14 @@ public void refreshSoftInput() { public void setFontStyle(String style) { if (TypeFaceUtil.TEXT_FONT_STYLE_ITALIC.equals(style) != mItalic) { mItalic = !mItalic; - updateTypeface(); + mShouldUpdateTypeface = true; } } public void setFontFamily(String family) { if (!Objects.equals(mFontFamily, family)) { mFontFamily = family; - updateTypeface(); + mShouldUpdateTypeface = true; } } @@ -731,7 +780,7 @@ public void setFontWeight(String weight) { } if (fontWeight != mFontWeight) { mFontWeight = fontWeight; - updateTypeface(); + mShouldUpdateTypeface = true; } } diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java index 0a5cf1da622..c5e87494e82 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java @@ -37,23 +37,30 @@ import android.view.inputmethod.InputMethodManager; 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; import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.uimanager.ControllerManager; 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.view.HippyViewGroup; import com.tencent.renderer.NativeRender; import com.tencent.renderer.NativeRenderException; import com.tencent.renderer.NativeRendererManager; import com.tencent.renderer.component.text.TextRenderSupplier; +import com.tencent.renderer.node.RenderNode; +import com.tencent.renderer.node.TextInputRenderNode; import com.tencent.renderer.node.TextVirtualNode; +import com.tencent.renderer.node.ViewPagerRenderNode; import com.tencent.renderer.utils.ArrayUtils; import java.util.LinkedList; import java.util.List; +import java.util.Map; import static com.tencent.renderer.NativeRenderException.ExceptionCode.HANDLE_CALL_UI_FUNCTION_ERR; @@ -61,6 +68,7 @@ public class HippyTextInputController extends HippyViewController { public static final String CLASS_NAME = "TextInput"; + public final static int UNSET = -1; public static final int DEFAULT_TEXT_COLOR = Color.BLACK; public static final int DEFAULT_PLACEHOLDER_TEXT_COLOR = Color.GRAY; private static final String TAG = "HippyTextInputControlle"; @@ -84,6 +92,17 @@ protected View createViewImpl(Context context) { return new HippyTextInput(context); } + @Override + public RenderNode createRenderNode(int rootId, int id, @Nullable Map props, + @NonNull String className, @NonNull ControllerManager controllerManager, boolean isLazy) { + return new TextInputRenderNode(rootId, id, props, className, controllerManager, isLazy); + } + + @Override + public void onBatchComplete(@NonNull HippyTextInput textInput) { + textInput.onBatchComplete(); + } + @Override protected void updateExtra(@NonNull View view, Object object) { super.updateExtra(view, object); @@ -276,6 +295,24 @@ public void setLetterSpacing(HippyTextInput view, float letterSpacing) { view.setLetterSpacing(PixelUtil.dp2px(letterSpacing)); } + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.LINE_HEIGHT, defaultType = HippyControllerProps.NUMBER) + public void setLineHeight(HippyTextInput view, int lineHeight) { + view.setTextLineHeight(Math.round(PixelUtil.dp2px(lineHeight))); + } + + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.LINE_SPACING_MULTIPLIER, defaultType = HippyControllerProps.NUMBER, defaultNumber = UNSET) + public void setLineSpacingMultiplier(HippyTextInput view, float lineSpacingMultiplier) { + view.setLineSpacingMultiplier(lineSpacingMultiplier); + } + + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.LINE_SPACING_EXTRA, defaultType = HippyControllerProps.NUMBER, defaultNumber = 0.0f) + public void setLineSpacingExtra(HippyTextInput view, float lineSpacingExtra) { + view.setLineSpacingExtra(PixelUtil.dp2px(lineSpacingExtra)); + } + @HippyControllerProps(name = "value", defaultType = HippyControllerProps.STRING) public void value(HippyTextInput view, String value) { int selectionStart = view.getSelectionStart(); diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/node/TextInputRenderNode.java b/renderer/native/android/src/main/java/com/tencent/renderer/node/TextInputRenderNode.java new file mode 100644 index 00000000000..537b9a80b5b --- /dev/null +++ b/renderer/native/android/src/main/java/com/tencent/renderer/node/TextInputRenderNode.java @@ -0,0 +1,34 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.renderer.node; + +import androidx.annotation.Nullable; +import com.tencent.mtt.hippy.uimanager.ControllerManager; +import java.util.Map; + +public class TextInputRenderNode extends RenderNode { + + public TextInputRenderNode(int rootId, int id, @Nullable Map props, + String className, ControllerManager componentManager, boolean isLazyLoad) { + super(rootId, id, props, className, componentManager, isLazyLoad); + } + + @Override + protected boolean shouldNotifyNonBatchingChange() { + return !isBatching(); + } +}