React Native教程第三部分: 计算器触摸事件与计算功能

本教程的完整代码在GitHub

介绍

第二部分我们创建了calculator的布局和样式,但是仍然不能做任何事情。在第三部分,我们继续从这里开始,处理触摸事件,执行数学计算,使用State更新UI。

处理触摸事件

首先要做的就是处理InputButtons上的触摸事件

首先,我们使用一个可以触摸的View来替代当前InputButton上使用的View:

// InputButton.js
...
    render() {
        return (
            <TouchableHighlight style={Style.inputButton}
                                underlayColor="#193441"
                                onPress={this.props.onPress}>
                <Text style={Style.inputButtonText}>{this.props.value}</Text>
            </TouchableHighlight>
        )
    }
    
...

别忘了从react-native导入TouchableHighlight!你还应该注意到我们我们把onPress属性传递给了TouchableHighlight视图,因此我们需要在使用控件的时候传入这个属性的值。

// ReactCalculator.js
...
    _renderInputButtons() {
        ...
        inputRow.push(
            <InputButton
                value={input}
                onPress={this._onInputButtonPressed.bind(this, input)}
                key={r + "-" + i}/>
        );
    }
    
    _onInputButtonPressed(input) {
        alert(input)
    }
    
...

在_renderInputButtons函数中我们用一个名叫_onInputButtonPressed函数的引用来设置onPress属性。我们把函数和用户输入的值做绑定,这让我们可以知道哪个按钮被点击,作出相应的反应。

我们暂时是根据提供的输入数据显示一个对话框。如果你一直紧跟着教程去做了的话,此时你点击一个InputButton之后得到的结果应该是这样的:

react-native-tutorial-5.png

现在我们可以处理触摸事件了,我们也知道是哪个按钮被点击了,可以开始开发实际的计算器功能了。

使用State

State允许我们根据动态的数据更新应用的UI。我们要用State做的第一件事就是根据用户的输入更新显示屏。

首先,让我们向ReactCalculator类添加一个构造函数,把输入数字初始化为0:

// ReactCalculator.js
...
class ReactCalculator extends Component {
    
    constructor(props) {
        super(props);
        
        this.state = {
            inputValue: 0
        }
    }

构造函数只带一个参数props并把它传递给父类构造函数。不过这里我们更感兴趣的是在哪里把state设置为{ inputValue: 0 }。构造函数是你可以直接修改state的唯一机会,之后只能通过setState来修改,稍后我们将看到。

好了,现在我们有了这个inputValue,但是它对我们有什么用处呢?我们将在第二部分创建的显示屏部分用到它。我们将修改render函数,在displayContainer中添加一个Text视图显示输入的值。

// ReactCalculator.js
...
    render() {
        return (
            <View style={Style.rootContainer}>
                <View style={Style.displayContainer}>
                    <Text style={Style.displayText}>{this.state.inputValue}</Text>
                </View>
                <View style={Style.inputContainer}>
                    {this._renderInputButtons()}
                </View>
            </View>
        )
    }
...

另外,这里是新添加的displayText的样式以及更新后的displayContainer样式:

// Style.js
...
    displayContainer: {
        flex: 2,
        backgroundColor: '#193441',
        justifyContent: 'center'
    },
    displayText: {
        color: 'white',
        fontSize: 38,
        fontWeight: 'bold',
        textAlign: 'right',
        padding: 20
    },
...

运行app,你应该可以看到Text view中显示的0。这是因为我们把值设置为了this.state.inputValue,它在构造函数中被初始化为了0。那么我们该如何在用户输入数字的时候更新这个值呢?

回到我们的_onInputButtonPressed函数,我们将检查输入的类型。如果是数字,我们将state上的inputValue设置为 (inputValue * 10) + input。这样当用户每次输入一个数字的时候,它都会被放在现有值的后面,每个数字向左移。比如,输入1的结果是(0 * 10) + 1 = 1 ,因为inputValue初始值为0,接着输入2的结果是 (1 * 10) + 2 = 12,因为新的inputValue设置成了1.

看看代码:

// ReactCalculator.js
...
    _onInputButtonPressed(input) {
        switch (typeof input) {
            case 'number':
                return this._handleNumberInput(input)
        }
    }
    _handleNumberInput(num) {
        let inputValue = (this.state.inputValue * 10) + num;
        this.setState({
            inputValue: inputValue
        })
    }
    
...

再次运行app,你应该可以输入数字并把它显示在显示屏上了!那么这到底是如何工作的呢?只要你调用setState,React会判断需要重新加载哪个控件才能反应出状态的更新。在我们这个场景中,文字视图displayText根据state.inputValue的值进行更新,因此使用一个新的inputValue调用setState,文字视图将自动重新加载。

The Final Stretch

到此我们的应用就基本完成了。还需要做的最后一件事是处理运算操作。

我们将从存储操作的类型开始并且当一个运算符被选择的时候清除显示屏,让被选择的符号高亮显示:

// ReactCalculator.js
...
    constructor(props) {
        super(props);
        this.state = {
            previousInputValue: 0,
            inputValue: 0,
            selectedSymbol: null
        }
    }
    
    _renderInputButtons() {
        ...
        inputRow.push(
            <InputButton
                value={input}
                highlight={this.state.selectedSymbol === input}
                onPress={this._onInputButtonPressed.bind(this, input)}
                key={r + "-" + i}/>
        );
        ...
    }
    
    _onInputButtonPressed(input) {
        switch (typeof input) {
            case 'number':
                return this._handleNumberInput(input)
            case 'string':
                return this._handleStringInput(input)
        }
    }
    ...
    _handleStringInput(str) {
        switch (str) {
            case '/':
            case '*':
            case '+':
            case '-':
                this.setState({
                    selectedSymbol: str,
                    previousInputValue: this.state.inputValue,
                    inputValue: 0
                });
                break;
        }
    }
...

那么这里我们做了些什么呢?在构造函数中,我们又添加了两个属性:previousInputValue 和 selectedSymbol。

在InputButton的定义中,我们传入了一个新的属性highlight:当selectedSymbol的值和InputButton的值匹配时为true。这可以让你选择*号的时候在*按钮上显示高亮。

在触摸事件的处理中,现在我们还通过调用_handleStringInput处理了string类型的输入。_handleStringInput函数负责更新selectedSymbol(也就是运算符号),清除inputValue,并把它设置到previousInputValue中。

在InputButton类中,我们需要支持这个新的highlight属性:

// InputButton.js
...
    render() {
        return (
            <TouchableHighlight style={\[Style.inputButton, this.props.highlight ? Style.inputButtonHighlighted : null\]}
            ...

这演示了我们还没有做过的事情,使用多个样式。这个工作原理和CSS基本是一样的,每个样式都有自己的属性。我们可以覆盖前面样式的属性,根据条件应用样式。

只有在highlight属性值为true时才会应用inputButtonHighlighted样式,这个样式的定义如下:

// Style.js
...
    inputButtonHighlighted: {
        backgroundColor: '#193441'
    },
...

我们还需要做的最后一件事就是处理等号(=)。为此我们将更改_handleStringInput函数:

// ReactCalculator.js
...
    _handleStringInput(str) {
        switch (str) {
           ...
            
            case '=':
                let symbol = this.state.selectedSymbol,
                    inputValue = this.state.inputValue,
                    previousInputValue = this.state.previousInputValue;
                if (!symbol) {
                    return;
                }
                this.setState({
                    previousInputValue: 0,
                    inputValue: eval(previousInputValue + symbol + inputValue),
                    selectedSymbol: null
                });
                break;
        }
    }
...

这里我们确保设置了一个selectedSymbol(也就是确保点击了+、-、* 、/中的一个)。假设这样的话,我们清除previousInputValue和selectedSymbol,并把inputValue的值设置为当前数学运算的结果。这样就能用计算结果更新UI,清除掉高亮按钮的选中,让用户可以继续在结果上执行计算。

下面是 24 / 3 的界面:

react-native-tutorial-6 (1).png

何去何从

即便教程已经结束,也有几个故意遗留的问题没有解决,你可以自己尝试解决:

  1. 小数点(.)操作符还没有实现。该如何利用学到的东西去实现它呢?

  2. 尝试除以0.

  3. 没有清空当前输入或者清空所有东西以便开始一个新的运算的功能。

欢迎尝试这些功能,如果你有解决方案请留言!

总结

希望本教程能让你对React Native有了足够的认识,从而可以开始编写自己的应用。在React Native的官网上还有大量的内容,这不是一篇教程就能涵盖完的。对于跨平台的移动开发来说,这个框架的确是一个游戏规则的制定者,我将持续关注这个工具,未来很可能会在项目中使用它。

如果你在本教程的代码示例中发现了什么问题,请在Github上的ReactCalculator项目中提交issue告知我,如果能提供pull request就更好了!