1. 程式人生 > >react native 彈出編輯對話方塊

react native 彈出編輯對話方塊

類似於蘋果手機的彈出效果,動畫很流暢,效果如圖所示:http://www.jianshu.com/p/36ec413f7098


效果圖

分析實現過程

1.設定應用到的元件狀態
this.state = {
    isShow: false,
    inputText: '',
    opacityAnimationValue: new Animated.Value(0),
    scaleAnimationValue: new Animated.Value(0)
}

opacityAnimationValue:表示彈出框的透明度;
scaleAnimationValue:表示彈出框的縮放倍數,模擬它從小變大的動畫;

2.設定開啟對話方塊的動畫效果
// 開啟對話方塊
show() {
    this.setState({
        isShow: true,
        inputText: this.props.inputText
    });

    //Animated.parallel == 同時執行多個動畫
    Animated.parallel([
        //Animated.timing == 推動一個值按照一個過渡曲線而隨時間變化
        Animated.timing(this.state.opacityAnimationValue, {
            toValue
: 1, duration: 200 + 100 }), //Animated.spring == 產生一個基於Rebound和Origami實現的Spring動畫。它會在toValue值更新的同時跟蹤當前的速度狀態,以確保動畫連貫,比timing動畫連貫流暢 Animated.spring(this.state.scaleAnimationValue, { toValue: 1, duration: 200, friction: 5 }) ]).start
(); }

注意:Animated.parallel表示輔助函式,可以同時執行多個動畫;
Animated.timing和Animated.spring有一些動畫效果上的區別,其中Animated.spring動畫效果更連貫漂亮;

3.設定關閉對話方塊的方法
// 關閉對話方塊
_close() {
    this.setState({isShow: false});
    this.state.opacityAnimationValue.setValue(0);
    this.state.scaleAnimationValue.setValue(0);
}
4.重點看一下render方法
render() {
    // 如果消失,返回null 用於控制顯示隱藏
    if (!this.state.isShow) return null;
    // 向父元件開放兩個介面,自定義標題文字和點選確定後傳送的事件 
    const {ensureCallback,titleTxt} = this.props;

    return (
        // 最外層是一個半透明的黑色蒙版背景,點選的時候對話方塊也會消失
        <Animated.View style={[styles.container, {opacity: this.state.opacityAnimationValue}]}>
            <TouchableOpacity
                activeOpacity={1}
                style={{flex: 1, alignItems: 'center', paddingTop: 100}}
                // 點選外層的黑色蒙版,對話方塊也會消失
                onPress={this._close}
            >
                <Animated.View
                    style={[styles.contentContainer, {transform: [{scale: this.state.scaleAnimationValue}]}]}
                >
                    <TouchableOpacity
                        activeOpacity={1}
                        style={styles.promptContainer}
                    >
                        <Text style={{fontSize: 15, color: 'black'}}>{titleTxt}</Text>
                        <View style={{flexDirection: 'row', margin: 15}}>
                            <View style={[styles.center, {width: 230}]}>
                                <TextInput
                                    style={{fontSize: 16, color: '#999',width:150,padding:0}}
                                    value={this.state.inputText}
                                    autoFocus={true}
                                    underlineColorAndroid="transparent"
                                    onChangeText={text => this.setState({inputText:text})}
                                />
                            </View>
                            <TouchableOpacity
                                onPress={() => this.setState({inputText: ''})}
                                style={[styles.center, {width: 20}]}>
                                <Image
                                    source={require('../../../assets/img/close.png')}
                                    style={{width: 18, height: 18}}
                                />
                            </TouchableOpacity>
                        </View>
                    </TouchableOpacity>
                    <View style={styles.buttonContainer}>
                        <TouchableOpacity
                            activeOpacity={0.75}
                            style={[styles.center, {flex: 4.5}]}
                            onPress={this._close}
                        >
                            <Text style={{fontSize: 16, color: 'black'}}>取消</Text>
                        </TouchableOpacity>
                        <View style={[styles.line]}/>
                        <TouchableOpacity
                            activeOpacity={0.75}
                            style={[styles.center, {flex: 4.5}]}
                            onPress={() => {
                                this._close();
                                // 子元件傳遞資料到父元件 
                                ensureCallback(this.state.inputText);
                            }}
                        >
                            <Text style={{fontSize: 16, color: 'black'}}>確定</Text>
                        </TouchableOpacity>
                    </View>
                </Animated.View>
            </TouchableOpacity>
        </Animated.View>
    )
}

著重說一下transform樣式的書寫,這裡用到了陣列的形式:

transform [{perspective: number}, {rotate: string}, {rotateX: string}, {rotateY: string}, {rotateZ: string}, {scale: number}, {scaleX: number}, {scaleY: number}, {translateX: number}, {translateY: number}, {skewX: string}, {skewY: string}]

5.來看樣式:
const styles = StyleSheet.create({
    container: {
        position: 'absolute',
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
        backgroundColor: 'rgba(1, 1, 1, 0.5)'
    },
    contentContainer: {
        justifyContent: 'center',
        alignItems: 'center',
        borderColor: '#d9d9d9',
        borderWidth: 1,
        height: 150,
        width: screenW * 0.75,
        backgroundColor: 'rgb(234, 234, 235)',
        borderRadius: 5,
    },
    promptContainer: {
        height: 100,
        width: screenW * 0.75,
        alignItems: 'center',
        justifyContent: 'center'
    },
    buttonContainer: {
        height: 50,
        width: screenW * 0.75,
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-between',
        borderTopWidth: 1,
        borderColor: '#d9d9d9'
    },
    line: {
        height: 46,
        width: 1,
        backgroundColor: '#d9d9d9'
    },
    center: {
        justifyContent: 'center',
        alignItems: 'center'
    }
})
5.如何引用到專案中

1.引入元件

import EditView from './EditView';

2.把元件放到合適的位置,最好是最外層容器的裡邊

<EditView
        // 在元件中使用this.editView即可訪拿到EditView元件
        ref={editView => this.editView = editView}
        inputText={this.state.name}
        titleTxt={'修改XXX'}
        ensureCallback={name => this.setState({name})}
    />

3.呼叫它出現的方法

...
<TouchableOpacity onPress={()=>this.editView.show()}     style={[styles.receiveViewStyle,{marginTop:20}]}>
    <View style={[styles.receiveViewStyle,{backgroundColor:'red'}]}>
      <Text style={styles.receiveTxtStyle}>點選編輯</Text>
    </View>
  </TouchableOpacity>
...

4.或者直接這樣呼叫:

<EditView
        // 在元件中使用this.editView即可訪拿到EditView元件
        ref="editView"
        inputText={this.state.name}
        titleTxt={'修改XXX'}
        ensureCallback={name => this.setState({name})}
    />

    ...


<TouchableOpacity onPress={()=>this.refs.editView.show()}     style={[styles.receiveViewStyle,{marginTop:20}]}>
    <View style={[styles.receiveViewStyle,{backgroundColor:'red'}]}>
      <Text style={styles.receiveTxtStyle}>點選編輯</Text>
    </View>
  </TouchableOpacity>
最後,貼出EditView.js的原始碼
/**
 * Created by gewd on 2017/5/2.
 */
import React, {Component} from 'react';
import {
    StyleSheet,
    View,
    Text,
    TouchableOpacity,
    Animated,
    Platform,
    TextInput,
    Image,
    Dimensions
} from 'react-native';
const screenW = Dimensions.get('window').width;

export default class EditView extends Component {
    constructor(props) {
        super(props);
        this.show = this.show.bind(this);
        this._close = this._close.bind(this);
        this.state = {
            isShow: false,
            inputText: '',
            opacityAnimationValue: new Animated.Value(0),
            scaleAnimationValue: new Animated.Value(0)
        }
    }

    // 開啟對話方塊
    show() {
        this.setState({
            isShow: true,
            inputText: this.props.inputText
        });

        //Animated.parallel == 同時執行多個動畫
        Animated.parallel([
            //Animated.timing == 推動一個值按照一個過渡曲線而隨時間變化
            Animated.timing(this.state.opacityAnimationValue, {
                toValue: 1,
                duration: 200 + 100
            }),
            //Animated.spring == 產生一個基於Rebound和Origami實現的Spring動畫。它會在toValue值更新的同時跟蹤當前的速度狀態,以確保動畫連貫,比timing動畫連貫流暢
            Animated.spring(this.state.scaleAnimationValue, {
                toValue: 1,
                duration: 200,
                friction: 5
            })
        ]).start();
    }

    // 關閉對話方塊
    _close() {
        this.setState({isShow: false});
        this.state.opacityAnimationValue.setValue(0);
        this.state.scaleAnimationValue.setValue(0);
    }

    render() {
        if (!this.state.isShow) return null;

        const {ensureCallback,titleTxt} = this.props;

        return (
            // 最外層是一個半透明的黑色蒙版背景,點選的時候對話方塊也會消失
            <Animated.View style={[styles.container, {opacity: this.state.opacityAnimationValue}]}>
                <TouchableOpacity
                    activeOpacity={1}
                    style={{flex: 1, alignItems: 'center', paddingTop: 100}}
                    onPress={this._close}
                >
                    <Animated.View
                        style={[styles.contentContainer, {transform: [{scale: this.state.scaleAnimationValue}]}]}
                    >
                        <TouchableOpacity
                            activeOpacity={1}
                            style={styles.promptContainer}
                        >
                            <Text style={{fontSize: 15, color: 'black'}}>{titleTxt}</Text>
                            <View style={{flexDirection: 'row', margin: 15}}>
                                <View style={[styles.center, {width: 230}]}>
                                    <TextInput
                                        style={{fontSize: 16, color: '#999',width:150,padding:0}}
                                        value={this.state.inputText}
                                        autoFocus={true}
                                        underlineColorAndroid="transparent"
                                        onChangeText={text => this.setState({inputText:text})}
                                    />
                                </View>
                                <TouchableOpacity
                                    onPress={() => this.setState({inputText: ''})}
                                    style={[styles.center, {width: 20}]}>
                                    <Image
                                        source={require('../../../assets/img/close.png')}
                                        style={{width: 18, height: 18}}
                                    />
                                </TouchableOpacity>
                            </View>
                        </TouchableOpacity>
                        <View style={styles.buttonContainer}>
                            <TouchableOpacity
                                activeOpacity={0.75}
                                style={[styles.center, {flex: 4.5}]}
                                onPress={this._close}
                            >
                                <Text style={{fontSize: 16, color: 'black'}}>取消</Text>
                            </TouchableOpacity>
                            <View style={[styles.line]}/>
                            <TouchableOpacity
                                activeOpacity={0.75}
                                style={[styles.center, {flex: 4.5}]}
                                onPress={() => {
                                    this._close();
                                    // 子元件傳遞資料到父元件 
                                    ensureCallback(this.state.inputText);
                                }}
                            >
                                <Text style={{fontSize: 16, color: 'black'}}>確定</Text>
                            </TouchableOpacity>
                        </View>
                    </Animated.View>
                </TouchableOpacity>
            </Animated.View>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        position: 'absolute',
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
        backgroundColor: 'rgba(1, 1, 1, 0.5)'
    },
    contentContainer: {
        justifyContent: 'center',
        alignItems: 'center',
        borderColor: '#d9d9d9',
        borderWidth: 1,
        height: 150,
        width: screenW * 0.75,
        backgroundColor: 'rgb(234, 234, 235)',
        borderRadius: 5,
    },
    promptContainer: {
        height: 100,
        width: screenW * 0.75,
        alignItems: 'center',
        justifyContent: 'center'
    },
    buttonContainer: {
        height: 50,
        width: screenW * 0.75,
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-between',
        borderTopWidth: 1,
        borderColor: '#d9d9d9'
    },
    line: {
        height: 46,
        width: 1,
        backgroundColor: '#d9d9d9'
    },
    center: {
        justifyContent: 'center',
        alignItems: 'center'
    }
})