Android動畫系列 --- 屬性動畫詳解

前言

屬性動畫系統(tǒng)是一個強(qiáng)健的框架孕讳,用于為幾乎任何內(nèi)容添加動畫效果涣易。您可以定義一個隨時間更改任何對象屬性的動畫良哲,無論其是否繪制到屏幕上。屬性動畫會在指定時長內(nèi)更改屬性(對象中的字段)的值矗钟。要添加動畫效果唆香,請指定要添加動畫效果的對象屬性,例如對象在屏幕上的位置吨艇、動畫效果持續(xù)多長時間以及要在哪些值之間添加動畫效果躬它。

動畫構(gòu)造

ObjectAnimator animator = ObjectAnimator.ofFloat(text, "translationX", 0f, 100f);
animator.setDuration(1000);
animator.start();

一個簡單的屬性動畫實(shí)現(xiàn),它指定了 TextView 中名稱為 translationX 的屬性在 1000ms 的時間內(nèi)由 0 變化到 100东涡。translationX 屬性是相對于父容器左上角的偏移量冯吓,所以整個動畫相當(dāng)于補(bǔ)間動畫中 X軸平移動畫。

插值器

插值器是根據(jù)動畫時間來計(jì)算出特定的動畫完成比例的值软啼。在實(shí)際應(yīng)用中桑谍,動畫可以是勻速運(yùn)動延柠,也可以是變速運(yùn)動比如先加速在減速等多種運(yùn)動方式祸挪。Android系統(tǒng)默認(rèn)提供了九種插值器的實(shí)現(xiàn),我們可以根據(jù)自身需求去靈活選擇需要的插值器實(shí)現(xiàn)贞间。

說明
AccelerateDecelerateInterpolator 該插值器的變化率在開始和結(jié)束時緩慢但在中間會加快贿条。
AccelerateInterpolator 該插值器的變化率在開始時較為緩慢雹仿,然后會加快。
AnticipateInterpolator 該插值器先反向變化整以,然后再急速正向變化胧辽。
AnticipateOvershootInterpolator 該插值器先反向變化,再急速正向變化公黑,然后超過定位值邑商,最后返回到最終值。
BounceInterpolator 該插值器的變化會跳過結(jié)尾處凡蚜。
CycleInterpolator 該插值器的動畫會在指定數(shù)量的周期內(nèi)重復(fù)人断。
DecelerateInterpolator 該插值器的變化率開始很快,然后減速朝蜘。
LinearInterpolator 該插值器的變化率恒定不變恶迈。
OvershootInterpolator 該插值器會急速正向變化,再超出最終值谱醇,然后返回暇仲。

除去默認(rèn)的九種插值器實(shí)現(xiàn),我們也可以通過實(shí)現(xiàn) TimeInterpolator 接口來自定義插值器副渴。

估值器

估值器是根據(jù)時間比例算出屬性值奈附,默認(rèn)實(shí)現(xiàn)有 Int、Float和rgb值估值器煮剧,如果動畫對象屬性不是以上三種桅狠,就需要去實(shí)現(xiàn) TypeEvaluator 接口來計(jì)算對應(yīng)屬性的值。

說明
IntEvaluator 計(jì)算Int類型估值器
FloatEvaluator 計(jì)算Float類型估值器
ArgbEvaluator 計(jì)算顏色argb估值器

動畫原理

//一個簡單的例子
ObjectAnimator animator = ObjectAnimator.ofFloat(text, "translationX", 0f, 100f);//step1
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.setDuration(1000);
animator.start();//step2

ObjectAnimator.ofFloat()中做的事情很簡單轿秧,對屬性對象和屬性名稱變量進(jìn)行賦值中跌,然后處理傳入的屬性值。

// ObjectAnimator.java
private WeakReference<Object> mTarget; //動畫對象
private String mPropertyName; //動畫對象屬性
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
    //mTarget和mPropertyName賦值
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    //處理傳入的動畫屬性值
    anim.setFloatValues(values);
    return anim;
}

需要注意的是 propertyName需要在target中有對應(yīng)的set/get方法實(shí)現(xiàn)菇篡。values屬性值會經(jīng)過一系列的處理進(jìn)行保存漩符,在動畫進(jìn)行中獲取對應(yīng)的屬性值。我們需要弄清楚values屬性值的轉(zhuǎn)換邏輯和存儲結(jié)構(gòu)驱还,可以從setFloatValues開始入手分析嗜暴。

// ObjectAnimator.java
PropertyValuesHolder[] mValues;
public void setFloatValues(float... values) {
    if (mValues == null || mValues.length == 0) {
        if (mProperty != null) {
            setValues(PropertyValuesHolder.ofFloat(mProperty, values));
        } else {
            setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
        }
    } else {
        super.setFloatValues(values);
    }
}

從上面代碼中可以看到構(gòu)造了一個PropertyValuesHolder對象,PropertyValuesHolder內(nèi)部主要有兩個作用议蟆,

  • 通過Keyframes數(shù)組存儲屬性值
  • 獲取和設(shè)置屬性值闷沥,根據(jù)給出的屬性名稱和類名通過反射拿到對應(yīng)的getter/setter方法。

Keyframes默認(rèn)有兩個擴(kuò)展IntKeyframesFloatKeyframes咐容,KeyframeSet是具體的實(shí)現(xiàn)類提供了int,float和Object類型數(shù)據(jù)的存儲轉(zhuǎn)換舆逃,另外int和float作為系統(tǒng)可識別類型也有兩個默認(rèn)擴(kuò)展實(shí)現(xiàn)IntKeyframeSetFloatKeyframeSet

object_animator_keyframe.jpg
// PropertyValuesHolder.java
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);
}

static class FloatPropertyValuesHolder extends PropertyValuesHolder{
   ...
  Keyframes.FloatKeyframes mFloatKeyframes;
  
  // -----1   
  public FloatPropertyValuesHolder(String propertyName, float... values) {
     super(propertyName); //mPropertyName = propertyName 賦值
     setFloatValues(values); // ###2
  }  
 
  // -----2
  public void setFloatValues(float... values) {
    super.setFloatValues(values); //###3
    mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
  }
  
  // -----3
  public void setFloatValues(float... values) {
    mValueType = float.class;
    mKeyframes = KeyframeSet.ofFloat(values);
   }
   ...
}

KeyframeSet主要負(fù)責(zé)將屬性值進(jìn)行包裝存儲,內(nèi)部通過Keyframe數(shù)組來保存屬性值路狮。Keyframe也有int虫啥、floatObject的三個默認(rèn)擴(kuò)展實(shí)現(xiàn)。

// Keyframe.java
public abstract class Keyframe implements Cloneable {
  
  public static Keyframe ofInt(float fraction) {
        return new IntKeyframe(fraction);
  }
  
  public static Keyframe ofFloat(float fraction) {
        return new FloatKeyframe(fraction);
  }
  
  public static Keyframe ofObject(float fraction) {
        return new ObjectKeyframe(fraction, null);
  }
  
  static class FloatKeyframe extends Keyframe{ ... }
  static class IntKeyframe extends Keyframe{ ... }
  static class ObjectKeyframe extends Keyframe{ ... }
}


KeyframeSet中處理屬性值轉(zhuǎn)換的邏輯:

// KeyframeSet.ofFloat(values)
// 繼承關(guān)系 FloatKeyframeSet extends KeyframeSet implements KeyFrames.FloatKeyframes
// KeyframeSet implements KeyFrames
// FloatKeyframe extends Keyframe(抽象類)  
public static KeyframeSet ofFloat(float... values) {
  boolean badValue = false;
  int numKeyframes = values.length;
  //初始化數(shù)組容量大小最少為2
  FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
  //輸入的值如果是一個 默認(rèn)補(bǔ)0作為開始值
  if (numKeyframes == 1) {
    keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
    keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
    //非指定類型數(shù)值 badValue置為true
    if (Float.isNaN(values[0])) {
      badValue = true;
    }
  } else {
    keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
    //循環(huán)對數(shù)組賦值
    for (int i = 1; i < numKeyframes; ++i) {
      keyframes[i] =
        (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
       //非指定類型數(shù)值 badValue置為true
      if (Float.isNaN(values[i])) {
        badValue = true;
      }
    }
  }
  if (badValue) {
    Log.w("Animator", "Bad value (NaN) in float animator");
  }
  return new FloatKeyframeSet(keyframes);
 }

首先初始化FloatKeyframe數(shù)據(jù)奄妨,默認(rèn)最小容量為2即至少有初始值和結(jié)束值涂籽。當(dāng)numKeyframes的大小為1時,則將傳入的屬性值作為結(jié)束值砸抛,開始值用0填充评雌。當(dāng)numKeyframes的大小大于1的時候,先確定屬性值數(shù)組第一個為初始值直焙,后續(xù)根據(jù)傳入屬性值數(shù)組的容量大小從1開始遍歷賦值柳骄,最后返回給定類別的KeyframeSet

PropertyValuesHolder初始化成功后調(diào)用setValues方法對mValues進(jìn)行賦值,將屬性名稱和PropertyValuesHolder進(jìn)行key-value綁定箕般,變更初始化標(biāo)簽mInitialized為false等待重新初始化耐薯。

// ValueAnimator.java
public void setValues(PropertyValuesHolder... values) {
    int numValues = values.length;
    mValues = values;
    //Map保存屬性名稱和屬性值
    mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
    for (int i = 0; i < numValues; ++i) {
        PropertyValuesHolder valuesHolder = values[i];
        mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
    }
    // 屬性變更重新初始化
    mInitialized = false;
}

屬性值的存儲處理過程分析完畢,取值和調(diào)用setter方法先在start()方法之后在分析丝里,這樣整體串下來思路會比較清晰曲初。

animator.start();

這是開啟動畫的方法調(diào)用,看起來很簡單只有兩步操作杯聚。但實(shí)際上內(nèi)部處理的東西可不止兩步臼婆,讓我們一步一步分析

// ObjectAnimator.java
public void start() {
    // AnimationHandler.java
    //step1  
    AnimationHandler.getInstance().autoCancelBasedOn(this);
    ...
    //step2  
    super.start();
}

先從AnimationHandler.getInstance().autoCancelBasedOn(this)開始,這個方法的作用是將存在的幀回調(diào)處理取消幌绍。如何來理解可以等分析完super.start()方法在來具體看下颁褂,super.start()ValueAnimator中的方法,內(nèi)部對一些狀態(tài)進(jìn)行初始化傀广。

// ValueAnimator.java
private void start(boolean playBackwards) {
    //當(dāng)前線程不存在事件循環(huán)拋出異常
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    //是否反轉(zhuǎn) 默認(rèn)false
    mReversing = playBackwards; // false
    mSelfPulse = !mSuppressSelfPulseRequested; //true
    ...
    //狀態(tài)初始化  
    mStarted = true;
    mPaused = false;
    mRunning = false;
    mAnimationEndRequested = false;
    //重置時間相關(guān)狀態(tài)
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;
    //重點(diǎn)方法:添加事件到Handler
    addAnimationCallback(0);

    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
        //如果沒有延遲則立即啟動動畫監(jiān)聽
        startAnimation();
        if (mSeekFraction == -1) {
            //尚未開始啟動設(shè)置播放時間為0
            setCurrentPlayTime(0);
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}

addAnimationCallback做的很簡單颁独,判斷mSelfPulse,為真則退出否則調(diào)用AnimationHandelr中的addAnimationFrameCallback()將自身和延遲時間傳入。

// ValueAnimator.java
private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}

接下來是核心重點(diǎn)類AnimationHadnler 這個類的主要作用是通過Choreographer來設(shè)置動畫幀回調(diào)伪冰,等到VSYNC信號的時候誓酒,額外執(zhí)行動畫幀回調(diào)。getProvider()是獲取的MyFrameCallbackProvider對象贮聂,內(nèi)部持有Choreographer實(shí)例靠柑,getProvider().postFrameCallback(mFrameCallback)實(shí)際上是調(diào)用了Choreographer中的postFrameCallback(callback)方法。

/ AnimationHandler.java
private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
            new ArrayMap<>();
private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
  new ArrayList<>();
private final ArrayList<AnimationFrameCallback> mCommitCallbacks =
  new ArrayList<>();
private AnimationFrameCallbackProvider mProvider;
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    if (mAnimationCallbacks.size() == 0) {
       //調(diào)用Choreographer中的postFrameCallback()
        getProvider().postFrameCallback(mFrameCallback);
    }
    //將回調(diào)添加到列表中
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }
    //如果需要延遲執(zhí)行吓懈,將回調(diào)和執(zhí)行的時間點(diǎn)保存
    if (delay > 0) {
        mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
    }
}

看一下mFrameCallback聲明是Choreographer.FrameCallback的實(shí)現(xiàn)歼冰,Choreographer在執(zhí)行postFrameCallback()后,會放入一個類別為CALLBACK_ANIMATION的回調(diào)隊(duì)列中耻警,當(dāng)VSYNC信號來到是觸發(fā)doFrame()內(nèi)部通過doCallbacks來執(zhí)行回調(diào)隊(duì)列中的回調(diào)隔嫡。代碼片段如下:

// Choreographer-doFrame()
try {
    ...
    mFrameInfo.markAnimationsStart();
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);//這個正是我們傳入的回調(diào)類別
    doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
    ...
}
// AnimationHandler.java
//Choreographer 觸發(fā) doCallbacks()后會執(zhí)行改回調(diào)
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            //開始處理執(zhí)行動畫幀
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                //如果還有幀沒有處理完甸怕,等待下次vsync信號處理
                getProvider().postFrameCallback(this);
            }
        }
 };

看下觸發(fā)了動畫幀是如何執(zhí)行的。

//  AnimationHandler.java
private void doAnimationFrame(long frameTime) {
  long currentTime = SystemClock.uptimeMillis();
  final int size = mAnimationCallbacks.size();
  for (int i = 0; i < size; i++) {
    final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
    if (callback == null) {
      continue;
    }
    //判斷callback是否需要延遲執(zhí)行
    if (isCallbackDue(callback, currentTime)) {
      //執(zhí)行callback回調(diào)
      callback.doAnimationFrame(frameTime);
      ...
    }
  }
  cleanUpList();
}

顯示判斷了callback()是否需要延遲執(zhí)行畔勤,如果不需要就開始調(diào)用doAnimationFrame開始執(zhí)行。

//ValueAnimator.java
//精簡代碼顯示主要邏輯  
public final boolean doAnimationFrame(long frameTime) {
    ...
    mLastFrameTime = frameTime;
    //當(dāng)前時間取幀時間和開始時間最大值
    final long currentTime = Math.max(frameTime, mStartTime);
    boolean finished = animateBasedOnTime(currentTime);
    //執(zhí)行完畢后結(jié)束動畫
    if (finished) {
        endAnimation();
    }
    return finished;
}

獲取當(dāng)前幀時間和開始時間最大值扒磁,傳入animateBasedOnTime()執(zhí)行庆揪,如果執(zhí)行完畢,結(jié)束動畫執(zhí)行妨托。

//顯示主要邏輯 重復(fù)執(zhí)行邏輯省略
boolean animateBasedOnTime(long currentTime) {
    boolean done = false;
    if (mRunning) {
        //獲取縮放后的時長 默認(rèn)不縮放
        final long scaledDuration = getScaledDuration();
        //根據(jù)時間計(jì)算動畫比例
        final float fraction = scaledDuration > 0 ?
                (float)(currentTime - mStartTime) / scaledDuration : 1f;
        ...
        //計(jì)算重復(fù)執(zhí)行時候的動畫比例  
        mOverallFraction = clampFraction(fraction);
        float currentIterationFraction = getCurrentIterationFraction(
                mOverallFraction, mReversing);
        //執(zhí)行屬性值得更改
        animateValue(currentIterationFraction);
    }
    return done;
}

接下來就是執(zhí)行屬性值得更改缸榛,首先通過插值器獲取過慮后的動畫比例,然后通過該動畫比例計(jì)算出當(dāng)前屬性值按照我們之前的實(shí)例這里實(shí)際會調(diào)用FloatKeyframeSet中的getValue()方法兰伤,這個方法內(nèi)部會根據(jù)保存的初始和結(jié)束的屬性值以及動畫比例計(jì)算出當(dāng)前的屬性值内颗,當(dāng)然如果有設(shè)置估值器那么會使用估值器計(jì)算的屬性值返回。

// ObjectAnimator.java
void animateValue(float fraction) {
    final Object target = getTarget();
    if (mTarget != null && target == null) {
        cancel();
        return;
    }

    super.animateValue(fraction); //ValueAnimator.java中的animateValue()
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        //通過 setter/getter 將屬性值變化
        mValues[i].setAnimatedValue(target); 
    }
}

// ValueAnimator.java 
void animateValue(float fraction) {
        //獲取插值器過慮后的動畫比例
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        //根據(jù)動畫比例計(jì)算當(dāng)前屬性值
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
        //動畫監(jiān)聽onAnimationUpdate()觸發(fā)
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

最后一步就是給動畫對象對應(yīng)屬性名稱的屬性進(jìn)行賦值敦腔。如果我們動畫對象是視圖均澳,視圖繪制關(guān)聯(lián)了某個屬性,那么在這里在進(jìn)行屬性更改后觸發(fā)重繪從而構(gòu)成我們想要的動畫效果符衔。

// FloatPropertyValuesHolder
void setAnimatedValue(Object target) {
    if (mFloatProperty != null) {
        mFloatProperty.setValue(target, mFloatAnimatedValue);
        return;
    }
    if (mProperty != null) {
        mProperty.set(target, mFloatAnimatedValue);
        return;
    }
    if (mJniSetter != 0) {
        nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
        return;
    }
    if (mSetter != null) {
        try {
            mTmpValueArray[0] = mFloatAnimatedValue;
            mSetter.invoke(target, mTmpValueArray);
        }
    }
}

附上一份整理的流程圖:


屬性動畫流程.jpg

以上就是對屬性動畫的原理通過源碼進(jìn)行分析找前,大概的總結(jié)一下就是內(nèi)部通過等待VSYNC信號來執(zhí)行動畫比例和屬性值得計(jì)算,并通過setter方法重設(shè)屬性值判族,觸發(fā)重繪來構(gòu)造最后的動畫效果躺盛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市形帮,隨后出現(xiàn)的幾起案子槽惫,更是在濱河造成了極大的恐慌,老刑警劉巖辩撑,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件界斜,死亡現(xiàn)場離奇詭異,居然都是意外死亡合冀,警方通過查閱死者的電腦和手機(jī)锄蹂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來水慨,“玉大人得糜,你說我怎么就攤上這事∥鳎” “怎么了朝抖?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谍珊。 經(jīng)常有香客問我治宣,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任侮邀,我火速辦了婚禮坏怪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绊茧。我一直安慰自己铝宵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布华畏。 她就那樣靜靜地躺著鹏秋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亡笑。 梳的紋絲不亂的頭發(fā)上侣夷,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機(jī)與錄音仑乌,去河邊找鬼百拓。 笑死,一個胖子當(dāng)著我的面吹牛晰甚,可吹牛的內(nèi)容都是我干的耐版。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼压汪,長吁一口氣:“原來是場噩夢啊……” “哼粪牲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起止剖,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤腺阳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后穿香,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亭引,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年皮获,在試婚紗的時候發(fā)現(xiàn)自己被綠了焙蚓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡洒宝,死狀恐怖购公,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雁歌,我是刑警寧澤宏浩,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站靠瞎,受9級特大地震影響比庄,放射性物質(zhì)發(fā)生泄漏求妹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一佳窑、第九天 我趴在偏房一處隱蔽的房頂上張望制恍。 院中可真熱鬧,春花似錦神凑、人聲如沸净神。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽强挫。三九已至岔霸,卻和暖如春薛躬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背呆细。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工型宝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人絮爷。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓趴酣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坑夯。 傳聞我的和親對象是個殘疾皇子岖寞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容