吐血总结【Android动画】必知必会

Animations

动画系统:Developer-Animation
Android框架提供了两个动画系统:属性动画和视图动画。两个动画系统都是可行的选择,但是总的来说,属性动画系统是首选的方法,因为它更灵活,提供了更多的特性。
android3.0之后 动画分类如图:
image.png

接下来我们具体讲解这三种动画:


一、视图动画:【ViewAnimation】

1. 帧动画(FrameAnimation):它允许您加载可绘制的资源,并将它们显示为一个接一个的帧。一帧一帧的动画,加载一系列Drawable资源来创建动画,这种传统动画某种程度上就是创建不同图片序列,顺序播放,就像电影胶片、幻灯片一样。(这种确实很简单,直接使用UI切好的图就行,但是图片过多,也十分容易内存溢出oom,所以一般不建议使用)

2. 补间动画(TweenAnimation) :AlphaAnimation(xml中alpha标签), RotateAnimation(xml中rotate标签), ScaleAnimation(xml中scale标签), TranslateAnimation(xml中translate标签),分别对应透明度、旋转、缩放、位移四种变化。

使用方式一:xml定制动画(文件处于res/anim/地址下)

  • alpha xml 淡入效果

<?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android"> 
<alpha 
    android:fromAlpha="0.0"  
    android:toAlpha="1.0"  
    android:duration="500"  /> 
</set> 
<!--  
    fromAlpha:开始时透明度 
    toAlpha: 结束时透明度 
    duration:动画持续时间 -->
  • rotate.xml 旋转效果:

<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate                                         
      android:interpolator="@android:anim/accelerate_decelerate_interpolator" 
    android:fromDegrees="300" 
    android:toDegrees="-360" 
    android:pivotX="10%" 
    android:pivotY="100%" 
    android:duration="10000" /> 
</set> 
<!--  
  fromDegrees   动画开始时的角度 
  toDegrees     动画结束时物件的旋转角度,正代表顺时针   
  pivotX    属性为动画相对于物件的X坐标的开始位置
----旋转中心的x坐标,浮点数或是百分比。浮点数表示相对于Object的左边缘,如5; 百分比表示相对于Object的左边缘,如5%; 另一种百分比表示相对于父容器的左边缘,如5%p; 一般设置为50%表示在Object中心
  pivotY    属性为动画相对于物件的Y坐标的开始位置 
--- 旋转中心的Y坐标,浮点数或是百分比。浮点数表示相对于Object的上边缘,如5; 百分比表示相对于Object的上边缘,如5%; 另一种百分比表示相对于父容器的上边缘,如5%p; 一般设置为50%表示在Object中心
duration  持续时间,表示从android:fromDegrees转动到android:toDegrees所花费的时间,单位为毫秒。1000ms=1s 可以用来计算速度。
interpolator表示变化率,但不是运行速度。一个插补属性,可以将动画效果设置为加速,减速,匀速,反复,反弹等。默认为开始和结束慢中间快.
其他:
android:repeatCount 重复的次数,默认为0,必须是int,可以为-1表示不停止
android:repeatMode 重复的模式,默认为restart,即重头开始重新运行,可以为reverse即从结束开始向前重新运行。在android:repeatCount大于0或为infinite时生效
android:detachWallpaper 表示是否在壁纸上运行
android:startOffset 在调用start函数之后等待开始运行的时间,单位为毫秒,若为10,表示10ms后开始运行
android:zAdjustment 表示被animated的内容在运行时在z轴上的位置,默认为normal。
normal保持内容当前的z轴顺序
top运行时在最顶层显示
bottom运行时在最底层显示
   -->
  • scale.xml 缩放效果:

<?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android"> 
<scale   
    android:interpolator= "@android:anim/decelerate_interpolator"       
    android:fromXScale="0.0"   
    android:toXScale="1.5"   
    android:fromYScale="0.0"   
    android:toYScale="1.5"   
    android:pivotX="50%"   
    android:pivotY="50%"   
    android:startOffset="0"   
    android:duration="10000"  
    android:repeatCount="1"   
    android:repeatMode="reverse"  /> 
</set> 
 
<!--  
fromXDelta,fromYDelta       起始时X,Y座标,屏幕右下角的座标是X:320,Y:480 
toXDelta, toYDelta      动画结束时X,Y的座标 --> <!--  
interpolator                    指定动画插入器
常见的有加速减速插入器         accelerate_decelerate_interpolator
加速插入器               accelerate_interpolator,
减速插入器               decelerate_interpolator。 
fromXScale,fromYScale,         动画开始前X,Y的缩放,0.0为不显示,  1.0为正常大小
toXScale,toYScale,          动画最终缩放的倍数, 1.0为正常大小,大于1.0放大
pivotX,  pivotY         动画起始位置,相对于屏幕的百分比,两个都为50%表示动画从屏幕中间开始 
startOffset,                动画多次执行的间隔时间,如果只执行一次,执行前会暂停这段时间,
                    单位毫秒 duration,一次动画效果消耗的时间,单位毫秒,
                    值越小动画速度越快 repeatCount,动画重复的计数,动画将会执行该值+1次 
                    repeatMode,动画重复的模式,reverse为反向,当第偶次执行时,动画方向会相反。
                    restart为重新执行,方向不变 -->
  • translate.xml 移动效果:

<?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate 
    android:fromXDelta="320" 
    android:toXDelta="0" 
    android:fromYDelta="480" 
    android:toYDelta="0" 
    android:duration="10000" /> 
</set> 
<!--  
fromXDelta,fromYDelta   起始时X,Y座标,屏幕右下角的座标是X:320,Y:480 
toXDelta, toYDelta     动画结束时X,Y的座标 -->

加载xml动画:

//xml动画文件
ImageView imageBg = (ImageView) content.findViewById(R.id.imageView);
Animation animation = AnimationUtils.loadAnimation(context, R.anim.splash_scale);
animation.setInterpolator(new LinearInterpolator());
imageBg.startAnimation(animation);

使用方式二、java代码方式实现定制并加载动画

xml里有的动画属性都可以通过java代码创建。根据需要定制自己的动画

//平移动画 四个参数fromX,toX,fromY,toY。而这个X,Y都是相对于当前这个控件的位置来说的。
//就是从当前控件的(X+fromX,Y+fromY)移动到(X+toX,Y+toY),
//(其中这个X和Y是最刚开始这个控件的X和Y坐标。就是动画都还没开始的时候的坐标。)
ImageView imageBg = (ImageView) content.findViewById(R.id.imageView);
TranslateAnimation anim = new TranslateAnimation(0,100,0,100);
anim.setDuration(5000);
anim.setInterpolator(new LinearInterpolator());
anim.setFillAfter(true);
imageBg .startAnimation(anim);

AnimationListener和AnimationSet

通过AnimationListener可以监听Animation的运行过程,有三个方法分别是Animation开始的时候调用,完成的时候调用,重复的时候调用。
AnimationSet,动画集合。 我们最常用的是调用其 addAnimation 将一个个不一样的动画组织到一起来,然后调用view 的 startAnimation 方法触发这些动画执行。

            AnimationSet as=new AnimationSet(true);  
            RotateAnimation al=new RotateAnimation(0,-720,Animation.RELATIVE_TO_PARENT,0.5f,Animation.RELATIVE_TO_PARENT,0.5f);  
            al.setDuration(3000);  
            al.setAnimationListener(new AnimationListener(){  
  
                public void onAnimationStart(Animation animation) {  
                    // TODO Auto-generated method stub  
                      
                }  
  
                public void onAnimationEnd(Animation animation) {  
                    // TODO Auto-generated method stub  
                      
                }  
  
                public void onAnimationRepeat(Animation animation) {  
                    // TODO Auto-generated method stub  
                      
                }  
                  
            });  
            as.addAnimation(al);  
            iv.startAnimation(as);  

setAnimation和startAnimation区别

setAnimation是告诉该控件我待会要执行什么动画,而要执行的的动画,是需要手动添加的。并且需要父view在动画快要开启的时候,调用invalidate。需要一定的条件限制。
而startAnimation告诉该控件,我要立马执行该动画,该动画就是已经设置好的动画。调用它时就会立即开始动画。


属性动画 【PropertyAnimation】

对比

ViewAnimation本身是通过改变View的绘制方式来实现动画的,View对象本身的属性值并没有改变,对象仍然停留在原始位置.
Property Animation系统可以通过修改 View 对象实际的属性值(属性会有相应的 getter、setter )来实现屏幕上的动画效果。此外,当属性值发生变化时,Views 也会自动调用 invalidate() 方法来刷新屏幕。

View Animation相当简单,不过只能支持简单的缩放、平移、旋转、透明度基本的动画和帧动画,且有一定的局限性。比如:

  • 你希望View有一个颜色的切换动画;
  • 你希望可以使用3D旋转动画;
  • 你希望当动画停止时,View的位置就是当前的位置;
  • 修改组件的背景颜色
    这些View Animation都无法做到。
    这也就是Property Animation产生的原因。
    PropertyAnimation就是通过动画的方式改变对象的属性。
    接下来我们详细讲解PropertyAnimation。
1.PropertyAnimation-沿一定时间顺序,通过改变View的属性,从而得到动画效果。使得“眼见为实”,动画之后,Object对象的属性值被实实在在的改变了。Property animation能够通过改变View对象的实际属性来实现View动画,任何时候View属性的改变,View能自动调用invalidate()来实时刷新。
2.总的来说,属性动画就是,动画的执行类来设置动画操作的对象的属性(持续时间,开始和结束的属性值,时间差值等),然后系统会根据设置的参数动态的变化对象的属性。
  • 动画的执行类:
    • ObjectAnimator
    • ValueAnimator
  • 对象的属性:
    • Duration:动画的持续时间,默认300ms
    • Time Interpolation : 时间插值【LinearInterpolator、AccelerateInterpolator、DecelerateInterpolator等】
    • Repeat count and behavior:重复次数、以及重复模式;可以定义重复多少次;重复时从头开始,还是反向。
    • Animator sets : 动画集合,你可以定义一组动画,一起执行或者顺序执行。
  • 其他重要的类:
    • AnimatorInflater 用户加载属性动画的xml文件
    • TypeEvaluator 类型估值,主要用于设置动画操作属性的值。
    • TimeInterpolator 时间插值
    • AnimatorSet 用于控制一组动画的执行:线性,一起,每个动画的先后执行等。
    • PropertyValuesHolder 针对同一个对象多个属性,同时作用多种动画

接下来讲解属性动画在实践中的使用:

【关于Interpolator、TypeEvaluator】

参看这篇文章:Android动画之Interpolator插补器和TypeEvaluator估值器

【关于Animator监听器 】

Property Animation提供了Animator.AnimatorListener和Animator.AnimatorUpdateListener两个监听器用于动画在播放过程中的重要动画事件。下面是两个监听器接口和方法的一些介绍和说明:

  • Animator.AnimatorListener:
    onAnimationStart() —— 动画开始时调用;
    onAnimationEnd() —— 动画结束时调用;
    onAnimationRepeat() —— 动画循环播放时调用;
    onAnimationCancel() —— 动画被取消时调用。不管终止的方式如何,被取消的动画仍然会调onAnimationEnd();
    • 此监听器还有一个子类AnimatorListenerAdapter,两者的区别是AnimatorListener的方法需要全部实现,而AnimatorListenerAdapter可以选择我们所需要的方法实现,比如通常情况我们只关心onAnimationEnd。
       animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                
            }

            @Override
            public void onAnimationEnd(Animator animation) {

            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
  • Animator.AnimatorUpdateListener(通常和ValueAnimator搭配使用):
    onAnimationUpdate() —— 动画每播放一帧时调用。
    在动画过程中,可侦听此事件来获取并使用 ValueAnimator 计算出来的属性值;
    利用传入事件的 ValueAnimator 对象,调用其 getAnimatedValue() 方法即可获取当前的属性值。如果使用ValueAnimator来实现动画的话 ,则必需实现此侦听器。

【关于AnimatorInflater】

上面讲到ViewAnimation有许多xml加载,当然PropertyAnimation也可以对应xml加载,位置为res/animator/
animator.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:propertyName="alpha"
android:valueFrom="0.1"
android:valueTo="1.0"
android:valueType="floatType" />

调用就用到了AnimatorInflater类了

 Animator animator = AnimatorInflater.loadAnimator(getApplicationContext(), R.animator.animator);
animator.setTarget(imageView);
animator.start();

组合动画也可以xml加载

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
orderring属性设置为together,还有另一个值:sequentially(表示一个接一个执行)。
    <objectAnimator
        android:duration="1000"
        android:propertyName="scaleX"
        android:valueFrom="1"
        android:valueTo="0.5" />
    <objectAnimator
        android:duration="1000"
        android:propertyName="scaleY"
        android:valueFrom="1"
        android:valueTo="0.5" />
</set>

【关于动画属性值】

在 Android 3.0 中给 View 增加了一些新的属性以及相应的 getter、setter 方法。Property Animation系统可以通过修改 View 对象实际的属性值来实现屏幕上的动画效果。此外,当属性值发生变化时,Views 也会自动调用 invalidate() 方法来刷新屏幕。 View 类中新增的便于实现 property 动画的属性包括:

  • translationX和translationY:增量控制view的坐标偏移
  • rotation、rotationX 和 rotationY:这三个属性控制着 2D 旋转角度(rotation属性)和围绕某枢轴点的 3D 旋转角度;
  • scaleX、scaleY:控制view绕支点进行2D缩放
  • x 和 y:这是指 View 在容器内的最终位置,等于 View 左上角相对于容器的坐标加上 translationX 和 translationY 后的值;
  • alpha:表示 View 的 alpha 透明度。缺省值为 1 (不透明),为 0 则表示完全透明(看不见);

上述我们提到的属性也就是接下来ObjectAnimator使用中的第二个参数,操作对象的属性值。

当然我们也可以自定义view的动画属性,但必须带有一个 setter 方法(以骆驼拼写法命名),格式类似 set<propertyName>()。 因为 ObjectAnimator 会在动画期间自动更新属性值,它必须能够用此 setter 方法访问到该属性。getter不是必须,而什么时候必须需要getter方法呢?如果在调用 ObjectAnimator 的某个工厂方法时,我们只为 values... 参数指定了一个值,那此值将被认定为动画属性的结束值。 这样的话,动画显示的属性必须带有一个 getter 方法,用于获取动画的起始值。 此 getter 方法必须以get<propertyName>()的格式命名。

【使用ObjectAnimator实现动画】

  • 关于使用ObjectAnimator实现动画的步骤和实践

1.通过调用ofFloat()、ofInt()等方法创建ObjectAnimator对象,并设置目标对象、需要改变的目标属性名、初始值和结束值;
2.设置动画的持续时间、是否重复及重复次数等属性;
3.启动动画。

ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(imageView, "translationX", 300);
objectAnimator1.setInterpolator(new AccelerateInterpolator());
objectAnimator1.setDuration(2000);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);//Animation.INFINITE 表示重复多次
objectAnimator.setRepeatMode(ValueAnimator.RESTART);//RESTART表示从头开始,REVERSE表示从末尾倒播
objectAnimator1.start();

1 . 第一个参数:设置目标对象,即操纵的view
2 . 第二个参数:设置操作的动画的属性值(见上面讲解的动画属性值)
3 . 第三个参数:可变数组参数 (初始值,中间值,结束值)。可以有一个到N个,如果是一个值的话默认这个值是动画过渡值的结束值。如果有N个值,动画就在这N个值之间过渡。

【使用ValueAnimator实现动画】

  • 关于使用ValueAnimator实现动画的步骤及实践

那一般使用ValueAnimator实现动画分为以下七个步骤:
1. 调用ValueAnimation类中的ofInt(int...values)、ofFloat(String propertyName,float...values)等静态方法实例化ValueAnimator对象,并设置目标属性的属性名、初始值或结束值等值;
2.调用addUpdateListener(AnimatorUpdateListener mListener)方法为ValueAnimator对象设置属性变化的监听器;
3.创建自定义的Interpolator,调用setInterpolator(TimeInterpolator value)为ValueAniamtor设置自定义的Interpolator;(可选,不设置默认为缺省值)
4.创建自定义的TypeEvaluator,调用setEvaluator(TypeEvaluator value)为ValueAnimator设置自定义的TypeEvaluator;(可选,不设置默认为缺省值)
5.在AnimatorUpdateListener 中的实现方法为目标对象的属性设置计算好的属性值。
6.设置动画的持续时间、是否重复及重复次数等属性;
7.为ValueAnimator设置目标对象并开始执行动画。

【ObjectAnimator和ValueAnimator的对比】

  • ObjectAnimator类作为ValueAnimator的子类不仅继承了ValueAnimator的所有方法和特性,并且还封装很多实用的方法,方便开发人员快速实现动画。
  • ObjectAnimator属性值会自动更新,使用ObjectAnimator实现动画不需要像ValueAnimator那样必须实现 ValueAnimator.AnimatorUpdateListener ,因此实现任意对象的动画显示就更加容易了。
  • ValueAnimator并没有在属性上做操作,不需要操作的对象的属性一定要有getter和setter方法,你可以自己根据当前动画的计算值,来操作任何属性。

【PropertyValuesHolder】

针对同一个对象多个属性,同时作用多种动画

PropertyValuesHolder propertyValuesHolder1 = PropertyValuesHolder.ofFloat("translationX", 300f);
PropertyValuesHolder propertyValuesHolder2 = PropertyValuesHolder.ofFloat("alpha", 1f, 0.5f);
PropertyValuesHolder propertyValuesHolder3 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
PropertyValuesHolder propertyValuesHolder4 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
ObjectAnimator.ofPropertyValuesHolder(imageView, propertyValuesHolder1, propertyValuesHolder2, propertyValuesHolder3, propertyValuesHolder4)
        .setDuration(5000).start();

【关于AnimatorSet动画集合】

可以调用其playTogether(同时执行)、playSequentially(顺序执行)、play、before、with、after 等方法设置动画的执行顺序,然后调用其start 触发动画执行。

ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0.5f);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(imageView, "translationY", 300);
ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(imageView, "scaleX", 1f, 0, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(5000);
animatorSet.playTogether(objectAnimator1, objectAnimator2,objectAnimator3);
animatorSet.start();
AnimatorSet 和 AnimationSet 区别

AnimationSet 与 AnimatorSet 最大的不同在于:
AnimationSet 使用的是 Animation 子类、AnimatorSet 使用的是 Animator 的子类。
-----Animation 是针对视图外观的动画实现,动画被应用时外观改变但视图的触发点不会发生变化,还是在原来定义的位置。 (也就是作用于视图动画)
-----Animator 是针对视图属性的动画实现,动画被应用时对象属性产生变化,最终导致视图外观变化。(也就是作用于属性动画)

AnimatorSet 和 PropertyValuesHolder 区别

--- AnimatorSet可以将作用于多个view多个属性的动画集合起来,而PropertyValuesHolder针对同一个对象多个属性。
--- AnimatorSet多了playTogether(同时执行)、playSequentially(顺序执行)、play(objectAnimator1).with(objectAnimator2)、before、after这些方法协同工作。

【关于View的animate方法】

Android 3.0后,谷歌给View增加animate方法直接驱动属性动画。

可以直接这样写就完成了imageview 的透明度有1-0.5以及位置移动到300动画:
imageView.animate()
.alpha(0.5f)
.y(300)
.setDuration(2000);

此后在SDK12,SDK16又分别添加了withStartAction和withEndAction用于在动画前,和动画后执行一些操作。
当然也可以.setListener(listener)等操作
                imageView.animate()//
                .alpha(0)//  由1到0
                .y(mScreenHeight / 2)
                .setDuration(1000)  
                // need API 12  
                .withStartAction(new Runnable()  
                {  
                    @Override  
                    public void run()  
                    {  
                    }  
                    // need API 16  
                }).withEndAction(new Runnable()  
                {  
  
                    @Override  
                    public void run()  
                    {  
              
                        runOnUiThread(new Runnable()  
                        {  
                            @Override  
                            public void run()  
                            {  
                                imageView.setY(0);  
                                imageView.setAlpha(1.0f);  
                            }  
                        });  
                    }  
                }).start();  
其实上面的这段操作通过PropertyValuesHolder也可以实现:
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f,  
            0f, 1f);  
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 0,  
            mScreenHeight / 2, 0);  
ObjectAnimator.ofPropertyValuesHolder(imageView, pvhX, pvhY).setDuration(1000).start();  

【布局动画】

Android动画学习之为ViewGroup中的Layout设置动画实例

之后会总结出一篇动画实践的文章,汇总一些好看的常用的动画。

最后的最后不要吝啬你的小心心❤️ 你们的鼓励是我最大的动力~

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

推荐阅读更多精彩内容