Android 自定義鍵盤實(shí)現(xiàn)

最近項(xiàng)目中在做一個股票交易需求升級, 產(chǎn)品對于輸入方式有一些特殊的要求, 具體就是對于輸入鍵盤加了諸多限制. 這就必須需要自定義鍵盤來完成需求.

效果如下:


股票交易鍵盤.png

具體需求:

  • 當(dāng)焦點(diǎn)在股票價(jià)格編輯框上時, 鍵盤彈出時不能遮蓋住賣出數(shù)量.
    即鍵盤彈出是以兩個輸入框底部為基線的.
  • 鍵盤彈擊要有一個向上推出的動畫效果.
  • 兩個輸入框彈出不同的鍵盤界面,
    股票價(jià)格輸入框 彈出數(shù)字鍵盤
    股票數(shù)量輸入框 彈出數(shù)量鍵盤(如上圖)

最終的實(shí)現(xiàn)效果:


最終效果.gif

簡書上找了些自定義鍵盤的例子, 基本都不能滿足我的需求, 但是給了我一個很好的切入點(diǎn). 在此非常感謝!
參考其實(shí)現(xiàn), 我做了些封裝. 做了一個自定義鍵盤的工具類,

設(shè)計(jì)原則:與外界充分解耦,通過自定議鍵盤管理者, 綁定對應(yīng)輸入框和鍵盤,鍵盤的實(shí)現(xiàn)者僅需要關(guān)注特殊按鍵的響應(yīng)處理.

設(shè)計(jì)原理:通過傳入activity獲得其DecorView淑趾,添加鍵盤布局。將鍵盤布局set到屏幕底部桥胞,當(dāng)輸入框獲得焦點(diǎn)時,如果設(shè)置了基線view, 則判斷基線view所在位置, 否則默認(rèn)以輸入框?yàn)榛€View考婴,若鍵盤彈出會遮擋基線View,則屏幕整體向上滑動一定的距離:
屏幕移動高度為:
移動距離 = 基線View到屏幕頂部距離 + 自定義鍵盤高度 - 整個屏幕高度
if 移動距離 > 0 則說明當(dāng)鍵盤加入到根布局后, 屏幕無法完成加載, 需要屏幕向上滾動一定的偏移量.
if 移動距離 <= 0 則說明鍵盤彈出后還沒有達(dá)到基線設(shè)置位置, 不需要滾動整個屏幕.

計(jì)算屏幕需要移動的偏移量:

    /**
     * 計(jì)算屏幕向上移動距離
     * @param view 響應(yīng)輸入焦點(diǎn)的控件
     * @return 移動偏移量
     */
    private int getMoveHeight(View view) {
        Rect rect = new Rect();
        mRootView.getWindowVisibleDisplayFrame(rect); //獲取當(dāng)前顯示區(qū)域的寬高

        int[] vLocation = new int[2];
        view.getLocationOnScreen(vLocation); //計(jì)算輸入框在屏幕中的位置
        int keyboardTop = vLocation[1] + view.getHeight() + view.getPaddingBottom() + view.getPaddingTop();
        if (keyboardTop - mKeyboardHeight < 0) { //如果輸入框到屏幕頂部已經(jīng)不能放下鍵盤的高度, 則不需要移動了.
            return 0;
        }
        if (null != mShowUnderView) { //如果有基線View. 則計(jì)算基線View到屏幕的距離
            int[] underVLocation = new int[2];
            mShowUnderView.getLocationOnScreen(underVLocation);
            keyboardTop = underVLocation[1] + mShowUnderView.getHeight() + mShowUnderView.getPaddingBottom() + mShowUnderView.getPaddingTop();
        }
        //輸入框或基線View的到屏幕的距離 + 鍵盤高度 如果 超出了屏幕的承載范圍, 就需要移動.
        int moveHeight = keyboardTop + mKeyboardHeight - rect.bottom;
        return moveHeight > 0 ? moveHeight : 0;
    }

顯示自定義的鍵盤:

    public void showSoftKeyboard(EditText view) {
        BaseKeyboard keyboard = getKeyboard(view); //獲取輸入框所綁定的鍵盤BaseKeyboard
        if (null == keyboard) {
            Log.e(TAG, "The EditText not bind BaseKeyboard!");
            return;
        }
        keyboard.setCurEditText(view);
        keyboard.setNextFocusView(etFocusScavenger); //為鍵盤設(shè)置下一個焦點(diǎn)響應(yīng)控件.
        refreshKeyboard(keyboard); //設(shè)置鍵盤keyboard到KeyboardView中.

        //將鍵盤布局加入到根布局中.
        mRootView.addView(mKeyboardViewContainer, mKeyboardViewLayoutParams);
        //設(shè)置加載動畫.
        mKeyboardViewContainer.setAnimation(AnimationUtils.loadAnimation(mActivity, R.anim.down_to_up));

        int moveHeight = getMoveHeight(view);
        if (moveHeight > 0) {
            mRootView.getChildAt(0).scrollBy(0, moveHeight); //移動屏幕
        } else {
            moveHeight = 0;
        }

        view.setTag(R.id.keyboard_view_move_height, moveHeight);
    }

隱藏自定義的鍵盤

    public void hideSoftKeyboard(EditText view) {
        int moveHeight = 0;
        Object tag = view.getTag(R.id.keyboard_view_move_height);
        if (null != tag) moveHeight = (int) tag;
        if (moveHeight > 0) { //復(fù)原屏幕
            mRootView.getChildAt(0).scrollBy(0, -1 * moveHeight);
            view.setTag(R.id.keyboard_view_move_height, 0);
        }

        mRootView.removeView(mKeyboardViewContainer); //將鍵盤從根布局中移除.

        mKeyboardViewContainer.setAnimation(AnimationUtils.loadAnimation(mActivity, R.anim.up_to_hide));
    }

為了適應(yīng)不同的鍵盤布局, 有必要定義一個Keyboard的基類, 所有的自定義鍵盤都繼承于它. 并且它響應(yīng)KeyboardView.OnKeyboardActionListener的所有接口.

public abstract class CustomBaseKeyboard extends Keyboard implements KeyboardView.OnKeyboardActionListener{

    protected EditText etCurrent;
    protected View nextFocusView;
    protected CustomKeyStyle customKeyStyle;

    public CustomBaseKeyboard(Context context, int xmlLayoutResId) {
        super(context, xmlLayoutResId);
    }

    public CustomBaseKeyboard(Context context, int xmlLayoutResId, int modeId, int width, int height) {
        super(context, xmlLayoutResId, modeId, width, height);
    }

    public CustomBaseKeyboard(Context context, int xmlLayoutResId, int modeId) {
        super(context, xmlLayoutResId, modeId);
    }

    public CustomBaseKeyboard(Context context, int layoutTemplateResId, CharSequence characters, int columns, int horizontalPadding) {
        super(context, layoutTemplateResId, characters, columns, horizontalPadding);
    }

    protected int getKeyCode(int resId) {
        if (null != etCurrent) {
            return etCurrent.getContext().getResources().getInteger(resId);
        } else {
            return Integer.MIN_VALUE;
        }
    }

    public void setCurEditText(EditText etCurrent) {
        this.etCurrent = etCurrent;
    }

    public EditText getCurEditText() {
        return etCurrent;
    }

    public void setNextFocusView(View view) {
        this.nextFocusView = view;
    }

    public CustomKeyStyle getCustomKeyStyle() {
        return customKeyStyle;
    }

    public void setCustomKeyStyle(CustomKeyStyle customKeyStyle) {
        this.customKeyStyle = customKeyStyle;
    }

    @Override
    public void onPress(int primaryCode) {

    }

    @Override
    public void onRelease(int primaryCode) {

    }

    @Override
    public void onKey(int primaryCode, int[] keyCodes) {
        if (null != etCurrent && etCurrent.hasFocus() && !handleSpecialKey(etCurrent, primaryCode)) {
            Editable editable = etCurrent.getText();
            int start = etCurrent.getSelectionStart();

            if (primaryCode == Keyboard.KEYCODE_DELETE) { //回退
                if (!TextUtils.isEmpty(editable)) {
                    if (start > 0) {
                        editable.delete(start - 1, start);
                    }
                }
            } else if (primaryCode == getKeyCode(R.integer.keycode_empty_text)) { //清空
                editable.clear();
            } else if (primaryCode == getKeyCode(R.integer.keycode_hide_keyboard)) { //隱藏
                hideKeyboard();
            } else if (primaryCode == 46) { //小數(shù)點(diǎn)
                if (!editable.toString().contains(".")) {
                    editable.insert(start, Character.toString((char) primaryCode));
                }
            } else { //其他默認(rèn)
                editable.insert(start, Character.toString((char) primaryCode));
            }
        }
        //getKeyboardView().postInvalidate();
    }

    public void hideKeyboard() {
        //hideSoftKeyboard(etCurrent);
        if (null != nextFocusView) nextFocusView.requestFocus();
    }

    /**
     * 處理自定義鍵盤的特殊定制鍵
     * 注: 所有的操作要針對etCurrent來操作
     *
     * @param etCurrent   當(dāng)前操作的EditText
     * @param primaryCode 選擇的Key
     * @return true: 已經(jīng)處理過, false: 沒有被處理
     */
    public abstract boolean handleSpecialKey(EditText etCurrent, int primaryCode);
...... //其它的默認(rèn)空實(shí)現(xiàn)

}

當(dāng)自定義鍵盤時, 僅需要去實(shí)現(xiàn)handleSpecialKey接口, 處理鍵盤中自定義鍵
在BaseKeyboard中已經(jīng)默認(rèn)實(shí)現(xiàn)了基礎(chǔ)的輸入字符, 和 回退, 清空, 隱藏.
當(dāng)然在構(gòu)造時也必須傳入Keyboard所必需的參數(shù) context 和 鍵盤布局xml
如下:

        customKeyboardManager = new CustomKeyboardManager(mActivity);

        CustomKeyboardManager.BaseKeyboard priceKeyboard = new CustomKeyboardManager.BaseKeyboard(getContext(), R.xml.stock_price_num_keyboard) {
            @Override
            public boolean handleSpecialKey(EditText etCurrent, int primaryCode) {
                if (primaryCode == getKeyCode( R.integer.keycode_cur_price)) {
                    etCurrent.setText("9.99");
                    return true;
                }
                return false;
            }
        };
        //為etInputPrice1和etInputPrice2都定制priceKeyboard鍵盤.
        customKeyboardManager.attachTo(etInputPrice1, priceKeyboard);
        customKeyboardManager.attachTo(etInputPrice2, priceKeyboard);
        
        customKeyboardManager.attachTo(etInputNum, new CustomKeyboardManager.BaseKeyboard(getContext(), R.xml.stock_trade_num_keyboard) {
            @Override
            public boolean handleSpecialKey(EditText etCurrent, int primaryCode) {
                Editable editable = etCurrent.getText();
                int start = etCurrent.getSelectionEnd();
                if (primaryCode == getKeyCode( R.integer.keycode_stocknum_000)) {
                    editable.insert(start, "000");
                    return true;
                } else if (primaryCode == getKeyCode(R.integer.keycode_stocknum_all)){ //全倉
                    setStockNumAll(etCurrent);
                    return true;
                }
                return false;
            }
        });
        customKeyboardManager.setShowUnderView(underView); //設(shè)置鍵盤彈出所達(dá)到的基線View

另外在attachTo(editText, baseKeyboard)時, 會設(shè)置editText隱藏系統(tǒng)鍵盤. 設(shè)置其綁定的keyboard, 設(shè)置FocusChangeListener事件監(jiān)聽.
下面是鍵盤布局:

<?xml version="1.0" encoding="UTF-8"?><!-- 數(shù)字鍵盤 -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
    android:horizontalGap="2dp"
    android:keyHeight="62dp"
    android:keyWidth="20%p"
    android:verticalGap="2dp">
    <Row>
        <Key
            android:codes="@integer/keycode_stocknum_all"
            android:keyEdgeFlags="left"
            android:keyLabel="全倉"/>

        <Key
            android:codes="49"
            android:keyLabel="1" />

        <Key
            android:codes="50"
            android:keyLabel="2" />

        <Key
            android:codes="51"
            android:keyLabel="3" />

        <Key
            android:codes="-5"
            android:keyLabel="回退"
            android:iconPreview="@drawable/bg_custom_key_light_gray"/>
    </Row>

    <Row>
        <Key
            android:codes="@integer/keycode_stocknum_half"
            android:keyEdgeFlags="left"
            android:keyLabel="半倉"/>

        <Key
            android:codes="52"
            android:keyLabel="4" />

        <Key
            android:codes="53"
            android:keyLabel="5" />

        <Key
            android:codes="54"
            android:keyLabel="6" />

        <Key
            android:codes="@integer/keycode_empty_text"
            android:keyLabel="清空"
            android:iconPreview="@drawable/bg_custom_key_light_gray"/>
    </Row>

    <Row>
        <Key
            android:codes="@integer/keycode_stocknum_1_3"
            android:keyEdgeFlags="left"
            android:keyLabel="1/3倉"/>

        <Key
            android:codes="55"
            android:keyLabel="7" />

        <Key
            android:codes="56"
            android:keyLabel="8" />

        <Key
            android:codes="57"
            android:keyLabel="9" />

        <Key
            android:codes="@integer/keycode_hide_keyboard"
            android:keyLabel="隱藏"
            android:iconPreview="@drawable/bg_custom_key_light_gray"/>
    </Row>

    <Row>
        <Key
            android:codes="@integer/keycode_stocknum_1_4"
            android:keyEdgeFlags="left"
            android:keyLabel="1/4倉"
            android:keyWidth="20%p"/>

        <Key
            android:codes="@integer/keycode_stocknum_000"
            android:isRepeatable="true"
            android:keyLabel="000"
            android:keyWidth="20%p"/>

        <Key
            android:codes="48"
            android:keyLabel="0"
            android:keyWidth="20%p"/>

        <Key
            android:codes="@integer/keycode_stock_sell"
            android:keyLabel="賣出"
            android:iconPreview="@drawable/bg_custom_key_blue"
            android:keyWidth="40%p"/>
    </Row>
</Keyboard>

對于我們特殊定制的key的code為了唯一性的原則, 這里將其統(tǒng)一定義在res/values/custom_keyboard.xml中

    <!--股票數(shù)量鍵盤-->
    <integer name="keycode_stocknum_000">-10200</integer>
    <integer name="keycode_stocknum_all">-10201</integer>
    <integer name="keycode_stocknum_half">-10202</integer>
    <integer name="keycode_stocknum_1_3">-10203</integer>
    <integer name="keycode_stocknum_1_4">-10204</integer>
    <integer name="keycode_stock_sell">-10205</integer>

可是至此, 仍有一個問題沒法解決, 那就是對于每個Key的樣式的定制. 看遍源碼中, 也沒有找到關(guān)于這些設(shè)置, 有的只是針對KeyboardView的設(shè)置. 但是這些設(shè)置會統(tǒng)一應(yīng)用到所有按鍵上, 還是無法實(shí)現(xiàn)對每個按鍵的獨(dú)立定制樣式.

//源碼中對xml布局中key的解析如下: 
        public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser) {
            this(parent);
            ...........
            width = getDimensionOrFraction(a, 
                    com.android.internal.R.styleable.Keyboard_keyWidth,
                    keyboard.mDisplayWidth, parent.defaultWidth);
            height = getDimensionOrFraction(a, 
                    com.android.internal.R.styleable.Keyboard_keyHeight,
                    keyboard.mDisplayHeight, parent.defaultHeight);
            gap = getDimensionOrFraction(a, 
                    com.android.internal.R.styleable.Keyboard_horizontalGap,
                    keyboard.mDisplayWidth, parent.defaultHorizontalGap);
            ........

源碼參考:
http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/inputmethodservice/Keyboard.java#331

難道以上都白做了么?
...
...
...

經(jīng)過一番細(xì)讀源碼, 決定對KeyboardView進(jìn)行擴(kuò)展.

  • 首先Keyboard描述了鍵盤的布局(通過給定的xml),并解析它,
    CustomBaseKeyboard及其實(shí)現(xiàn),擴(kuò)展了其對按鍵的處理與EditText的聯(lián)系.
  • KeyboardView 是承載不同的keyboard并繪制keyboard, 就像是鍵盤布局的繪制板, 并與系統(tǒng)交互.

擴(kuò)展思路:
通過擴(kuò)展的KeyboardView, 對其繪制過程做定制操作, 就可以實(shí)現(xiàn)對每個按鍵樣式的定制了

而KeyboardView的繪制過程并沒有給我們?nèi)魏螜C(jī)會去對其擴(kuò)展定制.
源碼參考
http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/inputmethodservice/KeyboardView.java#634
為此只能通過對KeyboardView的重新繪制才能實(shí)現(xiàn).
具體就是重寫onDraw方法, 在onDraw方法中通過接口調(diào)用實(shí)現(xiàn)定制.
并用反射的方法解決需要依賴的KeyboardView中的屬性.
代碼片段如下:

public class CustomKeyboardView extends KeyboardView {
    private static final String TAG = "CustomKeyboardView";
    private Drawable rKeyBackground;
    private int rLabelTextSize;
    private int rKeyTextSize;
    private int rKeyTextColor;
    private float rShadowRadius;
    private int rShadowColor;

    private Rect rClipRegion;
    private Keyboard.Key rInvalidatedKey;
    ...........
    private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        rKeyBackground = (Drawable) ReflectionUtils.getFieldValue(this, "mKeyBackground");
        rLabelTextSize = (int) ReflectionUtils.getFieldValue(this, "mLabelTextSize");
        rKeyTextSize = (int) ReflectionUtils.getFieldValue(this, "mKeyTextSize");
        rKeyTextColor = (int) ReflectionUtils.getFieldValue(this, "mKeyTextColor");
        rShadowColor = (int) ReflectionUtils.getFieldValue(this, "mShadowColor");
        rShadowRadius = (float) ReflectionUtils.getFieldValue(this, "mShadowRadius");
    }

    @Override
    public void onDraw(Canvas canvas) {
        //說明CustomKeyboardView只針對CustomBaseKeyboard鍵盤進(jìn)行重繪,
        // 且CustomBaseKeyboard必需有設(shè)置CustomKeyStyle的回調(diào)接口實(shí)現(xiàn), 才進(jìn)行重繪, 這才有意義
        if(null == getKeyboard() || !(getKeyboard() instanceof CustomBaseKeyboard) || null == ((CustomBaseKeyboard)getKeyboard()).getCustomKeyStyle()){
            Log.e(TAG, "");
            super.onDraw(canvas);
            return;
        }
        rClipRegion = (Rect) ReflectionUtils.getFieldValue(this, "mClipRegion");
        rInvalidatedKey = (Keyboard.Key) ReflectionUtils.getFieldValue(this, "mInvalidatedKey");
        super.onDraw(canvas);
        onRefreshKey(canvas);
    }

    /**
     * onRefreshKey是對父類的private void onBufferDraw()進(jìn)行的重寫. 只是在對key的繪制過程中進(jìn)行了重新設(shè)置.
     * @param canvas
     */
    private void onRefreshKey(Canvas canvas) {
        ........

        //拿到當(dāng)前鍵盤被彈起的輸入源 和 鍵盤為每個key的定制實(shí)現(xiàn)customKeyStyle
        EditText etCur = ((CustomBaseKeyboard)getKeyboard()).getCurEditText();
        CustomBaseKeyboard.CustomKeyStyle customKeyStyle = ((CustomBaseKeyboard)getKeyboard()).getCustomKeyStyle();

        List<Keyboard.Key> keys = getKeyboard().getKeys();
        final int keyCount = keys.size();
        //canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
        for (int i = 0; i < keyCount; i++) {
            final Keyboard.Key key = keys.get(i);

            //獲取為Key自定義的背景, 若沒有定制, 使用KeyboardView的默認(rèn)屬性keyBackground設(shè)置
            keyBackground = customKeyStyle.getKeyBackground(key, etCur);
            if(null == keyBackground){ keyBackground = rKeyBackground; }
            ......
            //獲取為Key自定義的Label, 若沒有定制, 使用xml布局中指定的
            CharSequence keyLabel = customKeyStyle.getKeyLabel(key, etCur);
             .....
            canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
            keyBackground.draw(canvas);

            if (label != null) {
                //獲取為Key的Label的字體大小, 若沒有定制, 使用KeyboardView的默認(rèn)屬性keyTextSize設(shè)置
                Float customKeyTextSize = customKeyStyle.getKeyTextSize(key, etCur);
                // For characters, use large font. For labels like "Done", use small font.
                if(null != customKeyTextSize){
                    paint.setTextSize(customKeyTextSize);
                    paint.setTypeface(Typeface.DEFAULT_BOLD);
                } else {
                   ....
                }

                //獲取為Key的Label的字體顏色, 若沒有定制, 使用KeyboardView的默認(rèn)屬性keyTextColor設(shè)置
                Integer customKeyTextColor = customKeyStyle.getKeyTextColor(key, etCur);
                if(null != customKeyTextColor) {
                    paint.setColor(customKeyTextColor);
                } else {
                    paint.setColor(rKeyTextColor);
                }
   

具體的定制樣式接口在CustomBaseKeyboard中定義:

  public interface CustomKeyStyle {
        Drawable getKeyBackground(Key key, EditText etCur);

        Float getKeyTextSize(Key key, EditText etCur);

        Integer getKeyTextColor(Key key, EditText etCur);

        CharSequence getKeyLabel(Key key, EditText etCur);
    }

為了保證我們自定義的鍵盤都能夠在使用了CustomKeyboardView時, 都能進(jìn)行重繪, 在CustomKeyboardManager的attachTo中還要主動為其設(shè)置一個默認(rèn)的實(shí)現(xiàn).

    public void attachTo(EditText editText, CustomBaseKeyboard keyboard) {
        hideSystemSoftKeyboard(editText);
        editText.setTag(R.id.edittext_bind_keyboard, keyboard);
        if(null == keyboard.getCustomKeyStyle()) keyboard.setCustomKeyStyle(defaultCustomKeyStyle);
        editText.setOnFocusChangeListener(this);
    }

在使用的時候就需要加入對keyboard的樣式設(shè)置

        numKeyboard.setCustomKeyStyle(new CustomBaseKeyboard.SimpleCustomKeyStyle(){
            @Override
            public Drawable getKeyBackground(Keyboard.Key key, EditText etCur) {
                if(getKeyCode(etCur.getContext(), R.integer.keycode_stock_sell) == key.codes[0]) {
                    if (R.id.et_input_num_sell == etCur.getId()) {
                        return getDrawable(etCur.getContext(), R.drawable.bg_custom_key_blue);
                    } else if (R.id.et_input_num_buy == etCur.getId()) {
                        return getDrawable(etCur.getContext(), R.drawable.bg_custom_key_red);
                    }
                }
                return super.getKeyBackground(key, etCur);
            }

            @Override
            public CharSequence getKeyLabel(Keyboard.Key key, EditText etCur) {
                if(getKeyCode(etCur.getContext(), R.integer.keycode_stock_sell) == key.codes[0]) {
                    if (R.id.et_input_num_sell == etCur.getId()) {
                        return "賣出";
                    } else if (R.id.et_input_num_buy == etCur.getId()) {
                        return "買入";
                    }
                }
                return super.getKeyLabel(key, etCur);
            }
        });

文中代碼多有省略, 時間倉促且本人能力有限, 僅是對當(dāng)前項(xiàng)目中的實(shí)現(xiàn)做的?定制, 不一定能適用所有的項(xiàng)目, 只是提供了一種參考實(shí)現(xiàn), 相信一定有更好的解決方案, 還請留下你的思路方案, 共同進(jìn)步, 如有缺陷還請留言, 共同解決成長! _

參考:
http://www.reibang.com/p/8fb70cadca27
http://www.reibang.com/p/aedf6f456560
http://931360439-qq-com.iteye.com/blog/938886
具體請參考我的Github
https://github.com/kangqiao182/CustomKeyboard

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末催烘,一起剝皮案震驚了整個濱河市沥阱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伊群,老刑警劉巖考杉,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異舰始,居然都是意外死亡崇棠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門丸卷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枕稀,“玉大人,你說我怎么就攤上這事∥溃” “怎么了凹联?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哆档。 經(jīng)常有香客問我蔽挠,道長,這世上最難降的妖魔是什么瓜浸? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任澳淑,我火速辦了婚禮,結(jié)果婚禮上插佛,老公的妹妹穿的比我還像新娘杠巡。我一直安慰自己,他們只是感情好朗涩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布忽孽。 她就那樣靜靜地躺著,像睡著了一般谢床。 火紅的嫁衣襯著肌膚如雪兄一。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天识腿,我揣著相機(jī)與錄音出革,去河邊找鬼。 笑死渡讼,一個胖子當(dāng)著我的面吹牛骂束,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播成箫,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼展箱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蹬昌?” 一聲冷哼從身側(cè)響起混驰,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎皂贩,沒想到半個月后栖榨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡明刷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年婴栽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辈末。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡愚争,死狀恐怖映皆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情准脂,我是刑警寧澤劫扒,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站狸膏,受9級特大地震影響沟饥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜湾戳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一贤旷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砾脑,春花似錦幼驶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至畅铭,卻和暖如春氏淑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背硕噩。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工假残, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炉擅。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓辉懒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谍失。 傳聞我的和親對象是個殘疾皇子眶俩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

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