【Android】MPAndroidChart 定制性開發(fā)筆記

前言

MPAndroidChart 作為Android平臺(tái)上最火的圖表庫,功能齊全,但是相對(duì)而言宏所,文檔確很少肚邢,公司項(xiàng)目中有圖表的需求壹堰,這里也使用的是MPAndroidChart拭卿,有部分定制性的功能,特此記錄贱纠。

BarChart

  • 1.X峻厚、Y方向值的自定義

默認(rèn)的支持顯示數(shù)字,如果要自定義的話谆焊,需要使用ValueFormatter

   public static class MyValueFormatter extends ValueFormatter {
        private List<String> list;

        public MyValueFormatter(List<String> list) {
            this.list = list;
        }

        @Override
        public String getFormattedValue(float value) {
            if (list != null && list.size() > value) {
                return list.get((int) value);
            }
            return super.getFormattedValue(value);
        }
    }

如何使用惠桃?

                List<String> titles = new ArrayList<>();
                for (int i = 0; i < personList.size(); i++) {
                    JSONObject object = personList.getJSONObject(i);
                    values.add(new BarEntry(i, object.getFloatValue("requestTime")));
                    titles.add(object.getString("realName"));
                    colors.add(allColors.get(new Random().nextInt(6)));
                }
                XAxis xAxis = barChartOvertimePerson.getXAxis();
                xAxis.setValueFormatter(new MyValueFormatter(titles));
  • 2.圓角


    image.png

默認(rèn)的BarChart只支持直角,要實(shí)現(xiàn)圓角的需要修改源碼辖试,通過閱讀源碼辜王,我們發(fā)現(xiàn),繪制柱狀圖的方法在
com.github.mikephil.charting.renderer.BarChartRenderer類下的drawDataSet方法完成罐孝。

...省略...
 c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                    buffer.buffer[j + 3], mRenderPaint);
...省略...

所以首先呐馆,我們需要使用我們自定義的CustBarChartRenderer繼承于BarChartRenderer

public class CustBarChartRenderer extends BarChartRenderer {
    private RectF mBarShadowRectBuffer = new RectF();

    public CustBarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
        super(chart, animator, viewPortHandler);
    }
}

其次,重寫BarChartRenderer#drawDataSet方法莲兢,然后粘貼原本方法的完整代碼,添加圓角代碼汹来;

 @Override
    protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {

        Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());

        mBarBorderPaint.setColor(dataSet.getBarBorderColor());
        mBarBorderPaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getBarBorderWidth()));

        final boolean drawBorder = dataSet.getBarBorderWidth() > 0.f;

        float phaseX = mAnimator.getPhaseX();
        float phaseY = mAnimator.getPhaseY();

        // draw the bar shadow before the values
        if (mChart.isDrawBarShadowEnabled()) {
            mShadowPaint.setColor(dataSet.getBarShadowColor());

            BarData barData = mChart.getBarData();

            final float barWidth = barData.getBarWidth();
            final float barWidthHalf = barWidth / 2.0f;
            float x;

            for (int i = 0, count = Math.min((int) (Math.ceil((float) (dataSet.getEntryCount()) * phaseX)), dataSet.getEntryCount());
                 i < count;
                 i++) {

                BarEntry e = dataSet.getEntryForIndex(i);

                x = e.getX();

                mBarShadowRectBuffer.left = x - barWidthHalf;
                mBarShadowRectBuffer.right = x + barWidthHalf;

                trans.rectValueToPixel(mBarShadowRectBuffer);

                if (!mViewPortHandler.isInBoundsLeft(mBarShadowRectBuffer.right)) {
                    continue;
                }

                if (!mViewPortHandler.isInBoundsRight(mBarShadowRectBuffer.left)) {
                    break;
                }

                mBarShadowRectBuffer.top = mViewPortHandler.contentTop();
                mBarShadowRectBuffer.bottom = mViewPortHandler.contentBottom();

                c.drawRect(mBarShadowRectBuffer, mShadowPaint);
            }
        }

        // initialize the buffer
        BarBuffer buffer = mBarBuffers[index];
        buffer.setPhases(phaseX, phaseY);
        buffer.setDataSet(index);
        buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency()));
        buffer.setBarWidth(mChart.getBarData().getBarWidth());

        buffer.feed(dataSet);

        trans.pointValuesToPixel(buffer.buffer);

        final boolean isSingleColor = dataSet.getColors().size() == 1;

        if (isSingleColor) {
            mRenderPaint.setColor(dataSet.getColor());
        }

        for (int j = 0; j < buffer.size(); j += 4) {

            if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) {
                continue;
            }

            if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) {
                break;
            }

            if (!isSingleColor) {
                // Set the color for the currently drawn value. If the index
                // is out of bounds, reuse colors.
                mRenderPaint.setColor(dataSet.getColor(j / 4));
            }

            if (dataSet.getGradientColor() != null) {
                GradientColor gradientColor = dataSet.getGradientColor();
                mRenderPaint.setShader(
                        new LinearGradient(
                                buffer.buffer[j],
                                buffer.buffer[j + 3],
                                buffer.buffer[j],
                                buffer.buffer[j + 1],
                                gradientColor.getStartColor(),
                                gradientColor.getEndColor(),
                                android.graphics.Shader.TileMode.MIRROR));
            }

            if (dataSet.getGradientColors() != null) {
                mRenderPaint.setShader(
                        new LinearGradient(
                                buffer.buffer[j],
                                buffer.buffer[j + 3],
                                buffer.buffer[j],
                                buffer.buffer[j + 1],
                                dataSet.getGradientColor(j / 4).getStartColor(),
                                dataSet.getGradientColor(j / 4).getEndColor(),
                                android.graphics.Shader.TileMode.MIRROR));
            }

            float left = buffer.buffer[j];
            float top = buffer.buffer[j + 1];
            float right = buffer.buffer[j + 2];
            float bottom = buffer.buffer[j + 3];
            float radius = (right - left) / 2;

            //高度減去圓的半徑
            c.drawRect(left, top + radius, right, bottom, mRenderPaint);
            //畫圓
            c.drawCircle(left + radius, top + radius, radius, mRenderPaint);

            if (drawBorder) {
                c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                        buffer.buffer[j + 3], mBarBorderPaint);
            }
        }
    }

原理也很簡單,先繪制一個(gè)減去半徑高度的矩形怒见,然后在矩形的左右居中位置繪制一個(gè)圓
最后俗慈,讓我們自定義的CustBarChartRenderer生效

public class CustBarChart extends BarChart {
    public CustBarChart(Context context) {
        super(context);
    }

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

    public CustBarChart(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void init() {
        super.init();
        mRenderer = new CustBarChartRenderer(this, mAnimator, mViewPortHandler);
    }
}
  • 3.矩形的寬度
    當(dāng)x方向的數(shù)據(jù)比較少時(shí),矩形的寬度太寬了遣耍,這顯然不符合產(chǎn)品大大的需要闺阱,我們還需要限制矩形的寬度


    image.png

    如何限制呢?在上一個(gè)問題中舵变,我們找到了繪制矩形的地方酣溃,我們只需要對(duì)矩形的坐標(biāo)重新進(jìn)行計(jì)算,就可以達(dá)到限制寬度的作用

...省略...
            float left = buffer.buffer[j];
            float top = buffer.buffer[j + 1];
            float right = buffer.buffer[j + 2];
            float bottom = buffer.buffer[j + 3];
            float radius = (right - left) / 2;

            float max = 40;
            if (radius > max) {
                float xl = right - left - max;
                //控制大小
                left = left + xl / 2;
                right = right - xl / 2;
                radius = max / 2;
            }

            //高度減去圓的半徑
            c.drawRect(left, top + radius, right, bottom, mRenderPaint);
            //畫圓
            c.drawCircle(left + radius, top + radius, radius, mRenderPaint);
...省略...

當(dāng)然纪隙,這個(gè)最大值可以根據(jù)項(xiàng)目的實(shí)際情況做修改赊豌。

  • 4.x方向文字過長


    image.png

可以看到,當(dāng)x方向文字過長時(shí)绵咱,文字之間會(huì)覆蓋碘饼,作者也考慮到了這個(gè)問題,提供了一個(gè)方法

         XAxis xAxis = barChart.getXAxis();
        xAxis.setLabelRotationAngle(90);

效果如下

image.png

可惜的是悲伶,產(chǎn)品大大一般都不會(huì)滿足于此艾恼,下圖才是人家需要的。
image.png

可能有人說了麸锉,使用\n可以不钠绍,例如丈\n八\n一\n路湃交,實(shí)際上并沒有什么用溯祸,有興趣的同學(xué)可以試試谨湘。
還是翻源碼吧稠曼。通過翻源碼,我們發(fā)現(xiàn)x方向的文字繪制是在com.github.mikephil.charting.renderer.XAxisRenderer類下drawLabel方法中磷脯,還是同樣的套路蛾找,我們重寫這個(gè)方法。

思路還是通\n來實(shí)現(xiàn)争拐,只是原先的用的Canvas#drawText方法不支持腋粥,我們使用StaticLayout來替代

  @Override
    protected void drawLabel(Canvas c, String formattedLabel, float x, float y, MPPointF anchor, float angleDegrees) {
        if (isMultilineText(formattedLabel)) {
            TextPaint textPaint = new TextPaint(mAxisLabelPaint);
            drawMultilineText(c, formattedLabel, x, y,
                    textPaint,
                    new FSize(c.getWidth(), c.getHeight()), new MPPointF(0f, 0f), 0);
        } else {
            Utils.drawXAxisValue(c, formattedLabel, x, y, mAxisLabelPaint, anchor, angleDegrees);
        }
    }

首先需要判斷是否是多行文字,單行的話架曹,走之前默認(rèn)的方法,多行的話闹瞧,調(diào)用drawMultilineText()方法

   private boolean isMultilineText(String text) {
        if (TextUtils.isEmpty(text)) {
            return false;
        }
        return text.contains("\n");
    }

drawMultilineText()

    private void drawMultilineText(Canvas c, String text,
                                   float x, float y,
                                   TextPaint paint,
                                   FSize constrainedToSize,
                                   MPPointF anchor, float angleDegrees) {

        StaticLayout textLayout = new StaticLayout(
                text, 0, text.length(),
                paint,
                (int) Math.max(Math.ceil(constrainedToSize.width), 1.f),
                Layout.Alignment.ALIGN_NORMAL, 1.f, 0.f, false);


        drawMultilineText(c, textLayout, x, y, paint, anchor, angleDegrees);
    }

 public void drawMultilineText(Canvas c, StaticLayout textLayout,
                                  float x, float y,
                                  TextPaint paint,
                                  MPPointF anchor, float angleDegrees) {

        float drawOffsetX = 0.f;
        float drawOffsetY = 0.f;
        float drawWidth;
        float drawHeight;

        final float lineHeight = paint.getFontMetrics(mFontMetricsBuffer);

        drawWidth = textLayout.getWidth();
        drawHeight = textLayout.getLineCount() * lineHeight;
 
        // Android sometimes has pre-padding
        drawOffsetX -= mDrawTextRectBuffer.left;

        // Android does not snap the bounds to line boundaries,
        //  and draws from bottom to top.
        // And we want to normalize it.
//        drawOffsetY += drawHeight;

        // To have a consistent point of reference, we always draw left-aligned
        Paint.Align originalTextAlign = paint.getTextAlign();
        paint.setTextAlign(Paint.Align.CENTER);

        if (angleDegrees != 0.f) {

            // Move the text drawing rect in a way that it always rotates around its center
//            drawOffsetX -= drawWidth * 0.5f;
            drawOffsetX += x + (mDrawTextRectBuffer.width() / 2.0f);
            drawOffsetY -= drawHeight * 0.5f;

            float translateX = x;
            float translateY = y;

            // Move the "outer" rect relative to the anchor, assuming its centered
            if (anchor.x != 0.5f || anchor.y != 0.5f) {
                final FSize rotatedSize = getSizeOfRotatedRectangleByDegrees(
                        drawWidth,
                        drawHeight,
                        angleDegrees);

                translateX -= rotatedSize.width * (anchor.x - 0.5f);
                translateY -= rotatedSize.height * (anchor.y - 0.5f);
                FSize.recycleInstance(rotatedSize);
            }

            c.save();
            c.translate(translateX, translateY);
            c.rotate(angleDegrees);

            c.translate(drawOffsetX, drawOffsetY);
            textLayout.draw(c);

            c.restore();
        } else {
            if (anchor.x != 0.f || anchor.y != 0.f) {

                drawOffsetX -= drawWidth * anchor.x;
                drawOffsetY -= drawHeight * anchor.y;
            }

            drawOffsetX += x;
            drawOffsetY += y;

            c.save();

            c.translate(drawOffsetX, drawOffsetY);
            textLayout.draw(c);

            c.restore();
        }

        paint.setTextAlign(originalTextAlign);
    }

    /**
     * Returns a recyclable FSize instance.
     * Represents size of a rotated rectangle by degrees.
     *
     * @param rectangleWidth
     * @param rectangleHeight
     * @param degrees
     * @return A Recyclable FSize instance
     */
    public static FSize getSizeOfRotatedRectangleByDegrees(float rectangleWidth, float
            rectangleHeight, float degrees) {
        final float radians = degrees * ((float) Math.PI / 180.f);
        return getSizeOfRotatedRectangleByRadians(rectangleWidth, rectangleHeight, radians);
    }

該方法摘選自com.github.mikephil.charting.utils.Utils绑雄,這里做了簡單修改。
然后是使我們的CustXAxisRenderer生效奥邮,還是在init方法中

public class CustBarChart extends BarChart {
    public CustBarChart(Context context) {
        super(context);
    }

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

    public CustBarChart(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void init() {
        super.init();
        mRenderer = new CustBarChartRenderer(this, mAnimator, mViewPortHandler);
        mXAxisRenderer = new CustXAxisRenderer(mViewPortHandler, mXAxis, mLeftAxisTransformer);
    }
}

然后是搭配ValueFormatter使用

    public static class MyValueFormatter1 extends ValueFormatter {
        private List<String> list;
        private boolean needMultLine;

        public MyValueFormatter1(List<String> list, boolean needMultLine) {
            this.list = list;
            this.needMultLine = needMultLine;
        }

        public MyValueFormatter1(List<String> list) {
            this.list = list;
        }

        @Override
        public String getFormattedValue(float value) {
            if (list != null && list.size() > value) {
                if (needMultLine) {
                    String text = list.get((int) value);
                    if (!TextUtils.isEmpty(text) && text.length() > 0) {
                        StringBuilder builder = new StringBuilder();
                        for (int i = 0; i < text.length(); i++) {
                            builder.append(text.charAt(i)).append("\n");
                        }
                        return builder.toString();
                    }
                    return text;
                }
                return list.get((int) value);
            }
            return super.getFormattedValue(value);
        }
    }
         XAxis xAxis = barChart.getXAxis();
        xAxis.setValueFormatter(new CustBarChart.MyValueFormatter1(titles, true));

最后万牺,還需要設(shè)置barChart.setExtraBottomOffset(50);,需要注意的是這里的50需要根據(jù)實(shí)際情況做修改洽腺,如需動(dòng)態(tài)設(shè)置脚粟,這里提供個(gè)思路,drawMultilineText方法中的drawHeight即為每個(gè)多行文字的高度蘸朋,然后需要計(jì)算最大值核无,然后在調(diào)用setExtraBottomOffset即可。

PieChart

  • 1.灰色背景
    餅狀圖當(dāng)數(shù)據(jù)的數(shù)值是0時(shí)藕坯,默認(rèn)只有文字描述团南。但是產(chǎn)品大大的需求是這樣
    image.png

    思路是這樣,判斷當(dāng)前所有數(shù)據(jù)的數(shù)值都是0時(shí)炼彪,在數(shù)據(jù)后再添加一個(gè)100的數(shù)據(jù)吐根,顏色為灰色。
    需要重寫setData方法
public class CustPieChart extends PieChart {
    public CustPieChart(Context context) {
        super(context);
    }

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

    public CustPieChart(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void init() {
        super.init();
    }



    @Override
    public void setData(PieData data) {
        for (IDataSet<?> set : data.getDataSets()) {
            set.setDrawValues(false);
            set.setDrawIcons(false);
            if (set instanceof PieDataSet) {
                PieDataSet pieDataSet = (PieDataSet) set;
                if (pieDataSet.getValues() != null && pieDataSet.getValues().size() > 0) {
                    boolean isEmpty = true;
                    for (PieEntry p : pieDataSet.getValues()) {
                        if (p.getValue() != 0) {
                            isEmpty = false;
                        }
                    }
                    if (isEmpty) {
                        pieDataSet.getValues().add(new PieEntry(100f, "empty"));
                        pieDataSet.getColors().add(Color.parseColor("#eeeeee"));
                    }
                }
            }

        }
        super.setData(data);
    }
}

這樣的話辐马,指示欄中也會(huì)相應(yīng)的多一條數(shù)據(jù)拷橘,我們需要排除這條數(shù)據(jù),通過翻源碼喜爷,我們發(fā)現(xiàn)指示欄的數(shù)據(jù)處理是在com.github.mikephil.charting.renderer.LegendRenderercomputeLegend方法冗疮,我們重寫這個(gè)方法

public class MyLegendRenderer extends LegendRenderer {
    public MyLegendRenderer(ViewPortHandler viewPortHandler, Legend legend) {
        super(viewPortHandler, legend);
    }

    @Override
    public void computeLegend(ChartData<?> data) {
        if (!mLegend.isLegendCustom()) {

            computedEntries.clear();

            // loop for building up the colors and labels used in the legend
            for (int i = 0; i < data.getDataSetCount(); i++) {

                IDataSet dataSet = data.getDataSetByIndex(i);

                List<Integer> clrs = dataSet.getColors();
                int entryCount = dataSet.getEntryCount();

                // if we have a barchart with stacked bars
                if (dataSet instanceof IBarDataSet && ((IBarDataSet) dataSet).isStacked()) {

                    IBarDataSet bds = (IBarDataSet) dataSet;
                    String[] sLabels = bds.getStackLabels();

                    for (int j = 0; j < clrs.size() && j < bds.getStackSize(); j++) {

                        computedEntries.add(new LegendEntry(
                                sLabels[j % sLabels.length],
                                dataSet.getForm(),
                                dataSet.getFormSize(),
                                dataSet.getFormLineWidth(),
                                dataSet.getFormLineDashEffect(),
                                clrs.get(j)
                        ));
                    }

                    if (bds.getLabel() != null) {
                        // add the legend description label
                        computedEntries.add(new LegendEntry(
                                dataSet.getLabel(),
                                Legend.LegendForm.NONE,
                                Float.NaN,
                                Float.NaN,
                                null,
                                ColorTemplate.COLOR_NONE
                        ));
                    }

                } else if (dataSet instanceof IPieDataSet) {

                    IPieDataSet pds = (IPieDataSet) dataSet;

                    for (int j = 0; j < clrs.size() && j < entryCount; j++) {
                        //排除我們添加的空數(shù)據(jù)
                        if (!"empty".equals(pds.getEntryForIndex(j).getLabel())) {
                            computedEntries.add(new LegendEntry(
                                    pds.getEntryForIndex(j).getLabel(),
                                    dataSet.getForm(),
                                    dataSet.getFormSize(),
                                    dataSet.getFormLineWidth(),
                                    dataSet.getFormLineDashEffect(),
                                    clrs.get(j)
                            ));
                        }
                    }

                    if (pds.getLabel() != null) {
                        // add the legend description label
                        computedEntries.add(new LegendEntry(
                                dataSet.getLabel(),
                                Legend.LegendForm.NONE,
                                Float.NaN,
                                Float.NaN,
                                null,
                                ColorTemplate.COLOR_NONE
                        ));
                    }

                } else if (dataSet instanceof ICandleDataSet && ((ICandleDataSet) dataSet).getDecreasingColor() !=
                        ColorTemplate.COLOR_NONE) {

                    int decreasingColor = ((ICandleDataSet) dataSet).getDecreasingColor();
                    int increasingColor = ((ICandleDataSet) dataSet).getIncreasingColor();

                    computedEntries.add(new LegendEntry(
                            null,
                            dataSet.getForm(),
                            dataSet.getFormSize(),
                            dataSet.getFormLineWidth(),
                            dataSet.getFormLineDashEffect(),
                            decreasingColor
                    ));

                    computedEntries.add(new LegendEntry(
                            dataSet.getLabel(),
                            dataSet.getForm(),
                            dataSet.getFormSize(),
                            dataSet.getFormLineWidth(),
                            dataSet.getFormLineDashEffect(),
                            increasingColor
                    ));

                } else { // all others

                    for (int j = 0; j < clrs.size() && j < entryCount; j++) {

                        String label;

                        // if multiple colors are set for a DataSet, group them
                        if (j < clrs.size() - 1 && j < entryCount - 1) {
                            label = null;
                        } else { // add label to the last entry
                            label = data.getDataSetByIndex(i).getLabel();
                        }

                        computedEntries.add(new LegendEntry(
                                label,
                                dataSet.getForm(),
                                dataSet.getFormSize(),
                                dataSet.getFormLineWidth(),
                                dataSet.getFormLineDashEffect(),
                                clrs.get(j)
                        ));
                    }
                }
            }

            if (mLegend.getExtraEntries() != null) {
                Collections.addAll(computedEntries, mLegend.getExtraEntries());
            }

            mLegend.setEntries(computedEntries);
        }

        Typeface tf = mLegend.getTypeface();

        if (tf != null)
            mLegendLabelPaint.setTypeface(tf);

        mLegendLabelPaint.setTextSize(mLegend.getTextSize());
        mLegendLabelPaint.setColor(mLegend.getTextColor());

        // calculate all dimensions of the mLegend
        mLegend.calculateDimensions(mLegendLabelPaint, mViewPortHandler);
    }
}

最后,使我們的MyLegendRenderer生效

    @Override
    protected void init() {
        super.init();
        mLegendRenderer = new MyLegendRenderer(mViewPortHandler, mLegend);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贞奋,一起剝皮案震驚了整個(gè)濱河市赌厅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌轿塔,老刑警劉巖特愿,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仲墨,死亡現(xiàn)場離奇詭異,居然都是意外死亡揍障,警方通過查閱死者的電腦和手機(jī)目养,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毒嫡,“玉大人癌蚁,你說我怎么就攤上這事《祷” “怎么了努释?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咬摇。 經(jīng)常有香客問我伐蒂,道長,這世上最難降的妖魔是什么肛鹏? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任逸邦,我火速辦了婚禮,結(jié)果婚禮上在扰,老公的妹妹穿的比我還像新娘缕减。我一直安慰自己,他們只是感情好芒珠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布桥狡。 她就那樣靜靜地躺著,像睡著了一般妓局。 火紅的嫁衣襯著肌膚如雪总放。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天好爬,我揣著相機(jī)與錄音局雄,去河邊找鬼。 笑死存炮,一個(gè)胖子當(dāng)著我的面吹牛炬搭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播穆桂,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼宫盔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了享完?” 一聲冷哼從身側(cè)響起灼芭,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎般又,沒想到半個(gè)月后彼绷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巍佑,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年寄悯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了萤衰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猜旬,死狀恐怖脆栋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情洒擦,我是刑警寧澤椿争,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站秘遏,受9級(jí)特大地震影響丘薛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜邦危,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舍扰。 院中可真熱鬧倦蚪,春花似錦、人聲如沸边苹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽个束。三九已至慕购,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茬底,已是汗流浹背沪悲。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阱表,地道東北人殿如。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像最爬,于是被迫代替她去往敵國和親涉馁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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