Android 动画的使用

最新总结,请移步 Android 动画解析

0x01 分类

视图动画(逐帧动画/补间动画)
属性动画
转场动画

1. 逐帧动画

frame-by-frame animation

包位置:android.graphics.drawable
类名:AnimationDrawable

将多张图片,连贯起来进行播放,类似动画片的工作性质。

2. 补间动画

tweened animation

包位置:android.view.animation
类名:Animation

仅对 View 进行淡入淡出 alpha、平移 translate、旋转 rotate、缩放 scale 四种操作。不可对 View 的属性做操作,例如颜色、长度等

AlphaAnimation
TraslateAnimation
RotateAnimation
ScaleAnimation
AnimationSet

Interpolator
AnimationUtils

3. 属性动画

Property Animator

包位置:android.animation
类名:Animator
Android3.0引入,弥补了补间动画的缺陷(只能对 View 进行操作)

ViewPropertyAnimator(包位于android.view,内部使用ValueAnimator)

ObjectAnimator
ValueAnimator

AnimatorInflater

PropertyValuesHolder 对同一动画属性操作
AnimatorSet 组合多个动画

Interpolator
TypeEvaluator

0x02 使用

1. 逐帧动画

方式1 XML实现

animation-list 标签指定;
oneshot 是否仅播放一次 false;
duration 每一帧的显示时长

# 注意位置 res/drawable/frame_animation.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/img_0"
        android:duration="50" />
    <item
        android:drawable="@drawable/img_1"
        android:duration="50" />
    <item
        android:drawable="@drawable/img_2"
        android:duration="50" />
</animation-list >

--- // 代码加载
ImageView imageView  = new ImageView(this);
imageView.setImageResource(R.drawable.frame_animation);
AnimationDrawable animationDrawable = (AnimationDrawable)imageView.getDrawable();
// 播放开始
animationDrawable.start();
// 播放结束
animationDrawable.stop();

方式2 代码实现

AnimationDrawable frameAnimation = new AnimationDrawable();
// 参数1 图片,参数2 出现时间
frameAnimation.addFrame(getResources().getDrawable(R.drawable.img_0), 50);
frameAnimation.addFrame(getResources().getDrawable(R.drawable.img_1), 50);
frameAnimation.addFrame(getResources().getDrawable(R.drawable.img_2), 50);
// 是否重复
frameAnimation.setOneShot(false); 
// 播放开始
frameAnimation.start();
// 播放结束
frameAnimation.stop();

优缺点

缺点:使用大量图片,易导致OOM
建议:直接使用 gif,大小可以明显改善

2. 补间动画

方式1 XML 实现

  1. 平移动画
# 注意位置 res/anim/view_animation.xml
<translate
 android:fromXDelta="0" // 水平x方向的起始值
 android:fromYDelta="0" // 竖直y方向的起始值
 android:toXDelta="500" // 水平x方向的结束值
 android:toYDelta="500" // 竖直y方向的结束值
/>
  1. 透明动画
<alpha
 android:fromAlpha="1.0" // 开始透明度 0.0-1.0
 android:toAlpha="0.0" //结束透明度 0.0-1.0
/>
  1. 旋转动画
<rotate
 android:fromDegrees="0" // 开始的角度
 android:toDegrees="180" // 结束的角度
 android:pivotX="20%" // 旋转中心点 x 坐标
 android:pivotY="20%" // 旋转中心点 y 坐标
/>
  1. 缩放动画
<scale
 android:fromXScale="0.0" // 起始缩放x的倍数
 android:fromYScale="0.0" // 起始缩放y的倍数
 android:toXScale="1.0" // 结束缩放x的倍数
 android:toYScale="2.0" // 结束缩放y的倍数
 android:pivotX="50%" // 缩放中心点的x坐标
 android:pivotY="50%" // 缩放中心点的y坐标
/>

pivotX / pivotY 的取值有三个类型

  1. 数字, eg:android:pivotX="50", 该View左上角在x方向上平移50px的点,对应Java代码中设置参数 Animation.ABSOLUTE
  2. 百分比, eg:android:pivotX="50%", 该View左上角在x方向上平移自身宽度50%的点,对应Java代码中设置参数 Animation.RELATIVE_TO_SELF
  3. 百分比p (parent), eg:android:pivotX="50%p"该View左上角在x方向上平移父布局宽度50%的点,对应Java代码中设置参数 Animation.RELATIVE_TO_PARENT
  1. 公共属性
android:duration="3000" // 动画时长
android:fillAfter="true" // 动画结束后,是否留在结束位置,优先级高于fillBefore属性,默认是false
android:fillBefore="false" //动画结束后,是否留在开始位置,经试验证明只有fillBefore=false没有fillAfter属性时,不会停留在结束位置,默认是true
android:fillEnabled="true" //是否应用fillBefore的值,对fillAfter无影响,默认是true
android:interpolator="@android:anim/overshoot_interpolator"
android:repeatCount="0" //infinite无限重复
android:repeatMode="restart" // restart正序重播/reverse反转重播

android:shareInterpolator="true"  // 组合动画属性,组合动画是否和集合(<set></set>)共享一个插值器,去过不指定,子动画需要单独设定
android:startOffset="100" //组合动画默认是全部动画同时开始,如果不同动画不同开始需要使用该属性延迟开始时间

  1. 在代码中使用
// 步骤1:创建 需要设置动画的 视图View
Button mButton = (Button) findViewById(R.id.Button);
// 步骤2:创建 动画对象 并传入设置的动画效果xml文件
Animation translateAnimation = AnimationUtils.loadAnimation(this, R.anim.view_animation);
// 步骤3:播放动画
mButton.startAnimation(translateAnimation);

方式2 代码实现

Button mButton = (Button) findViewById(R.id.Button);

// 组合动画设置
// 步骤1:创建组合动画shareInterpolator对象(设置为true)
AnimationSet setAnimation = new AnimationSet(true);

// 步骤2:设置组合动画的属性
// 特别说明以下情况
// 因为在下面的旋转动画设置了无限循环(RepeatCount = INFINITE)
// 所以动画不会结束,而是无限循环
// 所以组合动画的下面两行设置是无效的
setAnimation.setRepeatMode(Animation.RESTART);
setAnimation.setRepeatCount(1);// 设置了循环一次,但无效

// 步骤3:逐个创建子动画(方式同单个动画创建方式,此处不作过多描述)

// 子动画1:旋转动画
Animation rotate = new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
rotate.setDuration(1000);
rotate.setRepeatMode(Animation.RESTART);
rotate.setRepeatCount(Animation.INFINITE);

// 子动画2:平移动画
Animation translate = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_PARENT,-0.5f,
TranslateAnimation.RELATIVE_TO_PARENT,0.5f,
TranslateAnimation.RELATIVE_TO_SELF,0
,TranslateAnimation.RELATIVE_TO_SELF,0);
translate.setDuration(10000);

// 子动画3:透明度动画
Animation alpha = new AlphaAnimation(1,0);
alpha.setDuration(3000);
alpha.setStartOffset(7000);

// 子动画4:缩放动画
Animation scale1 = new ScaleAnimation(1,0.5f,1,0.5f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scale1.setDuration(1000);
scale1.setStartOffset(4000);

// 步骤4:将创建的子动画添加到组合动画里
setAnimation.addAnimation(alpha);
setAnimation.addAnimation(rotate);
setAnimation.addAnimation(translate);
setAnimation.addAnimation(scale1);

// 步骤5:播放动画
mButton.startAnimation(setAnimation);

优缺点

缺点:

  1. 作用对象局限:根据包分类可知 android.view.animation,只针对View进行动画操作
  2. 只改变视觉效果,没有改变属性,例如点击只在原始位置有效(经API27测试,当属性动画View完全不可见时,点击位置和范围为原始位置)
  3. 动画效果单一

3. 属性动画

ObjectAnimator extends ValueAnimator extends Animator 位于 android.animation

ViewPropertyAnimator 位于 android.view ,其本质内部使用ValueAnimator

ViewPropertyAnimator - ObjectAnimator - ValueAnimator

使用难度越来越高,但越来越灵活。

举个例子:

View view = new View(this);

# ViewPropertyAnimator
view.animate().alphaBy(0.8f) // xxxBy 以当前状态为参考,translate等同理, 默认时间为300ms

# ObjectAnimator
ObjectAnimator objectAnimator = Object.ofFloat(view, "alpha", 1.0f, 0.0f, 1.0f);
objectAnimator.start();
// "alpha" 的本质不是view的属性,而是view的setAlpha 和 getAlpha方法,然后再setAlpha方法中调用 invalidate() 方法不断重绘。方法名严格,属性名可以不同,如mAlpha等,待查看源码体现

# ValueAnimator
ValueAnimator valueAnimator = ValueAnimator.ofFloat(1.0f, 0.0f, 1.0f);
value.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@Override
 public void onAnimationUpdate(ValueAnimator animation) {
        float animatedValue = (float) animation.getAnimatedValue();
        view.setAlpha(animatedValue);
 }
});
valueAnimator.start();

3.1 ViewPropertyAnimator

为了满足面向对象编程思想,引入了ViewPropertyAnimator,可以链式调用。但是没有 和setRepeatMode() 和 setRepeatCount() 方法

View 中的方法 功能 对应 ViewPropertyAnimator 的方法
setTranslationX() 设置x轴偏移 translationX() translationXBy()
setTranslationY() 设置y轴偏移 translationY() translationYBy()
setTranslationZ() 设置z轴偏移 translationZ() translationZBy()
setX() 设置x轴绝对位置 x() xBy()
setY() 设置y轴绝对位置 y() yBy()
setZ() 设置z轴绝对位置 z() zBy()
setRotation() 设置平面旋转 rotation() rotationBy()
setRotationX() 设置沿x轴旋转 rotationX() rotationXBy()
setRotationY() 设置沿y轴旋转 rotationY() rotationYBy()
setScaleX() 设置横向放缩 scaleX() scaleXBy()
setScaleY() 设置纵向放缩 scaleY() scaleYBy()
setAlpha() 设置透明度 alpha() alphaBy()
  1. View#setX()没有动画渐变效果,直接将该View放到设置位置;而 ViewPropertyAnimator#x() 有平移过渡动画。
  2. ViewPropertyAnimator#x() 如果有动画执行x()将取消。例如view.x(500).translationX(50),将只执行translationX(50),不执行x(500)
  3. ViewPropertyAnimator#By() 表示在当前位置的基础上进行操作

3.2 Animator的使用

方式1 XML实现
# 注意位置 res/animator/view_animator.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially">
    
    <objectAnimator
        android:duration="2000"
        android:propertyName="translationX"
        android:valueFrom="0"
        android:valueTo="500"
        android:valueType="floatType" />
    
    <set android:ordering="together">
        
        <objectAnimator
            android:duration="3000"
            android:propertyName="rotation"
            android:valueFrom="0"
            android:valueTo="360"
            android:valueType="floatType" />
        
        <set android:ordering="sequentially">
            <objectAnimator
                android:duration="1500"
                android:propertyName="alpha"
                android:valueFrom="1"
                android:valueTo="0"
                android:valueType="floatType" />
            <objectAnimator
                android:duration="1500"
                android:propertyName="alpha"
                android:valueFrom="0"
                android:valueTo="1"
                android:valueType="floatType" />
        </set>
    </set>
</set>


--- // 代码调用,注意区分animation AnimationUtils

Animator animator = AnimatorInflater.loadAnimator(context,R.animator.view_animator);
animator.setTarget(view);
animator.start();

方式2 Java代码实现
ObjectAnimator moveIn = ObjectAnimator.ofFloat(view, "translationX", 0, 500f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).before(moveIn); //  with / before / after
animSet.setDuration(5000);
animSet.start();
自定义控件实现

步骤

  1. 添加setter / getter 方法
  2. 用ObjectAnimator.ofXXX() 创建 ObjectAnimator 对象(或者使用ValueAnimator的方式)
  3. start()执行动画
public class SportsView extends View {  
    float progress = 0;

    ......

    // 创建 getter 方法
    public float getProgress() {
        return progress;
    }

    // 创建 setter 方法
    public void setProgress(float progress) {
        this.progress = progress;
        invalidate();
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        ......

        canvas.drawArc(arcRectF, 135, progress * 2.7f, false, paint);

        ......
    }
}

......

// 创建 ObjectAnimator 对象
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "progress", 0, 65);  
// 执行动画
animator.start(); 

大部分情况下,初始位置和结束位置不能提前确定,所有一般使用代码实现。

对比ObjectAnimator和ValueAnimator的使用

ofFloat 对比

// 注意setTranslationX参数是float类型,ofInt不能用translationX属性
// View # setTranslationX(float translationX)
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "translationX", 0.0f, 500.0f);
objectAnimator.start();

// value 在回调中更新view的状态
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, 500.0f);
valueAnimator.addUpdateListener(new valueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
          float animatedValue = (float) animation.getAnimatedValue();
          view.setTranslationX(animatedValue);
          view.requestLayout();
      }
});
valueAnimator.start();

ofObject 对比, 需要自定义 TypeEvaluator<T>

ObjectAnimator objectAnimator = ObjectAnimator.ofObject(view, "customFiled", new MyObjectEvaluator(), object1, object2);
objectAnimator.start();
  
---

ValueAnimator valueAnimator = ValueAnimator.ofObject(new MyObjectEvaluator(), object1, object2);
valueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
          MyObject value = (MyObject) animation.getAnimatedValue();
          view.setCustomFiled(value);
          view.requestLayout();
      }
});
valueAnimator.start();

4. 插值器和估值器

4.1 对比区别

类型 定义 作用
插值器 Interpolator 一个辅助动画实现的接口 确定属性值0-1变化规律
估值器 TypeEvaluator 一个协助插值器的接口,属性动画特有属性 计算属性值0-1具体数值(通过变化规律计算数字)

4.2 插值器

4.2.1 本质

根据动画的进度(0% - 100%)计算当前属性值改变的百分比

4.2.2 实现
  1. 实现下面两个接口
    android.animation # interface TimeInterpolator
    android.view.animation / interface Interpolator extends TimeInterpolator

  2. 重写 float getInterpolation(float input) 方法

补间动画实现 Interpolator接口;属性动画实现TimeInterpolator接口。
TimeInterpolator 接口是属性动画中新增的,用于兼容Interpolator 接口,这使原先的Interpolator实现累都可以直接在属性动画使用

4.2.3 举例说明
public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {  
      // 仅贴出关键代码
  ...
    public float getInterpolation(float input) {  
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
        // input的运算逻辑如下:
        // 使用了余弦函数,因input的取值范围是0到1,那么cos函数中的取值范围就是π到2π。
        // 而cos(π)的结果是-1,cos(2π)的结果是1
        // 所以该值除以2加上0.5后,getInterpolation()方法最终返回的结果值还是在0到1之间。只不过经过了余弦运算之后,最终的结果不再是匀速增加的了,而是经历了一个先加速后减速的过程
        // 所以最终,fraction值 = 运算后的值 = 先加速后减速
        // 所以该差值器是先加速再减速的
    }  
}
4.2.4 列举官方插值器
Java类 描述
LinearInterpolator 匀速
AccelerateInterpolator 加速
DecelerateInterpolator 减速
AccelerateDecelerateInterpolator 先加速后减速(默认)
AnticipateInterpolator 先退后然后加速前进
OvershootInterpolator 完成动画,超出后回到结束位置
AnticipateOvershootInterpolator 先退后再加速前进,超出终点后再回终点
BounceInterpolator 最后阶段弹球效果
CycleInterpolator 周期运动
PathInterpolator 自定义动画完成度/时间完成度曲线(0-1)
FastOutLinearInInterpolator 加速
LinearOutSlowInInterpolator 持续减速
FastOutSlowInInterpolator 先加速再减速

最后三个是Android5.0(API21)新增,和之前的类似,但是轨迹稍有区别

4.3 估值器

4.3.1 本质

根据当前属性值变化的百分比初始值结束值来计算当前属性的具体数值。当前属性值 = 初始值 + (结束值 - 初始值) * 百分比

4.3.2 实现
  1. 实现TypeEvaluator<T> 接口
  2. 重写public T evaluate(float fraction, T startValue, T endValue) 方法
4.3.3 官方示例
public class FloatEvaluator implements TypeEvaluator<Number> {
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}
4.3.4 完整示例
public class Point {

    private float x;
    private float y;

    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public float getX() {
        return x;
    }

    public float getY() {
        return y;
    }
}
public class PointEvaluator implements TypeEvaluator<Point> {

    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        float startValueX = startValue.getX();
        float startValueY = startValue.getY();
        float currentX = startValueX + (endValue.getX() - startValueX) * fraction;
        float currentY = startValueY + (endValue.getY() - startValueY) * fraction;
        return new Point(currentX, currentY);
    }
}
public class PointView extends View {

    public static final float RADIUS = 70f;
    private Point ccurrentPoint;
    private Paint mPaint;

    public PointView(Context context) {
        this(context, null);
    }

    public PointView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (ccurrentPoint == null) {
            ccurrentPoint = new Point(RADIUS, RADIUS);
        }
        canvas.drawCircle(ccurrentPoint.getX(), ccurrentPoint.getY(), RADIUS, mPaint);
    }

    public Point getCurrentPoint() {
        return ccurrentPoint;
    }

    public void setCurrentPoint(Point currentPoint) {
        this.ccurrentPoint = currentPoint;
        invalidate();
    }
}
Point pointStart = new Point(70, 70);
Point pointEnd = new Point(700, 1000);

// 使用 ObjectAnimator
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(point_view, "currentPoint", new PointEvaluator(), pointStart, pointEnd);
objectAnimator.setDuration(1000);
objectAnimator.start();
      
// 使用 ValueAnimator
ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvaluator(), pointStart, pointEnd);
valueAnimator.setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     @Override
     public void onAnimationUpdate(ValueAnimator animation) {
          Point animatedValue = (Point) animation.getAnimatedValue();
          point_view.setCurrentPoint(animatedValue);
     }
});
valueAnimator.start();

5.设置监听器

添加监听器
ViewPropertyAnimator#setListener()/setUpdateListener()
ObjectAnimator#addListener()/addUpdateListener/addPauseListener()

移除监听器
ViewPropertyAnimator#setListener(null) / setUpdateListener(null) 填null来移除
ObjectAnimator #removeListener() / removeUpdateListener() / removePauseListener()

ObjectAnimator 支持pause()方法暂停
ViewPropertyAnimator 不支持setRepeatMode() / setRepeatCount() 方法
ViewPropertyAnimator 独有withStartAction(Runnable runnable) 和 withEndAction(Runnable runnable) 方法,可设置一次动画开始或结束的监听。即使重新开始动画,也不会回调,是一次性的。而AnimatorListener是持续有效的。
withEndAction() 只有在动画正常结束才会调用,而在动画被取消时是不会执行的。而 AnimatorListener.onAnimationEnd() 在取消之后也会被调用,在调用 onAnimationCancel()之后调用

6. PropertyValuesHolder 同一动画中改变多个属性 ObjectAnimator.ofPropertyValuesHolder

关键字:一边一边,一个动画属性同时执行,区别多个动画先后执行。一个动画需要共享开始时间/结束时间/Interpolator等等设定,PropertyValuesHolder不能有先后次序执行动画了。

很多时候,在同一个动画中需要改变多个属性,例如改变透明度的同时改变尺寸。

使用 ViewPropertyAnimator如下:

view.animate()
        .scaleX(0.0f)
        .scaleY(0.0f)
        .alpha(0.0f)

但是ObjectAnimator,是不能这么用的。需要使用PropertyValuesHolder来同时在一个动画里改变多个属性

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 0.0f);  
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 0.0f);  
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 0.0f);

ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3)  
animator.start();  

ViewPropertyAnimator 动画完成之后会停留在结束位置,再次点击执行动作操作不会执行动画
ObjectAnimator 动画完成之后会停留在结束位置,再次点击执行动作操作会从最原始状态重新执行一次动画。且Animator没有Animation的setFillAfter() 和setFillBefore()方法
关于点击范围的测试说明(API27),属性动画执行后属性发生变化,即点击范围和位置会更新,但经测试当View完全不可见时,点击位置和范围为原始位置

ObjectAnimator.ofInt()
ObjectAnimator.ofFloat()
ObjectAnimator.ofMultiFloat()
ObjectAnimator.ofPropertyValuesHolder

6.AnimatorSet 多个动画配合执行

关键字:一边一边,一个动画属性同时执行,区别多个动画先后执行。一个动画需要共享开始时间/结束时间/Interpolator等等设定,PropertyValuesHolder不能有先后次序执行动画了。

区别 AnimationSet,只能通过设置单个动画的 setStartOffset 来延迟时间进行先后执行顺序。

animatorSet.play(a1) / playTogether(a1,a2) / playSequentially(a1,a2)
         .with(a3)
         .before(a4)
         .after(a5)

其中以 playXXX 开始得到 AnimatorSet.Builder 对象,然后调用with/before/after进行管理。

7.PropertyValuesHolders.ofKeyframe() 把同一个属性拆分

把一个属性拆分成多段,执行更加精细的属性动画。

Keyframe keyframe1 = Keyframe.ofFloat(0.0f, 0);
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
Keyframe keyframe3 = Keyframe.ofFloat(1.0f, 80);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("translationX", keyframe1, keyframe2, keyframe3);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTextMessage, holder);
animator.start();

0x03 参考资料

感谢以下文章作者
HenCoder Android 自定义 View 1-6:属性动画 Property Animation(上手篇)
Android:这是一份全面 & 详细的补间动画使用教程

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

推荐阅读更多精彩内容