自定义简单字母索引栏

说到字母索引栏,在联系人??榫;峒?。iOS中这种控件是自带的,安卓中是没有的,所以只能自定义解决了。

需求分析

  1. 拖动索引栏时。选中状态
    • 字母栏背景加深
    • 文字颜色变白色
    • 悬浮字母提示显示
  2. 松手时,非选中状态
    • 字母栏背景透明
    • 文字颜色变黑色
    • 悬浮字母提示隐藏

需要自定义的属性

  1. 选中时字体颜色
  2. 未选中时字体颜色
  3. 选中时,索引栏背景颜色
  4. 非选中时,索引栏背景颜色

提供的回调

选中和未选中时,回调位置和字母文字。

索引条定义步骤(注释上已经写得很清楚,就不再一步步赘述了)

  • 自定义需要的属性
<declare-styleable name="SlideBar">
    <!-- 选中时,文字颜色 -->
    <attr name="slb_select_txt_color" format="color|reference" />
    <!-- 非选中时,文字颜色 -->
    <attr name="slb_un_select_txt_color" format="color|reference" />
    <!-- 选中时,滑动条背景颜色 -->
    <attr name="slb_select_bg_color" format="color|reference" />
    <!-- 非选中时,滑动条背景颜色 -->
    <attr name="slb_un_select_bg_color" format="color|reference" />
</declare-styleable>
  • 自定义View,继承View,获取设置的自定义属性,配置画笔等
public class SlideBar extends View {
    /**
     * 默认选中时,文字颜色
     */
    private final int mDefaultSelectTextColor = Color.parseColor("#FFFFFF");
    /**
     * 默认未选中时,文字颜色
     */
    private final int mDefaultUnSelectTextColor = Color.parseColor("#202020");
    /**
     * 默认选中时,滑动条背景颜色
     */
    private final int mDefaultSelectBgColor = Color.parseColor("#66202020");
    /**
     * 默认未选中时,滑动条背景颜色
     */
    private final int mDefaultUnSelectBgColor = Color.parseColor("#00000000");
    
     /**
     * 字母表
     */
    private String[] mLetter = new String[]{"#", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
    
    /**
     * 是否拖动索引条中
     */
    private boolean mTouched = false;
    
     /**
     * 画笔
     */
    private Paint mPaint;

    private int mSelectTextColor;
    private int mUnSelectTextColor;
    private int mSelectBgColor;
    private int mUnSelectBgColor;
    
    public SlideBar(Context context) {
        super(context);
        init(context, null);
    }

    public SlideBar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public SlideBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, @Nullable AttributeSet attrs) {
        initAttrs(context, attrs);
        initPaint();
    }

    private void initAttrs(Context context, @Nullable AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlideBar);
        //选中时文字的颜色
        mSelectTextColor = array.getColor(R.styleable.SlideBar_slb_select_txt_color, mDefaultSelectTextColor);
        //未选中时文字的颜色
        mUnSelectTextColor = array.getColor(R.styleable.SlideBar_slb_un_select_txt_color, mDefaultUnSelectTextColor);
        //选中时,滑动条背景颜色
        mSelectBgColor = array.getColor(R.styleable.SlideBar_slb_select_bg_color, mDefaultSelectBgColor);
        //未选中时,滑动条背景颜色
        mUnSelectBgColor = array.getColor(R.styleable.SlideBar_slb_un_select_bg_color, mDefaultUnSelectBgColor);
        array.recycle();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setTextSize(sp2px(getContext(), 11f));
        if (mTouched) {
            mPaint.setColor(mSelectTextColor);
        } else {
            mPaint.setColor(mUnSelectTextColor);
        }
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mTextRect = new Rect();
    }
}
  • 定义回调接口
public interface OnSelectItemListener {
    /**
     * 选择时回调
     *
     * @param position     选中的位置
     * @param selectLetter 选中的字母
     */
    void onItemSelect(int position, String selectLetter);

    /**
     * 松手取消选中时回调
     */
    void onItemUnSelect();
}

public void setOnSelectItemListener(OnSelectItemListener listener) {
    mListener = listener;
}
  • 配置测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    setMeasuredDimension(measureWidth(widthMeasureSpec), heightMeasureSpec);
}

/**
 * 测量本身的大小,这里只是测量宽度
 *
 * @param widthMeaSpec 传入父View的测量标准
 * @return 测量的宽度
 */
private int measureWidth(int widthMeaSpec) {
    /*定义view的宽度*/
    int width;
    /*获取当前 View的测量模式*/
    int mode = MeasureSpec.getMode(widthMeaSpec);
    /*
     * 获取当前View的测量值,这里得到的只是初步的值,
     * 我们还需根据测量模式来确定我们期望的大小
     * */
    int size = MeasureSpec.getSize(widthMeaSpec);
    /*
     * 如果,模式为精确模式
     * 当前View的宽度,就是我们的size
     * */
    if (mode == MeasureSpec.EXACTLY) {
        width = size;
    } else {
        /*否则的话我们就需要结合padding的值来确定*/
        int desire = size + getPaddingLeft() + getPaddingRight();
        if (mode == MeasureSpec.AT_MOST) {
            width = Math.min(desire, size);
        } else {
            width = desire;
        }
    }
    return width;
}
  • 获取到宽高时,计算每个字母的高度
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mWidth = w;
    //每行的高度,计算平均分每个字母占的高度
    mCellHeight = h / mLetter.length;
}
  • 绘制文字和背景,触摸和松手时改变变量来达到绘制不同的颜色
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //触摸时改变背景颜色
    if (mTouched) {
        canvas.drawColor(mSelectBgColor);
        mPaint.setColor(mSelectTextColor);
    } else {
        canvas.drawColor(mUnSelectBgColor);
        mPaint.setColor(mUnSelectTextColor);
    }
    for (int i = 0; i < mLetter.length; i++) {
        String text = mLetter[i];
        //测量文字宽高
        mPaint.getTextBounds(text, 0, text.length(), mTextRect);
        int textWidth = mTextRect.width();
        int textHeight = mTextRect.height();

        //文字一半的宽度
        float textHalfWidth = textWidth / 2.0f;
        //字母文字的起点X坐标,控件的宽度的一半再减去文字的一半
        float x = (mWidth / 2.0f) - textHalfWidth;
        //起点文字的Y坐标
        float y = (mCellHeight / 2.0f + textHeight / 2.0f + mCellHeight * i);
        //画文字
        canvas.drawText(mLetter[i], x, y, mPaint);
    }
}
  • 处理触摸和松手判断,并进行回调
@Override
public boolean onTouchEvent(MotionEvent event) {
    float y = event.getY();
    //计算当前触摸的字母的位置
    int index = (int) (y / mCellHeight);
    //触摸
    if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {
        mTouched = true;
        if (index >= 0 && index < mLetter.length) {
            if (mListener != null) {
                mListener.onItemSelect(index, mLetter[index]);
            }
        } else {
            return super.onTouchEvent(event);
        }
    } else {
        //松手
        mTouched = false;
        if (mListener != null) {
            mListener.onItemUnSelect();
        }
    }
    //触摸改变时,不断通知重绘来绘制索引条
    invalidate();
    return true;
}

界面布局

  • RecyclerView列表
  • 索引栏
  • 字母提示(我们直接用个TextView,设置背景,显示隐藏即可)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/refresh_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <me.zh.indexslidebar.SlideBar
        android:id="@+id/slide_bar"
        android:layout_width="24dp"
        android:layout_height="match_parent"
        android:layout_gravity="end"
        android:visibility="visible"
        app:slb_select_bg_color="@android:color/darker_gray"
        app:slb_select_txt_color="@android:color/white"
        app:slb_un_select_bg_color="@android:color/transparent"
        app:slb_un_select_txt_color="@color/colorPrimary" />

    <TextView
        android:id="@+id/check_letter"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center"
        android:background="@drawable/bg_contact_letter_flow"
        android:gravity="center"
        android:textColor="#FFFFFF"
        android:textSize="40sp"
        android:visibility="gone"
        tools:text="A"
        tools:visibility="visible" />
</FrameLayout>

具体使用

  • 提供一组联系人名字
/**
 * 联系人数组
 */
public static String[] mNames = new String[]{"宋江", "卢俊义", "吴用",
        "公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进", "李应", "朱仝", "鲁智深",
        "武松", "董平", "张清", "杨志", "徐宁", "索超", "戴宗", "刘唐", "李逵", "史进", "穆弘",
        "雷横", "李俊", "阮小二", "张横", "阮小五", "张顺", "阮小七", "杨雄", "石秀", "解珍",
        "解宝", "燕青", "朱武", "黄信", "孙立", "宣赞", "郝思文", "韩滔", "彭玘", "单廷珪",
        "魏定国", "萧让", "裴宣", "欧鹏", "邓飞", "燕顺", "杨林", "凌振", "蒋敬", "吕方",
        "郭 盛", "安道全", "皇甫端", "王英", "扈三娘", "鲍旭", "樊瑞", "孔明", "孔亮", "项充",
        "李衮", "金大坚", "马麟", "童威", "童猛", "孟康", "侯健", "陈达", "杨春", "郑天寿",
        "陶宗旺", "宋清", "乐和", "龚旺", "丁得孙", "穆春", "曹正", "宋万", "杜迁", "薛永", "施恩",
        "周通", "李忠", "杜兴", "汤隆", "邹渊", "邹润", "朱富", "朱贵", "蔡福", "蔡庆", "李立",
        "李云", "焦挺", "石勇", "孙新", "顾大嫂", "张青", "孙二娘", "王定六", "郁保四", "白胜",
        "时迁", "段景柱", "&张三", "11级李四", "12级小明"};
}
  1. 字母和字母条的位置映射
/**
 * 用于保存联系人首字母在列表的位置
 */
private HashMap<String, Integer> mLetterPositionMap = new HashMap<>();
  1. 配置好3个View(Rv使用自己熟悉的框架即可,这里使用的是MultiType)
public class MainActivity extends AppCompatActivity {
    private RecyclerView vRefreshList;
    /**
     * 字母侧滑栏
     */
    private SlideBar mSlideBar;
    /**
     * 提示View
     */
    private TextView vCheckLetterView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findView();
        bindView();
        setData();
    }
    
    private void findView() {
        mSlideBar = findViewById(R.id.slide_bar);
        vRefreshList = findViewById(R.id.refresh_list);
        vCheckLetterView = findViewById(R.id.check_letter);
    }

    private void bindView() {
        //配置列表
        vRefreshList.setLayoutManager(new LinearLayoutManager(this));
        mListItems = new Items();
        mListAdapter = new MultiTypeAdapter(mListItems);
        //联系人字母条目
        mListAdapter.register(ContactLetterModel.class, new ContactLetterViewBinder());
        //联系人条目
        mListAdapter.register(ContactModel.class, new ContactViewBinder());
        vRefreshList.setAdapter(mListAdapter);
    }
  1. 配置索引条回调,在选中时,将列表滚动到记录的位置,有些字母在我们的字母表里面可能没有对应的,会找不到,所以需要判空
//配置索引条
mSlideBar.setOnSelectItemListener(new SlideBar.OnSelectItemListener() {
    @Override
    public void onItemSelect(int position, String selectLetter) {
        if (vCheckLetterView.getVisibility() != View.VISIBLE) {
            vCheckLetterView.setVisibility(View.VISIBLE);
        }
        vCheckLetterView.setText(selectLetter);
        Integer letterStickyPosition = mLetterPositionMap.get(selectLetter);
        //这里可能拿不到,因为并不是所有的字母联系人名字上都有
        if (letterStickyPosition != null) {
            vRefreshList.scrollToPosition(letterStickyPosition);
        }
    }

    @Override
    public void onItemUnSelect() {
        vCheckLetterView.setVisibility(View.GONE);
    }
});
  1. 根据姓名表,构造联系人列表和记录字母条位置条目,这里需要将姓名的第一个子的首字母,用到了一个拼音库。
implementation 'com.github.promeg:tinypinyin:2.0.3'

先来将姓名表按姓名的第一个字的英文字母来排序,这样是为了后面遍历判断字母,对同一组字母的条目的第一个条目插入一个字母条目。

List<String> nameList = Arrays.asList(mNames);
Collections.sort(nameList, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        char letter = Character.toUpperCase(Pinyin.toPinyin(o1.charAt(0)).charAt(0));
        char nextLetter = Character.toUpperCase(Pinyin.toPinyin(o2.charAt(0)).charAt(0));
        if (letter == nextLetter) {
            return 0;
        } else {
            return letter - nextLetter;
        }
    }
});
  1. 遍历姓名列表,构造条目模型。怎么让多个同英文字母的姓名分组前插入一个字母条目呢?很简单,每次遍历时判断是不是和上一个字母一致,不一致才添加一个。记录字母条目的位置,其实就是添加字母条目时,获取自己在列表数据集的位置,因为每次都添加到尾部,所以直接取列表数据集的最后一位的位置即可。
//字母
char letter = 0;
for (int i = 0; i < nameList.size(); i++) {
    if (i == 0) {
        //获取文字第一个字母
        String letterPinyin = Pinyin.toPinyin(nameList.get(i).charAt(0));
        letter = Character.toUpperCase(letterPinyin.charAt(0));
        mListItems.add(new ContactLetterModel(String.valueOf(letter)));
    } else {
        //如果下一个条目的首字母和上一个的不一样,则插入一条新的字母条目
        String letterPinyin = Pinyin.toPinyin(nameList.get(i).charAt(0));
        char nextLetter = Character.toUpperCase(letterPinyin.charAt(0));
        if (nextLetter != letter) {
            letter = nextLetter;
            mListItems.add(new ContactLetterModel(String.valueOf(letter)));
            //记录字母条目的位置,后续拉动字母选择条时跳转位置
            mLetterPositionMap.put(String.valueOf(letter).toUpperCase(), mListItems.size() - 1);
        }
    }
    //添加联系人条目
    mListItems.add(new ContactModel(nameList.get(i)));
}
mListAdapter.notifyDataSetChanged();

完整代码

public class SlideBar extends View {
    /**
     * 默认选中时,文字颜色
     */
    private final int mDefaultSelectTextColor = Color.parseColor("#FFFFFF");
    /**
     * 默认未选中时,文字颜色
     */
    private final int mDefaultUnSelectTextColor = Color.parseColor("#202020");
    /**
     * 默认选中时,滑动条背景颜色
     */
    private final int mDefaultSelectBgColor = Color.parseColor("#66202020");
    /**
     * 默认未选中时,滑动条背景颜色
     */
    private final int mDefaultUnSelectBgColor = Color.parseColor("#00000000");
    /**
     * 索引条宽度
     */
    private int mWidth;
    /**
     * 字母表
     */
    private String[] mLetter = new String[]{"#", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
    /**
     * 文字区域,保存为成员变量是为了复用
     */
    private Rect mTextRect;
    /**
     * 每个字母的高度
     */
    private int mCellHeight;
    /**
     * 是否拖动索引条中
     */
    private boolean mTouched = false;
    /**
     * 选择回调
     */
    private OnSelectItemListener mListener;
    /**
     * 画笔
     */
    private Paint mPaint;

    private int mSelectTextColor;
    private int mUnSelectTextColor;
    private int mSelectBgColor;
    private int mUnSelectBgColor;

    public SlideBar(Context context) {
        super(context);
        init(context, null);
    }

    public SlideBar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public SlideBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, @Nullable AttributeSet attrs) {
        initAttrs(context, attrs);
        initPaint();
    }

    private void initAttrs(Context context, @Nullable AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlideBar);
        //选中时文字的颜色
        mSelectTextColor = array.getColor(R.styleable.SlideBar_slb_select_txt_color, mDefaultSelectTextColor);
        //未选中时文字的颜色
        mUnSelectTextColor = array.getColor(R.styleable.SlideBar_slb_un_select_txt_color, mDefaultUnSelectTextColor);
        //选中时,滑动条背景颜色
        mSelectBgColor = array.getColor(R.styleable.SlideBar_slb_select_bg_color, mDefaultSelectBgColor);
        //未选中时,滑动条背景颜色
        mUnSelectBgColor = array.getColor(R.styleable.SlideBar_slb_un_select_bg_color, mDefaultUnSelectBgColor);
        array.recycle();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setTextSize(sp2px(getContext(), 11f));
        if (mTouched) {
            mPaint.setColor(mSelectTextColor);
        } else {
            mPaint.setColor(mUnSelectTextColor);
        }
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mTextRect = new Rect();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        //每行的高度,计算平均分每个字母占的高度
        mCellHeight = h / mLetter.length;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec), heightMeasureSpec);
    }

    /**
     * 测量本身的大小,这里只是测量宽度
     *
     * @param widthMeaSpec 传入父View的测量标准
     * @return 测量的宽度
     */
    private int measureWidth(int widthMeaSpec) {
        /*定义view的宽度*/
        int width;
        /*获取当前 View的测量模式*/
        int mode = MeasureSpec.getMode(widthMeaSpec);
        /*
         * 获取当前View的测量值,这里得到的只是初步的值,
         * 我们还需根据测量模式来确定我们期望的大小
         * */
        int size = MeasureSpec.getSize(widthMeaSpec);
        /*
         * 如果,模式为精确模式
         * 当前View的宽度,就是我们的size
         * */
        if (mode == MeasureSpec.EXACTLY) {
            width = size;
        } else {
            /*否则的话我们就需要结合padding的值来确定*/
            int desire = size + getPaddingLeft() + getPaddingRight();
            if (mode == MeasureSpec.AT_MOST) {
                width = Math.min(desire, size);
            } else {
                width = desire;
            }
        }
        return width;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //触摸时改变背景颜色
        if (mTouched) {
            canvas.drawColor(mSelectBgColor);
            mPaint.setColor(mSelectTextColor);
        } else {
            canvas.drawColor(mUnSelectBgColor);
            mPaint.setColor(mUnSelectTextColor);
        }
        for (int i = 0; i < mLetter.length; i++) {
            String text = mLetter[i];
            //测量文字宽高
            mPaint.getTextBounds(text, 0, text.length(), mTextRect);
            int textWidth = mTextRect.width();
            int textHeight = mTextRect.height();

            //文字一半的宽度
            float textHalfWidth = textWidth / 2.0f;
            //字母文字的起点X坐标,控件的宽度的一半再减去文字的一半
            float x = (mWidth / 2.0f) - textHalfWidth;
            //起点文字的Y坐标
            float y = (mCellHeight / 2.0f + textHeight / 2.0f + mCellHeight * i);
            //画文字
            canvas.drawText(mLetter[i], x, y, mPaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float y = event.getY();
        //计算当前触摸的字母的位置
        int index = (int) (y / mCellHeight);
        //触摸
        if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {
            mTouched = true;
            if (index >= 0 && index < mLetter.length) {
                if (mListener != null) {
                    mListener.onItemSelect(index, mLetter[index]);
                }
            } else {
                return super.onTouchEvent(event);
            }
        } else {
            //松手
            mTouched = false;
            if (mListener != null) {
                mListener.onItemUnSelect();
            }
        }
        //触摸改变时,不断通知重绘来绘制索引条
        invalidate();
        return true;
    }

    public interface OnSelectItemListener {
        /**
         * 选择时回调
         *
         * @param position     选中的位置
         * @param selectLetter 选中的字母
         */
        void onItemSelect(int position, String selectLetter);

        /**
         * 松手取消选中时回调
         */
        void onItemUnSelect();
    }

    public void setOnSelectItemListener(OnSelectItemListener listener) {
        mListener = listener;
    }

    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    public static int px2dp(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    private int sp2px(Context context, float spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, context.getResources().getDisplayMetrics());
    }
}
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容