Fragment 特殊轉(zhuǎn)場動畫

這篇文章在說什么冗恨?

3d翻頁部分其實比較簡單答憔,因為Google在ApiDemos里給了動畫部分的實現(xiàn)源碼。麻煩的是FragmentTransaction.setCustomAnimations如何設(shè)置一個特殊的不是通過xml創(chuàng)建的Animation掀抹。本文給了解決方法虐拓,以及是如何發(fā)現(xiàn)這個解決辦法的。
這個地址有完整的源碼傲武。
https://github.com/aesean/Rotate3d
源碼包含:

  • 如何讓Fragment實現(xiàn)翻轉(zhuǎn)
  • 如何讓View實現(xiàn)翻轉(zhuǎn)
  • 以及一個跟Google的Rotate3dAnimation效果一摸一樣的Animator實現(xiàn)蓉驹。

正文

最近遇到一個需求,是某個界面有兩種顯示樣式揪利。然后有按鈕可以在這兩種樣式之間隨意切換态兴。大致有點像下面圖中效果。


Rotate3d

最終效果差不多就是類似這個圖疟位。那假如第一次看到這個效果圖瞻润,思考下,我們應(yīng)該如何實現(xiàn)呢甜刻?

實現(xiàn)思路

雖然圖有左右兩個绍撞,實際其實只要實現(xiàn)其中一個另一個其實就做同樣實現(xiàn)就可以了。下面所有討論都只針對左半部分罢吃。
圖中效果就兩部分組成:View+動畫楚午。

  • View
    View的話用Fragment實現(xiàn)就OK(當然ViewGroup嵌套也能做到,但為了更方便的封裝復用尿招,顯然Fragment會更好)矾柜。
  • 動畫
    然后動畫的話可以直接用Fragment(V4)的CustomAnimations來實現(xiàn)阱驾。
  • Rotate3dAnimation
    剩下一個唯一難點,CustomAnimations是個Animation動畫怪蔑,那這個效果如何實現(xiàn)呢黔州?如果你看過或者用過Google在AndroidSDK中附帶的ApiDemos的話戳玫,有個類完全就是一摸一樣的效果莫绣。
    https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/animation/Rotate3dAnimation.java
    再梳理下思路材泄。左右兩部分都用Fragment實現(xiàn)。然后左邊是兩個Fragment(正面一個背面一個)弓坞,右邊也是兩個隧甚。然后需要切換的時候就通過transaction.setCustomAnimations設(shè)置切換需要的動畫,然后通過show/hide(根據(jù)你需要也可以add/remove)來控制Fragment的顯示與消失渡冻。這樣一來動畫效果完全與Fragment解耦戚扳,相當于是任意Fragment都可以使用,似乎沒什么問題族吻。

開始實現(xiàn)

  • 定義Fragment
    先定義好自己的Fragment帽借。左邊需要兩個Fragment,假如就叫:FragmentA和FragmentB超歌,分別對應(yīng)正面和背面砍艾。
  • 控制顯示與消失
    控制顯示與消失,可以用add/remove(每次會重新創(chuàng)建Fragment實例)巍举,也可以使用show/hide(會復用Fragment實例)脆荷。當然這里我們肯定用show/hide了。在Activity中你可能會寫出類似下面的代碼禀综。
    private static final String FRAGMENT_TAG_A = "FRAGMENT_TAG_A";
    private static final String FRAGMENT_TAG_B = "FRAGMENT_TAG_B";

    public void showFragmentA() {
        showFragment(FRAGMENT_TAG_A, FRAGMENT_TAG_B);
    }

    public void showFragmentB() {
        showFragment(FRAGMENT_TAG_B, FRAGMENT_TAG_A);
    }

    public void showFragment(String showTag, String hideTag) {
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        // 設(shè)置動畫
        transaction.setCustomAnimations(enterId ?, exitId ?);
        Fragment fragment = getSupportFragmentManager().findFragmentByTag(showTag);
        if (fragment == null) {
            // 沒有找到表示沒有被創(chuàng)建過
            fragment = new FragmentA();
            // 直接add
            transaction.add(R.id.fragment_content, fragment, showTag);
        } else {
            // 找到了简烘,表示已經(jīng)被add了苔严,所以直接show
            transaction.show(fragment);
        }

        fragment = getSupportFragmentManager().findFragmentByTag(hideTag);
        if (fragment != null) {
            // 找到了定枷,直接hide
            transaction.hide(fragment);
        }
        transaction.commit();
    }

所有代碼都非常順利,唯獨

transaction.setCustomAnimations(enterId ?, exitId ?);

出問題了届氢,我們這里有個Google寫好的Rotate3dAnimation欠窒,但這里只能指定RId,也就是說這里只能指定xml定義的Animation退子。而且沒有任何重載方法可以設(shè)置Animation實例岖妄。

怎么設(shè)置自定義Animation實例

怎么辦呢?先看下setCustomAnimations注釋怎么寫的寂祥。

    /**
     * Set specific animation resources to run for the fragments that are
     * entering and exiting in this transaction. These animations will not be
     * played when popping the back stack.
     */

解釋的非常清楚荐虐,然并卵。
然后丸凭,最直接的就是把Fragment相關(guān)源碼讀一遍福扬,看下整個處理過程腕铸,看看Google有沒有留下什么方式能做到自定義Animation。
但Fragment源碼代碼量還是非常大的铛碑,如果你之前完全沒有細讀過Fragment實現(xiàn)狠裹,那效率會比較低,這里不急著看Fragment實現(xiàn)代碼汽烦,我們來猜測下Google這里是如何通過Rid來實現(xiàn)切換動畫的涛菠。

  • 雖然這時候還沒細讀Fragment源碼,但這個轉(zhuǎn)場動畫撇吞,最終一定是把Animation作用到View上俗冻,而且代碼非常可能就是view.startAnimation牍颈。
  • transaction.setCustomAnimations之后言疗,應(yīng)該是保存了動畫資源Id,然后再某個時候把xml加載成Animation颂砸。加載xml定義的Animation基本跑不了肯定就是AnimationUtils.loadAnimation

這時候最簡單的噪奄,先去Fragment類源碼中搜下“.startAnimation”和“AnimationUtils.loadAnimation”,非常遺憾都沒有找到人乓。
不要緊勤篮,F(xiàn)ragment有三個很重要的類:Fragment、FragmentTransaction和FragmentManager色罚。分別去另外兩個實現(xiàn)類中搜下碰缔。FragmentTransaction和實現(xiàn)類是BackStackRecord,F(xiàn)ragmentManager的實現(xiàn)類是FragmentManagerImpl戳护。
在FragmentManagerImpl類中搜到了startAnimation金抡,而且還不止一處。這里其實隨便選一處就可以了(幾個地方其實都能找到需要的信息)腌且。這里選個相關(guān)代碼最簡單的梗肝。

                // run animations:
                Animation anim = loadAnimation(f, f.getNextTransition(), true,
                        f.getNextTransitionStyle());
                if (anim != null) {
                    setHWLayerAnimListenerIfAlpha(f.mView, anim);
                    f.mView.startAnimation(anim);
                }

這里其實就是Animation實際是怎么從Rid變成Animation實例的。f.getNextTransition就是之前設(shè)置的動畫資源id铺董,true表示是enter還是exit巫击。這里通過loadAnimation方法來加載動畫。

    Animation loadAnimation(Fragment fragment, int transit, boolean enter,
            int transitionStyle) {
        Animation animObj = fragment.onCreateAnimation(transit, enter, fragment.getNextAnim());
        if (animObj != null) {
            return animObj;
        }

        if (fragment.getNextAnim() != 0) {
            Animation anim = AnimationUtils.loadAnimation(mHost.getContext(),
                    fragment.getNextAnim());
            if (anim != null) {
                return anim;
            }
        }
        ......
    }

代碼不多精续,這里一下子答案就清晰了坝锰。最終確實是通過AnimationUtils.loadAnimation來加載動畫資源的。但在加載之前會先
調(diào)用fragment.onCreateAnimation方法重付,如果這個方法返回空才會去調(diào)用AnimationUtils.loadAnimation顷级。辦法來了,可以復寫Fragment的onCreateAnimation方法來攔截Animation的創(chuàng)建确垫。復寫這個方法弓颈,return Rotate3dAnimation就可以了拣凹。
這里我們創(chuàng)建兩個空xml anim(只是為了用這個id)。名字叫:rotate_3d_enter和rotate_3d_exit恨豁。實現(xiàn)都是空嚣镜。
然后復寫Fragment的

    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
        if (nextAnim == R.anim.rotate_3d_enter) {
            return Rotate3dAnimation;
        }
        if (nextAnim == R.anim.rotate_3d_exit) {
            return Rotate3dAnimation;
        }
        return super.onCreateAnimation(transit, enter, nextAnim);
    }

這樣就可以使用Rotate3dAnimation了。

Rotate3dAnimation參數(shù)

現(xiàn)在可以transaction.setCustomAnimations已經(jīng)可以使用自定義的Animation了橘蜜。但上面還遺留了一個問題菊匿,怎么創(chuàng)建Rotate3dAnimation。這個類有6個參數(shù)计福。
float fromDegrees 起始角度
float toDegrees 結(jié)束角度
角度參數(shù)很簡單跌捆,正面的應(yīng)該是從0度到90度,背面的應(yīng)該是從270度到360度象颖。
float centerX 中心點x
float centerY 中心點y
float depthZ 深度
中心點第一感覺就是通過getView.getWidth()0.5f getView().getHeight()0.5f佩厚。實際這樣是會有問題的,因為onCreateAnimation并不一定就是在View全部繪制完成才回調(diào)的说订。但是因為initialize方法會把View實際大小傳過來抄瓦。所以我們可以不需要自己計算View的寬和高。

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
    }

我們可以把構(gòu)造方法改造下陶冷,寬和高不再傳實際的像素钙姊,而是傳對應(yīng)的比例。


/**
 * An animation that rotates the view on the Y axis between two specified angles.
 * This animation also adds a translation on the Z axis (depth) to improve the effect.
 */
public class Rotate3dAnimation extends Animation {

    private static final int TYPE_SCALE = 0;
    private static final int TYPE_PX = 1;

    private final float mFromDegrees;
    private final float mToDegrees;
    private float mCenterX;
    private float mCenterY;
    private float mDepthZ;
    private int mType = TYPE_PX;
    private final boolean mReverse;
    private Camera mCamera;

    /**
     * Creates a new 3D rotation on the Y axis. The rotation is defined by its
     * start angle and its end angle. Both angles are in degrees. The rotation
     * is performed around a center point on the 2D space, definied by a pair
     * of X and Y coordinates, called centerX and centerY. When the animation
     * starts, a translation on the Z axis (depth) is performed. The length
     * of the translation can be specified, as well as whether the translation
     * should be reversed in time.
     *
     * @param fromDegrees the start angle of the 3D rotation
     * @param toDegrees   the end angle of the 3D rotation
     * @param centerX     the X center of the 3D rotation
     * @param centerY     the Y center of the 3D rotation
     * @param reverse     true if the translation should be reversed, false otherwise
     */
    public Rotate3dAnimation(float fromDegrees, float toDegrees,
                             float centerX, float centerY, float depthZ, boolean reverse) {
        mFromDegrees = fromDegrees;
        mToDegrees = toDegrees;
        mCenterX = centerX;
        mCenterY = centerY;
        mDepthZ = depthZ;
        mReverse = reverse;
    }

    public Rotate3dAnimation(float fromDegrees, float toDegrees
            , float centerX, float centerY, float depthZ
            , boolean reverse, int type) {
        mFromDegrees = fromDegrees;
        mToDegrees = toDegrees;
        mCenterX = centerX;
        mCenterY = centerY;
        mDepthZ = depthZ;
        mReverse = reverse;
        mType = type;
    }

    public Rotate3dAnimation(float fromDegrees, float toDegrees, boolean reverse) {
        this(fromDegrees, toDegrees, 0.5f, 0.5f, 0.5f, reverse, TYPE_SCALE);
    }

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        mCamera = new Camera();
        if (mType == TYPE_SCALE) {
            mCenterX = width * mCenterX;
            mCenterY = height * mCenterY;
            mDepthZ = width * mDepthZ;
        }
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final float fromDegrees = mFromDegrees;
        float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);

        final float centerX = mCenterX;
        final float centerY = mCenterY;
        final Camera camera = mCamera;
        final Matrix matrix = t.getMatrix();
        camera.save();
        if (mReverse) {
            camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
        } else {
            camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
        }
        camera.rotateY(degrees);
        camera.getMatrix(matrix);
        camera.restore();

        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);
    }
}

Rotate3dAnimation整個類被改造成上面的樣子埂伦∩范睿可以直接使用三個參數(shù)的構(gòu)造方法Rotate3dAnimation(float fromDegrees, float toDegrees, boolean reverse)以中心點為旋轉(zhuǎn)軸心,以寬度一半為旋轉(zhuǎn)深度沾谜。這里為什么要取一半呢膊毁?仔細思考下,當翻轉(zhuǎn)進行到一半的時候View處于什么狀態(tài)基跑?這時候View剛好與屏幕垂直婚温,View深度也剛好是View寬度的一半,而此時也是翻轉(zhuǎn)過程中的最大深度涩僻,所以默認取寬度一半的深度效果比較好缭召。
boolean reverse 反轉(zhuǎn)(這個參數(shù)Google給的注釋是:true if the translation should be reversed, false otherwise。這個參數(shù)看源碼會發(fā)現(xiàn)其實只影響深度depthZ逆日,表示深度是從0變到depthZ,還是從depthZ變到0)正面翻的時候應(yīng)該是從0到depthZ萄凤,而此時背面應(yīng)該是從depthZ到0室抽。

Rotate3dHelper

public class AnimationHelper {
    private AnimationHelper(){
        
    }

    public static void setUpRotate3dAnimation(android.support.v4.app.FragmentTransaction transaction) {
        transaction.setCustomAnimations(R.anim.rotate_3d_enter, R.anim.rotate_3d_exit);
    }

    public static Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
        if (nextAnim == R.anim.rotate_3d_enter) {
            final Rotate3dAnimation animation = new Rotate3dAnimation(270, 360, false);
            animation.setDuration(600);
            animation.setStartOffset(300);
            animation.setFillAfter(false);
            animation.setInterpolator(new DecelerateInterpolator());
            return animation;
        }
        if (nextAnim == R.anim.rotate_3d_exit) {
            Rotate3dAnimation animation = new Rotate3dAnimation(0, 90, true);
            animation.setDuration(300);
            animation.setFillAfter(false);
            animation.setInterpolator(new AccelerateInterpolator());
            return animation;
        }
        return null;
    }

}

寫個工具類,方便調(diào)用靡努。然后在對應(yīng)需要用到這個效果的Fragment中添加下面的代碼坪圾。


    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
        Animation animation = AnimationHelper.onCreateAnimation(transit, enter, nextAnim);
        if (animation == null) {
            return super.onCreateAnimation(transit, enter, nextAnim);
        } else {
            return animation;
        }
    }

最終實現(xiàn)

此時問題就全部排除了晓折。前面顯示與隱藏Fragment的代碼改造成下面這樣:

    private static final String FRAGMENT_TAG_A = "FRAGMENT_TAG_A";
    private static final String FRAGMENT_TAG_B = "FRAGMENT_TAG_B";

    public void showFragmentA() {
        showFragment(FRAGMENT_TAG_A, FRAGMENT_TAG_B);
    }

    public void showFragmentB() {
        showFragment(FRAGMENT_TAG_B, FRAGMENT_TAG_A);
    }

    public void showFragment(String showTag, String hideTag) {
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        // 設(shè)置動畫
        AnimationHelper.setUpRotate3dAnimation(transaction);
        Fragment fragment = getSupportFragmentManager().findFragmentByTag(showTag);
        if (fragment == null) {
            // 沒有找到表示沒有被創(chuàng)建過
            fragment = new FragmentA();
            // 直接add
            transaction.add(R.id.fragment_content, fragment, showTag);
        } else {
            // 找到了,表示已經(jīng)被add了兽泄,所以直接show
            transaction.show(fragment);
        }

        fragment = getSupportFragmentManager().findFragmentByTag(hideTag);
        if (fragment != null) {
            // 找到了漓概,直接hide
            transaction.hide(fragment);
        }
        transaction.commit();
    }

這樣就可以setCustomAnimations使用自己自定義的Animation了。

其他

這里主要是介紹一種思路病梢,setCustomAniamtions不能set自定義Animation的時候怎么辦胃珍?看注釋,Google蜓陌,都不能解決的時候觅彰,如果通過分析猜測快速定位解決問題。當然中間還有很多Fragment相關(guān)的一些東西并沒有直接分析到钮热。比如Fragment填抬,F(xiàn)ragmentManageer,F(xiàn)ragmentTransaction之間的關(guān)系等隧期。主要是Fragment本身相對還是比較復雜的飒责,什么時候有空了,會把Fragment的源碼寫個文章分析下仆潮,會解釋清楚读拆,F(xiàn)ragment到底是什么,F(xiàn)ragment最后是如何顯示的鸵闪,DialogFragment明明沒有指定ContainerId檐晕,為什么它還是能顯示等等。

Rotate3dAnimator

最后再加一個Rotate3dAnimator蚌讼。為什么有個Animator辟灰?前面Google給的是Animation,但是假如你的項目使用的是android.app.Fragment篡石。那么你在Fragment需要復寫的就是

    @Override
    public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
        Animator animator = AnimationHelper.onCreateAnimator(transit, enter, nextAnim);
        if (animator == null) {
            return super.onCreateAnimator(transit, enter, nextAnim);
        } else {
            return animator;
        }
    }

這里就是3.0之后的屬性動畫了芥喇。所以前面Google給的Rotate3dAnimation就不能用了。那怎么辦凰萨?這里就需要寫一個3d變換的Animator實現(xiàn)了继控。下面給出實現(xiàn)代碼。

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.support.annotation.Nullable;
import android.view.View;

public class Rotate3dAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener {
    private static double K = Math.sqrt(2.0f);

    private static final int TYPE_SCALE = 0;
    private static final int TYPE_PX = 1;

    private View mTargetView;

    private final float mFromDegrees;
    private final float mToDegrees;
    private float mCenterX;
    private float mCenterY;
    private float mDepthZ;
    private int mType = TYPE_PX;
    private boolean mNeedInit = true;
    private final boolean mReverse;
    private boolean mException = true;

    private boolean mVisibleBeforeStart = false;

    public Rotate3dAnimator(float fromDegrees, float toDegrees, boolean reverse) {
        this(fromDegrees, toDegrees, 0.5f, 0.5f, 0.5f, reverse, TYPE_SCALE);
    }

    public Rotate3dAnimator(float fromDegrees, float toDegrees,
                            float centerX, float centerY, float depthZ,
                            boolean reverse, int type) {
        this.mFromDegrees = fromDegrees;
        this.mToDegrees = toDegrees;
        this.mReverse = reverse;
        this.mCenterX = centerX;
        this.mCenterY = centerY;
        this.mDepthZ = depthZ;
        this.mType = type;
        addUpdateListener(this);
        addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                if (!mVisibleBeforeStart) {
                    mTargetView.setVisibility(View.VISIBLE);
                }
                if (mNeedInit) {
                    if (mType == TYPE_SCALE) {
                        mCenterX = mCenterX * mTargetView.getWidth();
                        mCenterY = mCenterY * mTargetView.getHeight();
                    }
                    mTargetView.setPivotX(mCenterX);
                    mTargetView.setPivotY(mCenterY);
                    mNeedInit = false;
                }
                removeListener(this);
            }
        });
        setFloatValuesSafe(0f, 1f);
    }

    private void setFloatValuesSafe(float... values) {
        mException = false;
        setFloatValues(values);
        mException = true;
    }

    @Override
    public void setFloatValues(float... values) {
        if (mException) {
            throw new IllegalAccessError("Disable call. ");
        }
        super.setFloatValues(values);
    }

    public void setStartDelay(long startDelay, boolean visibleBeforeStart) {
        super.setStartDelay(startDelay);
        mVisibleBeforeStart = visibleBeforeStart;
    }

    View getTargetView() {
        return mTargetView;
    }

    @Override
    public void setTarget(@Nullable Object target) {
        super.setTarget(target);
        if (target == null) {
            throw new NullPointerException("Target can't be null.");
        }
        mTargetView = (View) target;
        if (!mVisibleBeforeStart) {
            mTargetView.setVisibility(View.INVISIBLE);
        }
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float progress = (float) animation.getAnimatedValue();
        final float fromDegrees = mFromDegrees;
        float degrees = fromDegrees + ((mToDegrees - fromDegrees) * progress);
        double value = mDepthZ * K;
        if (mReverse) {
            // progress 0 - 1
            // exit 1 -> value
            mTargetView.setScaleX((float) (1 - (1 - value) * progress));
            mTargetView.setScaleY((float) (1 - (1 - value) * progress));
        } else {
            // progress 0 - 1
            // enter value -> 1
            mTargetView.setScaleX((float) (value + (1 - value) * progress));
            mTargetView.setScaleY((float) (value + (1 - value) * progress));
        }
        mTargetView.setRotationY(degrees);
    }
}

注意有個setStartDelay方法有兩個參數(shù)胖眷,第二個參數(shù)是讓View在start前不顯示武通。為什么要這樣?因為翻轉(zhuǎn)的時候珊搀,背面的View需要在第一個View動畫處理完了才開始顯示冶忱,如果這個參數(shù)不指定false,那么第一次翻轉(zhuǎn)時候會有問題境析。具體可以自行嘗試下囚枪。
另外就是為什么這里翻轉(zhuǎn)時候不是移動Z軸派诬,而是對XY軸做Scale變換?這個链沼。默赂。。怎么解釋呢括勺?首先translationZ是5.0之后的Api缆八。其次translationZ是不能實現(xiàn)3d效果的翻轉(zhuǎn)的。整個翻轉(zhuǎn)的深度效果其實就是盡量保證翻轉(zhuǎn)時候有一條邊的高度搞好一直與容器高度相同朝刊。所以這里通過Scale來實現(xiàn)耀里。具體大家可自行嘗試translationZ看看實際是什么效果就明白了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拾氓,一起剝皮案震驚了整個濱河市冯挎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌咙鞍,老刑警劉巖房官,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異续滋,居然都是意外死亡翰守,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門疲酌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜡峰,“玉大人,你說我怎么就攤上這事朗恳∈” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵粥诫,是天一觀的道長油航。 經(jīng)常有香客問我,道長怀浆,這世上最難降的妖魔是什么谊囚? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮执赡,結(jié)果婚禮上镰踏,老公的妹妹穿的比我還像新娘。我一直安慰自己搀玖,他們只是感情好余境,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著灌诅,像睡著了一般芳来。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猜拾,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天即舌,我揣著相機與錄音,去河邊找鬼挎袜。 笑死顽聂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的盯仪。 我是一名探鬼主播紊搪,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼全景!你這毒婦竟也來了耀石?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤爸黄,失蹤者是張志新(化名)和其女友劉穎滞伟,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炕贵,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡梆奈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了称开。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亩钟。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鳖轰,靈堂內(nèi)的尸體忽然破棺而出清酥,到底是詐尸還是另有隱情,我是刑警寧澤脆霎,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布总处,位于F島的核電站,受9級特大地震影響睛蛛,放射性物質(zhì)發(fā)生泄漏鹦马。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一忆肾、第九天 我趴在偏房一處隱蔽的房頂上張望荸频。 院中可真熱鬧,春花似錦客冈、人聲如沸旭从。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽和悦。三九已至退疫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸽素,已是汗流浹背褒繁。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留馍忽,地道東北人棒坏。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像遭笋,于是被迫代替她去往敵國和親坝冕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,088評論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫瓦呼、插件喂窟、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,096評論 4 62
  • 前段時間<我的前半生>終于迎來了大結(jié)局。這好像是我近三年來第一次從頭看到尾一集不落的國產(chǎn)電視劇吵血。追劇的7月也將結(jié)束...
    玲夏ling閱讀 259評論 0 0
  • 微風拂過谎替,他眉眼帶笑的樣子我還記得很清楚
    顧野有北方閱讀 154評論 0 0
  • 良好的邏輯設(shè)計和物理設(shè)計是高性能的基石,應(yīng)該根據(jù)系統(tǒng)將要執(zhí)行的查詢語句設(shè)計schema蹋辅,但記住這往往需要權(quán)衡各種因...
    CaesarXia閱讀 1,354評論 0 3