/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.react.views.textinput; import javax.annotation.Nullable; import android.text.Spanned; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import com.facebook.csslayout.CSSNode; import com.facebook.csslayout.MeasureOutput; import com.facebook.csslayout.Spacing; import com.facebook.infer.annotation.Assertions; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ReactProp; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.UIViewOperationQueue; import com.facebook.react.uimanager.ViewDefaults; import com.facebook.react.views.text.ReactTextShadowNode; @VisibleForTesting public class ReactTextInputShadowNode extends ReactTextShadowNode implements CSSNode.MeasureFunction { private static final int MEASURE_SPEC = View.MeasureSpec.makeMeasureSpec( ViewGroup.LayoutParams.WRAP_CONTENT, View.MeasureSpec.UNSPECIFIED); private @Nullable EditText mEditText; private @Nullable float[] mComputedPadding; private int mJsEventCount = UNSET; public ReactTextInputShadowNode() { super(false); setMeasureFunction(this); } @Override protected void setThemedContext(ThemedReactContext themedContext) { super.setThemedContext(themedContext); // TODO #7120264: cache this stuff better mEditText = new EditText(getThemedContext()); // This is needed to fix an android bug since 4.4.3 which will throw an NPE in measure, // setting the layoutParams fixes it: https://code.google.com/p/android/issues/detail?id=75877 mEditText.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); setDefaultPadding(Spacing.LEFT, mEditText.getPaddingLeft()); setDefaultPadding(Spacing.TOP, mEditText.getPaddingTop()); setDefaultPadding(Spacing.RIGHT, mEditText.getPaddingRight()); setDefaultPadding(Spacing.BOTTOM, mEditText.getPaddingBottom()); mComputedPadding = spacingToFloatArray(getPadding()); } @Override public void measure(CSSNode node, float width, MeasureOutput measureOutput) { // measure() should never be called before setThemedContext() EditText editText = Assertions.assertNotNull(mEditText); measureOutput.width = width; editText.setTextSize( TypedValue.COMPLEX_UNIT_PX, mFontSize == UNSET ? (int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP)) : mFontSize); mComputedPadding = spacingToFloatArray(getPadding()); editText.setPadding( (int) Math.ceil(getPadding().get(Spacing.LEFT)), (int) Math.ceil(getPadding().get(Spacing.TOP)), (int) Math.ceil(getPadding().get(Spacing.RIGHT)), (int) Math.ceil(getPadding().get(Spacing.BOTTOM))); if (mNumberOfLines != UNSET) { editText.setLines(mNumberOfLines); } editText.measure(MEASURE_SPEC, MEASURE_SPEC); measureOutput.height = editText.getMeasuredHeight(); } @Override public void onBeforeLayout() { // We don't have to measure the text within the text input. return; } @ReactProp(name = "mostRecentEventCount") public void setMostRecentEventCount(int mostRecentEventCount) { mJsEventCount = mostRecentEventCount; } @Override public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { super.onCollectExtraUpdates(uiViewOperationQueue); if (mComputedPadding != null) { uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), mComputedPadding); mComputedPadding = null; } if (mJsEventCount != UNSET) { Spanned preparedSpannedText = fromTextCSSNode(this); ReactTextUpdate reactTextUpdate = new ReactTextUpdate(preparedSpannedText, mJsEventCount); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate); } } @Override public void setPadding(int spacingType, float padding) { super.setPadding(spacingType, padding); mComputedPadding = spacingToFloatArray(getPadding()); markUpdated(); } private static float[] spacingToFloatArray(Spacing spacing) { return new float[] { spacing.get(Spacing.LEFT), spacing.get(Spacing.TOP), spacing.get(Spacing.RIGHT), spacing.get(Spacing.BOTTOM), }; } }