Android:自定义带有删除按钮的输入框

1 主要知识点介绍

乍一看标题,你觉得很简单,你可能心里再想 ——“呵,这么简单的东西有什么看头,谁还不会写个输入框!” 所以呢,为了让你能有看下去的欲望,我先把用到的主要知识点列一下吧。如果你看过了主要知识点之后觉得有点看头就请继续。

主要知识点:

  • 自定义属性的时候如何使用 android 已有的属性?
  • 当某个属性取值是颜色,并且既支持 #fff 也支持 @color/white 时,如何获取其值并设置给相应控件?
  • 当某个属性的取值是数值,并且既支持 px 也支持 dp 时,如何获取其值并设置给相应控件?
  • 分析 TextView 源码中 setTextSize( ) 的实现,并通过反射获取父类中私有的方法并使用
  • 回调的使用

2 需求分析

这是一个什么样的需求呢?其实很简单,就是一个输入框,能输入文字,而且具有删除按钮,输入文字之后呢,点击一下删除按钮就能将文字清空,就像下面这个样子:

Test.gif

是不是很简单?对的,很简单,就是一个线性布局或者相对布局,然后包含EditText和一个ImageView,为了方便复用呢,我们就通过自定义将它封装成一个控件。

实现的最终功能及效果包含如下:

  • 内容为空时不显示删除图标 √
  • 焦点改变时不显示删除图标 √
  • 删除图标的点击事件 √
  • 内容变化时控制删除图标的展示 √
  • EditText 的字号,颜色,hint 的字号,颜色, maxLines, inputType ,maxLength √
  • 暴露替换删除按钮图片的属性、图标和文字之间的padding设置属性 √

3 如何实现?

(1)自定义属性时如何使用原生的属性?

自定义属性时有这么三种方式:

  • 为每一个自定义控件声明一组属性。如下:
 <declare-styleable name="RoundAngleImageView">
        <attr name="Width" format="dimension"/>
        <attr name="Height" format="dimension"/>
    </declare-styleable>

    <!--CnPeng 自定义的自动搜索框-->
    <declare-styleable name="CustomAutoSearchET">
        <attr name="Width" format="dimension"/>
        <attr name="Height" format="dimension"/>
        <attr name="parentPadding_right" format="dimension"/>
        <attr name="parentPadding_bottom" format="dimension"/>
        <attr name="parentPadding" format="dimension"/>
    </declare-styleable>

上面这种方式呢,就是在每一个自定义控件中声明了一组属性,像上面的 width 和 height 其实完全可以抽取出来——也就是第二种方式:

  • 将多个控件中都声明的相同属性抽取 , 在外部定义,各个控件中直接引用,不再需要重复定义format。具体如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
      <attr name="Width" format="dimension"/>
      <attr name="Height" format="dimension"/>

      <declare-styleable name="RoundAngleImageView">
         <attr name="Width" />
         <attr name="Height" />
      </declare-styleable>

     <!--CnPeng 自定义的自动搜索框-->
     <declare-styleable name="CustomAutoSearchET">
        <attr name="Width" />
        <attr name="Height"/>
        <attr name="parentPadding_right" format="dimension"/>
        <attr name="parentPadding_bottom" format="dimension"/>
        <attr name="parentPadding" format="dimension"/>
     </declare-styleable>
</resources>
  • 直接引用 android 系统已经定义过的属性。具体如下
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="EditTextWithDel">
        <attr name="android:textSize"/>
        <attr name="android:textColor"/>
        <attr name="android:textColorHint"/>
        <attr name="android:maxLines"/>
        <attr name="android:inputType"/>
        <attr name="android:maxLength"/>
        <attr name="android:drawableRight"/>
        <attr name="android:drawablePadding"/>
        <attr name="android:hint"/>
        <attr name="android:text"/>
        <attr name="android:ellipsize"/>
        <attr name="android:cursorVisible"/>
        <attr name="android:textColorLink"/>
        <attr name="android:autoLink"/>
        <attr name="android:gravity"/>
        <attr name="android:enabled"/>
        <attr name="showRightDrawable" format="boolean"/>
    </declare-styleable>
</resources>

瞧见没,就是上面这个样子,如果你要定义的某个属性android已经定义过了,那么就直接拿来使用吧。格式: <attr name="android:text"/> 由于是引用的 android 的,所以在引用时必须加上 android:

(2) 当某个属性取值是颜色,并且既支持 #fff 也支持 @color/white 时,如何获取其值并设置给相应控件?

其实对于这种情况呢,以前我也不会,后来翻了下 TextView 的源码,发现源码里是用 ColorStateList 来接收值的。代码如下:

case R.styleable.EditTextWithDel_android_textColor:
       
       ColorStateList textColor = a.getColorStateList(attr);  // a 就是 TypeArray 对象
       editText.setTextColor(textColor);
break;

那么 ColorStateList 是个啥呢?先摘录一段 ColorStateList 类中的注释

ColorStateLists are created from XML resource files defined in the "color" subdirectory directory of an application's resource directory. 
The XML file contains a single "selector" element with a number of "item" elements inside. For example:

  <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_focused="true"
            android:color="@color/sample_focused" />
    <item android:state_pressed="true"
            android:state_enabled="false"
            android:color="@color/sample_disabled_pressed" />
    <item android:state_enabled="false"
            android:color="@color/sample_disabled_not_pressed" />
    <item android:color="@color/sample_default" />
  </selector>

啥意思呢?我的理解就是,android 底层中某个控件颜色的定义其实是成对的 -- 以 selector 的形式展现的,ColorStateList 就是用来接受selector 中的颜色值的。也就是说 ColorStateList 中不论何时都会有两个值,但同一时刻只能展示其中的一种值。

所以呢,在我们获取某个属性的颜色值时,不需要去关心是 16进制的 #fff 还是 10进制的 @color/white ,我们直接通过 ColorStateList 去获取即可。(其实,我以前也是在纠结,如果我传入的值是 #FFF 我在获取属性值的时候该怎么处理呢)

(3) 当某个属性的取值是数值,并且既支持 px 也支持 dp 时,如何获取其值并设置给相应控件?

TypeArray 中有 这么个方法:getDimensionPixelSize( attr , def )。有了这个方法之后,我们也不需要关心传入的是 dp , px ,还是 sp ,总之最终都会通过该方法转成 px。attr 是需要取值的属性名称

让我们瞅瞅TextView 中的源码是怎么写的:

 final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
        if (textSize != 0) {
            setRawTextSize(textSize);
        }

上面的代码中,通过 getDimensionPixelSize( ) 获取到相应的 PX 值之后,通过私有的 setRawSize( ) 设置字体的大小。

(4)如何通过反射获取父类中私有的方法并使用?

反射是 java 中的知识点,在 android 开发时使用频率也是蛮高的,比如我们这个自定义 带有删除按钮的输入框 中就有使用。

在 (3)中,我们知道获取 textSize 的时候可以直接使用 ta.getDimensionPiexlSize( ) ,如果我要将这个值设置给 text 就必须通过 setRawTextSize( ) ——因为 如果直接调用 setTextSize( ) 得到的大小是不对的!!!我们看 setText( ) 源码:


    @android.view.RemotableViewMethod
    public void setTextSize(float size) {
        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
    }


    public void setTextSize(int unit, float size) {
        Context c = getContext();
        Resources r;

        if (c == null)
            r = Resources.getSystem();
        else
            r = c.getResources();

        setRawTextSize(TypedValue.applyDimension(
                unit, size, r.getDisplayMetrics()));
    }

    private void setRawTextSize(float size) {
        if (size != mTextPaint.getTextSize()) {
            mTextPaint.setTextSize(size);

            if (mLayout != null) {
                nullLayouts();
                requestLayout();
                invalidate();
            }
        }
    }

瞅见没,setTextSize( float) 中会调用 setTextSize( TypedValue.COMPLEX_UNIT_SP ,size) , 这个方法会给你传入的值加上一个单位—— SP !!。在两个参数的 setTextSize( ) 中又去调了 setRawTextSize( ) ,但是要注意,这里 setRawTextSize( ) 的参数是 TypedValue.applyDimension( unit, size, r.getDisplayMetrics())。我们瞅瞅 applyDimension( ) 中干了啥。

 public static float applyDimension(int unit, float value,
                                       DisplayMetrics metrics)
    {
        switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f/25.4f);
        }
        return 0;
    }

呵,这个方法也是将传入的值转成 px 。but , 由于在setTextSize( ) 方法中,我们传入的 float 值被强加了 一个单位 —— sp , 所以在上面的方法中 还会把我们传入的 值 再乘上一个屏幕密度,然后。。。我们将 ta.getDimensionPiexlSize( ) 获取到的 px 再通过 setTextSize( ) 设置给文字时,得到的文字大小就出错了。。。

所以呢,当我们通过 ta.getDimensionPiexlSize( ) 获取字体大小时就需要调用 setRawTextSize( )去设置,但是, 我们在上面也看到了,setRawTextSize( ) 是 private 私有的!!这样,我们就需要祭出 反射 神器了。代码如下:

case R.styleable.EditTextWithDel_android_textSize:
        int textSize=a.getDimensionPixelSize(attr,16);

        //170621 使用反射的方式获取EditText 的父类 TextView 中的私有方法-- setRawTextSize(float)
        Class clazz=editText.getClass().getSuperclass();  //获取TextView的class
        try{
        Method method=clazz.getDeclaredMethod("setRawTextSize",float.class); //暴力反射获取全部方法,含私有和公有
        method.setAccessible(true);     //访问权限设置为true
        method.invoke(editText,textSize); //调用反射方法,第一个参数表示是哪个对象调用反射获取的方法,第二个参数表示方法的参数
        }catch(Exception e){
        e.printStackTrace();
        }
break;                

在上面的代码中:

  • Class clazz = editText.getClass().getSupperclass()是获取 editText 的父类 TextView 的字节码文件
  • Method method=clazz.getDeclaredMethod("setRawTextSize",float.class);中第一个参数表示需要获取的方法名,第二个参数表示 该方法的参数字节码。需要注意,第二个参数实际是一个可变数组!!所以,这句代码的意思就是,通过暴力反射获取TextView 中 的 setRawTextSize( ) 方法,该方法的参数 是 float 类型。(暴力反射既能获取到公有方法也能获取私有方法
  • method.setAccessible(true); 取得对反射到的方法操作的权限
  • method.invoke(editText,textSize); 中,第一个参数表示是谁在调用被反射到的方法,第二个参数表示要传递给方法的参数。这句代码就是说,调用被反射到的方法并传入参数。

170703 补充:其实,在上面设置字体大小的时候,可以不使用反射,直接使用两个参数的 setTextSize(int , float) , 第一个参数表示传入 的 float 值的单位 , 第二个代表你想要的字体大小。具体如下:

  editText.setTextSize(COMPLEX_UNIT_PX, textSize);    //170703 不使用反射方式时用这种方法

(5) 回调的使用

在自定义这个带有删除按钮的输入框时,我们用一个 单独的 ImageView 来展示。当EditText 中文本发生变化时我们动态的控制 这个删除按钮的展示和隐藏,那么这样我们在控件内部就需要去调用 onTextChangeListener 监听。

但是,如果我们还需要在使用这个自定义控件的界面中,继续去监听文本变化时该怎么办呢?我不能再去调用 onTextChangeListener
了,因为这样会把控件内部 监听覆盖,然后 删除按钮的事件就不好使了。。。那我到底该咋办呢??还能怎么办?写个回调呗。。。看代码:

 editText.addTextChangedListener(new TextWatcher() { //文字变化
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (s.length() > 0) {
                    iv_del.setVisibility(View.VISIBLE);
                } else {
                    iv_del.setVisibility(View.GONE);
                }

                if (onTextChangedListener != null) {
                    onTextChangedListener.onTextChanged(s, start, before, count);
                }
            }

            @Override
            public void afterTextChanged(Editable s) {
                if (editText.isFocused() && s.length() > 0) {
                    iv_del.setVisibility(View.VISIBLE);
                } else {
                    iv_del.setVisibility(View.GONE);
                }
            }
        });


    /**
     * 自定义文本变化监听,
     */
    public interface OnTextChangedListener {
        void onTextChanged(CharSequence s, int start, int before, int count);
    }

    /**
     * 对外暴露设置自定义的文本变化监听,
     */
    public void setOnTextChangedListener(OnTextChangedListener onTextChangedListener) {
        this.onTextChangedListener = onTextChangedListener;
    }

    OnTextChangedListener onTextChangedListener;

有了这个回调之后,当我们在使用这个自定义控件的界面中还需要监听文本变化时就去实现这个回调,这样,就不会担心监听器冲突的问题了。

4 完整代码

完整控件代码:

/**
 * 作者:CnPeng
 * <p>
 * 时间:2017/6/20:下午4:49
 * <p>
 * 说明:
 * 内容为空时不显示删除图标 √
 * 焦点改变时不显示删除图标 √
 * 删除图标的点击事件      √
 * 内容变化时控制删除图标的展示 √
 * EditText 的字号,颜色,hint 的字号,颜色, maxLines, inputType ,maxLength √
 * 暴露替换删除按钮图片的属性、图标和文字之间的padding设置属性 √
 */
public class EditTextWithDel extends LinearLayout {

    private Context      mContext;
    private LinearLayout ll_root;
    private EditText     editText;
    private ImageView    iv_del;

    public EditTextWithDel(Context paramContext) {
        this(paramContext, null);
    }

    public EditTextWithDel(Context paramContext, AttributeSet paramAttributeSet) {
        this(paramContext, paramAttributeSet, 0);
    }

    public EditTextWithDel(Context paramContext, AttributeSet paramAttributeSet, int defStyleAttr) {
        super(paramContext, paramAttributeSet, defStyleAttr);
        mContext = paramContext;
        initView();

        initAttr(paramAttributeSet, defStyleAttr);
    }

    private void initAttr(AttributeSet paramAttributeSet, int defStyleAttr) {
        TypedArray a = mContext.obtainStyledAttributes(paramAttributeSet, R.styleable.EditTextWithDel, defStyleAttr, 0);

        int n = a.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = a.getIndex(i);

            switch (attr) {
                case R.styleable.EditTextWithDel_android_autoLink:
                    int mAutoLinkMask = a.getInt(attr, 0);
                    editText.setAutoLinkMask(mAutoLinkMask);
                    break;

                case R.styleable.EditTextWithDel_android_drawableRight:
                    Drawable drawableRight = a.getDrawable(attr);
                    iv_del.setImageDrawable(drawableRight);
                    break;

                case R.styleable.EditTextWithDel_android_drawablePadding:
                    int drawablePadding = a.getDimensionPixelSize(attr, 0);
                    iv_del.setPadding(drawablePadding, 0, 0, 0);
                    break;

                case R.styleable.EditTextWithDel_android_maxLines:
                    editText.setMaxLines(a.getInt(attr, -1));
                    break;

                case R.styleable.EditTextWithDel_android_hint:
                    CharSequence hint = a.getText(attr);
                    editText.setHint(hint);
                    break;

                case R.styleable.EditTextWithDel_android_text:
                    CharSequence text = a.getText(attr);
                    editText.setText(text);
                    break;

                case R.styleable.EditTextWithDel_android_enabled:
                    editText.setEnabled(a.getBoolean(attr, isEnabled()));
                    break;
                case R.styleable.EditTextWithDel_android_ellipsize:
                    int ellipsize = a.getInt(attr, -1);
                    if (editText.getMaxLines() == 1 && editText.getKeyListener() == null && ellipsize < 0) {
                        ellipsize = 3; // END
                    }

                    switch (ellipsize) {
                        case 1:
                            editText.setEllipsize(TextUtils.TruncateAt.START);
                            break;
                        case 2:
                            editText.setEllipsize(TextUtils.TruncateAt.MIDDLE);
                            break;
                        case 3:
                            editText.setEllipsize(TextUtils.TruncateAt.END);
                            break;
                        case 4:
                            editText.setEllipsize(TextUtils.TruncateAt.MARQUEE);
                            break;
                        default:
                            break;
                    }
                    break;

                case R.styleable.EditTextWithDel_android_cursorVisible:
                    if (!a.getBoolean(attr, true)) {
                        editText.setCursorVisible(false);
                    }
                    break;

                case R.styleable.EditTextWithDel_android_maxLength:
                    int maxlength = a.getInt(attr, -1);
                    if (maxlength >= 0) {
                        editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxlength)});
                    } else {
                        editText.setFilters(new InputFilter[0]);
                    }
                    break;


                case R.styleable.EditTextWithDel_android_textColor:
                    ColorStateList textColor = a.getColorStateList(attr);
                    editText.setTextColor(textColor);
                    break;

                case R.styleable.EditTextWithDel_android_textColorHint:
                    ColorStateList textColorHint = a.getColorStateList(attr);
                    editText.setHintTextColor(textColorHint);
                    break;

                case R.styleable.EditTextWithDel_android_textColorLink:
                    ColorStateList textColorLink = a.getColorStateList(attr);
                    editText.setLinkTextColor(textColorLink);
                    break;

                case R.styleable.EditTextWithDel_android_textSize:
                    int textSize = a.getDimensionPixelSize(attr, 16);

                    //170621 使用反射的方式获取EditText 的父类 TextView 中的私有方法-- setRawTextSize(float)
                    Class clazz = editText.getClass().getSuperclass();  //获取TextView的class
                    try {
                        Method method = clazz.getDeclaredMethod("setRawTextSize", float.class); //暴力反射获取全部方法,含私有和公有
                        method.setAccessible(true);     //访问权限设置为true
                        method.invoke(editText, textSize); //调用反射方法,第一个参数表示是哪个对象调用反射获取的方法,第二个参数表示方法的参数
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;

                case R.styleable.EditTextWithDel_android_inputType:
                    int inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
                    editText.setInputType(inputType);
                    break;
                case R.styleable.EditTextWithDel_android_gravity:
                    editText.setGravity(a.getInt(attr, -1));
                    break;
                case R.styleable.EditTextWithDel_showRightDrawable:
                    boolean show = a.getBoolean(attr, true);
                    iv_del.setVisibility(show ? View.VISIBLE : View.GONE);
                    break;
            }
        }
        a.recycle();
    }

    private void initView() {
        LayoutInflater.from(mContext).inflate(R.layout.custom_edittext_withdel, this);
        ll_root = (LinearLayout) findViewById(R.id.ll_root_ETwithDel);
        editText = (EditText) findViewById(R.id.et_input_ETwithDel);
        iv_del = (ImageView) findViewById(R.id.iv_del_ETwithDel);
        initEvent();
    }

    /**
     * 初始化事件,点击,删除,文字变化
     */
    private void initEvent() {
        iv_del.setOnClickListener(new View.OnClickListener() {   //清空内容
            @Override
            public void onClick(View v) {
                editText.setText(null);
            }
        });

        editText.addTextChangedListener(new TextWatcher() { //文字变化
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (s.length() > 0) {
                    iv_del.setVisibility(View.VISIBLE);
                } else {
                    iv_del.setVisibility(View.GONE);
                }

                if (onTextChangedListener != null) {
                    onTextChangedListener.onTextChanged(s, start, before, count);
                }
            }

            @Override
            public void afterTextChanged(Editable s) {
                if (editText.isFocused() && s.length() > 0) {
                    iv_del.setVisibility(View.VISIBLE);
                } else {
                    iv_del.setVisibility(View.GONE);
                }
            }
        });

        editText.setOnFocusChangeListener(new OnFocusChangeListener() {    //焦点变化时也去控制 删除按钮的显示和隐藏
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    if (editText.getText().length() > 0) {
                        iv_del.setVisibility(View.VISIBLE);
                    }
                } else {
                    iv_del.setVisibility(View.GONE);
                }
            }
        });
    }


    /**
     * 返回EditText中的文本数据
     */
    public Editable getText() {
        return editText.getText();
    }

    /**
     * 设置文本到EditText中
     */
    public void setText(CharSequence charSequence) {
        editText.setText(charSequence);
    }

    /**
     * 设置EditText的选中
     */
    public void setSelection(int index) {
        editText.setSelection(index);
    }

    /**
     * 为EditText设置过滤器
     */
    public void setFilters(InputFilter[] filters) {
        editText.setFilters(filters);
    }

    /**
     * 自定义文本变化监听,
     */
    public interface OnTextChangedListener {
        void onTextChanged(CharSequence s, int start, int before, int count);
    }

    /**
     * 对外暴露设置自定义的文本变化监听,
     */
    public void setOnTextChangedListener(OnTextChangedListener onTextChangedListener) {
        this.onTextChangedListener = onTextChangedListener;
    }

    OnTextChangedListener onTextChangedListener;
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
    android:id="@+id/ll_root_ETwithDel"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="horizontal">

    <EditText
        android:id="@+id/et_input_ETwithDel"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@null"
        android:hint="请输入内容"
        android:maxLines="1"
        android:textColor="@color/c_3e3e3e"
        android:textColorHint="@color/acacac"
        android:textSize="14sp"/>
    <ImageView
        android:id="@+id/iv_del_ETwithDel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="@dimen/dp5"
        android:src="@drawable/del"
        android:visibility="gone"/>

</LinearLayout>

属性文件:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="EditTextWithDel">
        <attr name="android:textSize"/>
        <attr name="android:textColor"/>
        <attr name="android:textColorHint"/>
        <attr name="android:maxLines"/>
        <attr name="android:inputType"/>
        <attr name="android:maxLength"/>
        <attr name="android:drawableRight"/>
        <attr name="android:drawablePadding"/>
        <attr name="android:hint"/>
        <attr name="android:text"/>
        <attr name="android:ellipsize"/>
        <attr name="android:cursorVisible"/>
        <attr name="android:textColorLink"/>
        <attr name="android:autoLink"/>
        <attr name="android:gravity"/>
        <attr name="android:enabled"/>
        <attr name="showRightDrawable" format="boolean"/>
    </declare-styleable>
</resources>

5 附录:

源码地址:https://github.com/CnPeng/CrazyAndroid.git
该文中的内容对应 项目中 c_00_CommonCustomView 中的EditTextWithDel.java

注意:文章中的代码是我直接从我们项目中摘录的完善之后的,会和 gitHub 上的有略微差异

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

推荐阅读更多精彩内容