從 ViewPagerIndicator 改造一個(gè) ScrollViewTabIndicator

一耽梅、思路

現(xiàn)在很多應(yīng)用都采用 ViewPager 加 Fragment 的結(jié)構(gòu)薛窥,在 github 上隨便一搜也可以找出各種各樣的動(dòng)畫效果的 ViewPagerIndicator。前不久在項(xiàng)目詳情頁(yè)改版的需求中眼姐,需要把原來的 ViewPager 切換的結(jié)構(gòu)修改成垂直滾動(dòng)的結(jié)構(gòu)(如下圖)诅迷。

scrollviewindicator1.gif

第一個(gè)反應(yīng)就是把原來的 ViewPagerIndicator 替換成 RadioGroup 和 RadioButton 然后設(shè)置監(jiān)聽,但是又不想放棄原來的 ViewPagerIndicator 的 tab 的切換動(dòng)畫效果众旗。

然后我選擇了第二種方法——在原來的 NestedScrollView 包含的子 ViewGroup 中插入一個(gè)寬為 match_parent罢杉,高為 1px 的 ViewPager,起到輔助動(dòng)畫的功能贡歧,來與 NestedScrollView 聯(lián)動(dòng)達(dá)到上圖的效果滩租。

二赋秀、效果

講完了思路,先來看下最終實(shí)現(xiàn)的效果持际,效果圖就是上邊這張沃琅,這里主要是給大家看下代碼里如何使用,使用是否方便蜘欲。

public class MainActivity extends AppCompatActivity implements NestedScrollView.OnScrollChangeListener{

    private NestedScrollView mSv;
    private ScrollViewTabIndicator mTab;
    private ScrollViewTabIndicator mTab2;
    private int[] mTabMiddleLocation = new int[2];
    private int[] mTabTopLocation = new int[2];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        mSv = (NestedScrollView) findViewById(R.id.sv);
        mTab = (ScrollViewTabIndicator) findViewById(R.id.tab);//在TitleBar下方的indicator
        mTab2 = (ScrollViewTabIndicator) findViewById(R.id.tab2);//在ScrollView中的indicator
        View view1 = findViewById(R.id.tv_1);//詳情View
        View view2 = findViewById(R.id.tv_2);//評(píng)論View
        View view3 = findViewById(R.id.tv_3);//須知View
        List<String> names = new ArrayList<>();
        names.add("詳情");
        names.add("評(píng)論");
        names.add("須知");
        List<View> views = new ArrayList<>();
        views.add(view1);
        views.add(view2);
        views.add(view3);
        mTab.setScrollView(mSv,this,names,views);
        //將mTab本身作為參數(shù)傳入mTab2已達(dá)到同步狀態(tài)
        mTab2.setScrollView(mSv,mTab,names,views);
    }

    @Override
    public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
        setVisibleAndGone();
    }

    private void setVisibleAndGone() {
        mTab2.getLocationOnScreen(mTabMiddleLocation);
        mTab.getLocationOnScreen(mTabTopLocation);
        if (mTabMiddleLocation[1] <= mTabTopLocation[1]) {
            mTab.setVisibility(View.VISIBLE);
            mTab2.setVisibility(View.INVISIBLE);
        } else {
            mTab.setVisibility(View.INVISIBLE);
            mTab2.setVisibility(View.VISIBLE);
        }
    }
}

可以看到使用的方法僅僅是找出 ScrollView 中對(duì)應(yīng)的 View益眉,并給出對(duì)應(yīng)的 tab 標(biāo)題,然后調(diào)用 setScrollView 方法設(shè)置到 ScrollViewTabIndicator姥份,其余的事都交給 ScrollViewTabIndicator 來執(zhí)行郭脂,唯一要自己處理的就是監(jiān)聽滾動(dòng)來控制 mTab 和 mTab2 的顯示和隱藏。

三澈歉、封裝

當(dāng)然這里我將很多 ViewPager 和 ScrollView 的邏輯都封裝起來了展鸡,否則你會(huì)發(fā)現(xiàn)的 Activity 或者 Fragment 中你會(huì)發(fā)現(xiàn)要增加很多與業(yè)務(wù)無關(guān)的代碼,而且也不利于后期的復(fù)用埃难。下邊我就介紹下基于 ViewPagerIndicator 的一些修改莹弊。

3.1 設(shè)置邏輯
    /**
     * 因?yàn)闀?huì)替換 scrollview 上的 listener, 所以要傳進(jìn)來.
     * 如果傳進(jìn)來是一個(gè) TabIndicator 對(duì)象, 則兩者狀態(tài)會(huì)同步, 并且自定義的滾動(dòng)監(jiān)聽要設(shè)置到第一個(gè)上邊
     * @param scrollView 監(jiān)聽的 NestedScrollView
     * @param listener 原先設(shè)置在 NestedScrollView 上的監(jiān)聽
     * @param tabs tab 的標(biāo)題
     * @param views 各個(gè) tab 對(duì)應(yīng)的需要滾動(dòng)到的 View
     */ 
    public void setScrollView(NestedScrollView scrollView, NestedScrollView.OnScrollChangeListener listener, List<String> tabs, List<View> views) {
        if (mScrollView == scrollView) {
            return;
        }
        if (tabs == null || views == null) {
            throw new IllegalArgumentException("tabs and views should not be null!");
        }
        if (tabs.isEmpty() || views.isEmpty()) {
            throw new IllegalArgumentException("tabs and views should not be empty!");
        }
        if (tabs.size() != views.size()) {
            throw new IllegalArgumentException("tabs and views should be the same length!");
        }
        mScrollListener = listener;
        mScrollView = scrollView;
        mViews = views;
        if (mScrollView != null) {
            mScrollView.setOnScrollChangeListener(this);
        }
        initTabs(tabs);
        if (listener instanceof ScrollViewTabIndicator) {
            ScrollViewTabIndicator synchronize = (ScrollViewTabIndicator) listener;
            mAssistViewPager = synchronize.getAssistViewPager();
            //接收覆蓋監(jiān)聽,避免走多余的監(jiān)聽流程
            mScrollListener = synchronize.mScrollListener;
            if (mAssistViewPager != null) {
                mAssistViewPager.addOnPageChangeListener(this);
            } else {
                initAssistViewPager(tabs.size());
            }
        } else {
            initAssistViewPager(tabs.size());
        }
    }

去除了原來的具備的 setViewPager 方法涡尘,添加了 setScrollView忍弛,這里主要就是進(jìn)行各種判空,接收傳進(jìn)來的參數(shù)(listener)考抄,并調(diào)用 initTabs(tabs) 來生成對(duì)應(yīng)的 tab细疚。之后調(diào)用 initAssistViewPager(tabs.size()) 來創(chuàng)建輔助動(dòng)畫的 ViewPager,可以先不管 if() 里面的代碼川梅。

3.2 輔助ViewPager
    private void initAssistViewPager(int size) {
        if (mAssistViewPager != null) {
            return;
        }
        mAssistViewPager = new ViewPager(getContext());
        ViewGroup.LayoutParams p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1);
        mAssistViewPager.setLayoutParams(p);

        //請(qǐng)注意這段代碼, 因?yàn)闀?huì)在 ScrollView 的子 view 中插入一個(gè) ViewPager
        View viewGroup = mScrollView.getChildAt(0);
        if (viewGroup == null) {
            throw new IllegalStateException(" The child view of the ScrollView must be not null!");
        }
        if (!(viewGroup instanceof ViewGroup)) {
            throw new IllegalStateException(" The child view of the ScrollView must be a ViewGroup!");
        }
        viewGroup = mScrollView.getChildAt(0);
        ((ViewGroup) viewGroup).addView(mAssistViewPager);


        final List<View> viewList = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            viewList.add(new Space(getContext()));
        }
        mAssistViewPager.setAdapter(new PagerAdapter() {
            @Override
            public int getCount() {
                return viewList.size();
            }

            @Override
            public boolean isViewFromObject(View view, Object object) {
                return view == object;
            }

            @Override
            public Object instantiateItem(ViewGroup container, int position) {
                container.addView(viewList.get(position));
                return viewList.get(position);
            }

            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                container.removeView(viewList.get(position));
            }
        });
        mAssistViewPager.addOnPageChangeListener(this);
    }

創(chuàng)建了一個(gè)只有 1px 高度的 ViewPager 但是由于 ScrollViewTabIndicator 本身繼承的是一個(gè)水平方向的 LinearLayout疯兼,而且需要給予 ViewPager 一定寬度以保證 tab 切換有一定動(dòng)畫效果,所以這里只能在 ScrollView 的子 view 中插入 ViewPager贫途。并且這個(gè) ViewPager 僅僅是為了可以在它的 page 切換的時(shí)候在它的 OnPageChangeListener 中實(shí)現(xiàn) tab 切換的動(dòng)畫吧彪。ps:到這里我覺得針對(duì)任何一個(gè) ViewPagerIndicator 都可以采用這個(gè)形式來修改成我所謂的 ScrollViewTabIndicator。

3.3 對(duì)需要定位的 Views 在 onScrollChange 中的處理
@Override
    public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {

        if (mScrollListener != null) {
            mScrollListener.onScrollChange(v, scrollX, scrollY, oldScrollX, oldScrollY);
        }

        if (isScrolling()) {
            return;
        }

        if (mStatusBarHeight == 0) {
            mStatusBarHeight = getBarHeight();
        }
        int top = mActionBarHeight + getMeasuredHeight() + mStatusBarHeight;//TitleBar 高度 + 控件高度 + StatusBar 高度
        List<Integer> locations = new ArrayList<>();
        for (int i = 0, size = mViews.size(); i < size; i++) {
            locations.add(getViewLocation(i));
        }
        Collections.sort(locations);
        int position = 0;

        if (top < locations.get(0)) {
            position = 0;
        } else {
            for (int j = 0, size = locations.size(); j < size; j++) {
                if (j + 1 == size) {
                    position = j;
                    break;
                }
                if (top >= locations.get(j) && top < locations.get(j + 1)) {
                    //如果已經(jīng)不能向下滾動(dòng)了就
                    if (!v.canScrollVertically(VERTICAL)) {
                        position = size - 1;
                        break;
                    }

                    position = j;
                    break;
                }
            }
        }
        if (getCurrentIndex() == position) {
            return;
        }
        mAssistViewPager.setCurrentItem(position, true);
    }

首先這里的 mScrollListener 就是我們?cè)凇?」中 setScrollView 里面?zhèn)魅氲?listener潮饱;isScrolling()是 ViewPager 是否在滾動(dòng)来氧;然后給個(gè)需要定位的 View 計(jì)算在屏幕上的 y 坐標(biāo),并進(jìn)行從小到大排序。

    private int getViewLocation(int position) {
        if (mViews != null && mViews.size() > position) {
            View view = mViews.get(position);
            if (view != null) {
                int[] location = new int[2];
                view.getLocationOnScreen(location);
                return location[1];
            }
        }
        return 0;
    }

之后進(jìn)行判斷來確定是否需要切換 tab:
1.如果所有的坐標(biāo)都大于 top (TitleBar 高度 + 控件高度 + StatusBar 高度香拉,因?yàn)槲覀円龅接袘腋≡跇?biāo)題欄下方的視覺效果所以這里要加控件高度啦扬,大家可自行根據(jù)需求修改這里),則 position 為 0凫碌;
2.如果存在坐標(biāo)小于 top:

  1. 存在 top 介于坐標(biāo) j 和 坐標(biāo) j + 1 之間扑毡,且可以繼續(xù)向下滾,則取 position 為 j盛险;
  2. 存在 top 介于坐標(biāo) j 和 坐標(biāo) j + 1 之間瞄摊,且不可以向下滾勋又,取 position 為 size - 1;(為了解決最底部 View 過短永遠(yuǎn)也滾不到的情況)
  3. 如果所有坐標(biāo)都大于 top换帜,則取 position 為 size - 1楔壤;

如果取到的 position 和 之前的不同則讓 ViewPager 滾到新的一頁(yè),并且 tabs 進(jìn)行相應(yīng)的切換惯驼,當(dāng)然之后的動(dòng)畫邏輯其實(shí)是原來 ViewPagerIndicator 的代碼蹲嚣,這里就不進(jìn)行說明,有興趣的可以之后看下完整的代碼祟牲。

3.4 點(diǎn)擊 Tab 實(shí)現(xiàn)切換和滾動(dòng)
    @Override
    public void onClick(android.view.View v) {
        int position = (Integer) v.getTag();
        if (mScrollView != null) {
            int location;
            location = getViewLocation(position);
            // 待滑動(dòng)距離 = 當(dāng)前坐標(biāo) - (ActionBar高度) - indicator高度 - 狀態(tài)欄高度
            if (mStatusBarHeight == 0) {
                mStatusBarHeight = getBarHeight();
            }
            location += -mActionBarHeight - getMeasuredHeight() - mStatusBarHeight;
            mScrollView.smoothScrollBy(0, location);
        }

        if (mAssistViewPager != null) {
            mIsClick = true;
            mAssistViewPager.setCurrentItem(position, true);
        }
    }

這里的點(diǎn)擊事件是在 initTabs(tabs) 的時(shí)候設(shè)置在每個(gè) TabView 上的隙畜,這里 TabView 的僅僅是繼承了 AppCompatRadioButton 做了一些顏色和背景的設(shè)置。點(diǎn)擊事件做了兩件事说贝,一件是計(jì)算 ScrollView 需要滾動(dòng)的距離并進(jìn)行平滑滾動(dòng)议惰,另外一件就是讓 ViewPager 進(jìn)行平滑的滾動(dòng)。

上面在「3」中的 onScrollChange 我們剛才已經(jīng)知道它對(duì) ViewPager 的滾動(dòng)進(jìn)行了判斷乡恕,當(dāng) ViewPager 滾動(dòng)過程中不會(huì)進(jìn)一步進(jìn)行處理言询。但是事實(shí)上這里還是會(huì)有所影響,因?yàn)閮烧叩臐L動(dòng)時(shí)間不一致傲宜!ScrollView 往往會(huì)慢一點(diǎn)倍试,所以常常會(huì)發(fā)生點(diǎn)擊過后 tab 回滾的現(xiàn)象,所以用 mIsClick 進(jìn)行了進(jìn)一步判斷的處理蛋哭。


    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            TextView tv = getTabView(mSelectedPosition);
            if (tv != null)
                switch (mIndicatorMode) {
                    case MATCH_PARENT:
                        updateIndicator(tv.getLeft(), tv.getMeasuredWidth());
                        break;
                    case WRAP_CONTENT:
                        int textWidth = getTextWidth(tv);
                        updateIndicator(tv.getLeft() + tv.getWidth() / 2 - textWidth / 2, textWidth);
                        break;
                }

            /*
             * 因 ScrollView 的滾動(dòng)可能持續(xù)比ViewPager長(zhǎng),
             * 因此此處不設(shè)置延時(shí)將存在{@link #onScrollChange(NestedScrollView, int, int, int, int)} 中調(diào)用的 isScrolling() 不能攔截掉一些多余的處理,
             * 導(dǎo)致indicator回滾的現(xiàn)象, 暫時(shí)未考慮到更好的處理方式
             */
            if(mIsClick) {
                removeCallbacks(mScrollOffRunnable);
                postDelayed(mScrollOffRunnable, 200);
            }else{
                mScrolling = false;
            }
            mIsClick = false;
        } else {
            removeCallbacks(mScrollOffRunnable);
            mScrolling = true;
        }

    }

    private Runnable mScrollOffRunnable = new Runnable() {
        @Override
        public void run() {
            mScrolling = false;
        }
    };

可以看到 mIsClick 僅僅是把 mScrolling 延遲 200ms 設(shè)置成 false,為了讓 ScrollView 先滾完涮母,目前還沒想到其他的方法谆趾。到這里這個(gè)控件可以獨(dú)立使用了,但是為了實(shí)現(xiàn)下面的效果叛本,而不至于監(jiān)聽太亂所以進(jìn)一步進(jìn)行優(yōu)化沪蓬。

scrollviewindicator2.gif
3.5 ScrollViewTabIndicator 之間的同步

其實(shí)代碼很簡(jiǎn)單,細(xì)心的同學(xué)可能已經(jīng)看見了来候,就是「1」中讓大家跳過的 if() 中的語句跷叉。

    if (listener instanceof ScrollViewTabIndicator) {
        ScrollViewTabIndicator synchronize = (ScrollViewTabIndicator) listener;
        mAssistViewPager = synchronize.getAssistViewPager();
        //接收覆蓋監(jiān)聽,避免走多余的監(jiān)聽流程
        mScrollListener = synchronize.mScrollListener;
        if (mAssistViewPager != null) {
            mAssistViewPager.addOnPageChangeListener(this);
        } else {
            initAssistViewPager(tabs.size());
        }
    } else {
        initAssistViewPager(tabs.size());
    }

判斷如果傳入的 listener 如果是 ScrollVIewTabIndicator 對(duì)象則直接共用創(chuàng)建的輔助動(dòng)畫的 ViewPager营搅,并且接收其中的 mScrollListener云挟。最終會(huì)走的 onScrollChange 的只有最后一個(gè)控件實(shí)現(xiàn)的方法,和最初傳進(jìn)來的 listener转质。下邊看看 5 個(gè)控件的同步過程园欣。

        mTab.setScrollView(mSv,this,names,views);
        mTab3.setScrollView(mSv,mTab,names,views);
        mTab4.setScrollView(mSv,mTab3,names,views);
        mTab5.setScrollView(mSv,mTab4,names,views);
        mTab2.setScrollView(mSv,mTab5,names,views);

這里只走 this 和 mTab2 的 onScrollChange 方法。

四休蟹、重申幾個(gè)注意點(diǎn)

  • 該類會(huì)在 ScrollView 的子 View 中插入一個(gè)寬度為 match_parent, 高度為 1px 的 ViewPager (用來輔助動(dòng)畫), 因此確保 ScrollView 中包含的是 ViewGroup沸枯;
  • 使用時(shí)調(diào)用 {{@link #setScrollView(NestedScrollView, NestedScrollView.OnScrollChangeListener, List, List)}}與 NestedScrollView 關(guān)聯(lián),并且原來要設(shè)置再 NestedScrollView 上的監(jiān)聽要在此傳入, 否則講被替換日矫;
  • TabIndicator 本身也可以作為一個(gè){ NestedScrollView.OnScrollChangeListener} 傳入, 如果這樣, 兩個(gè)控件將會(huì)同步, 共享已經(jīng)創(chuàng)建的 ViewPager;
  • 默認(rèn)用48dp的像素值作為 ActionBar 的高度, 計(jì)算滾動(dòng)距離, 如果有需求用{{@link #setActionBarHeight(int)}} 來設(shè)置绑榴;

五哪轿、 5月11日更新

1. 修復(fù)快速滑動(dòng) tab 沒有切換的問題

不再使用 isScrolling() 方法和 mScrolling 攔截 ScrollView 中的監(jiān)聽,改用 mIsClick 判斷翔怎;修改原先 mScrollOffRunnable 中的 run 方法窃诉。

    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            mScrolling = false;
            TextView tv = getTabView(mSelectedPosition);
            if (tv != null)
                switch (mIndicatorMode) {
                    case MATCH_PARENT:
                        updateIndicator(tv.getLeft(), tv.getMeasuredWidth());
                        break;
                    case WRAP_CONTENT:
                        int textWidth = getTextWidth(tv);
                        updateIndicator(tv.getLeft() + tv.getWidth() / 2 - textWidth / 2, textWidth);
                        break;
                }

            /*
             * 因 ScrollView 的滾動(dòng)可能持續(xù)比 ViewPager 長(zhǎng),
             * 因此此處不設(shè)置延時(shí)將存在{@link #onScrollChange(NestedScrollView, int, int, int, int)} 中調(diào)用的 mIsClick 不能攔截掉一些多余的處理,
             * 導(dǎo)致indicator回滾的現(xiàn)象, 暫時(shí)未考慮到更好的處理方式
             */
            if (mIsClick) {
                removeCallbacks(mScrollOffRunnable);
                postDelayed(mScrollOffRunnable, 220);
            }
        } else {
            mScrolling = true;
        }
    }
    
    private Runnable mScrollOffRunnable = new Runnable() {
        @Override
        public void run() {
            mIsClick = false;
        }
    };

修改「3.5」中的同步代碼。

    if (listener instanceof ScrollViewTabIndicator) {
        mSynchronize = (ScrollViewTabIndicator) listener;
        mSynchronize.mNextSynchronize = this;
        mAssistViewPager = mSynchronize.getAssistViewPager();
        //接收覆蓋監(jiān)聽姓惑,避免走多余的監(jiān)聽流程
        mScrollListener = mSynchronize.mScrollListener;
        if (mAssistViewPager != null) {
            mAssistViewPager.addOnPageChangeListener(this);
        } else {
            initAssistViewPager(tabs.size());
        }
     } else {
        initAssistViewPager(tabs.size());
     }

可以看到這里不僅保持傳進(jìn)來的 ScrollViewTabIndicator 對(duì)象為 mSynchronize褐奴,而且如果本身如果被設(shè)置給其他的 ScrollViewTabIndicator 他的 mNextSynchronize 也會(huì)被賦值。保持這兩個(gè)引用主要是為了保證多個(gè) ScrollViewTabIndicator 同步時(shí)于毙,在點(diǎn)擊不同對(duì)象的 tab 的時(shí)候敦冬,他們的 mIsClick 能保持一致,下邊看一下 onClick(View v) 方法的改變唯沮。

    @Override
    public void onClick(android.view.View v) {

        int position = (Integer) v.getTag();

        if (mAssistViewPager != null) {
            synchronizeClickStatus();
            mAssistViewPager.setCurrentItem(position, true);
        }

        if (mScrollView != null) {
            int location;
            location = getViewLocation(position);
            // 待滑動(dòng)距離 = 當(dāng)前坐標(biāo) - (ActionBar高度) - indicator高度 - 狀態(tài)欄高度
            if (mStatusBarHeight == 0) {
                mStatusBarHeight = getBarHeight();
            }
            location += -mActionBarHeight - getMeasuredHeight() - mStatusBarHeight;

//            location -= getViewMarginTop(position);
            //因?yàn)檫@里經(jīng)常會(huì)出現(xiàn) scrollView 沒有滾動(dòng)的現(xiàn)象這里才加了 delay
            final int finalLocation = position == 0 ? (location > 0 ? location - 1 : location + 1) : location;
            mScrollView.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScrollView.smoothScrollBy(0, finalLocation);
                }
            }, 100);
        }

    }

    private void synchronizeClickStatus() {
        mIsClick = true;
        if (mSynchronize != null && !mSynchronize.mIsClick) {
            mSynchronize.synchronizeClickStatus();
        }
        if (mNextSynchronize != null && !mNextSynchronize.mIsClick) {
            mNextSynchronize.synchronizeClickStatus();
        }
    }

修改了兩方面脖旱,一是點(diǎn)擊的時(shí)候同時(shí)修改了前一個(gè)和后一個(gè)同步的 ScrollViewTabIndicator 的 mIsClick,二是因?yàn)橹苯诱{(diào)用 ScrollView 的 smoothScrollBy 方法介蛉,如果點(diǎn)擊速度過快 smoothScrollBy 方法中對(duì)點(diǎn)擊的時(shí)間間隔做了判斷萌庆,導(dǎo)致 ScrollView 常常滾動(dòng)不到預(yù)期的位置,所以做了 100 毫秒的延遲處理币旧。

到這里對(duì)于快速滾動(dòng)的處理算是完成了践险,可能還有其他的問題,如果大家有發(fā)現(xiàn)問題或者意見還望提醒我改正吹菱,謝謝巍虫。

六、 最后

感謝 J!nL!n 同學(xué)的 TabIndicator 以及 CF 同學(xué)的啟發(fā)鳍刷。

這里有源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末占遥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子输瓜,更是在濱河造成了極大的恐慌瓦胎,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尤揣,死亡現(xiàn)場(chǎng)離奇詭異搔啊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)北戏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門坯癣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人最欠,你說我怎么就攤上這事示罗〕兔ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵蚜点,是天一觀的道長(zhǎng)轧房。 經(jīng)常有香客問我,道長(zhǎng)绍绘,這世上最難降的妖魔是什么奶镶? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮陪拘,結(jié)果婚禮上厂镇,老公的妹妹穿的比我還像新娘。我一直安慰自己左刽,他們只是感情好捺信,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著欠痴,像睡著了一般迄靠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喇辽,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天掌挚,我揣著相機(jī)與錄音,去河邊找鬼菩咨。 笑死吠式,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抽米。 我是一名探鬼主播奇徒,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼缨硝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起罢低,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤查辩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后网持,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宜岛,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年功舀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了萍倡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辟汰,死狀恐怖列敲,靈堂內(nèi)的尸體忽然破棺而出阱佛,到底是詐尸還是另有隱情,我是刑警寧澤戴而,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布凑术,位于F島的核電站,受9級(jí)特大地震影響所意,放射性物質(zhì)發(fā)生泄漏淮逊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一扶踊、第九天 我趴在偏房一處隱蔽的房頂上張望泄鹏。 院中可真熱鬧,春花似錦秧耗、人聲如沸备籽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胶台。三九已至,卻和暖如春杂抽,著一層夾襖步出監(jiān)牢的瞬間诈唬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工缩麸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铸磅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓杭朱,卻偏偏與公主長(zhǎng)得像阅仔,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子弧械,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,161評(píng)論 25 707
  • 你每次都說算了吧刃唐,結(jié)果你依然拒絕不了他的消息
    她名星辰閱讀 104評(píng)論 0 0
  • 靜靜地座在椅子上 看看手機(jī) 趴著睡一會(huì)兒 一遍又一遍 重復(fù)著
    蕭雨彤閱讀 205評(píng)論 1 5
  • 這不是一個(gè)倡議悉抵,也不是一篇雞湯,而是我對(duì)自己的要求贼邓。 每個(gè)人的生活軌跡不同抖甘,生活環(huán)境不同热鞍,因此,沒有必須都按照統(tǒng)一...
    安和然閱讀 476評(píng)論 3 13