Flutter - 探索相機(jī)插件

如果您曾經(jīng)構(gòu)建或使用過任何大型移動(dòng)應(yīng)用程序龄章,則該應(yīng)用程序很有可能會(huì)使用相機(jī)功能想括。如果您查看PlayStore中的熱門圖表,您會(huì)發(fā)現(xiàn)許多應(yīng)用程序都使用相機(jī)執(zhí)行各種任務(wù)褪子。Flutter提供了一個(gè)相機(jī)插件辙售,可以訪問Android和iOS設(shè)備上的相機(jī)轻抱。在本文中,我們將探索Flutter相機(jī)插件旦部,并且將構(gòu)建一個(gè)小型相機(jī)應(yīng)用程序以查看該插件可以做什么和不能做什么祈搜。

在繼續(xù)前進(jìn)之前较店,讓我們看看我們將要構(gòu)建什么。這個(gè)應(yīng)用程式將可以拍照和錄制影片容燕。您可以在前置和后置攝像頭之間切換梁呈。還有一個(gè)畫廊,您可以在其中查看捕獲的圖像和錄制的視頻蘸秘,并與其他應(yīng)用程序共享它們或從設(shè)備中刪除它們官卡。


入門

該應(yīng)用程序使用以下5個(gè)依賴項(xiàng)。您需要將這些依賴項(xiàng)添加到pubspec.yaml醋虏。

  • camera:提供用于與設(shè)備上的攝像頭配合使用的工具味抖。
  • path_provider:查找正確的路徑來存儲媒體。
  • video_player:播放錄制的視頻灰粮。
  • esys_flutter_share:用于與其他應(yīng)用程序共享媒體文件。
  • thumbnails:用于從視頻生成縮略圖忍坷。
dependencies:
  camera:
  path_provider:
  thumbnails:
    git:
      url: https://github.com/divyanshub024/Flutter_Thumbnails.git
  video_player:
  esys_flutter_share:

接下來粘舟,將文件中的最低Android SDK版本更新為21(或更高)android/app/build.gradle

將以下幾行添加到您的ios/Runner/Info.plist

<key>NSCameraUsageDescription</key>
<string>Can I use the camera please?</string>
<key>NSMicrophoneUsageDescription</key>
<string>Can I use the mic please?</string>
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>

獲取可用相機(jī)列表

首先佩研,我們將使用相機(jī)插件獲取相機(jī)列表柑肴。

List<CameraDescription> _cameras;
@override
void initState() {
  _initCamera();
  super.initState();
}
Future<void> _initCamera() async {
  _cameras = await availableCameras();
}

初始化相機(jī)控制器

現(xiàn)在,我們有可用相機(jī)的列表旬薯。接下來晰骑,我們將初始化相機(jī)控制器。攝像機(jī)控制器用于控制設(shè)備攝像機(jī)绊序。CameraController接受兩個(gè)值CameraDescriptionResolutionPreset硕舆。最初,我們給出了一個(gè)攝像機(jī)說明骤公,因?yàn)?code>_camera[0]它是我們的后置攝像機(jī)抚官。

注意:這里我們ResolutionPreset以介質(zhì)為準(zhǔn)。如果凍結(jié)相機(jī)阶捆,請嘗試避免使用更高的分辨率凌节。請查看此問題以獲取更多詳細(xì)信息。

CameraController _controller;

Future<void> _initCamera() async {
  _controller = CameraController(_cameras[0], ResolutionPreset.medium);
  _controller.initialize().then((_) {
    if (!mounted) {
      return;
    }
    setState(() {});
  });
}

@override
void dispose() {
  _controller?.dispose();
  super.dispose();
}

相機(jī)預(yù)覽

設(shè)置好相機(jī)后洒试,我們將使用CameraPreview小部件顯示預(yù)覽供稿倍奢。在顯示攝像機(jī)預(yù)覽之前,我們必須等待CameraController初始化垒棋。

@override
Widget build(BuildContext context) {
  if (_controller != null) {
    if (!_controller.value.isInitialized) {
      return Container();
    }
  } else {
    return const Center(
      child: SizedBox(
        width: 32,
        height: 32,
        child: CircularProgressIndicator(),
      ),
    );
  }
}

初始化攝像機(jī)后卒煞,我們將顯示攝像機(jī)預(yù)覽。

return Scaffold(
  backgroundColor: Theme.of(context).backgroundColor,
  key: _scaffoldKey,
  extendBody: true,
  body: Stack(
    children: <Widget>[
      _buildCameraPreview(),
    ],
  ),
);

在內(nèi)部捕犬,_buildCameraPreview()我們將攝像機(jī)預(yù)覽縮放到屏幕尺寸跷坝,以使其看起來為全屏酵镜。

Widget _buildCameraPreview() {
  final size = MediaQuery.of(context).size;
  return ClipRect(
    child: Container(
      child: Transform.scale(
        scale: _controller.value.aspectRatio / size.aspectRatio,
        child: Center(
          child: AspectRatio(
            aspectRatio: _controller.value.aspectRatio,
            child: CameraPreview(_controller),
          ),
        ),
      ),
    ),
  );
}

切換相機(jī)

下一步是要能夠在前后攝像頭之間切換或切換。為此柴钻,我們首先將圖標(biāo)按鈕添加到stack widget中淮韭。

body: Stack(
  children: <Widget>[
    _buildCameraPreview(),
    Positioned(
      top: 24.0,
      left: 12.0,
      child: IconButton(
        icon: Icon(
          Icons.switch_camera,
          color: Colors.white,
        ),
        onPressed: _onCameraSwitch,
      ),
    ),
  ],
),

_onCameraSwitch按下時(shí)婉商,此圖標(biāo)按鈕調(diào)用方法渤早。在此方法中,我們將先處理休傍,CameraController然后使用new初始化CameraController和新的CameraDescription毫蚓。

Future<void> _onCameraSwitch() async {
  final CameraDescription cameraDescription =
      (_controller.description == _cameras[0]) ? _cameras[1] : _cameras[0];
  if (_controller != null) {
    await _controller.dispose();
  }
  _controller = CameraController(cameraDescription, ResolutionPreset.medium);
  _controller.addListener(() {
    if (mounted) setState(() {});
    if (_controller.value.hasError) {
      showInSnackBar('Camera error ${_controller.value.errorDescription}');
    }
  });

  try {
    await _controller.initialize();
  } on CameraException catch (e) {
    _showCameraException(e);
  }

  if (mounted) {
    setState(() {});
  }
}

相機(jī)控制視圖

在屏幕底部占键,我們將有一個(gè)控件視圖,該視圖基本上包含3個(gè)按鈕元潘。首先去畫廊畔乙,其次去捕捉圖像或錄制視頻,第三次在圖像捕捉和視頻錄制之間切換翩概。

return Scaffold(
  backgroundColor: Theme.of(context).backgroundColor,
  key: _scaffoldKey,
  extendBody: true,
  body: ...
  bottomNavigationBar: _buildBottomNavigationBar(),
);

該視圖將顯示在底部導(dǎo)航欄中牲距。不要忘記添加extendBody: true.

Widget _buildBottomNavigationBar() {
  return Container(
    color: Theme.of(context).bottomAppBarColor,
    height: 100.0,
    width: double.infinity,
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: <Widget>[
        FutureBuilder(
          future: getLastImage(),
          builder: (context, snapshot) {
            if (snapshot.data == null) {
              return Container(
                width: 40.0,
                height: 40.0,
              );
            }
            return GestureDetector(
              onTap: () => Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => Gallery(),
                ),
              ),
              child: Container(
                width: 40.0,
                height: 40.0,
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(4.0),
                  child: Image.file(
                    snapshot.data,
                    fit: BoxFit.cover,
                  ),
                ),
              ),
            );
          },
        ),
        CircleAvatar(
          backgroundColor: Colors.white,
          radius: 28.0,
          child: IconButton(
            icon: Icon(
              (_isRecordingMode)
                  ? (_isRecording) ? Icons.stop : Icons.videocam
                  : Icons.camera_alt,
              size: 28.0,
              color: (_isRecording) ? Colors.red : Colors.black,
            ),
            onPressed: () {
              if (!_isRecordingMode) {
                _captureImage();
              } else {
                if (_isRecording) {
                  stopVideoRecording();
                } else {
                  startVideoRecording();
                }
              }
            },
          ),
        ),
        IconButton(
          icon: Icon(
            (_isRecordingMode) ? Icons.camera_alt : Icons.videocam,
            color: Colors.white,
          ),
          onPressed: () {
            setState(() {
              _isRecordingMode = !_isRecordingMode;
            });
          },
        ),
      ],
    ),
  );
}

捕獲圖像

使用相機(jī)控制器捕獲圖像非常容易。

  1. 檢查相機(jī)控制器是否已初始化钥庇。
  2. 構(gòu)造目錄并定義路徑牍鞠。
  3. 使用CameraController捕獲圖像并將其保存到給定路徑。
void _captureImage() async {
  if (_controller.value.isInitialized) {
    final Directory extDir = await getApplicationDocumentsDirectory();
    final String dirPath = '${extDir.path}/media';
    await Directory(dirPath).create(recursive: true);
    final String filePath = '$dirPath/${_timestamp()}.jpeg';
    await _controller.takePicture(filePath);
    setState(() {});
  }
}

錄制視頻

我們可以將錄制視頻過程分為兩個(gè)部分:

開始錄像:

  1. 檢查相機(jī)控制器是否已初始化评姨。
  2. 啟動(dòng)計(jì)時(shí)器以顯示記錄的視頻時(shí)間难述。(可選的)
  3. 構(gòu)造目錄并定義路徑。
  4. 使用攝像機(jī)控制器開始錄制并將視頻保存在定義的路徑上吐句。
Future<String> startVideoRecording() async {
  print('startVideoRecording');
  if (!_controller.value.isInitialized) {
    return null;
  }
  setState(() {
    _isRecording = true;
  });
  _timerKey.currentState.startTimer();

  final Directory extDir = await getApplicationDocumentsDirectory();
  final String dirPath = '${extDir.path}/media';
  await Directory(dirPath).create(recursive: true);
  final String filePath = '$dirPath/${_timestamp()}.mp4';

  if (_controller.value.isRecordingVideo) {
    // A recording is already started, do nothing.
    return null;
  }

  try {
    await _controller.startVideoRecording(filePath);
  } on CameraException catch (e) {
    _showCameraException(e);
    return null;
  }
  return filePath;
}

停止錄像:

  1. 檢查相機(jī)控制器是否已初始化胁后。
  2. 停止計(jì)時(shí)器。
  3. 使用相機(jī)控制器停止視頻錄制嗦枢。
Future<void> stopVideoRecording() async {
  if (!_controller.value.isRecordingVideo) {
    return null;
  }
  _timerKey.currentState.stopTimer();
  setState(() {
    _isRecording = false;
  });

  try {
    await _controller.stopVideoRecording();
  } on CameraException catch (e) {
    _showCameraException(e);
    return null;
  }
}

這是相機(jī)屏幕的完整代碼择同。

import 'dart:io';

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_camera/gallery.dart';
import 'package:flutter_camera/video_timer.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:thumbnails/thumbnails.dart';

class CameraScreen extends StatefulWidget {
  const CameraScreen({Key key}) : super(key: key);

  @override
  CameraScreenState createState() => CameraScreenState();
}

class CameraScreenState extends State<CameraScreen>
    with AutomaticKeepAliveClientMixin {
  CameraController _controller;
  List<CameraDescription> _cameras;
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  bool _isRecordingMode = false;
  bool _isRecording = false;
  final _timerKey = GlobalKey<VideoTimerState>();

  @override
  void initState() {
    _initCamera();
    super.initState();
  }

  Future<void> _initCamera() async {
    _cameras = await availableCameras();
    _controller = CameraController(_cameras[0], ResolutionPreset.medium);
    _controller.initialize().then((_) {
      if (!mounted) {
        return;
      }
      setState(() {});
    });
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    if (_controller != null) {
      if (!_controller.value.isInitialized) {
        return Container();
      }
    } else {
      return const Center(
        child: SizedBox(
          width: 32,
          height: 32,
          child: CircularProgressIndicator(),
        ),
      );
    }

    if (!_controller.value.isInitialized) {
      return Container();
    }
    return Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,
      key: _scaffoldKey,
      extendBody: true,
      body: Stack(
        children: <Widget>[
          _buildCameraPreview(),
          Positioned(
            top: 24.0,
            left: 12.0,
            child: IconButton(
              icon: Icon(
                Icons.switch_camera,
                color: Colors.white,
              ),
              onPressed: () {
                _onCameraSwitch();
              },
            ),
          ),
          if (_isRecordingMode)
            Positioned(
              left: 0,
              right: 0,
              top: 32.0,
              child: VideoTimer(
                key: _timerKey,
              ),
            )
        ],
      ),
      bottomNavigationBar: _buildBottomNavigationBar(),
    );
  }

  Widget _buildCameraPreview() {
    final size = MediaQuery.of(context).size;
    return ClipRect(
      child: Container(
        child: Transform.scale(
          scale: _controller.value.aspectRatio / size.aspectRatio,
          child: Center(
            child: AspectRatio(
              aspectRatio: _controller.value.aspectRatio,
              child: CameraPreview(_controller),
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildBottomNavigationBar() {
    return Container(
      color: Theme.of(context).bottomAppBarColor,
      height: 100.0,
      width: double.infinity,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          FutureBuilder(
            future: getLastImage(),
            builder: (context, snapshot) {
              if (snapshot.data == null) {
                return Container(
                  width: 40.0,
                  height: 40.0,
                );
              }
              return GestureDetector(
                onTap: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => Gallery(),
                  ),
                ),
                child: Container(
                  width: 40.0,
                  height: 40.0,
                  child: ClipRRect(
                    borderRadius: BorderRadius.circular(4.0),
                    child: Image.file(
                      snapshot.data,
                      fit: BoxFit.cover,
                    ),
                  ),
                ),
              );
            },
          ),
          CircleAvatar(
            backgroundColor: Colors.white,
            radius: 28.0,
            child: IconButton(
              icon: Icon(
                (_isRecordingMode)
                    ? (_isRecording) ? Icons.stop : Icons.videocam
                    : Icons.camera_alt,
                size: 28.0,
                color: (_isRecording) ? Colors.red : Colors.black,
              ),
              onPressed: () {
                if (!_isRecordingMode) {
                  _captureImage();
                } else {
                  if (_isRecording) {
                    stopVideoRecording();
                  } else {
                    startVideoRecording();
                  }
                }
              },
            ),
          ),
          IconButton(
            icon: Icon(
              (_isRecordingMode) ? Icons.camera_alt : Icons.videocam,
              color: Colors.white,
            ),
            onPressed: () {
              setState(() {
                _isRecordingMode = !_isRecordingMode;
              });
            },
          ),
        ],
      ),
    );
  }

  Future<FileSystemEntity> getLastImage() async {
    final Directory extDir = await getApplicationDocumentsDirectory();
    final String dirPath = '${extDir.path}/media';
    final myDir = Directory(dirPath);
    List<FileSystemEntity> _images;
    _images = myDir.listSync(recursive: true, followLinks: false);
    _images.sort((a, b) {
      return b.path.compareTo(a.path);
    });
    var lastFile = _images[0];
    var extension = path.extension(lastFile.path);
    if (extension == '.jpeg') {
      return lastFile;
    } else {
      String thumb = await Thumbnails.getThumbnail(
          videoFile: lastFile.path, imageType: ThumbFormat.PNG, quality: 30);
      return File(thumb);
    }
  }

  Future<void> _onCameraSwitch() async {
    final CameraDescription cameraDescription =
        (_controller.description == _cameras[0]) ? _cameras[1] : _cameras[0];
    if (_controller != null) {
      await _controller.dispose();
    }
    _controller = CameraController(cameraDescription, ResolutionPreset.medium);
    _controller.addListener(() {
      if (mounted) setState(() {});
      if (_controller.value.hasError) {
        showInSnackBar('Camera error ${_controller.value.errorDescription}');
      }
    });

    try {
      await _controller.initialize();
    } on CameraException catch (e) {
      _showCameraException(e);
    }

    if (mounted) {
      setState(() {});
    }
  }

  void _captureImage() async {
    print('_captureImage');
    if (_controller.value.isInitialized) {
      SystemSound.play(SystemSoundType.click);
      final Directory extDir = await getApplicationDocumentsDirectory();
      final String dirPath = '${extDir.path}/media';
      await Directory(dirPath).create(recursive: true);
      final String filePath = '$dirPath/${_timestamp()}.jpeg';
      print('path: $filePath');
      await _controller.takePicture(filePath);
      setState(() {});
    }
  }

  Future<String> startVideoRecording() async {
    print('startVideoRecording');
    if (!_controller.value.isInitialized) {
      return null;
    }
    setState(() {
      _isRecording = true;
    });
    _timerKey.currentState.startTimer();

    final Directory extDir = await getApplicationDocumentsDirectory();
    final String dirPath = '${extDir.path}/media';
    await Directory(dirPath).create(recursive: true);
    final String filePath = '$dirPath/${_timestamp()}.mp4';

    if (_controller.value.isRecordingVideo) {
      // A recording is already started, do nothing.
      return null;
    }

    try {
//      videoPath = filePath;
      await _controller.startVideoRecording(filePath);
    } on CameraException catch (e) {
      _showCameraException(e);
      return null;
    }
    return filePath;
  }

  Future<void> stopVideoRecording() async {
    if (!_controller.value.isRecordingVideo) {
      return null;
    }
    _timerKey.currentState.stopTimer();
    setState(() {
      _isRecording = false;
    });

    try {
      await _controller.stopVideoRecording();
    } on CameraException catch (e) {
      _showCameraException(e);
      return null;
    }
  }

  String _timestamp() => DateTime.now().millisecondsSinceEpoch.toString();

  void _showCameraException(CameraException e) {
    logError(e.code, e.description);
    showInSnackBar('Error: ${e.code}\n${e.description}');
  }

  void showInSnackBar(String message) {
    _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(message)));
  }

  void logError(String code, String message) =>
      print('Error: $code\nError Message: $message');

  @override
  bool get wantKeepAlive => true;
}

圖庫視圖

我們的相機(jī)已經(jīng)準(zhǔn)備就緒,可以使用了净宵。但是敲才,我們?nèi)绾尾榭床东@的圖像和錄制的視頻?我們將創(chuàng)建一個(gè)畫廊視圖择葡。它將由一個(gè)水平的網(wǎng)頁瀏覽和一個(gè)底部的應(yīng)用欄以及一個(gè)共享按鈕和一個(gè)刪除按鈕組成紧武。

在內(nèi)部,PageView.builder我們正在檢查文件的擴(kuò)展名敏储。如果文件擴(kuò)展名為jpeg阻星,則將其顯示為圖像,否則,將使用VideoPreview小部件顯示視頻妥箕。

String currentFilePath;
@override
Widget build(BuildContext context) {
  return Scaffold(
    backgroundColor: Theme.of(context).backgroundColor,
    appBar: AppBar(
      backgroundColor: Colors.black,
    ),
    body: FutureBuilder(
      future: _getAllImages(),
      builder: (context, AsyncSnapshot<List<FileSystemEntity>> snapshot) {
        if (!snapshot.hasData || snapshot.data.isEmpty) {
          return Container();
        }
        print('${snapshot.data.length} ${snapshot.data}');
        if (snapshot.data.length == 0) {
          return Center(
            child: Text('No images found.'),
          );
        }

        return PageView.builder(
          itemCount: snapshot.data.length,
          itemBuilder: (context, index) {
            currentFilePath = snapshot.data[index].path;
            var extension = path.extension(snapshot.data[index].path);
            if (extension == '.jpeg') {
              return Container(
                height: 300,
                padding: const EdgeInsets.only(bottom: 8.0),
                child: Image.file(
                  File(snapshot.data[index].path),
                ),
              );
            } else {
              return VideoPreview(
                videoPath: snapshot.data[index].path,
              );
            }
          },
        );
      },
    ),
    bottomNavigationBar: BottomAppBar(
      child: Container(
        height: 56.0,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            IconButton(
              icon: Icon(Icons.share),
              onPressed: () => _shareFile(),
            ),
            IconButton(
              icon: Icon(Icons.delete),
              onPressed: _deleteFile,
            ),
          ],
        ),
      ),
    ),
  );
}

從設(shè)備獲取媒體文件

Future<List<FileSystemEntity>> _getAllImages() async {
  final Directory extDir = await getApplicationDocumentsDirectory();
  final String dirPath = '${extDir.path}/media';
  final myDir = Directory(dirPath);
  List<FileSystemEntity> _images;
  _images = myDir.listSync(recursive: true, followLinks: false);
  _images.sort((a, b) {
    return b.path.compareTo(a.path);
  });
  return _images;
}

刪除媒體文件

刪除文件非常容易滥酥。只需將目錄指向文件路徑,然后使用deleteSync函數(shù)將其刪除畦幢。

_deleteFile() {
  final dir = Directory(currentFilePath);
  dir.deleteSync(recursive: true);
  setState(() {});
}

共享媒體文件

為了共享文件坎吻,我們使用esys_flutter_share插件。您可以使用Share.file()將String title宇葱,String name瘦真,List < int >bytes,String mimeType作為強(qiáng)制參數(shù)的方法輕松共享文件黍瞧。您可以使用readAsBytesSync方法從文件中獲取字節(jié)诸尽。

_shareFile() async {
  var extension = path.extension(currentFilePath);
  await Share.file(
    'image',
    (extension == '.jpeg') ? 'image.jpeg' : '  video.mp4',
    File(currentFilePath).readAsBytesSync(),
    (extension == '.jpeg') ? 'image/jpeg' : '  video/mp4',
  );
}

我對相機(jī)插件的看法

在得出結(jié)論之前,我們應(yīng)該知道Flutter Camera插件仍在開發(fā)中印颤。該插件非常適合制作任何像樣的相機(jī)應(yīng)用程序您机,但是它有一些小問題,并且缺少許多高級功能年局,例如自動(dòng)曝光和閃光燈支持往产。如果您想了解有關(guān)相機(jī)插件即將發(fā)生的變化的最新信息,請關(guān)注“相機(jī)插件的未來”問題某宪。本期將討論相機(jī)插件中即將提供的一些很酷的功能。


您可以在此處查看該項(xiàng)目的完整源代碼锐朴。

翻譯自:https://levelup.gitconnected.com/exploring-flutter-camera-plugin-d2c54ac95f05

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兴喂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子焚志,更是在濱河造成了極大的恐慌衣迷,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酱酬,死亡現(xiàn)場離奇詭異壶谒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)膳沽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門汗菜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挑社,你說我怎么就攤上這事陨界。” “怎么了痛阻?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵菌瘪,是天一觀的道長。 經(jīng)常有香客問我阱当,道長俏扩,這世上最難降的妖魔是什么糜工? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮录淡,結(jié)果婚禮上捌木,老公的妹妹穿的比我還像新娘。我一直安慰自己赁咙,他們只是感情好钮莲,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著彼水,像睡著了一般崔拥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凤覆,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天链瓦,我揣著相機(jī)與錄音,去河邊找鬼盯桦。 笑死慈俯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拥峦。 我是一名探鬼主播贴膘,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼略号!你這毒婦竟也來了刑峡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤玄柠,失蹤者是張志新(化名)和其女友劉穎突梦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體羽利,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宫患,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了这弧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娃闲。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖匾浪,靈堂內(nèi)的尸體忽然破棺而出畜吊,到底是詐尸還是另有隱情,我是刑警寧澤户矢,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布玲献,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捌年。R本人自食惡果不足惜瓢娜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望礼预。 院中可真熱鬧眠砾,春花似錦、人聲如沸托酸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽励堡。三九已至谷丸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間应结,已是汗流浹背刨疼。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹅龄,地道東北人揩慕。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像扮休,于是被迫代替她去往敵國和親迎卤。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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