React Native 动画(Animated)

在上篇文章中介绍了 LayoutAnimation 的用法,本篇文章就来详细介绍一下 Animated 的用法。

Animated 简介

Animated 库用于创建更精细的交互控制的动画,它使得开发者可以非常容易地实现各种各样的动画和交互方式,并且具备极高的性能。Animated 旨在以声明的形式来定义动画的输入与输出,在其中建立一个可配置的变化函数,然后使用简单的 start/stop 方法来控制动画按顺序执行。

Animated 提供了两种类型的值:

  • Animated.Value() 用于单个值。
  • Animated.ValueXY() 用于矢量值。

Animated.Value() 可以绑定到样式或是其他属性上,也可以进行插值运算。单个 Animated.Value() 可以用在任意多个属性上。

Animated 用于创建动画的主要方法:

  • Animated.timing():最常用的动画类型,使一个值按照一个过渡曲线而随时间变化。
  • Animated.spring():弹簧效果,基础的单次弹跳物理模型实现的 spring 动画。
  • Animated.decay():衰变效果,以一个初始的速度和一个衰减系数逐渐减慢变为0。

Animated 实现组合动画的主要方式:

  • Animated.parallel():同时开始一个动画数组里的全部动画。默认情况下,如果有任何一个动画停止了,其余的也会被停止。可以通过stopTogether 选项设置为 false 来取消这种关联。
  • Animated.sequence():按顺序执行一个动画数组里的动画,等待一个完成后再执行下一个。如果当前的动画被中止,后面的动画则不会继续执行。
  • Animated.stagger():一个动画数组,传入一个时间参数来设置队列动画间的延迟,即在前一个动画开始之后,隔一段指定时间才开始执行下一个动画里面的动画,并不关心前一个动画是否已经完成,所以有可能会出现同时执行(重叠)的情况。

Animated 封装了四个可以动画化的组件:

  • Animated.View
  • Animated.Text
  • Animated.Image
  • Animated.ScrollView

也可以使用 Animated.createAnimatedComponent() 来封装你自己的组件(用 Animated.View 包裹可以达到同样的效果)。

合成动画值:

  • Animated.add()
  • Animated.divide()
  • Animated.modulo()
  • Animated.multiply()

可以使用加减乘除以及取余等运算来把两个动画值合成为一个新的动画值。

插值函数:

  • interpolate():将输入值范围转换为输出值范围。

譬如:把0-1映射到0-10。

接下来,我们结合一个个的例子介绍它们的用法:

1. Animated.timing()

最常用的动画类型,使一个值按照一个过渡曲线而随时间变化,格式如下:
Animated.timing(animateValue, conf<Object>),conf参数格式:

{
  duration:动画持续的时间(单位是毫秒),默认为500。
  easing:一个用于定义曲线的渐变函数。Easing模预置了 linear、ease、elastic、bezier 等诸多缓动特性。iOS默认为Easing.inOut(Easing.ease),。
  delay:开始动画前的延迟时间(毫秒),默认为0。
}

一个简单的例子:

export default class Opacity extends Component {
    constructor(props) {
        super(props)

        this.state = {
            fadeOutOpacity: new Animated.Value(1),
        }

        this.fadeOutAnimated = Animated.timing(
            this.state.fadeOutOpacity,
            {
                toValue: 0,  //透明度动画最终值
                duration: 3000,   //动画时长3000毫秒
                easing: Easing.linear,
            }
        );
    }

    _startAnimated() {
        this.fadeOutAnimated.start(() => this.state.fadeOutOpacity.setValue(1));
    }

    render(){
        return (
            <View style={styles.mainStyle}>
                 <Animated.View style={{width: 200, height: 300, opacity: this.state.fadeOutOpacity}}>
                    <Image ref="image" style={{width:200,height:300}}
                           source={{uri:'beauty.jpg'}}>
                    </Image>
                 </Animated.View>

                <TouchableOpacity style={styles.touchStyle} onPress={this._startAnimated.bind(this)}>
                    <Text style={{width:200,height:100,textAlign:'center',lineHeight:100}}>点击开始动画</Text>
                </TouchableOpacity>
            </View>
        );
    }
}

效果图:


image.gif

上面的代码做了下面这些事情:

  • 1.首先实例化动画的初始值 fadeOutOpacity,作为图片的透明度值。
  • 2.然后定义了一个 Animated.timing() 动画事件,并配置相应的参数,把它赋予 this.fadeOutAnimated 变量,在后续可以使用 .start() 或 .stop() 方法来开始/停止该动画。
  • 3.把动画添加到在 <Animate.View></Animate.View> 上,我们把实例化的动画初始值传入 style 中的 opacity。
  • 4.最后我们点击按钮,调用 this.fadeOutAnimated.start() 方法开启动画,在这里将 () => this.state.fadeOutOpacity.setValue(1) 传给了 start(),用于在一次动画完成之后把图片的透明度再次设置为1,这也是创建无穷动画的方式。

interpolate() 这个函数很强大,实现了数值大小、单位的映射转换,下面我们结合插值函数:interpolate() 搞些事情:

export default class Mixture extends Component {

    constructor(props) {
        super(props)

        this.state = {
            animatedValue: new Animated.Value(0),
        }

        this.rotateAnimated = Animated.timing(
            this.state.animatedValue,
            {
                toValue: 1,
                duration: 3000,
                easing: Easing.in,
            }
        );
    }

    _startAnimated() {
        this.state.animatedValue.setValue(0);
        this.rotateAnimated.start(() => this._startAnimated());
    }

    render(){

        const rotateZ = this.state.animatedValue.interpolate({
            inputRange: [0, 1],
            outputRange: ['0deg', '360deg']
        });

        const opacity = this.state.animatedValue.interpolate({
            inputRange: [0, 0.5, 1],
            outputRange: [0, 1, 0]
        });

        const rotateX = this.state.animatedValue.interpolate({
            inputRange: [0, 0.5, 1],
            outputRange: ['0deg', '180deg', '0deg']
        });

        const textSize = this.state.animatedValue.interpolate({
            inputRange: [0, 0.5, 1],
            outputRange: [18, 32, 18]
        });

        const marginLeft = this.state.animatedValue.interpolate({
            inputRange: [0, 0.5, 1],
            outputRange: [0, 200, 0]
        });

        return (
            <View style={styles.mainStyle}>

                <Animated.View
                    style={{
                        marginTop: 10,
                        width: 100,
                        height: 100,
                        transform: [
                            {rotateZ:rotateZ},
                        ]
                    }}
                >
                    <Image style={{width:100,height:100}}
                           source={{uri:'out_loading_image.png'}}>
                    </Image>
                </Animated.View>

                <Animated.View
                    style={{
                        marginTop: 10,
                        width: 100,
                        height: 100,
                        opacity:opacity,
                        backgroundColor:'red',
                    }}
                />

                <Animated.Text
                    style={{
                        marginTop: 10,
                        width:100,
                        fontSize: 18,
                        color: 'white',
                        backgroundColor:'red',
                        transform: [
                            {rotateX:rotateX},
                        ]
                    }}
                >
                    窗外风好大,我没有穿褂。
                </Animated.Text>

                <Animated.Text
                    style={{
                        marginTop: 10,
                        height: 100,
                        lineHeight: 100,
                        fontSize: textSize,
                        color: 'red'
                    }}
                >
                    IAMCJ嘿嘿嘿
                </Animated.Text>

                <Animated.View
                    style={{
                        marginTop: 10,
                        width: 100,
                        height: 100,
                        marginLeft:marginLeft,
                        backgroundColor:'red',
                    }}
                />

                <TouchableOpacity style={styles.touchStyle} onPress={this._startAnimated.bind(this)}>
                    <Text style={{width:200,height:100,textAlign:'center',lineHeight:100}}>点击开始动画</Text>
                </TouchableOpacity>
            </View>
        );
    }
}

效果图:


Mixture.gif

其中 transform 是一个变换数组,常用的有 scale, scaleX, scaleY, translateX, translateY, rotate, rotateX, rotateY, rotateZ,其他用法与上面相同,在这就不多BB了。

2. Animated.spring()

弹簧效果,基础的单次弹跳物理模型实现的 spring 动画,格式如下:
Animated.spring(animateValue, conf<Object>),conf参数格式:

{
  friction: 控制“弹跳系数”、夸张系数,默认为7。
  tension: 控制速度,默认为40。
  speed: 控制动画的速度,默认为12。
  bounciness: 反弹系数,默认为8。
}

一个 Animated.spring() 的例子:

export default class AnimatedSpring extends Component {

    constructor(props) {
        super(props);

        this.state = {
            springValue: new Animated.Value(1),
        };

        this.springAnimated = Animated.spring(
            this.state.springValue,
            {
                toValue: 1,
                friction: 2,    //弹跳系数
                tension: 10,   // 控制速度
            }
        );
    }

    _startAnimated() {
        this.state.springValue.setValue(0.1);
        this.springAnimated.start();
    }

    render(){
        return (
            <View style={styles.mainStyle}>

                <Animated.View
                    style={{
                        width: 282,
                        height: 51,
                        transform:[
                            {scale:this.state.springValue}
                        ]
                    }}
                >
                    <Image ref="image" style={{width:282,height:51}}
                           source={{uri:'appstore_comment_image.png'}}>
                    </Image>
                </Animated.View>

                <TouchableOpacity style={styles.touchStyle} onPress={this._startAnimated.bind(this)}>
                    <Text style={{width:200,height:100,textAlign:'center',lineHeight:100}}>点击开始动画</Text>
                </TouchableOpacity>
            </View>
        );
    }
}

效果图:


AnimatedSpring.gif

上面的代码做了下面这些事情:

  • 1.首先实例化动画的初始值 springValue,作为图片的初始 scale 大小。
  • 2.然后定义了一个 Animated. spring() 动画事件,并配置相应的参数,把它赋予 this. springAnimated 变量。
  • 3.把动画添加到在 <Animate.View></Animate.View> 上,我们把实例化的动画初始值传入 style 中的 transform:[{scale:this.state.springValue}]。
  • 4.最后我们点击按钮,在动画开启之前把 scale 调到0.1,然后调用 this. springAnimated.start() 方法开启动画。

最近写 native 也有一个类似的需求,我的实现方式是先放大一个比例如1.05,然后动画完成之后再添加动画缩小到正常比例,个人感觉实现起来和这个复杂度差不多,而在 native 可以根据 position 和锚点设置图片是从哪个点以及自身的哪个方向开始放大,例如在屏幕右上角出现图片,并且是从图片右上角开始放大这样,我写了一个简单易用的工具类,设置位置和弹出方向即可,添加任意内容,几行代码就可以搞定,有需要的话点击这里获取。

3. Animated.decay()

衰变效果,以一个初始的速度和一个衰减系数逐渐减慢变为0,格式如下:
Animated.decay(animateValue, conf<Object>),conf参数格式:

{
  velocity: 起始速度,必填参数。
  deceleration: 速度衰减比例,默认为0.997。
}

一个 Animated.decay() 的例子:

export default class AnimatedDecay extends Component {

    constructor(props) {
        super(props);

        this.state = {
            decayValue: new Animated.ValueXY({x:0,y:0}),
        };

        this.decayAnimated = Animated.decay(
            this.state.decayValue,
            {
                velocity: 5,       // 起始速度,必填
                deceleration: 0.95,  // 速度衰减比例,默认为0.997
            }
        );
    }

    _startAnimated() {
        this.decayAnimated.start();
    }

    render(){
        return (
            <View style={styles.mainStyle}>

                <Animated.View
                    style={{
                        width: 100,
                        height: 150,
                        transform:[
                            {translateX: this.state.decayValue.x}, // x轴移动
                            {translateY: this.state.decayValue.y}, // y轴移动
                        ]
                    }}
                >
                    <Image ref="image" style={{width:100,height:150}}
                           source={{uri:'beauty.jpg'}}>
                    </Image>
                </Animated.View>

                <TouchableOpacity style={styles.touchStyle} onPress={this._startAnimated.bind(this)}>
                    <Text style={{width:200,height:100,textAlign:'center',lineHeight:100}}>点击开始动画</Text>
                </TouchableOpacity>
            </View>
        );
    }
}

效果图:
AnimatedDecay.gif

嗯,就这个样子。

这个就不说了,使用方式和 Animated.spring()、Animated.timing() 类似,如果想要使用的话,最好能了解一些数学的知识。

接下来做些组合动画:

4. Animated.parallel()

同时开始一个动画数组里的全部动画。默认情况下,如果有任何一个动画停止了,其余的也会被停止。可以通过stopTogether 选项设置为 false 来取消这种关联,格式如下:
Animated.parallel(Animates<Array>, [conf<Object>]):,conf参数格式:

{
  stopTogether: false
}

第一个参数接受一个元素为动画的数组,通过执行 start() 方法可以并行执行该数组中的所有方法。如果数组中任意动画被中断的话,该数组内对应的全部动画会一起停止,不过我们可以通过第二个(可选)参数 conf 中的 stopTogether 来取消这种牵连特性。

来个酷炫的例子:

export default class AnimatedParallel extends Component {

    constructor(props) {
        super(props);

        this.state = {
            dogOpacityValue: new Animated.Value(1),
            dogACCValue : new Animated.Value(0)
        };

        this.parallelAnimated = Animated.parallel(
            [
                Animated.timing(
                    this.state.dogOpacityValue,
                    {
                        toValue: 1,
                        duration: 1000,
                    }
                ),
                Animated.timing(
                    this.state.dogACCValue,
                    {
                        toValue: 1,
                        duration: 2000,
                        easing: Easing.linear,
                    }
                ),
            ],
            {
                stopTogether: false
            }
        );
    }

    _startAnimated() {
        this.state.dogOpacityValue.setValue(0);
        this.state.dogACCValue.setValue(0);
        this.parallelAnimated.start();
    }

    render(){

        //透明度
        const dogOpacity = this.state.dogOpacityValue.interpolate({
            inputRange: [0,0.2,0.4,0.6,0.8,1],
            outputRange: [0,1,0,1,0,1]
        });

        //项链上面
        const neckTop = this.state.dogACCValue.interpolate({
            inputRange: [0, 1],
            outputRange: [350, 235]
        });

        //眼镜左边
        const left = this.state.dogACCValue.interpolate({
            inputRange: [0, 1],
            outputRange: [-120, 127]
        });

        //眼镜旋转
        const rotateZ = this.state.dogACCValue.interpolate({
            inputRange: [0, 1],
            outputRange: ['0deg', '360deg']
        });

        return (
            <View style={styles.mainStyle}>

                {/*// 狗头*/}
                <Animated.View
                    style={{
                        width: 375,
                        height: 240,
                        opacity:dogOpacity,
                    }}
                >
                    <Image ref="image" style={{width:375,height:242}}
                           source={{uri:'dog.jpg'}}>
                    </Image>
                </Animated.View>

                {/*// 项链*/}
                <Animated.View
                    style={{
                        width: 250,
                        height: 100,
                        position: 'absolute',
                        top:neckTop,
                        left:93,
                    }}
                >
                    <Image ref="image" style={{width:250,height:100,resizeMode:'stretch'}}
                           source={{uri:'necklace.jpg'}}>
                    </Image>
                </Animated.View>

                <View
                    style={{
                        width: 375,
                        height: 200,
                        backgroundColor:'white',
                    }}
                />

                {/*// 眼镜*/}
                <Animated.View
                    style={{
                        width: 120,
                        height: 25,
                        position: 'absolute',
                        top:160,
                        left:left,
                        transform:[
                            {rotateZ:rotateZ}
                        ],
                    }}
                >
                    <Image ref="image" style={{width:120,height:25,resizeMode:'stretch'}}
                           source={{uri:'glasses.png'}}>
                    </Image>
                </Animated.View>

                <TouchableOpacity style={styles.touchStyle} onPress={this._startAnimated.bind(this)}>
                    <Text style={{width:200,height:100,textAlign:'center',lineHeight:100}}>点击开始动画</Text>
                </TouchableOpacity>
            </View>
        );
    }
}

效果图:


AnimatedParallel.gif

上面的代码做了下面这些事情:

  • 1.首先实例化动画的初始值 dogOpacityValue,作为狗头图片的初始透明度,实例化动画的初始值 dogACCValue,作为墨镜、项链图片的初始值。
  • 2.然后定义了两个 Animated. timing() 动画事件,并配置相应的参数,第一个是对应狗头图片的透明度,第二个对应的是墨镜、金链子的 timing 动画,然后把这两个动画事件放入到 Animated.parallel() 的动画数组内,最后把 Animated.parallel() 赋予 this.parallelAnimated 变量,用法其实和单独的动画事件差不多。
  • 3.在 render 方法中,创建了 dogOpacity、left、rotateZ、neckTop 等变量,并调用了 interpolate() 方法对它们一一映射,分别对应的是透明度、金链子 top 的距离、墨镜 left 距离、眼镜旋转的角度,结合上面代码查看具体信息。
  • 4.把动画添加到在不同的 <Animate.View></Animate.View> 上,我们把实例化的动画初始值传入 style 中。
  • 5.最后动画开启之前把 dogOpacityValue、dogACCValue 调到0,然后调用 this. parallelAnimated.start() 方法开启动画。
5.Animated.sequence()

按顺序执行一个动画数组里的动画,等待一个完成后再执行下一个。如果当前的动画被中止,后面的动画则不会继续执行,格式如下:
Animated.sequence(Animates<Array>)
刚才知道了 Animated.parallel() 的用法,那么使用起来 Animated.sequence() 会上手更快,他们用法几乎一样,区别就在于 Animated.sequence() 顺序执行一个动画数组里的动画,可以理解为同步操作,而 Animated.parallel() 是异步操作,这两种方式并没有好与坏,只有适合不适合,例如下面的这个效果就是适合使用 Animated.sequence() 来完成,而使用 Animated.parallel() 的话则不行。

再来一个酷炫的效果:

export default class AnimatedSequence extends Component {

    constructor(props) {
        super(props);

        this.state = {
            turnRotateValue: new Animated.Value(0),
            turnShakeValue : new Animated.Value(0),
            macValue : new Animated.Value(0),
        };

        this.sequenceAnimated = Animated.sequence(
            [
                Animated.timing(
                    this.state.turnRotateValue,
                    {
                        toValue: 1,
                        duration: 5000,
                        easing: Easing.in,
                    }
                ),
                Animated.timing(
                    this.state.turnShakeValue,
                    {
                        toValue: 1,
                        duration: 500,
                        easing: Easing.in,
                        delay:300,
                    }
                ),
                Animated.spring(
                    this.state.macValue,
                    {
                        toValue: 1,
                        friction: 3,
                        tension:10,
                    }
                ),
            ]
        );
    }

    _startAnimated() {
        this.sequenceAnimated.start();
    }

    render(){

        //转盘旋转
        const turnRotateZ = this.state.turnRotateValue.interpolate({
            inputRange: [0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1],
            outputRange: [
                '0deg',
                '180deg',
                '360deg',
                '720deg',
                '1080deg',
                '1800deg',
                '2520deg',
                '3060deg',
                '3420deg',
                '3600deg',
                '3690deg',
            ]
        });

        //转盘震动
        const marginLeft = this.state.turnShakeValue.interpolate({
            inputRange: [0,0.2,0.4,0.6,0.8,1],
            outputRange: [0,-40,40,-40,40,0]
        });

        //MacTop
        const macTop = this.state.macValue.interpolate({
            inputRange: [0, 1],
            outputRange: [-200,150]
        });

        return (
            <View style={styles.mainStyle}>

                {/*// 转盘*/}
                <Animated.View
                    style={{
                        width: 300,
                        height: 300,
                        marginLeft:marginLeft,
                        transform:[
                            {rotateZ:turnRotateZ}
                        ],
                    }}
                >
                    <Image ref="image" style={{width:300,height:300}}
                           source={{uri:'turntable.jpg'}}>
                    </Image>
                </Animated.View>

                {/*// mac*/}
                <Animated.View
                    style={{
                        width: 300,
                        height: 204,
                        position: 'absolute',
                        top:macTop,
                        left:screenW / 2 - 150,
                    }}
                >
                    <Image ref="image" style={{width:300,height:204}}
                           source={{uri:'macpro.png'}}>
                    </Image>
                </Animated.View>

                <TouchableOpacity style={styles.touchStyle} onPress={this._startAnimated.bind(this)}>
                    <Text style={{width:200,height:100,textAlign:'center',lineHeight:100}}>点击开始动画</Text>
                </TouchableOpacity>
            </View>
        );
    }
}

效果图:


AnimatedSequence.gif

这个动画的流程分为三个部分:

  • 1.转盘先由慢到快转了5秒钟;
  • 2.在转盘转圈停止之后,在短暂的延迟之后,震动了几下;
  • 3.最后 MacBook Pro 由天而降,惊不惊喜,意不意外,大奖来了!啊哈哈哈哈...

实现步骤就不一一解释了,对应上面 Animated.parallel() 的例子就可以看明白,主要区别在于动画的实现思路,这个动画就很好的利用了 Animated.sequence() 顺序执行的特性。

6.Animated.stagger()

一个动画数组,传入一个时间参数来设置队列动画间的延迟,即在前一个动画开始之后,隔一段指定时间才开始执行下一个动画里面的动画,并不关心前一个动画是否已经完成,所以有可能会出现同时执行(重叠)的情况,其格式如下:
Animated.stagger(delayTime<Number>, Animates<Array>)
其中 delayTime 为指定的延迟时间(毫秒),第二个和上面两个一样传入一个动画事件数组。

一个简单的红蓝块移动例子帮助理解:

export default class AnimatedStagger extends Component {

    constructor(props) {
        super(props);

        this.state = {
            redValue: new Animated.Value(0),
            blueValue : new Animated.Value(0),
        };

        this.staggerAnimated = Animated.stagger(2000,
            [
                Animated.timing(
                    this.state.redValue,
                    {
                        toValue: 1,
                        duration: 5000,
                        easing: Easing.in,
                    }
                ),
                Animated.timing(
                    this.state.blueValue,
                    {
                        toValue: 1,
                        duration: 5000,
                        easing: Easing.in,
                    }
                ),
            ]
        );
    }

    _startAnimated() {
        this.staggerAnimated.start();
    }

    render(){

        const redMarginLeft = this.state.redValue.interpolate({
            inputRange: [0,1],
            outputRange: [0,200]
        });

        const blueMarginLeft = this.state.blueValue.interpolate({
            inputRange: [0,1],
            outputRange: [0,200]
        });

        return (
            <View style={styles.mainStyle}>

                {/*// 红色*/}
                <Animated.View
                    style={{
                        width: 100,
                        height: 100,
                        backgroundColor:'red',
                        marginLeft:redMarginLeft,
                    }}
                >
                </Animated.View>


                {/*// 蓝色*/}
                <Animated.View
                    style={{
                        width: 100,
                        height: 100,
                        backgroundColor:'blue',
                        marginLeft:blueMarginLeft,
                    }}
                >
                </Animated.View>

                <TouchableOpacity style={styles.touchStyle} onPress={this._startAnimated.bind(this)}>
                    <Text style={{width:200,height:100,textAlign:'center',lineHeight:100}}>点击开始动画</Text>
                </TouchableOpacity>
            </View>
        );
    }
}

效果图:


AnimatedStagger.gif

这个动画的流程:
流程就是红的先移动,然后两秒后蓝的移动!(⊙﹏⊙)

看了这个例子,应该很容易明白 Animated.stagger() 的作用,在此不多做解释了。

OK,就到这吧,看完这篇文章,一般的动画效果应该都可以实现了,想了解 Animated 更多的特性可以去Animateddemo在这里。

参考:
ReactNative入门 —— 动画篇(下)
【译】详解React Native动画

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容