自定義地圖下方可拖拽列表布局DragUpDownLinearLayout

這個控件是仿制高德地圖下面的可拖拽列表欄做的盐股。實現(xiàn)主要就是一個LinearLayout響應(yīng)用戶手勢拖拽阱驾,有全屏拘泞,半屏,和隱藏三個模式叁扫。依據(jù)拖拽到松手的位置的y坐標(biāo)占屏幕的百分比來確定對應(yīng)的模式位置三妈,再利用動畫移動到對應(yīng)的模式位置。
完整的代碼我會貼在文末莫绣。


DragUpDownLinearlayout.gif

一畴蒲、確定三個模式的位置

我這里使用的是鋪滿contentView,占contentView的1/3,和全部在隱藏在下面只留一個拖拽條三個模式对室。contentView的概念我這里大概講一下模燥,android的布局是在decorView這個根布局下的咖祭,分為titleView和ContentView。
titleView放的是ActionBar等位置蔫骂,如果設(shè)置noActionBar就沒有titleView的位置了么翰。
而contentView就是我們平時Activity里面onCreate中setContent的那個ContentView,相當(dāng)于我們的內(nèi)容布局的父布局辽旋,在這個控件我們計算主要依靠它來完成浩嫌。
三個模式的height也就是Y坐標(biāo)值是:

 switch (customMode) {
                    case TOP_MODE:
                        top = topHeight;
                        bottom = getHeight() + topHeight;
                        break;
                    case MIDDLE_MODE:
                        top = middleHeight;
                        bottom = getHeight() + middleHeight;
                        break;
                    case BOTTOM_MODE:
                        int topUp = contentViewHeight - indicatorHeightPx;
                        top = topUp;
                        bottom = getHeight() + topUp;
                        break;
                }

主要看一個top的賦值 這個top就是我們要設(shè)給onLayout的參數(shù),控件的頂部的y坐標(biāo)补胚。

topHeight contentView里面除了這個控件之外 頂部還有其他控件占位置码耐,我們需要加上這個控件的高度,不然會覆蓋掉它糖儡,例如上面有一個自定義的標(biāo)題欄沒有加入到Toolbar的位置而是放在contentView里面伐坏,那么這個情況就需要被考慮。這個值我是由外部初始化的時候計算傳入的握联。

middleHeight 計算方法:

contentViewHeight = ((Activity) getContext()).getWindow().
                    findViewById(Window.ID_ANDROID_CONTENT).getMeasuredHeight();
middleHeight = (contentViewHeight / 3) * 2;

上面說的是1/3但這里寫的是2/3是因為android屏幕的Y坐標(biāo)是向下的桦沉,我們需要在1/3的位置就需要讓控件向下移動2/3。

**topUp ** 相當(dāng)于留一個indicatorHeightPx(那個灰色的長條)的位置 其他全部在屏幕下方金闽。

OK纯露,位置的計算就只有這些了。

二代芜、拖拽控件

接下來就是主要的功能拖拽了埠褪。
這里需要用到手勢類GestureDetector,不熟悉的同學(xué)可以去搜索一下看一看挤庇,它里面封裝了各種手勢的觸發(fā)條件和觸發(fā)回調(diào)钞速,使用起來比自己重寫onTouch再分類要有效率的多。它的使用就是在onTouch方法里將參數(shù)傳遞給它:

public boolean onTouch(View view, MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
        return true;
    }

它的實現(xiàn)類:

    @Override
    public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float distanceX, float distanceY) {
        int y = (int) motionEvent1.getY();
        // 獲取本次移動的距離
        int dy = y - y0;
        int top = getTop();
        int bottom = getBottom();
        if (top <= topHeight && dy < 0) {
            // 高出頂部 則不改變位置防止超出頂部
            return false;
        }
        layout(getLeft(), (top + dy),
                getRight(), (bottom + dy));
        isScrolling = true;
        return false;
    }

    @Override
    public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float x, float speedY) {
        float v = motionEvent1.getRawY() - rawYDown;
        switch (customMode) {
            case TOP_MODE:
                animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                        getTranslationY(), getTranslationY() + (middleHeight - getY()));
                customMode = MIDDLE_MODE;
                break;
            case MIDDLE_MODE:
                if (v > 0) {
                    animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                            getTranslationY(), getTranslationY() + contentViewHeight - getY() - indicatorHeightPx);
                    customMode = BOTTOM_MODE;
                } else {
                    animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                            getTranslationY(), getTranslationY() - getY() + topHeight);
                    customMode = TOP_MODE;
                }
                break;
            case BOTTOM_MODE:
                animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                        getTranslationY(), getTranslationY() + (middleHeight - getY()));
                customMode = MIDDLE_MODE;
                break;
            default:
        }

        animator.setDuration(500);
        animator.start();
        // 動畫結(jié)束時嫡秕,將控件的translation偏移量轉(zhuǎn)化為Top值渴语,便于計算
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                float translationY = getTranslationY();
                setTranslationY(0);
                layout(getLeft(), (int) (getTop() + translationY),
                        getRight(), (int) (getBottom() + translationY));
                animator = null;
            }
        });
        isScrolling = false;
        hasFiling = true;
        return true;
    }

就使用了這兩個方法。思路就是在onScroll里面響應(yīng)拖拽調(diào)用layout方法不斷修改布局位置昆咽,然后結(jié)束的時候通常情況下回觸發(fā)onFiling方法驾凶,在這個方法里計算位置開始動畫等將控件移動到指定的位置。
還需要注意的是當(dāng)你慢慢拖拽的時候會觸發(fā)不了onFiling這個方法 所以我在這里添加了一個hasFiling的標(biāo)志位去判斷onFiling是否調(diào)用了掷酗,沒調(diào)用的話在onTouch里面再處理一下:

 @Override
    public boolean onTouch(View view, MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
        // 是否有執(zhí)行filing
        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (!hasFiling) {
                isScrolling = false;
                // 松手時固定位置 計算占屏幕的百分比
                float yUP = getTop();
                float i = yUP / screenHeight;
                if (i < 0.30) {
                    animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                            getTranslationY(), getTranslationY() - getY() + topHeight);
                    customMode = TOP_MODE;
                } else if (i < 0.75) {
                    animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                            getTranslationY(), getTranslationY() + (middleHeight - getY()));
                    customMode = MIDDLE_MODE;
                } else {
                    animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                            getTranslationY(), getTranslationY() + contentViewHeight - getY() - indicatorHeightPx);
                    customMode = BOTTOM_MODE;
                }
                animator.setDuration(500);
                animator.start();
                // 動畫結(jié)束時调违,將控件的translation偏移量轉(zhuǎn)化為Top值,便于計算
                animator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        float translationY = getTranslationY();
                        setTranslationY(0);
                        layout(getLeft(), (int) (getTop() + translationY),
                                getRight(), (int) (getBottom() + translationY));
                        animator = null;
                    }
                });
            }
        }
        return true;
    }

整體的邏輯還是看文末的代碼吧 這里只是介紹一下功能的實現(xiàn)泻轰。

三技肩、解決事件分發(fā)沖突

一般在這里內(nèi)部都會有一個ListView控件來展示數(shù)據(jù),它與我們的這個控件就會有滑動沖突浮声。
解決方法是用外部攔截法來解決亩鬼。
我在這里新建了一個接口來回調(diào)給調(diào)用類

    public void setInterceptCallBack(RequestInterceptCallBack interceptCallBack) {
        this.interceptCallBack = interceptCallBack;
    }

    public interface RequestInterceptCallBack {
        boolean canIntercept(boolean isDown);
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept = false;
        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                y0 = (int) ev.getY();
                rawYDown = ev.getRawY();
                intercept = false;
                hasFiling = false;
                break;

            case MotionEvent.ACTION_MOVE:
                float dy = ev.getY() - y0;
                Log.i(TAG, "dy" + dy);
                if (Math.abs(dy) < 7 || animator != null || (customMode == TOP_MODE && dy < 0)) {
                    // 移動過小視為點擊事件殖告。不攔截 或者 動畫尚未結(jié)束 本次不攔截
                    intercept = false;
                } else if (dy > 0) {
                    intercept = interceptCallBack.canIntercept(true);
                } else {
                    intercept = interceptCallBack.canIntercept(false);
                }
                break;

            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
        }
        return intercept;
    }

canIntercept(boolean isDown)這個參數(shù)我設(shè)置的是手勢是否下滑,如果是recyclerView則在方法重寫里面判斷
recyclerView.canScrollVertically(-1);這個方法雳锋。
例如:

@Override
    public boolean canIntercept(boolean isDown) {
        if (isDown) {
            Log.i(TAG, "-1: " + recyclerView.canScrollVertically(-1));
            return !recyclerView.canScrollVertically(-1);
        } else {
            Log.i(TAG, "1: " + recyclerView.canScrollVertically(1));
            return !recyclerView.canScrollVertically(1);
        }
    }

其中的邏輯需要自己揣摩一下。

四羡洁、最后調(diào)用類的初始化工作

在Activity中:

  dragUpDownLinearLayout.setInterceptCallBack(this);
            dragUpDownLinearLayout.setTopHeight(relativeLayout.getMeasuredHeight());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    dragUpDownLinearLayout.setLocation(DragUpDownLinearLayout.MIDDLE_MODE);
                    dragUpDownLinearLayout.setVisibility(View.VISIBLE);
                }
            });  

我這里是直接設(shè)置的Middle_Mode模式玷过。布局里設(shè)置的空間隱藏,設(shè)置完再顯示筑煮,不這樣的話會出現(xiàn)閃一下的變化位置辛蚊,比較不好,其實也可以進(jìn)入的時候走一個動畫真仲。這些都看愛好和需求吧袋马。

代碼

/**
 * Created by Vito 
 */
public class DragUpDownLinearLayout extends LinearLayout implements View.OnTouchListener,
        GestureDetector.OnGestureListener {
    public final static String TAG = "DragUpDownLinearLayout";
    public final static int TOP_MODE = 1;
    public final static int MIDDLE_MODE = 2;
    public final static int BOTTOM_MODE = 3;
    public int customMode = 0;
    // 手勢監(jiān)聽對象
    private GestureDetector mGestureDetector;
    // 拖拽條的高度
    private final static int indicatorHeight = 30;
    private int indicatorHeightPx;
    // 中間位置的高度
    private int middleHeight;
    // contentView(去掉狀態(tài)欄、toolbar和導(dǎo)航欄部分)的高度
    private int contentViewHeight;
    // 頂部其他控件的高度
    private int topHeight;
    // 屏幕的高度
    private float screenHeight;
    // 滑動開始手指落點
    private int y0;
    private float rawYDown;
    // 第一次加載標(biāo)志位
    private boolean isFirstLayout = true;
    // 是否攔截事件接口回調(diào)秸应,用于判斷子控件的是否可滑動
    private RequestInterceptCallBack interceptCallBack;
    // 動畫對象
    private ObjectAnimator animator = null;
    private static final String ANIMATOR_MODE = "translationY";
    // 是否觸發(fā)了Filing方法虑凛,未觸發(fā)交由onTouch方法完成移動
    private boolean hasFiling;
    // 是否在滾動觸發(fā)的layout的標(biāo)志位
    private boolean isScrolling;

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

    public DragUpDownLinearLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragUpDownLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @SuppressLint("ClickableViewAccessibility")
    private void init(Context context) {
        // 界面
        indicatorHeightPx = dp2px(indicatorHeight);
        setBackgroundColor(Color.WHITE);
        FrameLayout frameLayout = new FrameLayout(context);
        frameLayout.setLayoutParams(
                new LinearLayoutCompat.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, indicatorHeightPx));
        addView(frameLayout);
        View view = new View(context);
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(dp2px(75), dp2px(8));
        params.gravity = Gravity.CENTER;
        view.setLayoutParams(params);
        view.setBackgroundResource(R.drawable.shape_drag_up_down_indicator);
        frameLayout.addView(view);
        // 獲取屏幕的高
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        screenHeight = dm.heightPixels;
        setOnTouchListener(this);
        mGestureDetector = new GestureDetector(getContext(), this);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept = false;
        if (interceptCallBack != null) {
            switch (ev.getAction()) {

                case MotionEvent.ACTION_DOWN:
                    y0 = (int) ev.getY();
                    rawYDown = ev.getRawY();
                    intercept = false;
                    hasFiling = false;
                    break;

                case MotionEvent.ACTION_MOVE:
                    float dy = ev.getY() - y0;
                    Log.i(TAG, "dy" + dy);
                    if (Math.abs(dy) < 7 || animator != null || (customMode == TOP_MODE && dy < 0)) {
                        // 移動過小視為點擊事件。不攔截 或者 動畫尚未結(jié)束 本次不攔截
                        intercept = false;
                    } else if (dy > 0) {
                        intercept = interceptCallBack.canIntercept(true);
                    } else {
                        intercept = interceptCallBack.canIntercept(false);
                    }
                    break;

                case MotionEvent.ACTION_UP:
                    intercept = false;
                    break;
            }
        }
        return intercept;
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
        // 是否有執(zhí)行filing
        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (!hasFiling) {
                isScrolling = false;
                // 松手時固定位置 計算占屏幕的百分比
                float yUP = getTop();
                float i = yUP / screenHeight;
                if (i < 0.30) {
                    animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                            getTranslationY(), getTranslationY() - getY() + topHeight);
                    customMode = TOP_MODE;
                } else if (i < 0.75) {
                    animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                            getTranslationY(), getTranslationY() + (middleHeight - getY()));
                    customMode = MIDDLE_MODE;
                } else {
                    animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                            getTranslationY(), getTranslationY() + contentViewHeight - getY() - indicatorHeightPx);
                    customMode = BOTTOM_MODE;
                }
                animator.setDuration(500);
                animator.start();
                // 動畫結(jié)束時软啼,將控件的translation偏移量轉(zhuǎn)化為Top值桑谍,便于計算
                animator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        float translationY = getTranslationY();
                        setTranslationY(0);
                        layout(getLeft(), (int) (getTop() + translationY),
                                getRight(), (int) (getBottom() + translationY));
                        animator = null;
                    }
                });
            }
        }
        return true;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        Log.e(TAG, "onLayout" + t);
        if (isFirstLayout) {
            contentViewHeight = ((Activity) getContext()).getWindow().
                    findViewById(Window.ID_ANDROID_CONTENT).getMeasuredHeight();
            middleHeight = (contentViewHeight / 3) * 2;
            isFirstLayout = false;
            Log.e(TAG, "contentViewHeight" + contentViewHeight);
        } else {
            Log.e(TAG, "isScrolling" + isScrolling);
            if (!isScrolling) {
                switch (customMode) {
                    case TOP_MODE:
                        t = topHeight;
                        b = getHeight() + topHeight;
                        break;
                    case MIDDLE_MODE:
                        t = middleHeight;
                        b = getHeight() + middleHeight;
                        break;
                    case BOTTOM_MODE:
                        int topUp = contentViewHeight - indicatorHeightPx;
                        t = topUp;
                        b = getHeight() + topUp;
                        break;
                }
                setTop(t);
                setBottom(b);
            }
        }
        super.onLayout(changed, l, t, r, b);
    }

    /**
     * 設(shè)置位置,同于指定初始化位置
     */
    public void setLocation(int mode) {
        switch (mode) {
            case TOP_MODE:
                layout(getLeft(),
                        topHeight,
                        getRight(),
                        getHeight() + topHeight);
                customMode = TOP_MODE;
                break;
            case MIDDLE_MODE:
                layout(getLeft(), middleHeight,
                        getRight(), middleHeight + getHeight());
                customMode = MIDDLE_MODE;
                break;
            case BOTTOM_MODE:
                int topUp = contentViewHeight - indicatorHeightPx;
                layout(getLeft(), topUp,
                        getRight(), topUp + getHeight());
                customMode = BOTTOM_MODE;
                break;
        }

    }

    @Override
    public boolean onDown(MotionEvent motionEvent) {

        return false;
    }


    @Override
    public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float distanceX, float distanceY) {
        int y = (int) motionEvent1.getY();
        // 獲取本次移動的距離
        int dy = y - y0;
        int top = getTop();
        int bottom = getBottom();
        if (top <= topHeight && dy < 0) {
            // 高出頂部 則不改變位置防止超出頂部
            return false;
        }
        layout(getLeft(), (top + dy),
                getRight(), (bottom + dy));
        isScrolling = true;
        return false;
    }

    @Override
    public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float x, float speedY) {
        float v = motionEvent1.getRawY() - rawYDown;
        switch (customMode) {
            case TOP_MODE:
                animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                        getTranslationY(), getTranslationY() + (middleHeight - getY()));
                customMode = MIDDLE_MODE;
                break;
            case MIDDLE_MODE:
                if (v > 0) {
                    animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                            getTranslationY(), getTranslationY() + contentViewHeight - getY() - indicatorHeightPx);
                    customMode = BOTTOM_MODE;
                } else {
                    animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                            getTranslationY(), getTranslationY() - getY() + topHeight);
                    customMode = TOP_MODE;
                }
                break;
            case BOTTOM_MODE:
                animator = ObjectAnimator.ofFloat(DragUpDownLinearLayout.this, ANIMATOR_MODE,
                        getTranslationY(), getTranslationY() + (middleHeight - getY()));
                customMode = MIDDLE_MODE;
                break;
            default:
        }

        animator.setDuration(500);
        animator.start();
        // 動畫結(jié)束時祸挪,將控件的translation偏移量轉(zhuǎn)化為Top值锣披,便于計算
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                float translationY = getTranslationY();
                setTranslationY(0);
                layout(getLeft(), (int) (getTop() + translationY),
                        getRight(), (int) (getBottom() + translationY));
                animator = null;
            }
        });
        isScrolling = false;
        hasFiling = true;
        return true;
    }

    @Override
    public void onLongPress(MotionEvent motionEvent) {
    }

    @Override
    public void onShowPress(MotionEvent motionEvent) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent motionEvent) {
        return false;
    }

    private int dp2px(float dipValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    public void setInterceptCallBack(RequestInterceptCallBack interceptCallBack) {
        this.interceptCallBack = interceptCallBack;
    }

    public interface RequestInterceptCallBack {
        boolean canIntercept(boolean isDown);
    }

    /**
     * 重新請求一次contentView 因為toolbar將它往下頂了一部分,也就是加一個偏移量
     */
    public void resetContentViewHeight(int off) {
        contentViewHeight = ((Activity) getContext()).getWindow().
                findViewById(Window.ID_ANDROID_CONTENT).getMeasuredHeight() - off;
        middleHeight = (contentViewHeight / 3) * 2;
        Log.e(TAG, "resetContentViewHeight" + contentViewHeight);
    }

    /**
     * 設(shè)置頂部高度
     */
    public void setTopHeight(int topHeight) {
        this.topHeight = topHeight;
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贿条,一起剝皮案震驚了整個濱河市雹仿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌整以,老刑警劉巖胧辽,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異悄蕾,居然都是意外死亡票顾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門帆调,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奠骄,“玉大人,你說我怎么就攤上這事番刊『郏” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵芹务,是天一觀的道長蝉绷。 經(jīng)常有香客問我鸭廷,道長,這世上最難降的妖魔是什么熔吗? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任辆床,我火速辦了婚禮,結(jié)果婚禮上桅狠,老公的妹妹穿的比我還像新娘讼载。我一直安慰自己,他們只是感情好中跌,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布咨堤。 她就那樣靜靜地躺著,像睡著了一般漩符。 火紅的嫁衣襯著肌膚如雪一喘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天嗜暴,我揣著相機(jī)與錄音凸克,去河邊找鬼。 笑死灼伤,一個胖子當(dāng)著我的面吹牛触徐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播狐赡,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼撞鹉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了颖侄?” 一聲冷哼從身側(cè)響起鸟雏,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎览祖,沒想到半個月后孝鹊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡展蒂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年又活,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锰悼。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡柳骄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出箕般,到底是詐尸還是另有隱情耐薯,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站曲初,受9級特大地震影響体谒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜臼婆,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一抒痒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颁褂,春花似錦评汰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽主儡。三九已至奖唯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間糜值,已是汗流浹背丰捷。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留寂汇,地道東北人病往。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像骄瓣,于是被迫代替她去往敵國和親停巷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355