Няма описание

photo_preview_page.dart 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. import 'dart:async';
  2. import 'dart:typed_data';
  3. import 'package:flutter/material.dart';
  4. import 'package:photo/src/entity/options.dart';
  5. import 'package:photo/src/provider/asset_provider.dart';
  6. import 'package:photo/src/provider/config_provider.dart';
  7. import 'package:photo/src/provider/selected_provider.dart';
  8. import 'package:photo/src/ui/page/photo_main_page.dart';
  9. import 'package:photo_manager/photo_manager.dart';
  10. class PhotoPreviewPage extends StatefulWidget {
  11. final SelectedProvider selectedProvider;
  12. final List<AssetEntity> list;
  13. final int initIndex;
  14. /// 这个参数是控制在内部点击check后是否实时修改provider状态
  15. final bool changeProviderOnCheckChange;
  16. /// 是否通过预览进来的
  17. final bool isPreview;
  18. /// 这里封装了结果
  19. final PhotoPreviewResult result;
  20. final AssetProvider assetProvider;
  21. const PhotoPreviewPage({
  22. Key key,
  23. @required this.selectedProvider,
  24. @required this.list,
  25. @required this.changeProviderOnCheckChange,
  26. @required this.result,
  27. @required this.assetProvider,
  28. this.initIndex = 0,
  29. this.isPreview = false,
  30. }) : super(key: key);
  31. @override
  32. _PhotoPreviewPageState createState() => _PhotoPreviewPageState();
  33. }
  34. class _PhotoPreviewPageState extends State<PhotoPreviewPage> {
  35. PhotoPickerProvider get config => PhotoPickerProvider.of(context);
  36. AssetProvider get assetProvider => widget.assetProvider;
  37. Options get options => config.options;
  38. Color get themeColor => options.themeColor;
  39. Color get textColor => options.textColor;
  40. SelectedProvider get selectedProvider => widget.selectedProvider;
  41. List<AssetEntity> get list {
  42. if (!widget.isPreview) {
  43. return assetProvider.data;
  44. }
  45. return widget.list;
  46. }
  47. StreamController<int> pageChangeController = StreamController.broadcast();
  48. Stream<int> get pageStream => pageChangeController.stream;
  49. bool get changeProviderOnCheckChange => widget.changeProviderOnCheckChange;
  50. PhotoPreviewResult get result => widget.result;
  51. /// 缩略图用的数据
  52. ///
  53. /// 用于与provider数据联动
  54. List<AssetEntity> get previewList {
  55. return selectedProvider.selectedList;
  56. }
  57. /// 选中的数据
  58. List<AssetEntity> _selectedList = [];
  59. List<AssetEntity> get selectedList {
  60. if (changeProviderOnCheckChange) {
  61. return previewList;
  62. }
  63. return _selectedList;
  64. }
  65. PageController pageController;
  66. @override
  67. void initState() {
  68. super.initState();
  69. pageChangeController.add(0);
  70. pageController = PageController(
  71. initialPage: widget.initIndex,
  72. );
  73. _selectedList.clear();
  74. _selectedList.addAll(selectedProvider.selectedList);
  75. result.previewSelectedList = _selectedList;
  76. }
  77. @override
  78. void dispose() {
  79. pageChangeController.close();
  80. super.dispose();
  81. }
  82. @override
  83. Widget build(BuildContext context) {
  84. int totalCount = assetProvider.current.assetCount ?? 0;
  85. if (!widget.isPreview) {
  86. totalCount = assetProvider.current.assetCount;
  87. } else {
  88. totalCount = list.length;
  89. }
  90. var data = Theme.of(context);
  91. var textStyle = TextStyle(
  92. color: options.textColor,
  93. fontSize: 14.0,
  94. );
  95. return Theme(
  96. data: data.copyWith(
  97. primaryColor: options.themeColor,
  98. ),
  99. child: Scaffold(
  100. appBar: AppBar(
  101. backgroundColor: config.options.themeColor,
  102. leading: BackButton(
  103. color: options.textColor,
  104. ),
  105. title: StreamBuilder(
  106. stream: pageStream,
  107. initialData: widget.initIndex,
  108. builder: (ctx, snap) {
  109. return Text(
  110. "${snap.data + 1}/$totalCount",
  111. style: TextStyle(
  112. color: options.textColor,
  113. ),
  114. );
  115. },
  116. ),
  117. actions: <Widget>[
  118. StreamBuilder(
  119. stream: pageStream,
  120. builder: (ctx, s) => FlatButton(
  121. splashColor: Colors.transparent,
  122. onPressed: selectedList.length == 0 ? null : sure,
  123. child: Text(
  124. config.provider.getSureText(options, selectedList.length),
  125. style: selectedList.length == 0
  126. ? textStyle.copyWith(color: options.disableColor)
  127. : textStyle,
  128. ),
  129. ),
  130. ),
  131. ],
  132. ),
  133. body: PageView.builder(
  134. controller: pageController,
  135. itemBuilder: _buildItem,
  136. itemCount: totalCount,
  137. onPageChanged: _onPageChanged,
  138. ),
  139. bottomSheet: _buildThumb(),
  140. bottomNavigationBar: _buildBottom(),
  141. ),
  142. );
  143. }
  144. Widget _buildBottom() {
  145. return Container(
  146. color: themeColor,
  147. child: SafeArea(
  148. child: Container(
  149. height: 52.0,
  150. child: Row(
  151. children: <Widget>[
  152. Expanded(
  153. child: Container(),
  154. ),
  155. _buildCheckbox(),
  156. ],
  157. ),
  158. ),
  159. ),
  160. );
  161. }
  162. Container _buildCheckbox() {
  163. return Container(
  164. constraints: BoxConstraints(
  165. maxWidth: 150.0,
  166. ),
  167. child: StreamBuilder<int>(
  168. builder: (ctx, snapshot) {
  169. var index = snapshot.data;
  170. var data = list[index];
  171. var checked = selectedList.contains(data);
  172. return Stack(
  173. children: <Widget>[
  174. IgnorePointer(
  175. child: _buildCheckboxContent(checked, index),
  176. ),
  177. Positioned(
  178. top: 0.0,
  179. bottom: 0.0,
  180. left: 0.0,
  181. right: 0.0,
  182. child: GestureDetector(
  183. onTap: () => _changeSelected(!checked, index),
  184. behavior: HitTestBehavior.translucent,
  185. child: Container(),
  186. ),
  187. ),
  188. ],
  189. );
  190. },
  191. initialData: widget.initIndex,
  192. stream: pageStream,
  193. ),
  194. );
  195. }
  196. Widget _buildCheckboxContent(bool checked, int index) {
  197. return options.checkBoxBuilderDelegate.buildCheckBox(
  198. context,
  199. checked,
  200. index,
  201. options,
  202. config.provider,
  203. );
  204. }
  205. void _changeSelected(bool isChecked, int index) {
  206. if (changeProviderOnCheckChange) {
  207. _onChangeProvider(isChecked, index);
  208. } else {
  209. _onCheckInOnlyPreview(isChecked, index);
  210. }
  211. }
  212. /// 仅仅修改预览时的状态,在退出时,再更新provider的顺序,这里无论添加与否不修改顺序
  213. void _onCheckInOnlyPreview(bool check, int index) {
  214. var item = list[index];
  215. if (check) {
  216. selectedList.add(item);
  217. } else {
  218. selectedList.remove(item);
  219. }
  220. pageChangeController.add(index);
  221. }
  222. /// 直接修改预览状态,会直接移除item
  223. void _onChangeProvider(bool check, int index) {
  224. var item = list[index];
  225. if (check) {
  226. selectedProvider.addSelectEntity(item);
  227. } else {
  228. selectedProvider.removeSelectEntity(item);
  229. }
  230. pageChangeController.add(index);
  231. }
  232. Widget _buildItem(BuildContext context, int index) {
  233. if (!widget.isPreview && index >= list.length - 5) {
  234. _loadMore();
  235. }
  236. var data = list[index];
  237. return BigPhotoImage(
  238. assetEntity: data,
  239. loadingWidget: _buildLoadingWidget(data),
  240. );
  241. }
  242. Future<void> _loadMore() async {
  243. assetProvider.loadMore();
  244. }
  245. Widget _buildLoadingWidget(AssetEntity entity) {
  246. return options.loadingDelegate
  247. .buildBigImageLoading(context, entity, themeColor);
  248. }
  249. void _onPageChanged(int value) {
  250. pageChangeController.add(value);
  251. }
  252. Widget _buildThumb() {
  253. return StreamBuilder(
  254. builder: (ctx, snapshot) => Container(
  255. height: 80.0,
  256. child: ListView.builder(
  257. itemBuilder: _buildThumbItem,
  258. itemCount: previewList.length,
  259. scrollDirection: Axis.horizontal,
  260. ),
  261. ),
  262. stream: pageStream,
  263. );
  264. }
  265. Widget _buildThumbItem(BuildContext context, int index) {
  266. var item = previewList[index];
  267. return RepaintBoundary(
  268. child: GestureDetector(
  269. onTap: () => changeSelected(item, index),
  270. child: Container(
  271. width: 80.0,
  272. child: Stack(
  273. children: <Widget>[
  274. ImageItem(
  275. themeColor: themeColor,
  276. entity: item,
  277. size: options.thumbSize,
  278. loadingDelegate: options.loadingDelegate,
  279. ),
  280. IgnorePointer(
  281. child: StreamBuilder(
  282. stream: pageStream,
  283. builder: (BuildContext context, AsyncSnapshot snapshot) {
  284. if (selectedList.contains(item)) {
  285. return Container();
  286. }
  287. return Container(
  288. color: Colors.white.withOpacity(0.5),
  289. );
  290. },
  291. ),
  292. ),
  293. ],
  294. ),
  295. ),
  296. ),
  297. );
  298. }
  299. void changeSelected(AssetEntity entity, int index) {
  300. var itemIndex = list.indexOf(entity);
  301. if (itemIndex != -1) pageController.jumpToPage(itemIndex);
  302. }
  303. void sure() {
  304. Navigator.pop(context, selectedList);
  305. }
  306. }
  307. class BigPhotoImage extends StatefulWidget {
  308. final AssetEntity assetEntity;
  309. final Widget loadingWidget;
  310. const BigPhotoImage({
  311. Key key,
  312. this.assetEntity,
  313. this.loadingWidget,
  314. }) : super(key: key);
  315. @override
  316. _BigPhotoImageState createState() => _BigPhotoImageState();
  317. }
  318. class _BigPhotoImageState extends State<BigPhotoImage>
  319. with AutomaticKeepAliveClientMixin {
  320. Widget get loadingWidget {
  321. return widget.loadingWidget ?? Container();
  322. }
  323. @override
  324. Widget build(BuildContext context) {
  325. super.build(context);
  326. var width = MediaQuery.of(context).size.width;
  327. var height = MediaQuery.of(context).size.height;
  328. return FutureBuilder(
  329. future:
  330. widget.assetEntity.thumbDataWithSize(width.floor(), height.floor()),
  331. builder: (BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
  332. var file = snapshot.data;
  333. if (snapshot.connectionState == ConnectionState.done && file != null) {
  334. print(file.length);
  335. return Image.memory(
  336. file,
  337. fit: BoxFit.contain,
  338. width: double.infinity,
  339. height: double.infinity,
  340. );
  341. }
  342. return loadingWidget;
  343. },
  344. );
  345. }
  346. @override
  347. bool get wantKeepAlive => true;
  348. }
  349. class PhotoPreviewResult {
  350. List<AssetEntity> previewSelectedList = [];
  351. }