Android仿IOS系统下发件人输入框

分享一个Android自定义控件,仿照IOS短信添加联系人输入框,供发送信件时选择联系人使用。
ChipInputView.java

package cn.com.jfyuan.mail.widget;

import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.text.Editable;
import android.text.InputType;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import cn.com.jfyuan.mail.R;

public class ChipInputView extends ScrollView {

    private static final String TAG = "ChipInputView";
    private static final int CHIP_HEIGHT = 25; // dp
    private static final int SPACING_LEFT = 4; // dp
    private static final int SPACING_TOP = 4; // dp
    private static final int SPACING_RIGHT = 4; // dp
    private static final int SPACING_BOTTOM = 4; // dp
    public static final int DEFAULT_VERTICAL_SPACING = 4; // dp
    private int mVerticalSpacing = DEFAULT_VERTICAL_SPACING;
    private int mChipsTextColor = Color.BLACK;
    private int mChipsTextColorClicked = Color.WHITE;
    private int mChipsTextColorErrorClicked = Color.RED;
    private float mDensity;
    private RelativeLayout mChipsContainer;
    private ChipsListener mChipsListener;
    private ChipsEditText mEditText;
    private ChipsVerticalLinearLayout mRootChipsLayout;
    private EditTextListener mEditTextListener;
    private List<Chip> mChipList = new ArrayList<>();
    private Object mCurrentEditTextSpan;
    private ChipValidator mChipsValidator;

    public ChipInputView(Context context) {
        super(context);
        init();
    }

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

    public ChipInputView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @Override
    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
        return true;
    }

    private void init() {
        mDensity = getResources().getDisplayMetrics().density;
        mChipsContainer = new RelativeLayout(getContext());
        addView(mChipsContainer);
        LinearLayout linearLayout = new LinearLayout(getContext());
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(0, 0);
        linearLayout.setLayoutParams(params);
        linearLayout.setFocusable(true);
        linearLayout.setFocusableInTouchMode(true);
        mChipsContainer.addView(linearLayout);
        mEditText = new ChipsEditText(getContext());
        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.leftMargin = (int) (SPACING_LEFT * mDensity);
        layoutParams.topMargin = (int) (SPACING_TOP * mDensity);
        layoutParams.rightMargin = (int) (SPACING_RIGHT * mDensity);
        layoutParams.bottomMargin = (int) (SPACING_BOTTOM * mDensity);
        mEditText.setLayoutParams(layoutParams);
        mEditText.setMinHeight((int) (CHIP_HEIGHT * mDensity));
        mEditText.setPadding(0, 0, 0, 0);
        mEditText.setLineSpacing(mVerticalSpacing, (CHIP_HEIGHT * mDensity) / mEditText.getLineHeight());
        mEditText.setBackgroundColor(Color.argb(0, 0, 0, 0));
        mEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_UNSPECIFIED);
        mEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
        mChipsContainer.addView(mEditText);
        mRootChipsLayout = new ChipsVerticalLinearLayout(getContext(), mVerticalSpacing);
        mRootChipsLayout.setOrientation(LinearLayout.VERTICAL);
        mRootChipsLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        mRootChipsLayout.setPadding(0, (int) ((SPACING_TOP + mVerticalSpacing) * mDensity), 0, 0);
        mChipsContainer.addView(mRootChipsLayout);
        initListener();
    }

    private void initListener() {
        mChipsContainer.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mEditText.requestFocus();
                unSelectAllChips();
            }
        });

        mEditTextListener = new EditTextListener();
        mEditText.addTextChangedListener(mEditTextListener);
        mEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    unSelectAllChips();
                }
            }
        });
    }

    public void addChip(String displayName, Contact contact) {
        addChip(displayName, contact, false, false);
        mEditText.setText("");
        addLeadingMarginSpan();
    }

    public void addChip(String displayName, Contact contact, boolean isIndelible, boolean isModifiable) {
        Chip chip = new Chip(displayName, contact, isIndelible, isModifiable);
        mChipList.add(chip);
        onChipsChanged(true);
        post(new Runnable() {
            @Override
            public void run() {
                fullScroll(View.FOCUS_DOWN);
            }
        });
    }

    public void addChips(List<Contact> contacts, boolean isIndelible, boolean isModifiable) {
        if (null != contacts) {
            for (Contact c : contacts) {
                Chip chip = new Chip(c.getDisplayName(), c, isIndelible, isModifiable);
                mChipList.add(chip);
            }
        }
        onChipsChanged(true);
        post(new Runnable() {
            @Override
            public void run() {
                fullScroll(View.FOCUS_DOWN);
            }
        });
    }

    public void clearAllChips() {
        mChipList.clear();
        onChipsChanged(false);
    }

    public boolean removeChipBy(Contact contact) {
        for (int i = 0; i < mChipList.size(); i++) {
            if (mChipList.get(i).mContact != null && mChipList.get(i).mContact.equals(contact)) {
                mChipList.remove(i);
                onChipsChanged(true);
                return true;
            }
        }
        return false;
    }

    public List<Chip> getChips() {
        return Collections.unmodifiableList(mChipList);
    }

    public boolean hasErrorChip() {
        List<Chip> chips = getChips();
        if (null != chips) {
            for (Chip chip : chips) {
                if (chip.isError) {
                    return true;
                }
            }
        }
        return false;
    }

    public void setChipsListener(ChipsListener chipsListener) {
        this.mChipsListener = chipsListener;
    }

    public void setChipsValidator(ChipValidator chipsValidator) {
        mChipsValidator = chipsValidator;
    }

    public EditText getEditText() {
        return mEditText;
    }

    private void onChipsChanged(final boolean moveCursor) {
        ChipsVerticalLinearLayout.TextLineParams textLineParams = mRootChipsLayout.onChipsChanged(mChipList);
        if (textLineParams == null) {
            post(new Runnable() {
                @Override
                public void run() {
                    onChipsChanged(moveCursor);
                }
            });
            return;
        }
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mEditText.getLayoutParams();
        params.topMargin = (int) ((SPACING_TOP + textLineParams.row * CHIP_HEIGHT) * mDensity) + textLineParams.row * mVerticalSpacing;
        mEditText.setLayoutParams(params);
        addLeadingMarginSpan(textLineParams.lineMargin);
        if (moveCursor) {
            mEditText.setSelection(mEditText.length());
        }
    }

    private void addLeadingMarginSpan(int margin) {
        Spannable spannable = mEditText.getText();
        if (mCurrentEditTextSpan != null) {
            spannable.removeSpan(mCurrentEditTextSpan);
        }
        mCurrentEditTextSpan = new android.text.style.LeadingMarginSpan.LeadingMarginSpan2.Standard(margin, 0);
        spannable.setSpan(mCurrentEditTextSpan, 0, 0, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        mEditText.setText(spannable);
    }

    private void addLeadingMarginSpan() {
        Spannable spannable = mEditText.getText();
        if (mCurrentEditTextSpan != null) {
            spannable.removeSpan(mCurrentEditTextSpan);
        }
        spannable.setSpan(mCurrentEditTextSpan, 0, 0, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        mEditText.setText(spannable);
    }

    private void onEnterPressed(String text) {
        if (text != null && text.length() > 0) {
            onEmailRecognized(text);
            mEditText.setSelection(0);
        }
    }

    private void onEmailRecognized(String email) {
        onEmailRecognized(new Contact(email, null, email));
    }

    private void onEmailRecognized(Contact contact) {
        Chip chip = new Chip(contact.getDisplayName(), contact, false, true);
        mChipList.add(chip);
        if (mChipsListener != null) {
            mChipsListener.onChipAdded(chip);
        }
        post(new Runnable() {
            @Override
            public void run() {
                onChipsChanged(true);
            }
        });
    }

    private void selectOrDeleteLastChip() {
        if (mChipList.size() > 0) {
            onChipInteraction(mChipList.size() - 1);
        }
    }

    private void onChipInteraction(int position) {
        try {
            Chip chip = mChipList.get(position);
            if (chip != null) {
                onChipInteraction(chip, true);
            }
        } catch (IndexOutOfBoundsException e) {
            Log.e(TAG, "Out of bounds", e);
        }
    }

    private void onChipInteraction(Chip chip, boolean nameClicked) {
        unSelectChipsExcept(chip);
        if (chip.isSelected()) {
            mChipList.remove(chip);
            if (mChipsListener != null) {
                mChipsListener.onChipDeleted(chip);
            }
            onChipsChanged(true);
            if (nameClicked && chip.isModifiable()) {
                mEditText.setText(chip.getContact().getEmailAddress());
                addLeadingMarginSpan();
                mEditText.requestFocus();
                mEditText.setSelection(mEditText.length());
            }
        } else {
            chip.setSelected(true);
            onChipsChanged(false);
        }
    }

    private void unSelectChipsExcept(Chip rootChip) {
        for (Chip chip : mChipList) {
            if (chip != rootChip) {
                chip.setSelected(false);
            }
        }
        onChipsChanged(false);
    }

    private void unSelectAllChips() {
        unSelectChipsExcept(null);
    }

    public InputConnection getInputConnection(InputConnection target) {
        return new KeyInterceptingInputConnection(target);
    }

    private class EditTextListener implements TextWatcher {
        private boolean mIsPasteTextChange = false;

        @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 (count > 1) {
                mIsPasteTextChange = true;
            }
        }

        @Override
        public void afterTextChanged(Editable s) {
            if (mIsPasteTextChange) {
                mIsPasteTextChange = false;
                // copy/paste
            } else {
                // no paste text change
                if (s.toString().contains("\n")) {
                    String text = s.toString();
                    text = text.replace("\n", "");
                    while (text.contains("  ")) {
                        text = text.replace("  ", " ");
                    }
                    s.clear();
                    if (text.length() > 1) {
                        onEnterPressed(text);
                    } else {
                        s.append(text);
                    }
                }
            }
        }
    }

    private class KeyInterceptingInputConnection extends InputConnectionWrapper {
        public KeyInterceptingInputConnection(InputConnection target) {
            super(target, true);
        }

        @Override
        public boolean commitText(CharSequence text, int newCursorPosition) {
            return super.commitText(text, newCursorPosition);
        }

        @Override
        public boolean sendKeyEvent(KeyEvent event) {
            if (mEditText.length() == 0) {
                if (event.getAction() == KeyEvent.ACTION_DOWN) {
                    if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
                        selectOrDeleteLastChip();
                        return true;
                    }
                }
            }
            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
                mEditText.append("\n");
                return true;
            }
            return super.sendKeyEvent(event);
        }

        @Override
        public boolean deleteSurroundingText(int beforeLength, int afterLength) {
            if (mEditText.length() == 0 && beforeLength == 1 && afterLength == 0) {
                return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
                        && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
            }
            return super.deleteSurroundingText(beforeLength, afterLength);
        }
    }

    class ChipsEditText extends EditText {
        public ChipsEditText(Context context) {
            super(context);
        }

        @Override
        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
            return getInputConnection(super.onCreateInputConnection(outAttrs));
        }
    }

    class ChipsVerticalLinearLayout extends LinearLayout {
        private List<LinearLayout> mLineLayouts = new ArrayList<>();
        private int mRowSpacing;

        public ChipsVerticalLinearLayout(Context context, int rowSpacing) {
            super(context);
            mRowSpacing = rowSpacing;
            init();
        }

        private void init() {
            setOrientation(VERTICAL);
        }

        private int getViewMeasuredWidth(View view) {
            if (null != view) {
                view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                return view.getMeasuredWidth();
            }
            return 0;
        }

        public TextLineParams onChipsChanged(List<ChipInputView.Chip> chips) {
            clearChipsViews();
            int width = getWidth();
            if (width == 0) {
                return null;
            }
            int widthSum = 0;//当前行的宽度
            int rowCounter = 0;//行数
            LinearLayout ll = createHorizontalView();
            for (ChipInputView.Chip chip : chips) {
                View chipView = chip.getView();
                int chipViewWidth = getViewMeasuredWidth(chipView);
                if (widthSum + chipViewWidth > width) {//宽度总和大于当前宽度,新起一行
                    rowCounter++;
                    widthSum = 0;
                    ll = createHorizontalView();
                }
                if (chipViewWidth > width) {//单个宽度大于当前宽度,重置文本框宽度防止超出界面
                    chip.resetLabelWidth((int) (width * 0.75));
                    chipViewWidth = getViewMeasuredWidth(chipView);
                }
                widthSum += chipViewWidth;
                ll.addView(chipView);
            }
            if (width - widthSum < width * 0.1f) {
                widthSum = 0;
                rowCounter++;
            }
            if (width == 0) {
                rowCounter = 0;
            }
            return new TextLineParams(rowCounter, widthSum);
        }

        private LinearLayout createHorizontalView() {
            LinearLayout ll = new LinearLayout(getContext());
            ll.setPadding(0, 0, 0, mRowSpacing);
            ll.setOrientation(HORIZONTAL);
            addView(ll);
            mLineLayouts.add(ll);
            return ll;
        }

        private void clearChipsViews() {
            for (LinearLayout linearLayout : mLineLayouts) {
                linearLayout.removeAllViews();
            }
            mLineLayouts.clear();
            removeAllViews();
        }

        class TextLineParams {
            public int row;
            public int lineMargin;

            public TextLineParams(int row, int lineMargin) {
                this.row = row;
                this.lineMargin = lineMargin;
            }
        }
    }

    public class Chip implements OnClickListener {
        private String mLabel;
        private final Contact mContact;
        private final boolean mIsIndelible;
        private final boolean mIsModifiable;
        private RelativeLayout mView;
        private View mIconWrapper;
        private TextView mTextView;
        private ImageView mCloseIcon;
        private ImageView mErrorIcon;
        private boolean isError = false;
        private boolean mIsSelected = false;

        public Chip(String label, Contact contact, boolean isIndelible, boolean isModifiable) {
            this.mLabel = label;
            this.mContact = contact;
            this.mIsIndelible = isIndelible;
            this.mIsModifiable = isModifiable;
            if (null == contact || (mChipsValidator != null && !mChipsValidator.isValid(mContact))) {
                isError = true;
            }
            if (mLabel == null) {
                mLabel = contact.getEmailAddress();
            }
        }

        public void resetLabelWidth(int width) {
            if (null != mTextView) {
                mTextView.setWidth(width);
            }
        }

        public View getView() {
            if (mView == null) {
                mView = (RelativeLayout) inflate(getContext(), R.layout.chips_view, null);
                mView.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, (int) (CHIP_HEIGHT * mDensity)));
                mIconWrapper = mView.findViewById(R.id.layout_icon_wrapper);
                mTextView = (TextView) mView.findViewById(R.id.tv_ch_name);
                mCloseIcon = (ImageView) mView.findViewById(R.id.iv_ch_close);
                mErrorIcon = (ImageView) mView.findViewById(R.id.iv_ch_error);
                mTextView.setTextColor(mChipsTextColor);
                mView.setOnClickListener(this);
                mIconWrapper.setOnClickListener(this);
            }
            updateViews();
            return mView;
        }

        private void updateViews() {
            mTextView.setText(mLabel);
            if (isSelected()) {
                mIconWrapper.setVisibility(VISIBLE);
                if (isError) {
                    mView.setSelected(true);
                    mTextView.setTextColor(mChipsTextColorErrorClicked);
                } else {
                    mView.setSelected(true);
                    mTextView.setTextColor(mChipsTextColorClicked);
                }
            } else {
                mIconWrapper.setVisibility(GONE);
                if (isError) {
                    mErrorIcon.setVisibility(View.VISIBLE);
                } else {
                    mErrorIcon.setVisibility(View.GONE);
                }
                mView.setSelected(false);
                mTextView.setTextColor(mChipsTextColor);
            }
        }

        @Override
        public void onClick(View v) {
            mEditText.clearFocus();
            if (v.getId() == mView.getId()) {
                onChipInteraction(this, true);
            } else {
                onChipInteraction(this, false);
            }
        }

        public boolean isSelected() {
            return mIsSelected;
        }

        public void setSelected(boolean isSelected) {
            if (mIsIndelible) {
                return;
            }
            this.mIsSelected = isSelected;
        }

        public boolean isModifiable() {
            return mIsModifiable;
        }

        public Contact getContact() {
            return mContact;
        }

        @Override
        public boolean equals(Object o) {
            if (mContact != null && o instanceof Contact) {
                return mContact.equals(o);
            }
            return super.equals(o);
        }

    }

    public interface ChipsListener {
        void onChipAdded(Chip chip);

        void onChipDeleted(Chip chip);
    }

    public static abstract class ChipValidator {
        public abstract boolean isValid(Contact contact);
    }

    public static class Contact {
        private String mEmailAddress;
        private String mDisplayName;
        private String mId;

        @Override
        public boolean equals(final Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Contact contact = (Contact) o;
            if (null == mId) {
                if (null != contact.getId()) return false;
                if (null == mEmailAddress) {
                    if (null != contact.getEmailAddress()) return false;
                } else {
                    if (!mEmailAddress.equals(contact.mEmailAddress)) return false;
                }
            } else {
                if (!mId.equals(contact.mId)) return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            if (null == mEmailAddress) {
                if (null == mId) {
                    return 31;
                } else {
                    return mId.hashCode();
                }
            } else {
                return mEmailAddress.hashCode();
            }
        }

        public Contact(String displayName, String id, String emailAddress) {
            mEmailAddress = emailAddress;
            mId = id;
            if (!TextUtils.isEmpty(displayName)) {
                mDisplayName = displayName;
            } else if (!TextUtils.isEmpty(emailAddress)) {
                mDisplayName = mEmailAddress;
            } else {
                mDisplayName = "None";
            }
        }

        public String getEmailAddress() {
            return mEmailAddress;
        }

        public String getDisplayName() {
            return mDisplayName;
        }

        public String getId() {
            return mId;
        }
    }
}

布局文件
chips_view.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="25dp"
    android:background="@drawable/chip_background"
    android:paddingRight="@dimen/padding_small">

    <RelativeLayout
        android:id="@+id/layout_icon_wrapper"
        android:layout_width="25dp"
        android:layout_height="25dp">

        <ImageView
            android:id="@+id/iv_ch_close"
            android:layout_width="18dp"
            android:layout_height="18dp"
            android:layout_centerInParent="true"
            android:background="@drawable/right_ad_close"
            android:gravity="center" />
    </RelativeLayout>

    <TextView
        android:id="@+id/tv_ch_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="@dimen/padding_small"
        android:layout_toRightOf="@id/layout_icon_wrapper"
        android:singleLine="true"
        android:textSize="@dimen/font_h5" />

    <ImageView
        android:id="@+id/iv_ch_error"
        android:layout_width="18dp"
        android:layout_height="18dp"
        android:layout_centerVertical="true"
        android:layout_marginLeft="@dimen/padding_small"
        android:layout_toRightOf="@id/tv_ch_name"
        android:src="@drawable/send_fail_nor"
        android:visibility="gone" />
</RelativeLayout>

最后附上gitHub飞机票

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

推荐阅读更多精彩内容

  • 你知道,故事的结尾并不重要,生活唯一确保我们的就是死亡。所以我们最好不要让那结尾,夺走了故事的光芒。 但是不努力,...
    留住时间阅读 234评论 0 0
  • 孩子睡了。 终于有时间可以做些自己的事情。 老公叹口气,周末真累!说完转身去看他的学习视频了。 我轻轻地给孩子翻身...
    干嘛非要起名字阅读 257评论 0 2
  • 熊孩子的爆棚能量,不管正负,我相信大家都曾经见识过。不管你是熊孩子的父母,小姑,还是路人甲。 有的时候熊孩子在公众...
    澳洲红豆豆阅读 719评论 2 2
  • 陌上,四季更迭。熙熙攘攘的人群,依旧熙熙攘攘。漂泊的人,依旧漂泊着。一此经过生命的人和事,渐次遁入流年深处。被我们...
    铂翰阅读 499评论 1 1