Android進階 - 二維碼掃描

scan_bg.jpg

摘要

最近,在公司項目上需要加入“二維碼掃描”的功能(Android端),筆者在網上查閱了一些資料,實現了這個功能。最后給自己做個筆記,給各位做下分享。

原理說明

“二維碼掃描”實際上就是通過手機相機掃描『二維碼圖片』,將『二維碼圖片』中的字符串數據通過解碼的方式解析出來。

實現方式

借助開源庫 ZXing Android Embedded 實現二維碼掃描。

Github地址: https://github.com/journeyapps/zxing-android-embedded

接下來,筆者分兩部分進行講解:

  • 第1部分:ZXing Android Embedded簡介及使用方法。

  • 第2部分:自定義掃描界面。


一、ZXing Android Embedded簡介及使用方法

1.簡介

ZXing Android Embedded 是用于Android的條形碼掃描庫,使用ZXing進行解碼。

注:二維碼是條形碼中的一種,該庫也可以掃描二維碼。

2.引入方法

添加gradle庫依賴:

dependencies {
    ......
    compile 'com.journeyapps:zxing-android-embedded:3.5.0'
}

注意事項:

  • 該庫在需要時會自動引入ZXing庫,無需額外手動引入。
  • buildToolsVersion '23.0.2'(構建工具的版本要>=23.0.2)
  • compile 'com.android.support:appcompat-v7:23.1.0' (support-v7包版本要在23+以上)
  • 最低支持的Android版本(API level 9+)

想要了解更多詳情,可打開Github鏈接研究學習。

3.使用方法

接下來,筆者用一個實例來介紹一下該庫的使用方法。

1.新建一個Android工程。
2.添加gradle庫依賴,引入ZXing Android Embedded庫。
gradle_setting.png
3.在MainActivity的布局文件中放置一個Button(用于打開二維碼掃描界面)。
activity_main.png
4.在MainActivity中為Button設置點擊事件,點擊后跳轉至掃描界面。
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 創建IntentIntegrator對象
                IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
                // 開始掃描
                intentIntegrator.initiateScan();
            }
        });
    }
}
5.重寫onActivityResult方法接收掃描結果。
public class MainActivity extends AppCompatActivity {

    ......

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // 獲取解析結果
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if (result != null) {
            if (result.getContents() == null) {
                Toast.makeText(this, "取消掃描", Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(this, "掃描內容:" + result.getContents(), Toast.LENGTH_LONG).show();
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }
}

完成此步,基本的二維碼掃描功能就已經出來了。

接下來,我們可以準備二維碼圖片試驗一下。如果沒有二維碼圖片,可以用草料二維碼生成器在線生成一個二維碼使用(如下圖所示)。

caoliao_qrcode.png
6.跑一下Android程序,掃描一下二維碼。(如下圖所示)
qrcode_scan1.gif

我們看到掃描成功了,最后Toast出了http://www.baidu.com這個信息。

但這個掃描過程怎么感覺天旋地轉的,一點也不流暢?.../(ㄒoㄒ)/~~

這是由于ZXing Android Embedded庫提供的掃碼Activity默認是橫屏的。

不過,掃描界面的方向是可調的,Github文檔也有說明,舉個例子。

固定豎屏(僅需在manifest文件中添加如下配置)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.wangnan7.qrcodescandemo">

    <application
        
        ......

        <!-- 調整二維碼掃描界面為豎屏 -->
        <activity
            android:name="com.journeyapps.barcodescanner.CaptureActivity"
            android:screenOrientation="portrait"
            tools:replace="screenOrientation" />

    </application>

</manifest>
        

重新跑下程序,如下所示:

qrcode_scan2.gif
7.其他配置項

在上述實例中,我們用兩行代碼(如下所示)實現了啟動二維碼掃描界面。

IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
intentIntegrator.initiateScan();

基本上沒有添加什么配置。但是,該庫還提供了其他配置項(如下所示)。

other_config.png

接下來,筆者詳解一下這8個配置項。


1. setBarcodeImageEnabled(boolean enabled)

該方法用于設置“被掃描的二維碼圖片”可以保存在本地。

other_config1.png

舉個例子說明一下:

接著之前的例子,我們在布局文件中添加一個ImageView(用于顯示二維碼圖片):

other_config2.png

MainActivity修改后的代碼如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
                // 設置可以保存條形碼(二維碼)圖片
                intentIntegrator.setBarcodeImageEnabled(true);
                intentIntegrator.initiateScan();
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // 獲取解析結果
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if (result != null) {
            if (result.getBarcodeImagePath() != null) {
                // 顯示條形碼(二維碼)圖片的保存路徑
                Toast.makeText(this, result.getBarcodeImagePath(), Toast.LENGTH_LONG).show();
                // 顯示條形碼(二維碼)圖片
                showBarcodeImage(result.getBarcodeImagePath());
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    /**
     * 加載并顯示條形碼圖片
     */
    private void showBarcodeImage(String barcodeImagePath) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(new File(barcodeImagePath));
            ((ImageView)findViewById(R.id.iv)).setImageBitmap(BitmapFactory.decodeStream(fis));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

跑下程序,如下圖所示:

other_config3.gif

可以看到,筆者Toast出了二維碼圖片被保存后的路徑信息,并根據文件保存路徑將二維碼圖片顯示了出來。

所以,如果添加這個配置:

intentIntegrator.setBarcodeImageEnabled(true);

掃描后的二維碼圖片會被保存;如果不添加這個配置或參數設置為false,二維碼圖片不會被保存,我們拿到的路徑result.getBarcodeImagePath()就會變成null。


2. setCaptureActivity(Class<?> captureActivity)

該方法用于設置掃描Activity。如果你不想用該庫提供的掃描Activity,可以自定義一個掃描Activity,將該Acitivty的運行時類作為參數傳進去,這個方法后續用到時再詳細說明。


3. setBeepEnabled(boolean enabled)

該方法用于設置掃碼成功后的提示音,傳true為開啟,不設置或設置false為關閉。


4. setCameraId(int cameraId)

該方法用于設置相機ID。我們使用的手機一般都有前置和后置攝像頭,該方法傳0將會使用后置攝像頭,傳1將會使用前置攝像頭。不設置則默認使用后置攝像頭。

現在有些手機后置雙攝像頭,相機ID可能有所變化,有興趣的朋友請自行研究。


5. setDesiredBarcodeFormats(Collection<String> desiredBarcodeFormats)

該方法用于設置你期望的條形碼格式。(該庫提供了5種格式,如下所示)

other_config4.png

注:不設置默認為全部類型

所以對于掃描二維碼,你可以選擇不設置,如果設置可以使用QR_CODE_TYPES和ALL_CODE_TYPES。但是,筆者建議設置QR_CODE_TYPES,即:

intentIntegrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES);

因為不設置或設置支持全部類型,會附帶掃描其他條形碼的功能,筆者認為實際功能應與描述功能相一致。


6. setOrientationLocked(boolean locked)

該方法用于設置方向鎖。(源碼解釋如下:)

other_config5.png

這個功能是用來調整掃描界面方向的,可以配合傳感器使用,舉個例子。

修改一下之前的manifest文件,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.wangnan7.qrcodescandemo">

    <application
        
        ......

        <!-- 調整二維碼掃描界面方向為"完全依賴傳感器" -->
        <activity
            android:name="com.journeyapps.barcodescanner.CaptureActivity"
            android:screenOrientation="fullSensor"
            tools:replace="screenOrientation" />

    </application>

</manifest>

在MainActivity中添加方向鎖設置,如下所示:

other_config6.png

運行一下程序,如下所示:

other_config7.gif

可以看到調整手機方向時,掃描布局也會重新布置,最后筆者按Back返回鍵取消了掃描。


7. setPrompt(String prompt)

該方法用于設置掃描界面的提示信息。

舉個例子,筆者設置一條提示信息(如下圖所示)

other_config8.png

運行一下程序,可以看到掃描界面的“提示文字”(如下圖所示)

other_config9.png

8. setTimeout(long timeout)

該方法用于設置掃描界面的超時時間。(避免用戶打開掃描頁面,忘記關閉)

舉個例子,筆者設置一個2秒的超時時間(如下圖所示)

other_config10.png

運行一下程序,如下圖所示:

other_config11.gif

可以看到,2秒后,掃描自動取消了。

ZXing Android Embedded的基本使用方法介紹完了。想了解更多用法的朋友可以通過GitHub鏈接或查看源碼的方式學習。

二、自定義掃描界面

各位可能發現 ZXing Android Embedded庫 提供的默認的掃描界面有些簡陋(或丑陋),滿足不了產品和設計的需求,舉個例子:

產品想要下圖這種效果,該怎么辦呢?

target_effect.png

這時就需要我們自定義掃描界面了...

自定義策略:比著葫蘆畫瓢

由于源碼中的類在AndroidStudio中默認是被加鎖的,我們無權直接修改。但我們可以仿寫其中的一些類,方便我們添加自己的邏輯。自定義起點可以從Activity開始

1.自定義掃描Activity

在源碼中可以查到,我們之前一直在使用一個CaptureActivity進行二維碼掃描(如下所示):

capture_activity.png

接下來,我們可以仿照CaptureActivity寫一個自己的Activity(直接Copy也可以)。

筆者仿寫的代碼如下:

/**
 * @Class: CustomCaptureActivity
 * @Description: 自定義條形碼/二維碼掃描
 * @Author: wangnan7
 * @Date: 2017/5/19
 */

public class CustomCaptureActivity extends AppCompatActivity {

    /**
     * 條形碼掃描管理器
     */
    private CaptureManager mCaptureManager;

    /**
     * 條形碼掃描視圖
     */
    private DecoratedBarcodeView mBarcodeView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        setContentView(com.google.zxing.client.android.R.layout.zxing_capture);
        mBarcodeView = (DecoratedBarcodeView)findViewById(com.google.zxing.client.android.R.id.zxing_barcode_scanner);

        mCaptureManager = new CaptureManager(this, mBarcodeView);
        mCaptureManager.initializeFromIntent(getIntent(), savedInstanceState);
        mCaptureManager.decode();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mCaptureManager.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mCaptureManager.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mCaptureManager.onDestroy();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mCaptureManager.onSaveInstanceState(outState);
    }

    /**
     * 權限處理
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
        mCaptureManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    /**
     * 按鍵處理
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return mBarcodeView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
    }
}

注:XML布局還是使用的源碼中CaptureActivity的布局。

緊接著,我們可以在manifest文件中聲明一下這個新創建的Activity。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.wangnan7.qrcodescandemo">

    <application
        
        .......

        <!-- 設置二維碼掃描界面方向為豎屏 -->
        <activity
            android:name=".CustomCaptureActivity"
            android:label="自定義掃描界面"
            android:screenOrientation="portrait"/>

    </application>

</manifest>

最后,我們就可以在MainActivity中調用這個新的掃描Activity了。

start_custom_capture.png

運行程序,效果如下:

custom_activity_success.gif

可以看到我們自定義的掃描Activity可以正常運行,掃碼也成功了。

但是,我們自定義Activty使用的布局還是源碼中的布局文件,對于這個布局文件我們沒有權限修改,接下來就需要自定義掃描布局了。

2.自定義掃描布局

源碼布局如下:

zxing_layout.png

筆者仿寫的自定義掃描布局 (activity_zxing_layout.xml):

activity_zxing_layout.png

屬性簡介:
app:zxing_preview_scaling_strategy : 預覽視圖的縮放策略,使用centerCrop即可
app:zxing_use_texture_view : 是否使用紋理視圖(黑色背景)

接下來,我們就可以把自定義掃描Activity的布局文件給替換掉了。

/**
 * @Class: CustomCaptureActivity
 * @Description: 自定義條形碼/二維碼掃描
 * @Author: wangnan7
 * @Date: 2017/5/19
 */

public class CustomCaptureActivity extends AppCompatActivity {

    ......

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_zxing_layout);
        mBarcodeView = (DecoratedBarcodeView)findViewById(R.id.zxing_barcode_scanner);

        ......
    }
    
    ......
}

最后,我們跑程序驗證一下:

use_texture_view.gif

可以看到我們的自定義布局文件也沒有問題。

我們的自定義Activity和自定義布局文件都完成了,剩下的就是修改掃描視圖的樣式了。

3.修改掃描視圖的樣式

想要修改掃描視圖的樣式,需要略微研究下DecoratedBarcodeView的源碼。

1.DecoratedBarcodeView初始化分析

source_code1.png

補充:可以看到 scannerLayout 最后被作為掃描布局inflate進了DecorateBarcodeView中。

2.默認布局R.layout.zxing_barcode_scanner分析

source_code2.png

分析到這里,我們需要做的工作就顯現出來了。那就是:

自定義View(繼承ViewfinderView),重寫onDraw方法,然后替換掉這里的ViewfinderView。

因為R.layout.zxing_barcode_scanner是源碼中的布局文件,無法直接修改,所以還要重寫一份布局文件給DecoratedBarcodeView加載。那么,接下來需要做兩步準備工作:

(1)仿寫默認布局文件R.layout.zxing_barcode_scanner

custom_barcode_scanner.png

(2)讓DecoratedBarcodeView加載剛剛仿寫布局,不再使用默認布局。

load_custom_scanner.png

3.開始自定義掃描視圖(繼承ViewfinderView重寫onDraw方法)

小技巧:如果不知道如何開始,可以先將原ViewfinderView的onDraw方法copy進來一點一點研究修改。

筆者直接將自己的自定義掃描布局粘貼出來,需要的朋友可以借鑒或Copy:

/**
 * @Class: CustomViewfinderView
 * @Description: 自定義掃描框樣式
 * @Author: wangnan7
 * @Date: 2017/5/22
 */

public class CustomViewfinderView extends ViewfinderView {

    /**
     * 重繪時間間隔
     */
    public static final long CUSTOME_ANIMATION_DELAY = 16;

    /* ******************************************    邊角線相關屬性    ************************************************/

    /**
     * "邊角線長度/掃描邊框長度"的占比 (比例越大,線越長)
     */
    public float mLineRate = 0.1F;

    /**
     * 邊角線厚度 (建議使用dp)
     */
    public float mLineDepth =  TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());

    /**
     * 邊角線顏色
     */
    public int mLineColor = Color.WHITE;

    /* *******************************************    掃描線相關屬性    ************************************************/

    /**
     * 掃描線起始位置
     */
    public int mScanLinePosition = 0;

    /**
     * 掃描線厚度
     */
    public float mScanLineDepth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());

    /**
     * 掃描線每次重繪的移動距離
     */
    public float mScanLineDy = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics());

    /**
     * 線性梯度
     */
    public LinearGradient mLinearGradient;

    /**
     * 線性梯度位置
     */
    public float[] mPositions = new float[]{0f, 0.5f, 1f};

    /**
     * 線性梯度各個位置對應的顏色值
     */
    public int[] mScanLineColor = new int[]{0x00FFFFFF, Color.WHITE, 0x00FFFFFF};


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

    @Override
    public void onDraw(Canvas canvas) {
        refreshSizes();
        if (framingRect == null || previewFramingRect == null) {
            return;
        }

        Rect frame = framingRect;
        Rect previewFrame = previewFramingRect;

        int width = canvas.getWidth();
        int height = canvas.getHeight();

        //繪制4個角
        paint.setColor(mLineColor); // 定義畫筆的顏色
        canvas.drawRect(frame.left, frame.top, frame.left + frame.width() * mLineRate, frame.top + mLineDepth, paint);
        canvas.drawRect(frame.left, frame.top, frame.left + mLineDepth, frame.top + frame.height() * mLineRate, paint);

        canvas.drawRect(frame.right - frame.width() * mLineRate, frame.top, frame.right, frame.top + mLineDepth, paint);
        canvas.drawRect(frame.right - mLineDepth, frame.top, frame.right, frame.top + frame.height() * mLineRate, paint);

        canvas.drawRect(frame.left, frame.bottom - mLineDepth, frame.left + frame.width() * mLineRate, frame.bottom, paint);
        canvas.drawRect(frame.left, frame.bottom - frame.height() * mLineRate, frame.left + mLineDepth, frame.bottom, paint);

        canvas.drawRect(frame.right - frame.width() * mLineRate, frame.bottom - mLineDepth, frame.right, frame.bottom, paint);
        canvas.drawRect(frame.right - mLineDepth, frame.bottom - frame.height() * mLineRate, frame.right, frame.bottom, paint);

        // Draw the exterior (i.e. outside the framing rect) darkened
        paint.setColor(resultBitmap != null ? resultColor : maskColor);
        canvas.drawRect(0, 0, width, frame.top, paint);
        canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
        canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
        canvas.drawRect(0, frame.bottom + 1, width, height, paint);

        if (resultBitmap != null) {
            // Draw the opaque result bitmap over the scanning rectangle
            paint.setAlpha(CURRENT_POINT_OPACITY);
            canvas.drawBitmap(resultBitmap, null, frame, paint);
        } else {
            // 繪制掃描線
            mScanLinePosition += mScanLineDy;
            if(mScanLinePosition > frame.height()){
                mScanLinePosition = 0;
            }
            mLinearGradient = new LinearGradient(frame.left, frame.top + mScanLinePosition, frame.right, frame.top + mScanLinePosition, mScanLineColor, mPositions, Shader.TileMode.CLAMP);
            paint.setShader(mLinearGradient);
            canvas.drawRect(frame.left, frame.top + mScanLinePosition, frame.right, frame.top + mScanLinePosition + mScanLineDepth, paint);
            paint.setShader(null);

            float scaleX = frame.width() / (float) previewFrame.width();
            float scaleY = frame.height() / (float) previewFrame.height();

            List<ResultPoint> currentPossible = possibleResultPoints;
            List<ResultPoint> currentLast = lastPossibleResultPoints;
            int frameLeft = frame.left;
            int frameTop = frame.top;
            if (currentPossible.isEmpty()) {
                lastPossibleResultPoints = null;
            } else {
                possibleResultPoints = new ArrayList<>(5);
                lastPossibleResultPoints = currentPossible;
                paint.setAlpha(CURRENT_POINT_OPACITY);
                paint.setColor(resultPointColor);
                for (ResultPoint point : currentPossible) {
                    canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY),
                            POINT_SIZE, paint);
                }
            }
            if (currentLast != null) {
                paint.setAlpha(CURRENT_POINT_OPACITY / 2);
                paint.setColor(resultPointColor);
                float radius = POINT_SIZE / 2.0f;
                for (ResultPoint point : currentLast) {
                    canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY),
                            radius, paint);
                }
            }
        }

        // Request another update at the animation interval, but only repaint the laser line,
        // not the entire viewfinder mask.
        postInvalidateDelayed(CUSTOME_ANIMATION_DELAY,
                frame.left,
                frame.top,
                frame.right,
                frame.bottom);
    }
}

代碼簡介:

(1)onDraw方法中的大部分代碼Copy自ViewfinderView,筆者添加了兩部分邏輯:第一部分是邊角線的繪制;第二部分是用“掃描線”替換掉了原有的“激光線”。

(2)代碼的核心是在onDraw方法的第5行代碼:

Rect frame = framingRect;

這個矩陣記錄了掃描框四個頂點的坐標,有了這個變量,各位可以發揮想象力自定義自己需要的掃描樣式。

接下來,我們用CustomViewfinderView替換掉ViewfinderView(如下圖所示)

custom_viewfinderview.png

最后,跑下程序(如下圖所示)

custom_success.gif

4.樣式調整(UI優化)

我們的自定義掃描界面搞定了,但UI樣式還需要再優化一下:

(1) 框體大小調整 (DecoratedBarcodeView有屬性支持修改)

zxing_frame_change.png

調整后的效果圖:

zxing_frame_change2.png

(2) 將掃描界面底部文字平移至掃描框底部

zxing_frame_change3.png

調整后的效果圖:

zxing_frame_change4.png

(3) 將掃描框向上平移

掃描框在默認情況下是相對于相機視圖居中的,想要調整掃描框的位置還要去修改源碼...

筆者想了一個投機取巧的辦法:透明掉標題欄和狀態欄讓相機預覽視圖向上延伸,使掃描框在視覺上略微上移

這部分代碼和二維碼掃描沒有直接關系,筆者就不貼代碼了,各位可以嘗試自己實現,但最后筆者會附上本Demo的GitHub鏈接。

最終的效果:

final_scan.gif

Demo的Github鏈接:

https://github.com/sinawangnan7/QRCodeScanDemo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,583評論 25 707
  • 摘要 最近,公司業務上有個生成二維碼圖片的需求(Android端),之后筆者在網上查閱了一些資料,實現了這個功能。...
    夢想編織者灬小楠閱讀 45,673評論 37 132
  • [TOC] 前言 上一篇,掃碼的基本功能已經實現,不過還存在一些問題 掃碼界面不是我們常見的二維碼掃描界面 方法調...
    好多個胖子閱讀 17,387評論 4 114
  • 棕櫚 茅草 長凳椅 在惠風和煦的清涼里 你才感覺到 自己 2014.06.25
    楊戲水閱讀 350評論 0 1
  • 在血液不斷循環中,血管內皮會因為高血壓,高血脂,糖尿病,肥胖,高脂飲食等,導致損傷! 當血管內皮損傷,血液中脂類成...
    淼姐兒閱讀 723評論 0 2