Android 滾輪選擇器的實(shí)現(xiàn)詳解

簡(jiǎn)介

最近用一個(gè)日期選擇控件裤园,感覺官方的DatePicker操作有點(diǎn)復(fù)雜,而且不同的Android版本樣式也都不一樣。后來發(fā)現(xiàn)小米日歷的日期選擇控件蠻好看的力喷,于是自己嘗試仿寫一個(gè)足删,感覺效果還不錯(cuò)。GitHub: https://github.com/ycuwq/DatePicker

效果圖:


預(yù)覽1

預(yù)覽2

功能分析

  • 滾輪:首先繪制一列文本匣距,然后添加一個(gè)偏移量,在onDraw中根據(jù)手指滑動(dòng)哎壳,改變偏移量并重新繪制這一列文本董栽,這樣就實(shí)現(xiàn)了滑動(dòng)的效果嗤谚。
  • Fling:這個(gè)應(yīng)該很常見了,用VelocityTrackerScroller來實(shí)現(xiàn)从媚。
  • 循環(huán)滾動(dòng):當(dāng)滾動(dòng)超過數(shù)據(jù)集的大小后歹撒,從頭繼續(xù)獲取數(shù)據(jù)即可潘鲫。
  • 幕布效果:在中心區(qū)域繪制一個(gè)矩形凡纳。
  • 字體顏色漸變:
    • 從中心到兩邊疏虫,逐漸將Paint的透明度變小。
    • 從中心相鄰項(xiàng)到中心特石,字體顏色漸變盅蝗。
    • 中心選項(xiàng)文字變大: 從中心相鄰項(xiàng)到中心,字體大小漸變姆蘸。
  • 指示器文字风科,在中間的Item后邊繪制一個(gè)文字。

到這里乞旦,所有的功能點(diǎn)的思路大概就清晰了。

實(shí)現(xiàn)方法

測(cè)量控件大小

這里主要是測(cè)量wrap_content模式的大小题山。
首先兰粉,要確定單個(gè)item的文字的寬高。代碼如下:

public void computeTextSize() {
    mTextMaxWidth = mTextMaxHeight = 0;
    if (mDataList.size() == 0) {    
        return;
    }

    //這里使用最大的,防止文字大小超過布局大小顶瞳。
    mPaint.setTextSize(mSelectedItemTextSize > mTextSize ? mSelectedItemTextSize : mTextSize);

    if (!TextUtils.isEmpty(mItemMaximumWidthText)) {
        mTextMaxWidth = (int) mPaint.measureText(mItemMaximumWidthText);
    } else {
        mTextMaxWidth = (int) mPaint.measureText(mDataList.get(0).toString());
    }
    Paint.FontMetrics metrics = mPaint.getFontMetrics();
    mTextMaxHeight = (int) (metrics.bottom - metrics.top);
}

然后確定布局的大小玖姑,布局的寬度就等于測(cè)量的mTextMaxWidth,高度為測(cè)量的mTextMaxHeight * itemCount慨菱。這里寬高中可以加入一個(gè)額外的Space焰络,要不然文字就會(huì)擠到一起,比較難看符喝。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int specWidthSize = MeasureSpec.getSize(widthMeasureSpec);
        int specWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int specHeightSize = MeasureSpec.getSize(heightMeasureSpec);
        int specHeightMode = MeasureSpec.getMode(heightMeasureSpec);
    
        int width = mTextMaxWidth + mItemWidthSpace;
        int height = (mTextMaxHeight + mItemHeightSpace) * getVisibleItemCount();
    
        width += getPaddingLeft() + getPaddingRight();
        height += getPaddingTop() + getPaddingBottom();
        setMeasuredDimension(measureSize(specWidthMode, specWidthSize, width),
                measureSize(specHeightMode, specHeightSize, height));
    }
    
    private int measureSize(int specMode, int specSize, int size) {
        if (specMode == MeasureSpec.EXACTLY) {
            return specSize;
        } else {
            return Math.min(specSize, size);
        }
    }

滾輪的繪制

一般滾輪被選中的位置都是在中間闪彼,所以顯示的設(shè)置為奇數(shù)比較合適。為了方便,定義mHalfVisibleItemCount作為顯示的個(gè)數(shù)的一半畏腕,總顯示個(gè)數(shù)為mHalfVisibleItemCount * 2 + 1缴川,這樣就可以保證選中的item在正中間

首先最簡(jiǎn)單的描馅,不考慮滾動(dòng)的情況把夸,直接繪制一列文字,這里為了方便計(jì)算铭污,設(shè)置中間的為數(shù)據(jù)集的第0個(gè)恋日,第一個(gè)item就為0-mHalfVisibleItemCount,最后一個(gè)則為mHalfVisibleItemCount嘹狞,代碼如下:

@Override
protected void onDraw(Canvas canvas) {
    for (int drawDataPos = -mHalfVisibleItemCount; drawDataPos <= mHalfVisibleItemCount; drawDataPos++) {
        if (pos < 0 || pos > mDataList.size() - 1) {
            continue;
        }
        int itemDrawY = mFirstItemDrawY + (drawDataPos + mHalfVisibleItemCount) * mItemHeight;
        T data = mDataList.get(pos);
        canvas.drawText(data.toString(), mFirstItemDrawX, itemDrawY, mPaint);
    }
}

接下來加入滾動(dòng)岂膳,獲取手指滑動(dòng)的值, 然后繪制的時(shí)候添加偏移量就好了刁绒。

手指滑動(dòng)闷营,這個(gè)應(yīng)該都懂,這里就不廢話了知市,直接上代碼:

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mLastDownY = (int) event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            float move = event.getY() - mLastDownY;
            mScrollOffsetY += move;     //滑動(dòng)的偏移量
            mLastDownY = (int) event.getY();
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            break;
    }

更改onDraw()的代碼傻盟。這邊有兩個(gè)問題:

  1. 上下兩邊要多繪制一個(gè)出來,因?yàn)樵跐L動(dòng)的時(shí)候嫂丙,實(shí)際在容器內(nèi)的item要比原定的item要多一個(gè)娘赴。
  2. 定位中間向位于數(shù)據(jù)集的位置,用偏移量 / item的高度即可跟啤。由于手指的坐標(biāo)是以左上角為原點(diǎn)的诽表,這里要注意坐標(biāo)正負(fù)問題。
    代碼如下:
@Override
protected void onDraw(Canvas canvas) {
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        if (drawDataPos < 0 || drawDataPos > mDataList.size() - 1) {
            continue;
        }
        int itemDrawY = mFirstItemDrawY + (drawDataPos + mHalfVisibleItemCount) * mItemHeight + mScrollOffsetY;
        T data = mDataList.get(drawDataPos);
        canvas.drawText(data.toString(), mFirstItemDrawX, itemDrawY, mPaint);
    }
}

Fling的效果

先上主要代碼:

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (mTracker == null) {
        mTracker = VelocityTracker.obtain();
    }
    mTracker.addMovement(event);
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mTracker.clear();
            mTouchDownY = mLastDownY = (int) event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            mTouchSlopFlag = false;
            float move = event.getY() - mLastDownY;
            mScrollOffsetY += move;
            mLastDownY = (int) event.getY();
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            mTracker.computeCurrentVelocity(1000, mMaximumVelocity);
            int velocity = (int) mTracker.getYVelocity();
            mScroller.fling(0, mScrollOffsetY, 0, velocity,
                    0, 0, mMinFlingY, mMaxFlingY);
            mScroller.setFinalY(mScroller.getFinalY() +
                    computeDistanceToEndPoint(mScroller.getFinalY() % mItemHeight));
            mHandler.post(mScrollerRunnable);
            mTracker.recycle();
            mTracker = null;
            break;
    }
    return true;
}

private int computeDistanceToEndPoint(int remainder) {
    if (Math.abs(remainder) > mItemHeight / 2) {
        if (mScrollOffsetY < 0) {
            return -mItemHeight - remainder;
        } else {
            return mItemHeight - remainder;
        }
    } else {
        return -remainder;
    }
}

private Runnable mScrollerRunnable = new Runnable() {
    @Override
    public void run() {
        if (mScroller.computeScrollOffset()) {
            int scrollerCurrY = mScroller.getCurrY();
            mScrollOffsetY = scrollerCurrY;
            postInvalidate();
            mHandler.postDelayed(this, 16);
        }
    }
}

Fling效果的實(shí)現(xiàn)主要是用的VelocityTrackerScroller隅肥,網(wǎng)上已經(jīng)有很多資料了竿奏,這里就不再說明了。這里主要就是獲取Scroller當(dāng)前滾動(dòng)的值腥放,然后加入到偏移量mScrollOffsetY后請(qǐng)求重新繪制泛啸。

這里有一個(gè)finalY的修正計(jì)算.當(dāng)動(dòng)畫停止的時(shí)候,停止的位置如果隨緣的話秃症,就會(huì)經(jīng)常在停在這種位置:


因?yàn)橐WC當(dāng)滑動(dòng)停止的時(shí)候候址,要保證item正好在中間。也就是這樣:


修正的方法就是:滾動(dòng)的值只能為item高度的整數(shù)种柑。這樣就能保證岗仑,滾動(dòng)結(jié)束中間的item只能在正中間。利用這個(gè)原理聚请,上邊的手指滑動(dòng)荠雕,也能在手指離開屏幕后來修正位置。

這里還要考慮一個(gè)問題,滾動(dòng)的時(shí)候可能會(huì)超過給定的數(shù)據(jù)集的大小舞虱,就是當(dāng)滾動(dòng)的值超過最后一個(gè)數(shù)據(jù)后欢际,當(dāng)手指松開后返回到最后一個(gè)數(shù)據(jù)的位置,即下圖的效果:


161098a4ab2fdbe1.gif

方法還是在ACTION_UP的時(shí)候判斷是否超過數(shù)據(jù)集的大小即可矾兜。代碼如下:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    ...
    mMinFlingY = - mItemHeight * (mDataList.size() - 1);
    mMaxFlingY = 0;
}


@Override
public boolean onTouchEvent(MotionEvent event) {
    ...
    
    case MotionEvent.ACTION_UP:
        mTracker.computeCurrentVelocity(1000, mMaximumVelocity);
        int velocity = (int) mTracker.getYVelocity();
        mScroller.fling(0, mScrollOffsetY, 0, velocity,
                0, 0, mMinFlingY, mMaxFlingY);
        mScroller.setFinalY(mScroller.getFinalY() +
                computeDistanceToEndPoint(mScroller.getFinalY() % mItemHeight));
                
        if (mScroller.getFinalY() > mMaxFlingY) {
            mScroller.setFinalY(mMaxFlingY);
        } else if (mScroller.getFinalY() < mMinFlingY) {
            mScroller.setFinalY(mMinFlingY);
        }
    
    ...
}

循環(huán)滾動(dòng)

接下來要考慮循環(huán)滾動(dòng)的問題损趋。

首先 上邊的mMinFlingYmMaxFlingY在循環(huán)滾動(dòng)的時(shí)候就要設(shè)置成Integer的極限值,如下

mMinFlingY = mIsCyclic ? Integer.MIN_VALUE : - mItemHeight * (mDataList.size() - 1);
mMaxFlingY = mIsCyclic ? Integer.MAX_VALUE : 0;

然后考慮繪制的時(shí)候取值問題椅寺,在上邊onDraw()方法的里面有一個(gè)安全值判斷浑槽,如果超過數(shù)據(jù)集的大小就跳過此條item繪制,代碼如下:

@Override
protected void onDraw(Canvas canvas) {
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        if (drawDataPos < 0 || drawDataPos > mDataList.size() - 1) {
            continue;
        }
        ...
    }
}

我們要改動(dòng)的就是這里返帕。當(dāng)循環(huán)滾動(dòng)的時(shí)候上下滾動(dòng)的極限都為無窮桐玻,所以- mScrollOffsetY / mItemHeight;得到的值應(yīng)該也在正負(fù)無窮之間,我們要把值都映射到數(shù)據(jù)集中荆萤。
假設(shè)數(shù)據(jù)集有10條數(shù)據(jù)镊靴,當(dāng)前滾動(dòng)的位置為pos:

  • 當(dāng)pos>10時(shí),假設(shè)當(dāng)pos = 10時(shí)链韭,我們想讓其回到第一個(gè)數(shù)據(jù)偏竟,對(duì)其取余即可:pos % 10
  • 當(dāng)pos<0時(shí)敞峭,假設(shè)當(dāng)pos = -1時(shí)踊谋,應(yīng)該要展示最后一個(gè)數(shù)據(jù),對(duì)其數(shù)據(jù)增加正數(shù)的修正為:10 + (pos % 10)
    所以上邊的代碼就可以改變成:
@Override
protected void onDraw(Canvas canvas) {
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        int pos = drawDataPos;
        if  (mIsCyclic) {
            if (pos < 0) {
                pos = mDataList.size() + (pos % mDataList.size());
            }
            if (pos >= mDataList.size()){
                pos = pos % mDataList.size();
            }
        } else {
            if (drawDataPos < 0 || drawDataPos > mDataList.size() - 1) {
                continue;
            }
        }
        ...
    }
}

幕布

這個(gè)就比較簡(jiǎn)單了旋讹,在中間畫一個(gè)矩形就好了,不過要在繪制滾輪之前繪制殖蚕,否則會(huì)遮蓋住文字,代碼如下:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    ...
    mDrawnRect.set(getPaddingLeft(), getPaddingTop(),
        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
    mSelectedItemRect.set(getPaddingLeft(), mItemHeight * mHalfVisibleItemCount,getWidth() - getPaddingRight(), mItemHeight + mItemHeight * mHalfVisibleItemCount);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setTextAlign(Paint.Align.CENTER);
    //是否繪制幕布
    if (mIsShowCurtain) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mCurtainColor);
        canvas.drawRect(mSelectedItemRect, mPaint);
    }
    //是否繪制幕布邊框
    if (mIsShowCurtainBorder) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(mCurtainBorderColor);
        canvas.drawRect(mSelectedItemRect, mPaint);
        canvas.drawRect(mDrawnRect, mPaint);
    }
    ...
}

字體顏色漸變

這個(gè)分三個(gè)部分:

1. 透明度漸變:

Paint在繪制文字的時(shí)候可以設(shè)置Alpha來設(shè)置透明度沉迹,Alpha的比例計(jì)算方法:

$$ \frac{繪制點(diǎn)到端點(diǎn)距離}{中心繪制點(diǎn)到端點(diǎn)距離} $$

如下圖所示睦疫,計(jì)算“03”的比例:



主要代碼如下:

@Override
protected void onDraw(Canvas canvas) {
    ...
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        int pos = drawDataPos;
        if  (mIsCyclic) {
            if (pos < 0) {
                pos = mDataList.size() + (pos % 10);
            } else {
                pos = pos % mDataList.size();
            }
        } else {
            if (drawDataPos < 0 || drawDataPos > mDataList.size() - 1) {
                continue;
            }
        }
        
        T data = mDataList.get(pos);
        int itemDrawY = mFirstItemDrawY + (drawDataPos + mHalfVisibleItemCount) * mItemHeight + mScrollOffsetY;
        //距離中心的Y軸距離
        int distanceY = Math.abs(mCenterItemDrawnY - itemDrawY);
        
        float alphaRatio;
        if (itemDrawY > mCenterItemDrawnY) {
            alphaRatio = (mDrawnRect.height() - itemDrawY) /
                    (float) (mDrawnRect.height() - (mCenterItemDrawnY));
        } else {
            alphaRatio = itemDrawY / (float) mCenterItemDrawnY;
        }
        mPaint.setAlpha((int) (alphaRatio * 255));
        canvas.drawText(data.toString(), mFirstItemDrawX, itemDrawY, mPaint);
        ...
    }
}

2. 文字顏色漸變

當(dāng)距離中心繪制點(diǎn)的距離小于一個(gè)ItemHeight時(shí),進(jìn)行文字顏色漸變鞭呕。
首先需要一個(gè)線性顏色漸變的工具笼痛,思路就是指定一個(gè)開始顏色和結(jié)束顏色,傳入比例獲取顏色琅拌。比較簡(jiǎn)單,直接看代碼:

public class LinearGradient {

    private int mStartColor;
    private int mEndColor;
    private int mRedStart;
    private int mBlueStart;
    private int mGreenStart;
    private int mRedEnd;
    private int mBlueEnd;
    private int mGreenEnd;
    
    public LinearGradient(@ColorInt int startColor, @ColorInt int endColor) {
        mStartColor = startColor;
        mEndColor = endColor;
        updateColor();
    }
    
    
    public void setStartColor(@ColorInt int startColor) {
        mStartColor = startColor;
        updateColor();
    }
    
    public void setEndColor(@ColorInt int endColor) {
        mEndColor = endColor;
        updateColor();
    }
    
    private void updateColor() {
        mRedStart = Color.red(mStartColor);
        mBlueStart = Color.blue(mStartColor);
        mGreenStart = Color.green(mStartColor);
        mRedEnd = Color.red(mEndColor);
        mBlueEnd = Color.blue(mEndColor);
        mGreenEnd = Color.green(mEndColor);
    }
    
    public int getColor(float ratio) {
        int red = (int) (mRedStart + ((mRedEnd - mRedStart) * ratio + 0.5));
        int greed = (int) (mGreenStart + ((mGreenEnd - mGreenStart) * ratio + 0.5));
        int blue = (int) (mBlueStart + ((mBlueEnd - mBlueStart) * ratio + 0.5));
        return Color.rgb(red, greed, blue);
    }
}

接下來就是計(jì)算文字顏色漸變了摘刑,和上邊的計(jì)算透明度的類似进宝,代碼如下:

@Override
protected void onDraw(Canvas canvas) {
    ...
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        ...
        
        T data = mDataList.get(pos);
        int itemDrawY = mFirstItemDrawY + (drawDataPos + mHalfVisibleItemCount) * mItemHeight + mScrollOffsetY;
        //距離中心的Y軸距離
        int distanceY = Math.abs(mCenterItemDrawnY - itemDrawY);
        
        //計(jì)算文字顏色漸變
        //文字顏色漸變要在設(shè)置透明度上邊,否則透明度會(huì)被覆蓋
        if (distanceY < mItemHeight) {
            float colorRatio = 1 - (distanceY / (float) mItemHeight);
            mPaint.setColor(mLinearGradient.getColor(colorRatio));
        } else {
            mPaint.setColor(mTextColor);
        }
        
        //計(jì)算透明度漸變
        float alphaRatio;
        if (itemDrawY > mCenterItemDrawnY) {
            alphaRatio = (mDrawnRect.height() - itemDrawY) /
                    (float) (mDrawnRect.height() - (mCenterItemDrawnY));
        } else {
            alphaRatio = itemDrawY / (float) mCenterItemDrawnY;
        }
        mPaint.setAlpha((int) (alphaRatio * 255));
        canvas.drawText(data.toString(), mFirstItemDrawX, itemDrawY, mPaint);
        ...
    }
}

3.文字大小漸變:

這個(gè)和文字顏色漸變實(shí)現(xiàn)思路一模一樣枷恕,變化的項(xiàng)由文字的顏色變?yōu)榇笮〉辰R彩窃谥行奈恢靡粋€(gè)ItemHeigh的距離進(jìn)行計(jì)算,直接上代碼:

@Override
protected void onDraw(Canvas canvas) {
    ...
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        ...
        //計(jì)算透明度漸變
        float alphaRatio;
        if (itemDrawY > mCenterItemDrawnY) {
            alphaRatio = (mDrawnRect.height() - itemDrawY) /
                    (float) (mDrawnRect.height() - (mCenterItemDrawnY));
        } else {
            alphaRatio = itemDrawY / (float) mCenterItemDrawnY;
        }
        mPaint.setAlpha((int) (alphaRatio * 255));
        
        //靠近中心的Item字體放大
        if (distanceY < mItemHeight) {
            float addedSize = (mItemHeight - distanceY) / (float) mItemHeight * (mSelectedItemTextSize - mTextSize);
            mPaint.setTextSize(mTextSize + addedSize);
        } else {
            mPaint.setTextSize(mTextSize);
        }
        canvas.drawText(data.toString(), mFirstItemDrawX, itemDrawY, mPaint);
        ...
    }
}

指示器文字實(shí)現(xiàn)

指示器文字就是直接在中間的item后邊繪制一個(gè)文字,只需要計(jì)算一下要繪制的坐標(biāo)即可未玻,直接在onDraw()方法的的最后加入就好了灾而。代碼如下:

@Override
protected void onDraw(Canvas canvas) {
    ...
    mPaint.setTextAlign(Paint.Align.CENTER);
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        ...
        canvas.drawText(data.toString(), mFirstItemDrawX, itemDrawY, mPaint);
    }
    
    mPaint.setTextAlign(Paint.Align.LEFT);
    canvas.drawText(mIndicatorText, mFirstItemDrawX + mTextMaxWidth / 2, mCenterItemDrawnY, mPaint);
}

小結(jié)

到此, 一個(gè)滾輪選擇器主要功能就實(shí)現(xiàn)了扳剿。接下來只要完善一些細(xì)節(jié)部分比如監(jiān)聽器旁趟、點(diǎn)擊效果等部分,就完成了庇绽。接下來根據(jù)需要利用滾輪選擇器就能很輕松的實(shí)現(xiàn)日期選擇器锡搜,省市選擇器等控件。

詳細(xì)代碼請(qǐng)移步我的GitHub: https://github.com/ycuwq/DatePicker瞧掺。
里邊還封裝了一個(gè)DatePicker和DatePicker從下方彈出的Dialog耕餐,使用方法請(qǐng)移步GitHub。

最后辟狈,非常歡迎到GitHub中提出您的問題或意見肠缔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市哼转,隨后出現(xiàn)的幾起案子明未,更是在濱河造成了極大的恐慌,老刑警劉巖释簿,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亚隅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡庶溶,警方通過查閱死者的電腦和手機(jī)煮纵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來偏螺,“玉大人行疏,你說我怎么就攤上這事√紫瘢” “怎么了酿联?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)夺巩。 經(jīng)常有香客問我贞让,道長(zhǎng),這世上最難降的妖魔是什么柳譬? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任喳张,我火速辦了婚禮,結(jié)果婚禮上美澳,老公的妹妹穿的比我還像新娘销部。我一直安慰自己摸航,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布舅桩。 她就那樣靜靜地躺著酱虎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪擂涛。 梳的紋絲不亂的頭發(fā)上读串,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天歼指,我揣著相機(jī)與錄音爹土,去河邊找鬼。 笑死踩身,一個(gè)胖子當(dāng)著我的面吹牛胀茵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挟阻,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼琼娘,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了附鸽?” 一聲冷哼從身側(cè)響起脱拼,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坷备,沒想到半個(gè)月后熄浓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡省撑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年赌蔑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竟秫。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娃惯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肥败,到底是詐尸還是另有隱情趾浅,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布馒稍,位于F島的核電站皿哨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏纽谒。R本人自食惡果不足惜往史,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望佛舱。 院中可真熱鬧椎例,春花似錦、人聲如沸请祖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肆捕。三九已至刷晋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慎陵,已是汗流浹背眼虱。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留席纽,地道東北人捏悬。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像润梯,于是被迫代替她去往敵國(guó)和親过牙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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