Skip to content

Commit

Permalink
Intrinsic content size for ReactTextInput (aka autoexpandable <TextIn…
Browse files Browse the repository at this point in the history
…put> on Android)

Summary:
After this diff the intrinsic content size of <TextInput> reflects the size of text inside EditText,
it means that if there is no additional style constraints, <TextInput> will grow with containing text.
If you want to constraint minimum or maximum height, just do it via Yoga styling.

Reviewed By: achen1

Differential Revision: D5828366

fbshipit-source-id: eccd0cb4ccf724c7096c947332a64a0a1e402673
  • Loading branch information
shergin authored and facebook-github-bot committed Oct 2, 2017
1 parent d0790fe commit c550f27
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 69 deletions.
96 changes: 67 additions & 29 deletions RNTester/js/TextInputExample.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ var {
TextInput,
View,
StyleSheet,
Slider,
Switch,
} = ReactNative;

class TextEventsExample extends React.Component<{}, $FlowFixMeState> {
Expand Down Expand Up @@ -70,27 +72,6 @@ class TextEventsExample extends React.Component<{}, $FlowFixMeState> {
}
}

class AutoExpandingTextInput extends React.Component<$FlowFixMeProps, $FlowFixMeState> {
constructor(props) {
super(props);
this.state = {
height: 0,
};
}
render() {
return (
<TextInput
{...this.props}
multiline={true}
onContentSizeChange={(event) => {
this.setState({height: event.nativeEvent.contentSize.height});
}}
style={[styles.default, {height: Math.min(200, Math.max(35, this.state.height))}]}
/>
);
}
}

class RewriteExample extends React.Component<$FlowFixMeProps, $FlowFixMeState> {
constructor(props) {
super(props);
Expand Down Expand Up @@ -330,20 +311,66 @@ class SelectionExample extends React.Component<$FlowFixMeProps, SelectionExample
}
}

class AutogrowingTextInputExample extends React.Component<{}> {

constructor(props) {
super(props);

this.state = {
width: 100,
multiline: true,
text: '',
};
}

componentDidReceiveProps(props) {
this.setState({
multiline: props.multiline,
});
}

render() {
var {style, multiline, ...props} = this.props;
return (
<View>
<Text>Width:</Text>
<Slider
value={100}
minimumValue={0}
maximumValue={100}
step={10}
onValueChange={(value) => this.setState({width: value})}
/>
<Text>Multiline:</Text>
<Switch
value={this.state.multiline}
onValueChange={(value) => this.setState({multiline: value})}
/>
<Text>TextInput:</Text>
<TextInput
multiline={this.state.multiline}
style={[style, {width: this.state.width + '%'}]}
onChangeText={(value) => this.setState({text: value})}
{...props}
/>
<Text>Plain text value representation:</Text>
<Text>{this.state.text}</Text>
</View>
);
}
}

var styles = StyleSheet.create({
multiline: {
height: 60,
fontSize: 16,
padding: 4,
marginBottom: 10,
},
eventLabel: {
margin: 3,
fontSize: 12,
},
singleLine: {
fontSize: 16,
padding: 4,
},
singleLineWithHeightTextInput: {
height: 30,
Expand All @@ -363,7 +390,8 @@ exports.examples = [
return (
<TextInput
autoFocus={true}
style={styles.singleLine}
multiline={true}
style={styles.input}
accessibilityLabel="I am the accessibility label for text input"
/>
);
Expand Down Expand Up @@ -613,12 +641,22 @@ exports.examples = [
render: function() {
return (
<View>
<AutoExpandingTextInput
placeholder="height increases with content"
defaultValue="React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. The focus of React Native is on developer efficiency across all the platforms you care about — learn once, write anywhere. Facebook uses React Native in multiple production apps and will continue investing in React Native."
<AutogrowingTextInputExample
enablesReturnKeyAutomatically={true}
returnKeyType="done"
/>
multiline={true}
style={{maxHeight: 400, minHeight: 20, backgroundColor: '#eeeeee'}}
>
generic generic generic
<Text style={{fontSize: 6, color: 'red'}}>
small small small small small small
</Text>
<Text>regular regular</Text>
<Text style={{fontSize: 30, color: 'green'}}>
huge huge huge huge huge
</Text>
generic generic generic
</AutogrowingTextInputExample>
</View>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.views.text.CustomStyleSpan;
import com.facebook.react.views.text.ReactTagSpan;
import com.facebook.react.views.text.ReactTextUpdate;
Expand Down Expand Up @@ -129,9 +131,7 @@ public boolean isLayoutRequested() {

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (mContentSizeWatcher != null) {
mContentSizeWatcher.onLayout();
}
onContentSizeChange();
}

@Override
Expand Down Expand Up @@ -366,7 +366,9 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) {
manageSpans(spannableStringBuilder);
mContainsImages = reactTextUpdate.containsImages();
mIsSettingTextFromJS = true;

getText().replace(0, length(), spannableStringBuilder);

mIsSettingTextFromJS = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (getBreakStrategy() != reactTextUpdate.getTextBreakStrategy()) {
Expand Down Expand Up @@ -446,6 +448,21 @@ private boolean isMultiline() {
return (getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
}

private void onContentSizeChange() {
if (mContentSizeWatcher != null) {
mContentSizeWatcher.onLayout();
}

setIntrinsicContentSize();
}

private void setIntrinsicContentSize() {
ReactContext reactContext = (ReactContext) getContext();
UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class);
final ReactTextInputLocalData localData = new ReactTextInputLocalData(this);
uiManager.setViewLocalData(getId(), localData);
}

/* package */ void setGravityHorizontal(int gravityHorizontal) {
if (gravityHorizontal == 0) {
gravityHorizontal = mDefaultGravityHorizontal;
Expand Down Expand Up @@ -621,9 +638,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
}
}

if (mContentSizeWatcher != null) {
mContentSizeWatcher.onLayout();
}
onContentSizeChange();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* 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 android.os.Build;
import android.text.SpannableString;
import android.util.TypedValue;
import android.widget.EditText;

/** Local state bearer for EditText instance. */
public final class ReactTextInputLocalData {

private final SpannableString mText;
private final float mTextSize;
private final int mMinLines;
private final int mMaxLines;
private final int mInputType;
private final int mBreakStrategy;

public ReactTextInputLocalData(EditText editText) {
mText = new SpannableString(editText.getText());
mTextSize = editText.getTextSize();
mMinLines = editText.getMinLines();
mMaxLines = editText.getMaxLines();
mInputType = editText.getInputType();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mBreakStrategy = editText.getBreakStrategy();
} else {
mBreakStrategy = 0;
}
}

public void apply(EditText editText) {
editText.setText(mText);
editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
editText.setMinLines(mMinLines);
editText.setMaxLines(mMaxLines);
editText.setInputType(mInputType);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
editText.setBreakStrategy(mBreakStrategy);
}
}
}
Loading

0 comments on commit c550f27

Please sign in to comment.