Android可持續(xù)滑動(dòng)布局:ConsecutiveScrollerLayout

ConsecutiveScrollerLayout是我在GitHub開源的一個(gè)Android自定義滑動(dòng)布局啥供,它可以讓多個(gè)滑動(dòng)布局和普通控件在界面上像一個(gè)整體一樣連續(xù)順暢地滑動(dòng)俘侠。

試想我們有這樣一個(gè)需求澈侠,在一個(gè)界面上有輪播圖窜锯、像九宮格一樣的分類布局盗痒、幾個(gè)樣式不一樣的列表腔稀,中間還夾雜著各種廣告圖和展示各類活動(dòng)的布局隧哮,這樣的設(shè)計(jì)在大型的app首頁上非常常見。又比如像咨詢類的文章詳情頁或者電商類的商品詳情頁這種一個(gè)WebView加上原生的評論列表、推薦列表和廣告位稽犁。這種復(fù)雜的布局實(shí)現(xiàn)起來往往比較困難焰望,而且對于頁面的滑動(dòng)流暢性和布局的顯示效率要求較高。在以前我遇到這種復(fù)雜的布局已亥,會(huì)使用我在Github開源的項(xiàng)目GroupedRecyclerViewAdapter 實(shí)現(xiàn)。當(dāng)初設(shè)計(jì)GroupedRecyclerViewAdapter虑椎,是為了能讓RecyclerView方便地實(shí)現(xiàn)二級列表震鹉、分組列表和在一個(gè)RecyclerView上顯示不同的列表。由于GroupedRecyclerViewAdapter支持設(shè)置不同item類型的頭部捆姜、尾部和子項(xiàng)传趾,所有它能在一個(gè)RecyclerView上顯示多種不同的布局和列表,也符合實(shí)現(xiàn)復(fù)雜布局的需求娇未。但是由于GroupedRecyclerViewAdapter并非為這種復(fù)雜布局設(shè)計(jì)的墨缘,用它來實(shí)現(xiàn)這種布局,需要使用者在GroupedRecyclerViewAdapter的子類上管理好頁面的數(shù)據(jù)和各種類型布局的顯示邏輯零抬,顯得臃腫又麻煩。如果不把它整合在一個(gè)RecyclerView上宽涌,而是使用布局的嵌套實(shí)現(xiàn)平夜,不僅嚴(yán)重影響布局的性能,而且解決滑動(dòng)沖突也是個(gè)令人頭疼的問題卸亮。盡管Google為了更好地解決滑動(dòng)布局間的滑動(dòng)沖突問題忽妒,在Android 5.0的時(shí)候推出了NestedScrolling機(jī)制,不過要自己來處理各種滑動(dòng)問題兼贸,依然不是一件容易的事情段直。

無論多么復(fù)雜的頁面,它都是由一個(gè)個(gè)小控件組成的溶诞。如果能有一個(gè)布局容器幫我們處理好布局內(nèi)所有的子View的滑動(dòng)問題鸯檬,使得無論是普通控件還是滑動(dòng)布局,在這個(gè)容器里都能像一個(gè)整體一樣滑動(dòng)螺垢,滑動(dòng)它就好像是滑動(dòng)一個(gè)普通的ScrollView一樣喧务。那么我們是否就可以不用再關(guān)心布局的滑動(dòng)沖突和滑動(dòng)性能問題。無論多么復(fù)雜的布局枉圃,我們都只需要考慮布局的各個(gè)小部分該用什么控件就用什么控件功茴,任何復(fù)雜的布局都將不再復(fù)雜。ConsecutiveScrollerLayout正是基于這樣的需求而設(shè)計(jì)的孽亲。

設(shè)計(jì)思路

在構(gòu)思ConsecutiveScrollerLayout時(shí)坎穿,我是考慮使用NestedScrolling機(jī)制實(shí)現(xiàn)的,但是后來我放棄了這種方案,主要原因有二:

1玲昧、NestedScrolling機(jī)制主要是協(xié)調(diào)父布局和子布局的滑動(dòng)沖突栖茉,分發(fā)滑動(dòng)事件,至于布局的滑動(dòng)是由它們自己各自完成的酌呆。這不符合我希望把ConsecutiveScrollerLayout的所有子View當(dāng)作一個(gè)滑動(dòng)整體的構(gòu)思衡载,我希望把子View的內(nèi)容視作是ConsecutiveScrollerLayout內(nèi)容的一部分,無論是ConsecutiveScrollerLayout自身還是它的子View隙袁,都由ConsecutiveScrollerLayout來統(tǒng)一處理滑動(dòng)事件痰娱。

2、NestedScrolling機(jī)制要求父布局實(shí)現(xiàn)NestedScrollingParent接口菩收,所有可滑動(dòng)的子View實(shí)現(xiàn)NestedScrollingChild接口梨睁。而我希望ConsecutiveScrollerLayout在使用上盡可能的沒有限制,任何View放進(jìn)它都可以很好的工作娜饵,而且子View無需關(guān)心它是怎么滑動(dòng)的坡贺。

否決了NestedScrolling機(jī)制后,我嘗試從View的內(nèi)容滑動(dòng)的相關(guān)方法來尋找突破點(diǎn)箱舞。我發(fā)現(xiàn)Android幾乎所有的View都是通過scrollBy() -> scrollTo()方法滑動(dòng)View的內(nèi)容遍坟,而且大部分的滑動(dòng)布局也是直接或者間接調(diào)用這個(gè)方法來實(shí)現(xiàn)滑動(dòng)的。所以這兩個(gè)方法是處理布局滑動(dòng)的入口晴股,通過重寫這兩個(gè)方法可以重新定義布局的滑動(dòng)邏輯愿伴。

具體的思路是通過攔截可滑動(dòng)的子view的滑動(dòng)事件,使它無法自己滑動(dòng)电湘,而把事件統(tǒng)一交由ConsecutiveScrollerLayout處理隔节,ConsecutiveScrollerLayout重寫scrollBy()、scrollTo()方法寂呛,在scrollTo()方法中通過計(jì)算分發(fā)滑動(dòng)的偏移量怎诫,決定是由自身還是具體的子View消費(fèi)滑動(dòng)的距離,調(diào)用自身的super.scrollTo()和子View的scrollBy()來滑動(dòng)自身和子View的內(nèi)容贷痪。

說了這么多幻妓,下面讓我們通過代碼,分析一下ConsecutiveScrollerLayout是如何實(shí)現(xiàn)的呢诬。下面給出的代碼是源碼的一些主要片段涌哲,刪除了一些與設(shè)計(jì)思路和主要流程無關(guān)的處理細(xì)節(jié),便于大家更好的理解它的設(shè)計(jì)和實(shí)現(xiàn)原理尚镰。

效果圖

在開始前阀圾,先讓大家看一下ConsecutiveScrollerLayout實(shí)現(xiàn)的效果。

sample.gif
sticky.gif

onMeasure狗唉、onLayout

ConsecutiveScrollerLayout繼承自ViewGroup初烘,一個(gè)自定義布局總是免不了重寫onMeasure、onLayout來測量和定位子View。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mScrollRange = 0;
        int childTop = t + getPaddingTop();
        int left = l + getPaddingLeft();

        List<View> children = getNonGoneChildren();
        int count = children.size();
        for (int i = 0; i < count; i++) {
            View child = children.get(i);
            int bottom = childTop + child.getMeasuredHeight();
            child.layout(left, childTop, left + child.getMeasuredWidth(), bottom);
            childTop = bottom;
            // 聯(lián)動(dòng)容器可滾動(dòng)最大距離
            mScrollRange += child.getHeight();
        }
        // 聯(lián)動(dòng)容器可滾動(dòng)range
        mScrollRange -= getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
    }

        /**
     * 返回所有的非GONE子View
     */
    private List<View> getNonGoneChildren() {
        List<View> children = new ArrayList<>();
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                children.add(child);
            }
        }
        return children;
    }

onMeasured的邏輯很簡單肾筐,遍歷測量子vew即可哆料。onLayout是把子view從上到下排列,就像一個(gè)垂直的LinearLayout一樣吗铐。getNonGoneChildren()方法過濾掉隱藏的子view东亦,隱藏的子view不參與布局。上面的mScrollRange變量是布局自身可滑動(dòng)的范圍唬渗,它等于所有子view的高度減去布局自身的內(nèi)容顯示高度典阵。在后面,它將用于計(jì)算布局的滑動(dòng)偏移和邊距限制镊逝。

攔截滑動(dòng)事件

前面說過ConsecutiveScrollerLayout會(huì)攔截它的可滑動(dòng)的子view的滑動(dòng)事件壮啊,由自己來處理所有的滑動(dòng)。下面是它攔截事件的實(shí)現(xiàn)撑蒜。

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            // 需要攔截事件
            if (isIntercept(ev)) {
                return true;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

如果是滑動(dòng)事件(ACTION_MOVE)歹啼,判斷是否需要攔截事件,攔截則直接返回true座菠,讓事件交由ConsecutiveScrollerLayout的onTouchEvent方法處理狸眼。判斷是否需要攔截的關(guān)鍵是isIntercept(ev)方法。

    /**
     * 判斷是否需要攔截事件
     */
    private boolean isIntercept(MotionEvent ev) {
            // 根據(jù)觸摸點(diǎn)獲取當(dāng)前觸摸的子view
        View target = getTouchTarget((int) ev.getRawX(), (int) ev.getRawY());

        if (target != null) {
          // 判斷子view是否允許父布局?jǐn)r截事件
            ViewGroup.LayoutParams lp = target.getLayoutParams();
            if (lp instanceof LayoutParams) {
                if (!((LayoutParams) lp).isConsecutive) {
                    return false;
                }
            }

          // 判斷子view是否可以垂直滑動(dòng)
            if (ScrollUtils.canScrollVertically(target)) {
                return true;
            }
        }

        return false;
    }

public class ScrollUtils {

  static boolean canScrollVertically(View view) {
        return canScrollVertically(view, 1) || canScrollVertically(view, -1);
    }
  
  static boolean canScrollVertically(View view, int direction) {
        return view.canScrollVertically(direction);
    }
}

判斷是否需要攔截事件浴滴,主要是通過判斷觸摸的子view是否可以垂直滑動(dòng)份企,如果可以垂直滑動(dòng),就攔截事件巡莹,讓事件由ConsecutiveScrollerLayout自己處理。如果不是甜紫,就不攔截降宅,一般不能滑動(dòng)的view不會(huì)消費(fèi)滑動(dòng)事件,所以事件最終會(huì)由ConsecutiveScrollerLayout所消費(fèi)囚霸。之所以不直接攔截腰根,是為了能讓子view盡可能的獲得事件處理和分發(fā)給下面的view的機(jī)會(huì)。

這里有一個(gè)isConsecutive的LayoutParams屬性拓型,它是ConsecutiveScrollerLayout.LayoutParams的自定義屬性额嘿,用于表示一個(gè)子view是否允許ConsecutiveScrollerLayout攔截它的滑動(dòng)事件,默認(rèn)為true劣挫。如果把它設(shè)置為false册养,父布局將不會(huì)攔截這個(gè)子view的事件,而是完全交由子view處理压固。這使得子view有了自己處理滑動(dòng)事件的機(jī)會(huì)和分發(fā)事件的主動(dòng)權(quán)球拦。這對于實(shí)現(xiàn)一些需要實(shí)現(xiàn)局部區(qū)域內(nèi)滑動(dòng)的特殊需求十分有用。我在GitHub中提供的demo和使用介紹中對isConsecutive有詳細(xì)的說明,在這就不做過多介紹了坎炼。

滑動(dòng)處理

把事件攔截后愧膀,就要在onTouchEvent方法中處理滑動(dòng)事件。

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                    // 記錄觸摸點(diǎn)
                mTouchY = (int) ev.getY();
                    // 追蹤滑動(dòng)速度
                initOrResetVelocityTracker();
                mVelocityTracker.addMovement(ev);
                break;
            case MotionEvent.ACTION_MOVE:
                if (mTouchY == 0) {
                    mTouchY = (int) ev.getY();
                    return true;
                }
                int y = (int) ev.getY();
                int dy = y - mTouchY;
                mTouchY = y;
                    // 滑動(dòng)布局
                scrollBy(0, -dy);
                                // 追蹤滑動(dòng)速度
                initVelocityTrackerIfNotExists();
                mVelocityTracker.addMovement(ev);
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mTouchY = 0;

                if (mVelocityTracker != null) {
                    // 處理慣性滑動(dòng)
                    mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int yVelocity = (int) mVelocityTracker.getYVelocity();
                    recycleVelocityTracker();
                    fling(-yVelocity);
                }
                break;
        }
        return true;
    }

        // 慣性滑動(dòng)
    private void fling(int velocityY) {
        if (Math.abs(velocityY) > mMinimumVelocity) {
            mScroller.fling(0, mOwnScrollY,
                    1, velocityY,
                    0, 0,
                    Integer.MIN_VALUE, Integer.MAX_VALUE);
            invalidate();
        }
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            int curY = mScroller.getCurrY();
            // 滑動(dòng)布局
            dispatchScroll(curY);
            invalidate();
        }
    }

onTouchEvent方法的邏輯非常簡單谣光,就是根據(jù)手指的滑動(dòng)距離通過view的scrollBy方法滑動(dòng)布局內(nèi)容檩淋,同時(shí)通過VelocityTracker追蹤手指的滑動(dòng)速度,使用Scroller配合computeScroll()方法實(shí)現(xiàn)慣性滑動(dòng)。

滑動(dòng)距離的分發(fā)

在處理慣性滑動(dòng)是時(shí)候飒泻,我們調(diào)用了dispatchScroll()方法主守,這個(gè)方法是整個(gè)ConsecutiveScrollerLayout的核心,它決定了應(yīng)該由誰來消費(fèi)這次滑動(dòng)熬芜,應(yīng)該滑動(dòng)那個(gè)布局。其實(shí)ConsecutiveScrollerLayout的scrollBy()和scrollTo()方法最終都是調(diào)用它來處理滑動(dòng)的分發(fā)的福稳。

    @Override
    public void scrollBy(int x, int y) {
        scrollTo(0, mOwnScrollY + y);
    }

    @Override
    public void scrollTo(int x, int y) {
        //所有的scroll操作都交由dispatchScroll()來分發(fā)處理
        dispatchScroll(y);
    }

        private void dispatchScroll(int y) {
        int offset = y - mOwnScrollY;
        if (mOwnScrollY < y) {
            // 向上滑動(dòng)
            scrollUp(offset);
        } else if (mOwnScrollY > y) {
            // 向下滑動(dòng)
            scrollDown(offset);
        }
    }

這里有個(gè)mOwnScrollY屬性涎拉,是用于記錄ConsecutiveScrollerLayout的整體滑動(dòng)距離的,相當(dāng)于View的mScrollY屬性的圆。

dispatchScroll()方法把滑動(dòng)分成向上和向下兩部分處理鼓拧。讓我們先看向上滑動(dòng)部分的處理。

    private void scrollUp(int offset) {
        int scrollOffset = 0;  // 消費(fèi)的滑動(dòng)記錄
        int remainder = offset; // 未消費(fèi)的滑動(dòng)距離
        do {
            scrollOffset = 0;
            // 是否滑動(dòng)到底部
            if (!isScrollBottom()) {
                // 找到當(dāng)前顯示的第一個(gè)View
                View firstVisibleView = findFirstVisibleView();
                if (firstVisibleView != null) {
                    awakenScrollBars();
                    // 獲取View滑動(dòng)到自身底部的偏移量
                    int bottomOffset = ScrollUtils.getScrollBottomOffset(firstVisibleView);
                    if (bottomOffset > 0) {
                        // 如果bottomOffset大于0越妈,表示這個(gè)view還沒有滑動(dòng)到自身的底部季俩,那么就由這個(gè)view來消費(fèi)這次的滑動(dòng)距離。
                        int childOldScrollY = ScrollUtils.computeVerticalScrollOffset(firstVisibleView);
                        // 計(jì)算需要滑動(dòng)的距離
                        scrollOffset = Math.min(remainder, bottomOffset);
                        // 滑動(dòng)子view
                        scrollChild(firstVisibleView, scrollOffset);
                        // 計(jì)算真正的滑動(dòng)距離
                        scrollOffset = ScrollUtils.computeVerticalScrollOffset(firstVisibleView) - childOldScrollY;
                    } else {
                        // 如果子view已經(jīng)滑動(dòng)到自身的底部梅掠,就由父布局消費(fèi)滑動(dòng)距離酌住,直到把這個(gè)子view滑出屏幕
                        int selfOldScrollY = getScrollY();
                        // 計(jì)算需要滑動(dòng)的距離
                        scrollOffset = Math.min(remainder,
                                firstVisibleView.getBottom() - getPaddingTop() - getScrollY());
                        // 滑動(dòng)父布局
                        scrollSelf(getScrollY() + scrollOffset);
                        // 計(jì)算真正的滑動(dòng)距離
                        scrollOffset = getScrollY() - selfOldScrollY;
                    }
                    // 計(jì)算消費(fèi)的滑動(dòng)距離,如果還沒有消費(fèi)完阎抒,就繼續(xù)循環(huán)消費(fèi)酪我。
                    mOwnScrollY += scrollOffset;
                    remainder = remainder - scrollOffset;
                }
            }
        } while (scrollOffset > 0 && remainder > 0);
    }

    public boolean isScrollBottom() {
        List<View> children = getNonGoneChildren();
        if (children.size() > 0) {
            View child = children.get(children.size() - 1);
            return getScrollY() >= mScrollRange && !child.canScrollVertically(1);
        }
        return true;
    }

    public View findFirstVisibleView() {
        int offset = getScrollY() + getPaddingTop();
        List<View> children = getNonGoneChildren();
        int count = children.size();
        for (int i = 0; i < count; i++) {
            View child = children.get(i);
            if (child.getTop() <= offset && child.getBottom() > offset) {
                return child;
            }
        }
        return null;
    }

    private void scrollSelf(int y) {
        int scrollY = y;

        // 邊界檢測
        if (scrollY < 0) {
            scrollY = 0;
        } else if (scrollY > mScrollRange) {
            scrollY = mScrollRange;
        }
        super.scrollTo(0, scrollY);
    }

    private void scrollChild(View child, int y) {
        child.scrollBy(0, y);
    }

向上滑動(dòng)的處理邏輯是,先找到當(dāng)前顯示的第一個(gè)子view且叁,判斷它的內(nèi)容是否已經(jīng)滑動(dòng)到它的底部都哭,如果沒有,則由它來消費(fèi)滑動(dòng)距離逞带。如果已經(jīng)滑動(dòng)到它的底部欺矫,則由ConsecutiveScrollerLayout來消費(fèi)滑動(dòng)距離,直到把這個(gè)子view滑出屏幕展氓。這樣下一次獲取顯示的第一個(gè)view就是它的下一個(gè)view了穆趴,重復(fù)以上的操作,直到把ConsecutiveScrollerLayout和所有的子view都滑動(dòng)到底部带饱,這樣就整體都滑動(dòng)到底部了毡代。

這里使用了一個(gè)while循環(huán)操作阅羹,這樣做是因?yàn)橐淮位瑒?dòng)距離,可能會(huì)由多個(gè)對象來消費(fèi)教寂,比如需要滑動(dòng)50px的距離捏鱼,但是當(dāng)前顯示的第一個(gè)子view還需要10px滑動(dòng)到自己的底部,那么這個(gè)子view會(huì)消費(fèi)10px的距離酪耕,剩下40px的距離就要進(jìn)行下一次的分發(fā)导梆,找到需要消費(fèi)它的對象,以此類推迂烁。

向下滑動(dòng)的處理跟向上滑動(dòng)是一摸一樣的看尼,只是判斷的對象和滑動(dòng)的方向不同。

    private void scrollDown(int offset) {
        int scrollOffset = 0;  // 消費(fèi)的滑動(dòng)記錄
        int remainder = offset;  // 未消費(fèi)的滑動(dòng)距離
        do {
            scrollOffset = 0;
            // 是否滑動(dòng)到頂部
            if (!isScrollTop()) {
                // 找到當(dāng)前顯示的最后一個(gè)View
                View lastVisibleView = findLastVisibleView();
                if (lastVisibleView != null) {
                    awakenScrollBars();
                    // 獲取View滑動(dòng)到自身頂部的偏移量
                    int childScrollOffset = ScrollUtils.getScrollTopOffset(lastVisibleView);
                    if (childScrollOffset < 0) {
                        // 如果childScrollOffset大于0盟步,表示這個(gè)view還沒有滑動(dòng)到自身的頂部藏斩,那么就由這個(gè)view來消費(fèi)這次的滑動(dòng)距離。
                        int childOldScrollY = ScrollUtils.computeVerticalScrollOffset(lastVisibleView);
                        // 計(jì)算需要滑動(dòng)的距離
                        scrollOffset = Math.max(remainder, childScrollOffset);
                        // 滑動(dòng)子view
                        scrollChild(lastVisibleView, scrollOffset);
                        // 計(jì)算真正的滑動(dòng)距離
                        scrollOffset = ScrollUtils.computeVerticalScrollOffset(lastVisibleView) - childOldScrollY;
                    } else {
                        // 如果子view已經(jīng)滑動(dòng)到自身的頂部却盘,就由父布局消費(fèi)滑動(dòng)距離狰域,直到把這個(gè)子view完全滑動(dòng)進(jìn)屏幕
                        int scrollY = getScrollY();
                        // 計(jì)算需要滑動(dòng)的距離
                        scrollOffset = Math.max(remainder,
                                lastVisibleView.getTop() + getPaddingBottom() - scrollY - getHeight());
                        // 滑動(dòng)父布局
                        scrollSelf(scrollY + scrollOffset);
                        // 計(jì)算真正的滑動(dòng)距離
                        scrollOffset = getScrollY() - scrollY;
                    }
                    // 計(jì)算消費(fèi)的滑動(dòng)距離,如果還沒有消費(fèi)完黄橘,就繼續(xù)循環(huán)消費(fèi)兆览。
                    mOwnScrollY += scrollOffset;
                    remainder = remainder - scrollOffset;
                }
            }
        } while (scrollOffset < 0 && remainder < 0);
    }

public boolean isScrollTop() {
        List<View> children = getNonGoneChildren();
        if (children.size() > 0) {
            View child = children.get(0);
            return getScrollY() <= 0 && !child.canScrollVertically(-1);
        }
        return true;
    }

public View findLastVisibleView() {
        int offset = getHeight() - getPaddingBottom() + getScrollY();
        List<View> children = getNonGoneChildren();
        int count = children.size();
        for (int i = 0; i < count; i++) {
            View child = children.get(i);
            if (child.getTop() < offset && child.getBottom() >= offset) {
                return child;
            }
        }
        return null;
    }

到這里,關(guān)于ConsecutiveScrollerLayout到實(shí)現(xiàn)思路和核心代碼就分析完了塞关。由于篇幅問題抬探,我把對布局吸頂功能的分析寫了另一篇文章:Android滑動(dòng)布局ConsecutiveScrollerLayout實(shí)現(xiàn)布局吸頂功能

另外我還寫了一篇文章是專門介紹ConsecutiveScrollerLayout的使用的,有興趣的朋友可以看一下:Android持續(xù)滑動(dòng)布局ConsecutiveScrollerLayout的使用

下面給出ConsecutiveScrollerLayout到項(xiàng)目地址帆赢,如果你喜歡我的作品小压,或者這個(gè)布局對你有所幫助,請給我點(diǎn)個(gè)star唄椰于!

https://github.com/donkingliang/ConsecutiveScroller

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末场航,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子廉羔,更是在濱河造成了極大的恐慌,老刑警劉巖僻造,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件憋他,死亡現(xiàn)場離奇詭異,居然都是意外死亡髓削,警方通過查閱死者的電腦和手機(jī)竹挡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來立膛,“玉大人揪罕,你說我怎么就攤上這事梯码。” “怎么了好啰?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵轩娶,是天一觀的道長。 經(jīng)常有香客問我框往,道長鳄抒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任椰弊,我火速辦了婚禮许溅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秉版。我一直安慰自己贤重,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布清焕。 她就那樣靜靜地躺著并蝗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪耐朴。 梳的紋絲不亂的頭發(fā)上借卧,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機(jī)與錄音筛峭,去河邊找鬼铐刘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛影晓,可吹牛的內(nèi)容都是我干的镰吵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼挂签,長吁一口氣:“原來是場噩夢啊……” “哼疤祭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起饵婆,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤勺馆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后侨核,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體草穆,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年搓译,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悲柱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡些己,死狀恐怖豌鸡,靈堂內(nèi)的尸體忽然破棺而出嘿般,到底是詐尸還是另有隱情,我是刑警寧澤涯冠,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布炉奴,位于F島的核電站,受9級特大地震影響功偿,放射性物質(zhì)發(fā)生泄漏盆佣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一械荷、第九天 我趴在偏房一處隱蔽的房頂上張望共耍。 院中可真熱鬧,春花似錦吨瞎、人聲如沸痹兜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽字旭。三九已至,卻和暖如春崖叫,著一層夾襖步出監(jiān)牢的瞬間遗淳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工心傀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屈暗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓脂男,卻偏偏與公主長得像养叛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子宰翅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

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

  • 第一章:Activity生命周期和啟動(dòng)模式 Activity關(guān)閉時(shí)會(huì)調(diào)用onPause()和onStop()弃甥,如果...
    loneyzhou閱讀 882評論 0 2
  • 開發(fā)中,為了增加更多炫麗的效果汁讼,我們經(jīng)常在應(yīng)用中添加滑動(dòng)效果淆攻,今天就來分析一下 View 中滑動(dòng)效果的實(shí)現(xiàn)原理以及...
    Ad大成閱讀 335評論 0 2
  • 一個(gè)社會(huì)在發(fā)展,如果精神跟不上時(shí)代的步伐嘿架,社會(huì)發(fā)展了又有什么用卜录? 從今天起,正式連載故鄉(xiāng)系列眶明,我?guī)銈內(nèi)ノ业募亦l(xiāng)看...
    懷俠閱讀 606評論 0 8
  • 村里文江他們弄的稻田蟹成熟了,這幾天開始放釣了筐高,九點(diǎn)多鐘帶著爍媽和兒子回村釣蟹搜囱,中午就在蟹坑旁邊三五好友涮這火鍋吃...
    易如人生閱讀 121評論 0 0
  • 工作~ 城建圖紙整理丑瞧,并畫家家悅圖紙 學(xué)習(xí)~ 1.本周讀書《委婉說話的藝術(shù)》并畫思維導(dǎo)圖 2.每天一篇普通話練習(xí) ...
    迎風(fēng)奔跑2021閱讀 155評論 0 0