ViewDragHelper實戰(zhàn)啦逆,實現(xiàn)滑動解鎖

說到滑動解鎖,就回到了2012~2014年笛洛,iPhone4S夏志、5、5S年代苛让,如今準(zhǔn)備踏入2020年沟蔑,這些年國產(chǎn)機(jī)崛起,再也不是公交車上都是iPhone4S的場景狱杰。本篇來使用ViewDragHelper實現(xiàn)滑動解鎖瘦材。

成品展示

ViewDragHelper實戰(zhàn),實現(xiàn)滑動解鎖.gif

先來分析一下頁面的元素

  1. 背景圖
  2. 圓角滑道
  3. 圓形滑塊
  4. 閃動提示文字

其他一些細(xì)節(jié):

  1. 滑道和圓形滑塊之間有些邊距仿畸,我們使用padding來處理食棕。

我們需要自定義的就是第2點,這個滑道包含一個滑塊的圖片和提示文字错沽,滑塊使用原生ImageView即可簿晓,而提示文字則是一個支持漸變著色的TextView(不是重點)。

漸變著色的TextView

先秒掉簡單的千埃,漸變著色的TextView憔儿,不是重點,代碼量不多镰禾。

public class ShineTextView extends TextView {
    private LinearGradient mLinearGradient;
    private Matrix mGradientMatrix;
    private int mViewWidth = 0;
    private int mTranslate = 0;

    public ShineTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (mViewWidth == 0) {
            mViewWidth = getMeasuredWidth();
            if (mViewWidth > 0) {
                Paint paint = getPaint();
                mLinearGradient = new LinearGradient(0,
                        0,
                        mViewWidth,
                        0,
                        new int[]{getCurrentTextColor(), 0xffffffff, getCurrentTextColor()},
                        null,
                        Shader.TileMode.CLAMP);
                paint.setShader(mLinearGradient);
                mGradientMatrix = new Matrix();
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mGradientMatrix != null) {
            mTranslate += mViewWidth / 5;
            if (mTranslate > 2 * mViewWidth) {
                mTranslate = -mViewWidth;
            }
            mGradientMatrix.setTranslate(mTranslate, 0);
            mLinearGradient.setLocalMatrix(mGradientMatrix);
            //每80毫秒執(zhí)行onDraw()
            postInvalidateDelayed(80);
        }
    }
}

下面重點介紹我們使用ViewDragHelper實現(xiàn)拽托皿曲、滑動的滑道View:SlideLockView

讓滑塊滑動起來

滑道實際就是一個FrameLayout,我們使用ViewDragHelper將滑塊ImageView進(jìn)行拽托吴侦,主要我們要做以下幾件事:

  1. 限制拽托的左側(cè)起點屋休、右側(cè)終點(否則滑塊就出去啦!)
  2. 松手時判斷滑塊的x坐標(biāo)是偏向滑道的左側(cè)還是右側(cè)备韧,來決定滑動到起點還是終點劫樟。
  3. 滾動結(jié)束,判斷是否到達(dá)了右側(cè)的終點织堂。
  4. 判斷拽托速度叠艳,如果超過指定速度,則自動滾動滑塊到右側(cè)終點易阳。

看到這4點附较,如果讓我們用事件分發(fā)來處理,代碼量和判斷會非常多潦俺,并且需要做速度檢測拒课,而使用ViewDragHelper徐勃,上面4點都封裝好啦,我們添加一個回調(diào)早像,再將事件委托給它僻肖,在回調(diào)中做事情上面4點的處理,一切都簡單起來了卢鹦。

  • 創(chuàng)建SlideLockView臀脏,繼承FrameLayout
public class SlideLockView extends FrameLayout {
    /**
     * 拽托幫助類
     */
    private ViewDragHelper mViewDragHelper;
    
    public SlideLockView(@NonNull Context context) {
        this(context, null);
    }

    public SlideLockView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlideLockView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }

    private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        //進(jìn)行初始化...
    }
}
  • 創(chuàng)建ViewDragHelper,使用create靜態(tài)方法創(chuàng)建冀自,有3個參數(shù)揉稚,第一個拽托控件的父控件(就是當(dāng)前View),第二個參數(shù)是拽托靈敏度凡纳,數(shù)值越大窃植,越靈敏帝蒿,默認(rèn)為1.0荐糜,第三個參數(shù)為回調(diào)對象。
private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    final SlideLockView slideRail = this;
    mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {
        ...
    });
}
  • 委托onInterceptTouchEvent葛超、onTouchEvent事件給ViewDragHelper
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    //將onInterceptTouchEvent委托給ViewDragHelper
    return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
    
@Override
public boolean onTouchEvent(MotionEvent event) {
    //將onTouchEvent委托給ViewDragHelper
    mViewDragHelper.processTouchEvent(event);
    return true;
}
  • 找到布局中的滑塊暴氏,我們要求滑塊的id為lock_btn,所以需要在ids.xml中預(yù)先定義這個id绣张,如果沒有查找到答渔,則拋出異常。
//文件名:ids.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="lock_btn" type="id" />
</resources>
@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    //找到需要拽托的滑塊
    mLockBtn = findViewById(R.id.lock_btn);
    if (mLockBtn == null) {
        throw new NullPointerException("必須要有一個滑動滑塊");
    }
}
  • 剩下的事情就在ViewDragHelper的回調(diào)中設(shè)置侥涵。

復(fù)寫tryCaptureView()沼撕、clampViewPositionHorizontal()、clampViewPositionVertical()芜飘。

  1. tryCaptureView為判斷子View是否可以拽托
  2. clampViewPositionHorizontal()則是橫向拽托子View時回調(diào)务豺,返回可以拽托到的位置。
  3. clampViewPositionVertical則是縱向拽托嗦明。
private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    final SlideLockView slideRail = this;
    mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(@NonNull View child, int pointerId) {
            //判斷能拽托的View笼沥,這里會遍歷內(nèi)部子控件來決定是否可以拽托,我們只需要滑塊可以滑動
            return child == mLockBtn;
        }
        
        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            //拽托子View橫向滑動時回調(diào)娶牌,回調(diào)的left奔浅,則是可以滑動的左上角x坐標(biāo)
            return left;
        }

        @Override
        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
            //拽托子View縱向滑動時回調(diào),鎖定頂部padding距離即可诗良,不能不復(fù)寫汹桦,否則少了頂部的padding,位置就偏去上面了
            return getPaddingTop();
        }
    });
}

限制滑塊滑動范圍

  • 經(jīng)過上面3個方法重寫鉴裹,滑塊已經(jīng)可以左右滑動了舞骆,但是可以滑動出滑道(父控件)灵嫌,我們需要限制橫向滑動的范圍,不能超過左側(cè)起點和右側(cè)終點葛作。我們需要修改clampViewPositionHorizontal這個方法寿羞。

  • 左側(cè)起點的x坐標(biāo),就是paddingStart赂蠢。

  • 右側(cè)終點绪穆,為滑道總長度 - 右邊邊距 - 滑塊寬度。

  • 判斷回調(diào)的left值虱岂,如果小于起點玖院,則強(qiáng)制為起點,如果大于右側(cè)終點值第岖,則強(qiáng)制為終點难菌。

這樣處理,滑塊則不會滑出滑道了蔑滓!代碼量不對郊酒,也很清晰。

private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    final SlideLockView slideRail = this;
    mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {
        //...
        
        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            //拽托子View橫向滑動時回調(diào)键袱,回調(diào)的left燎窘,則是可以滑動的左上角x坐標(biāo)
            int lockBtnWidth = mLockBtn.getWidth();
            //限制左右臨界點
            int fullWidth = slideRail.getWidth();
            //最少的左邊
            int leftMinDistance = getPaddingStart();
            //最多的右邊
            int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth;
            //修復(fù)兩端的臨界值
            if (left < leftMinDistance) {
                return leftMinDistance;
            } else if (left > leftMaxDistance) {
                return leftMaxDistance;
            }
            return left;
        }

        //...
    });
}

松手回彈和速度檢測

有了滑動和限制滑動范圍,我們還有一個松手回彈和速度檢測蹄咖,ViewDragHelper同樣給我們封裝了褐健,提供了一個onViewReleased()回調(diào),并且做了速度檢測澜汤,將速度也回傳給了我們蚜迅。

  • 復(fù)寫onViewCaptured(),主要是為了獲取一開始捕獲到滑塊時俊抵,他的top值谁不。
  • 復(fù)寫onViewReleased(),主要是計算松手時,滑塊比較近起點還比較近是終點务蝠,使用ViewDragHelper的settleCapturedViewAt()方法拍谐,開始彈性滾動滑塊去到起點或終點。
  • 判斷中馏段,我們添加判斷速度是否超過1000轩拨,如果超過,即使拽托距離比較小院喜,就當(dāng)為fling操作亡蓉,讓滑塊滾動到終點。

settleCapturedViewAt()這個方法喷舀,內(nèi)部是使用Scroller進(jìn)行彈性滾動的砍濒,所以我們需要復(fù)寫父View的computeScroll()方法淋肾,進(jìn)行內(nèi)容滾動處理。

如果不知道為什么這么做爸邢,搜索一下Scroller的資料了解一下~

private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    final SlideLockView slideRail = this;
    mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {
        private int mTop;
        
        //...
        
        @Override
        public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {
            super.onViewCaptured(capturedChild, activePointerId);
            //捕獲到拽托的View時回調(diào)樊卓,獲取頂部距離
            mTop = capturedChild.getTop();
        }

        @Override
        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            //獲取滑塊當(dāng)前的位置
            int currentLeft = releasedChild.getLeft();
            //獲取滑塊的寬度
            int lockBtnWidth = mLockBtn.getWidth();
            //獲取滑道寬度
            int fullWidth = slideRail.getWidth();
            //一般滑道的寬度,用來判斷滑塊距離起點近還是終點近
            int halfWidth = fullWidth / 2;
            //松手位置在小于一半杠河,并且滑動速度小于1000碌尔,則回到左邊
            if (currentLeft <= halfWidth && xvel < 1000) {
                mViewDragHelper.settleCapturedViewAt(getPaddingStart(), mTop);
            } else {
                //否則去到右邊(寬度,減去padding和滑塊寬度)
                mViewDragHelper.settleCapturedViewAt(fullWidth - getPaddingEnd() - lockBtnWidth, mTop);
            }
            invalidate();
        }
    });
}

@Override
public void computeScroll() {
    super.computeScroll();
    //判斷是否移動到頭了券敌,未到頭則繼續(xù)
    if (mViewDragHelper != null) {
        if (mViewDragHelper.continueSettling(true)) {
            invalidate();
        }
    }
}

解鎖回調(diào)

經(jīng)過上面的編碼唾戚,滑動解鎖就完成了,但還差一個解鎖回調(diào)待诅,進(jìn)行解鎖操作叹坦,并且我們需要一個時機(jī)知道滾動結(jié)束了(ViewDragHelper狀態(tài)回調(diào),滾動閑置了卑雁,并且滑塊位于終點募书,則為解鎖完成)。

  • 復(fù)寫onViewDragStateChanged()方法序厉,處理ViewDragHelper狀態(tài)改變锐膜,狀態(tài)主要有以下3個:

    1. STATE_IDLE = 0,滾動閑置弛房,可以認(rèn)為滾動停止了。
    2. STATE_DRAGGING = 1而柑,正在拽托文捶。
    3. STATE_SETTLING = 2,fling操作時媒咳。
  • 提供Callback接口回調(diào)和設(shè)置方法粹排。

我們在onViewDragStateChanged()回調(diào)中判斷,狀態(tài)為STATE_IDLE涩澡,并且滑塊位置為終點值時顽耳,就為解鎖,并且回調(diào)Callback對象妙同。

public class SlideLockView extends FrameLayout {
    /**
     * 回調(diào)
     */
    private Callback mCallback;
    /**
     * 是否解鎖
     */
    private boolean isUnlock = false;
    
    private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        final SlideLockView slideRail = this;
        mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {
            private int mTop;
            
            //...
            
            @Override
            public void onViewDragStateChanged(int state) {
                super.onViewDragStateChanged(state);
                int lockBtnWidth = mLockBtn.getWidth();
                //限制左右臨界點
                int fullWidth = slideRail.getWidth();
                //最多的右邊
                int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth;
                int left = mLockBtn.getLeft();
                if (state == ViewDragHelper.STATE_IDLE) {
                    //移動到最右邊射富,解鎖完成
                    if (left == leftMaxDistance) {
                        //未解鎖才進(jìn)行解鎖回調(diào),由于這個判斷會進(jìn)兩次粥帚,所以做了標(biāo)志位限制
                        if (!isUnlock) {
                            isUnlock = true;
                            if (mCallback != null) {
                                mCallback.onUnlock();
                            }
                        }
                    }
                }
            }
        });
    }

    public interface Callback {
        /**
         * 當(dāng)解鎖時回調(diào)
         */
        void onUnlock();
    }

    public void setCallback(Callback callback) {
        mCallback = callback;
    }
}

完整代碼

public class SlideLockView extends FrameLayout {
    /**
     * 滑動滑塊
     */
    private View mLockBtn;
    /**
     * 拽托幫助類
     */
    private ViewDragHelper mViewDragHelper;
    /**
     * 回調(diào)
     */
    private Callback mCallback;
    /**
     * 是否解鎖
     */
    private boolean isUnlock = false;

    public SlideLockView(@NonNull Context context) {
        this(context, null);
    }

    public SlideLockView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlideLockView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }

    private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        final SlideLockView slideRail = this;
        mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {
            private int mTop;

            @Override
            public boolean tryCaptureView(@NonNull View child, int pointerId) {
                //判斷能拽托的View胰耗,這里會遍歷內(nèi)部子控件來決定是否可以拽托,我們只需要滑塊可以滑動
                return child == mLockBtn;
            }

            @Override
            public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
                //拽托子View橫向滑動時回調(diào)芒涡,回調(diào)的left柴灯,則是可以滑動的左上角x坐標(biāo)
                int lockBtnWidth = mLockBtn.getWidth();
                //限制左右臨界點
                int fullWidth = slideRail.getWidth();
                //最少的左邊
                int leftMinDistance = getPaddingStart();
                //最多的右邊
                int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth;
                //修復(fù)兩端的臨界值
                if (left < leftMinDistance) {
                    return leftMinDistance;
                } else if (left > leftMaxDistance) {
                    return leftMaxDistance;
                }
                return left;
            }

            @Override
            public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
                //拽托子View縱向滑動時回調(diào)卖漫,鎖定頂部padding距離即可,不能不復(fù)寫赠群,否則少了頂部的padding羊始,位置就偏去上面了
                return getPaddingTop();
            }

            @Override
            public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {
                super.onViewCaptured(capturedChild, activePointerId);
                //捕獲到拽托的View時回調(diào),獲取頂部距離
                mTop = capturedChild.getTop();
            }

            @Override
            public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
                super.onViewReleased(releasedChild, xvel, yvel);
                //獲取滑塊當(dāng)前的位置
                int currentLeft = releasedChild.getLeft();
                //獲取滑塊的寬度
                int lockBtnWidth = mLockBtn.getWidth();
                //獲取滑道寬度
                int fullWidth = slideRail.getWidth();
                //一般滑道的寬度查描,用來判斷滑塊距離起點近還是終點近
                int halfWidth = fullWidth / 2;
                //松手位置在小于一半店枣,并且滑動速度小于1000,則回到左邊
                if (currentLeft <= halfWidth && xvel < 1000) {
                    mViewDragHelper.settleCapturedViewAt(getPaddingStart(), mTop);
                } else {
                    //否則去到右邊(寬度叹誉,減去padding和滑塊寬度)
                    mViewDragHelper.settleCapturedViewAt(fullWidth - getPaddingEnd() - lockBtnWidth, mTop);
                }
                invalidate();
            }

            @Override
            public void onViewDragStateChanged(int state) {
                super.onViewDragStateChanged(state);
                int lockBtnWidth = mLockBtn.getWidth();
                //限制左右臨界點
                int fullWidth = slideRail.getWidth();
                //最多的右邊
                int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth;
                int left = mLockBtn.getLeft();
                if (state == ViewDragHelper.STATE_IDLE) {
                    //移動到最右邊鸯两,解鎖完成
                    if (left == leftMaxDistance) {
                        //未解鎖才進(jìn)行解鎖回調(diào),由于這個判斷會進(jìn)兩次长豁,所以做了標(biāo)志位限制
                        if (!isUnlock) {
                            isUnlock = true;
                            if (mCallback != null) {
                                mCallback.onUnlock();
                            }
                        }
                    }
                }
            }
        });
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //找到需要拽托的滑塊
        mLockBtn = findViewById(R.id.lock_btn);
        if (mLockBtn == null) {
            throw new NullPointerException("必須要有一個滑動滑塊");
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //將onInterceptTouchEvent委托給ViewDragHelper
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //將onTouchEvent委托給ViewDragHelper
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        //判斷是否移動到頭了钧唐,未到頭則繼續(xù)
        if (mViewDragHelper != null) {
            if (mViewDragHelper.continueSettling(true)) {
                invalidate();
            }
        }
    }

    public interface Callback {
        /**
         * 當(dāng)解鎖時回調(diào)
         */
        void onUnlock();
    }

    public void setCallback(Callback callback) {
        mCallback = callback;
    }
}

基本使用

  • Xml控件布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/app_lock_screen_bg"
    tools:context=".ScreenLockActivity">

    <com.zh.android.slidelockscreen.widget.SlideLockView
        android:id="@+id/slide_rail"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_marginBottom="20dp"
        android:background="@drawable/app_slide_rail_bg"
        android:paddingStart="6dp"
        android:paddingTop="8dp"
        android:paddingEnd="6dp"
        android:paddingBottom="8dp">

        <com.zh.android.slidelockscreen.widget.ShineTextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginStart="20dp"
            android:gravity="center"
            android:text="右滑解鎖打開應(yīng)用"
            android:textColor="@color/app_tip_text"
            android:textSize="18sp" />

        <ImageView
            android:id="@id/lock_btn"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:src="@drawable/app_lock_btn" />
    </com.zh.android.slidelockscreen.widget.SlideLockView>
</FrameLayout>
  • Java代碼
public class ScreenLockActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_screen_lock);
        SlideLockView slideRail = findViewById(R.id.slide_rail);
        slideRail.setCallback(new SlideLockView.Callback() {
            @Override
            public void onUnlock() {
                //解鎖,跳轉(zhuǎn)到首頁
                Intent intent = new Intent(ScreenLockActivity.this, HomeActivity.class);
                startActivity(intent);
                finish();
            }
        });
    }
}

項目Github地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匠襟,一起剝皮案震驚了整個濱河市钝侠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酸舍,老刑警劉巖帅韧,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異啃勉,居然都是意外死亡忽舟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門淮阐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叮阅,“玉大人,你說我怎么就攤上這事泣特『评眩” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵状您,是天一觀的道長勒叠。 經(jīng)常有香客問我,道長膏孟,這世上最難降的妖魔是什么眯分? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮骆莹,結(jié)果婚禮上颗搂,老公的妹妹穿的比我還像新娘。我一直安慰自己幕垦,他們只是感情好丢氢,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布傅联。 她就那樣靜靜地躺著,像睡著了一般疚察。 火紅的嫁衣襯著肌膚如雪蒸走。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天貌嫡,我揣著相機(jī)與錄音比驻,去河邊找鬼。 笑死岛抄,一個胖子當(dāng)著我的面吹牛别惦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播夫椭,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼掸掸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蹭秋?” 一聲冷哼從身側(cè)響起扰付,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仁讨,沒想到半個月后羽莺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洞豁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年盐固,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片族跛。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡闰挡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出礁哄,到底是詐尸還是另有隱情,我是刑警寧澤溪北,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布桐绒,位于F島的核電站,受9級特大地震影響之拨,放射性物質(zhì)發(fā)生泄漏茉继。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一蚀乔、第九天 我趴在偏房一處隱蔽的房頂上張望烁竭。 院中可真熱鬧,春花似錦吉挣、人聲如沸派撕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽终吼。三九已至镀赌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間际跪,已是汗流浹背商佛。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留姆打,地道東北人良姆。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像幔戏,于是被迫代替她去往敵國和親玛追。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345