From 84f41bd60b2c557504a5c5bee564610cd540539b Mon Sep 17 00:00:00 2001 From: wushaocheng <15876365887@163.com> Date: Tue, 15 Nov 2022 20:45:46 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=89=E6=8B=A9=E5=9B=BE=E7=89=87=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E6=9B=BF=E6=8D=A2=E5=92=8CBase=E5=B1=82=E5=B0=81?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android_crop_lib/build.gradle | 6 +- app/build.gradle | 20 +- app/proguard-rules.pro | 3 +- app/src/main/AndroidManifest.xml | 1 - .../erban/avroom/activity/AVRoomActivity.java | 2 +- .../avroom/activity/RecordForPKActivity.java | 2 +- .../erban/avroom/dialog/MicQueueDialog.java | 2 +- .../erban/avroom/dialog/PKMicQueueDialog.java | 2 +- .../erban/avroom/dialog/PKResultDialog.java | 2 +- .../avroom/dialog/PKSelectPeopleDialog.java | 2 +- .../erban/avroom/dialog/RoomImposeDialog.java | 2 +- .../avroom/dialog/RoomOperationDialog.java | 2 +- .../erban/avroom/widget/MessageView.java | 2 +- .../erban/base/BaseDialogFragment.java | 233 +++ .../com/yizhuan/erban/base/BaseViewTag.java | 118 ++ .../base/fragmentation/ISupportActivity.java | 13 + .../base/fragmentation/ISupportFragment.java | 36 + .../SupportActivityDelegate.java | 99 ++ .../SupportFragmentDelegate.java | 172 ++ .../base/fragmentation/SupportHelper.java | 209 +++ .../fragmentation/TransactionDelegate.java | 305 ++++ .../fragmentation/internal/ResultRecord.java | 48 + .../internal/TransactionRecord.java | 9 + .../base/fragmentation/queue/Action.java | 35 + .../base/fragmentation/queue/ActionQueue.java | 81 + .../windowcallback/IWindowCallbackProxy.kt | 14 + .../windowcallback/WindowCallbackProxyUtil.kt | 25 + .../erban/base/util/ViewBindingUtil.kt | 97 ++ .../WithdrawBillsIndicatorAdapter.java | 2 +- .../erban/common/delegate/SpDelegate.kt | 50 + .../yizhuan/erban/common/file/FileHelper.java | 917 ++++++++++ .../erban/common/glide/GlideEngine.java | 73 + .../yizhuan/erban/common/glide/GlideUtils.kt | 1518 +++++++++++++++++ .../erban/common/photo/PhotoProvider.kt | 224 +++ .../transform/AssignScaleTransformation.kt | 96 ++ .../transform/ComplexTransformation.java | 152 ++ .../erban/common/util/ActivityHelper.java | 48 + .../com/yizhuan/erban/common/util/Config.java | 195 +++ .../yizhuan/erban/common/util/SPUtils.java | 107 ++ .../erban/common/util/StringUtils.java | 106 ++ .../marqueeview => common/util}/Utils.java | 6 +- .../erban/common/widget/CircleImageSpan.java | 2 +- .../widget/CustomAutoWidthImageSpan.java | 2 +- .../erban/common/widget/CustomImageSpan.java | 2 +- .../widget/dialog/CommonPopupDialog.java | 2 +- .../view/activity/FamilyHomeActivity.java | 2 +- .../view/adapter/FamilyMemberAdapter.java | 2 +- .../home/activity/NewUserListActivity.java | 2 +- .../adapter/ContactsIndicatorAdapter.java | 2 +- .../erban/ui/anim/FlowFaceDrawable.java | 2 +- .../erban/ui/anim/OverlayFaceDrawable.java | 2 +- .../erban/ui/widget/UserInfoDialog.java | 1 - .../widget/dynamicface/DynamicFaceDialog.java | 2 +- .../ui/widget/marqueeview/MarqueeView.java | 1 + app/src/main/res/drawable/photo_dialog_bg.xml | 7 + app/src/main/res/layout/photo_dialog.xml | 44 + app/src/main/res/values/ids.xml | 2 + app/src/main/res/values/strings.xml | 2 +- .../income/IncomeDetailActivity.java | 2 +- .../tutu/music/widget/VoiceSeekDialog.java | 2 +- core/build.gradle | 6 +- easyphotos/.gitignore | 1 + easyphotos/build.gradle | 38 + easyphotos/src/main/AndroidManifest.xml | 38 + .../huantansheng/easyphotos/EasyPhotos.java | 345 ++++ .../easyphotos/builder/AlbumBuilder.java | 618 +++++++ .../easyphotos/callback/PuzzleCallback.java | 22 + .../easyphotos/callback/SelectCallback.java | 26 + .../easyphotos/constant/Code.java | 21 + .../huantansheng/easyphotos/constant/Key.java | 23 + .../easyphotos/constant/Type.java | 15 + .../easyphotos/engine/ImageEngine.java | 63 + .../easyphotos/models/ad/AdEntity.java | 18 + .../easyphotos/models/ad/AdListener.java | 11 + .../easyphotos/models/ad/AdViewHolder.java | 20 + .../easyphotos/models/album/AlbumModel.java | 361 ++++ .../easyphotos/models/album/entity/Album.java | 49 + .../models/album/entity/AlbumItem.java | 34 + .../easyphotos/models/album/entity/Photo.java | 114 ++ .../easyphotos/models/puzzle/Area.java | 65 + .../models/puzzle/DegreeSeekBar.java | 354 ++++ .../easyphotos/models/puzzle/Line.java | 52 + .../easyphotos/models/puzzle/MatrixUtils.java | 167 ++ .../models/puzzle/PuzzleLayout.java | 88 + .../easyphotos/models/puzzle/PuzzlePiece.java | 433 +++++ .../easyphotos/models/puzzle/PuzzleUtils.java | 93 + .../easyphotos/models/puzzle/PuzzleView.java | 792 +++++++++ .../models/puzzle/SquarePuzzleView.java | 32 + .../models/puzzle/slant/CrossoverPointF.java | 34 + .../models/puzzle/slant/SlantArea.java | 294 ++++ .../models/puzzle/slant/SlantLine.java | 173 ++ .../puzzle/slant/SlantPuzzleLayout.java | 334 ++++ .../models/puzzle/slant/SlantUtils.java | 482 ++++++ .../models/puzzle/straight/StraightArea.java | 231 +++ .../models/puzzle/straight/StraightLine.java | 215 +++ .../puzzle/straight/StraightPuzzleLayout.java | 359 ++++ .../models/puzzle/straight/StraightUtils.java | 236 +++ .../template/slant/NumberSlantLayout.java | 33 + .../puzzle/template/slant/OneSlantLayout.java | 36 + .../template/slant/SlantLayoutHelper.java | 69 + .../template/slant/ThreeSlantLayout.java | 48 + .../puzzle/template/slant/TwoSlantLayout.java | 30 + .../straight/EightStraightLayout.java | 89 + .../template/straight/FiveStraightLayout.java | 94 + .../template/straight/FourStraightLayout.java | 52 + .../template/straight/NineStraightLayout.java | 76 + .../straight/NumberStraightLayout.java | 31 + .../template/straight/OneStraightLayout.java | 45 + .../straight/SevenStraightLayout.java | 77 + .../template/straight/SixStraightLayout.java | 93 + .../straight/StraightLayoutHelper.java | 69 + .../straight/ThreeStraightLayout.java | 50 + .../template/straight/TwoStraightLayout.java | 58 + .../models/sticker/StickerModel.java | 221 +++ .../models/sticker/cache/StickerCache.java | 115 ++ .../sticker/entity/TextStickerData.java | 48 + .../listener/OnStickerClickListener.java | 16 + .../models/sticker/view/BitmapSticker.java | 438 +++++ .../models/sticker/view/EditFragment.java | 202 +++ .../models/sticker/view/TextSticker.java | 535 ++++++ .../easyphotos/result/Result.java | 134 ++ .../easyphotos/setting/Setting.java | 121 ++ .../easyphotos/ui/EasyPhotosActivity.java | 1091 ++++++++++++ .../easyphotos/ui/PreviewActivity.java | 487 ++++++ .../easyphotos/ui/PreviewFragment.java | 74 + .../easyphotos/ui/PuzzleActivity.java | 665 ++++++++ .../easyphotos/ui/PuzzleSelectorActivity.java | 265 +++ .../ui/adapter/AlbumItemsAdapter.java | 183 ++ .../easyphotos/ui/adapter/PhotosAdapter.java | 306 ++++ .../ui/adapter/PreviewPhotosAdapter.java | 154 ++ .../adapter/PreviewPhotosFragmentAdapter.java | 104 ++ .../easyphotos/ui/adapter/PuzzleAdapter.java | 104 ++ .../ui/adapter/PuzzleSelectorAdapter.java | 99 ++ .../adapter/PuzzleSelectorPreviewAdapter.java | 101 ++ .../ui/adapter/TextStickerAdapter.java | 81 + .../easyphotos/ui/dialog/LoadingDialog.java | 49 + .../ui/widget/PressedImageView.java | 48 + .../easyphotos/ui/widget/PressedTextView.java | 73 + .../ui/widget/PreviewRecyclerView.java | 60 + .../easyphotos/utils/bitmap/BitmapUtils.java | 412 +++++ .../utils/bitmap/SaveBitmapCallBack.java | 17 + .../easyphotos/utils/color/ColorUtils.java | 37 + .../easyphotos/utils/media/DurationUtils.java | 48 + .../media/MediaScannerConnectionUtils.java | 36 + .../utils/permission/PermissionUtil.java | 88 + .../easyphotos/utils/result/EasyResult.java | 45 + .../utils/result/HolderFragment.java | 86 + .../utils/settings/SettingsUtils.java | 27 + .../easyphotos/utils/string/StringUtils.java | 19 + .../utils/system/MeiZuStatusUtils.java | 229 +++ .../easyphotos/utils/system/SystemUtils.java | 242 +++ .../easyphotos/utils/uri/UriUtils.java | 174 ++ .../ic_controller_easy_photos.png | Bin 0 -> 896 bytes .../drawable-xhdpi/ic_delete_easy_photos.png | Bin 0 -> 767 bytes .../drawable-xhdpi/ic_editor_easy_photos.png | Bin 0 -> 3538 bytes .../drawable-xhdpi/ic_mirror_easy_photos.png | Bin 0 -> 5642 bytes .../drawable-xhdpi/ic_rotate_easy_photos.png | Bin 0 -> 901 bytes .../ic_controller_easy_photos.png | Bin 0 -> 896 bytes .../drawable-xxhdpi/ic_delete_easy_photos.png | Bin 0 -> 767 bytes .../drawable-xxhdpi/ic_editor_easy_photos.png | Bin 0 -> 3538 bytes .../drawable-xxhdpi/ic_mirror_easy_photos.png | Bin 0 -> 5642 bytes .../drawable-xxhdpi/ic_rotate_easy_photos.png | Bin 0 -> 901 bytes ...log_album_items_background_easy_photos.xml | 7 + .../bg_dialog_loading_easy_photos.xml | 8 + .../res/drawable/bg_menu_done_easy_photos.xml | 8 + .../bg_second_level_menu_easy_photos.xml | 7 + .../bg_seek_bar_alpha_easy_photos.xml | 22 + .../drawable/bg_select_false_easy_photos.xml | 8 + .../bg_select_false_unable_easy_photos.xml | 8 + .../drawable/bg_select_true_easy_photos.xml | 8 + .../bg_selected_frame_easy_photos.xml | 8 + .../bg_selected_frame_puzzle_easy_photos.xml | 8 + .../bg_text_sticker_editor_easy_photos.xml | 8 + .../ic_album_item_choose_easy_photos.xml | 9 + .../ic_album_items_name_easy_photos.xml | 9 + .../drawable/ic_arrow_back_easy_photos.xml | 9 + .../ic_arrow_back_preview_easy_photos.xml | 9 + .../drawable/ic_arrow_down_easy_photos.xml | 9 + .../res/drawable/ic_arrow_up_easy_photos.xml | 9 + .../res/drawable/ic_black_easy_photos.xml | 12 + .../main/res/drawable/ic_blue_easy_photos.xml | 12 + .../res/drawable/ic_camera_easy_photos.xml | 12 + .../res/drawable/ic_clear_easy_photos.xml | 9 + .../main/res/drawable/ic_cyan_easy_photos.xml | 12 + .../res/drawable/ic_delete_easyy_photos.xml | 9 + .../main/res/drawable/ic_edit_easy_photos.xml | 12 + .../main/res/drawable/ic_gray_easy_photos.xml | 12 + .../res/drawable/ic_green_easy_photos.xml | 12 + .../drawable/ic_notifications_easy_photos.xml | 9 + .../res/drawable/ic_orange_easy_photos.xml | 12 + .../main/res/drawable/ic_play_easy_photos.xml | 12 + .../res/drawable/ic_purple_easy_photos.xml | 12 + .../drawable/ic_puzzle_corner_easy_photos.xml | 14 + .../drawable/ic_puzzle_flip_easy_photos.xml | 23 + .../drawable/ic_puzzle_mirror_easy_photos.xml | 14 + .../ic_puzzle_padding_easy_photos.xml | 20 + .../ic_puzzle_replace_easy_photos.xml | 22 + .../drawable/ic_puzzle_rotate_easy_photos.xml | 12 + .../main/res/drawable/ic_red_easy_photos.xml | 12 + .../res/drawable/ic_selector_easy_photos.xml | 9 + .../drawable/ic_selector_true_easy_photos.xml | 9 + .../res/drawable/ic_settings_easy_photos.xml | 9 + .../res/drawable/ic_white_easy_photos.xml | 12 + .../res/drawable/ic_yelow_easy_photos.xml | 12 + .../res/drawable/progress_bar_easy_photos.xml | 20 + .../thumb_seek_bar_alpha_easy_photos.xml | 9 + .../main/res/layout/activity_easy_photos.xml | 286 ++++ .../layout/activity_preview_easy_photos.xml | 166 ++ .../layout/activity_puzzle_easy_photos.xml | 213 +++ .../activity_puzzle_selector_easy_photos.xml | 158 ++ .../res/layout/dialog_loading_easy_photos.xml | 19 + .../layout/fragment_preview_easy_photos.xml | 17 + .../fragment_text_sticker_easy_photos.xml | 208 +++ .../main/res/layout/item_ad_easy_photos.xml | 5 + .../res/layout/item_camera_easy_photos.xml | 27 + .../item_dialog_album_items_easy_photos.xml | 64 + .../layout/item_preview_photo_easy_photos.xml | 25 + ...em_preview_selected_photos_easy_photos.xml | 43 + .../res/layout/item_puzzle_easy_photos.xml | 19 + .../item_puzzle_selector_easy_photos.xml | 44 + ...em_puzzle_selector_preview_easy_photos.xml | 52 + .../res/layout/item_rv_photos_easy_photos.xml | 79 + .../layout/item_text_sticker_easy_photos.xml | 16 + easyphotos/src/main/res/values-v19/styles.xml | 17 + easyphotos/src/main/res/values-v21/styles.xml | 20 + .../src/main/res/values-w480dp/integers.xml | 4 + .../src/main/res/values-w640dp/integers.xml | 4 + easyphotos/src/main/res/values/attrs.xml | 19 + easyphotos/src/main/res/values/colors.xml | 52 + easyphotos/src/main/res/values/dimens.xml | 4 + easyphotos/src/main/res/values/integers.xml | 4 + easyphotos/src/main/res/values/strings.xml | 79 + easyphotos/src/main/res/values/styles.xml | 15 + gradle.properties | 4 + library/build.gradle | 6 +- .../utils/TimeUtils.java | 11 + nim_uikit/build.gradle | 6 +- settings.gradle | 1 + 238 files changed, 21697 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/com/yizhuan/erban/base/BaseDialogFragment.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/BaseViewTag.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/ISupportActivity.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/ISupportFragment.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportActivityDelegate.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportFragmentDelegate.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportHelper.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/TransactionDelegate.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/internal/ResultRecord.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/internal/TransactionRecord.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/queue/Action.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/queue/ActionQueue.java create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/windowcallback/IWindowCallbackProxy.kt create mode 100644 app/src/main/java/com/yizhuan/erban/base/fragmentation/windowcallback/WindowCallbackProxyUtil.kt create mode 100644 app/src/main/java/com/yizhuan/erban/base/util/ViewBindingUtil.kt create mode 100644 app/src/main/java/com/yizhuan/erban/common/delegate/SpDelegate.kt create mode 100644 app/src/main/java/com/yizhuan/erban/common/file/FileHelper.java create mode 100644 app/src/main/java/com/yizhuan/erban/common/glide/GlideEngine.java create mode 100644 app/src/main/java/com/yizhuan/erban/common/glide/GlideUtils.kt create mode 100644 app/src/main/java/com/yizhuan/erban/common/photo/PhotoProvider.kt create mode 100644 app/src/main/java/com/yizhuan/erban/common/transform/AssignScaleTransformation.kt create mode 100644 app/src/main/java/com/yizhuan/erban/common/transform/ComplexTransformation.java create mode 100644 app/src/main/java/com/yizhuan/erban/common/util/ActivityHelper.java create mode 100644 app/src/main/java/com/yizhuan/erban/common/util/Config.java create mode 100644 app/src/main/java/com/yizhuan/erban/common/util/SPUtils.java create mode 100644 app/src/main/java/com/yizhuan/erban/common/util/StringUtils.java rename app/src/main/java/com/yizhuan/erban/{ui/widget/marqueeview => common/util}/Utils.java (98%) create mode 100644 app/src/main/res/drawable/photo_dialog_bg.xml create mode 100644 app/src/main/res/layout/photo_dialog.xml create mode 100644 easyphotos/.gitignore create mode 100644 easyphotos/build.gradle create mode 100644 easyphotos/src/main/AndroidManifest.xml create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/EasyPhotos.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/builder/AlbumBuilder.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/callback/PuzzleCallback.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/callback/SelectCallback.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Code.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Key.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Type.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/engine/ImageEngine.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdEntity.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdListener.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdViewHolder.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/AlbumModel.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Album.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/AlbumItem.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Photo.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Area.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/DegreeSeekBar.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Line.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/MatrixUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzlePiece.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleView.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/SquarePuzzleView.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/CrossoverPointF.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantArea.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantLine.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantPuzzleLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightArea.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightLine.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightPuzzleLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/NumberSlantLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/OneSlantLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/SlantLayoutHelper.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/ThreeSlantLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/TwoSlantLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/EightStraightLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FiveStraightLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FourStraightLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NineStraightLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NumberStraightLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/OneStraightLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SevenStraightLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SixStraightLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/StraightLayoutHelper.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/ThreeStraightLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/TwoStraightLayout.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/StickerModel.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/cache/StickerCache.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/entity/TextStickerData.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/listener/OnStickerClickListener.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/BitmapSticker.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/EditFragment.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/TextSticker.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/result/Result.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/setting/Setting.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/EasyPhotosActivity.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewActivity.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewFragment.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleActivity.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleSelectorActivity.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/AlbumItemsAdapter.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PhotosAdapter.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosAdapter.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosFragmentAdapter.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleAdapter.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorAdapter.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorPreviewAdapter.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/TextStickerAdapter.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/dialog/LoadingDialog.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedImageView.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedTextView.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PreviewRecyclerView.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/BitmapUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/SaveBitmapCallBack.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/color/ColorUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/media/DurationUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/media/MediaScannerConnectionUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/permission/PermissionUtil.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/result/EasyResult.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/result/HolderFragment.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/settings/SettingsUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/string/StringUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/system/MeiZuStatusUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/system/SystemUtils.java create mode 100644 easyphotos/src/main/java/com/huantansheng/easyphotos/utils/uri/UriUtils.java create mode 100644 easyphotos/src/main/res/drawable-xhdpi/ic_controller_easy_photos.png create mode 100644 easyphotos/src/main/res/drawable-xhdpi/ic_delete_easy_photos.png create mode 100644 easyphotos/src/main/res/drawable-xhdpi/ic_editor_easy_photos.png create mode 100644 easyphotos/src/main/res/drawable-xhdpi/ic_mirror_easy_photos.png create mode 100644 easyphotos/src/main/res/drawable-xhdpi/ic_rotate_easy_photos.png create mode 100644 easyphotos/src/main/res/drawable-xxhdpi/ic_controller_easy_photos.png create mode 100644 easyphotos/src/main/res/drawable-xxhdpi/ic_delete_easy_photos.png create mode 100644 easyphotos/src/main/res/drawable-xxhdpi/ic_editor_easy_photos.png create mode 100644 easyphotos/src/main/res/drawable-xxhdpi/ic_mirror_easy_photos.png create mode 100644 easyphotos/src/main/res/drawable-xxhdpi/ic_rotate_easy_photos.png create mode 100644 easyphotos/src/main/res/drawable/bg_dialog_album_items_background_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/bg_dialog_loading_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/bg_menu_done_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/bg_second_level_menu_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/bg_seek_bar_alpha_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/bg_select_false_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/bg_select_false_unable_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/bg_select_true_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/bg_selected_frame_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/bg_selected_frame_puzzle_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/bg_text_sticker_editor_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_album_item_choose_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_album_items_name_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_arrow_back_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_arrow_back_preview_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_arrow_down_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_arrow_up_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_black_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_blue_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_camera_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_clear_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_cyan_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_delete_easyy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_edit_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_gray_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_green_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_notifications_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_orange_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_play_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_purple_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_puzzle_corner_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_puzzle_flip_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_puzzle_mirror_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_puzzle_padding_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_puzzle_replace_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_puzzle_rotate_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_red_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_selector_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_selector_true_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_settings_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_white_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/ic_yelow_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/progress_bar_easy_photos.xml create mode 100644 easyphotos/src/main/res/drawable/thumb_seek_bar_alpha_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/activity_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/activity_preview_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/activity_puzzle_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/activity_puzzle_selector_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/dialog_loading_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/fragment_preview_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/fragment_text_sticker_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/item_ad_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/item_camera_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/item_dialog_album_items_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/item_preview_photo_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/item_preview_selected_photos_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/item_puzzle_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/item_puzzle_selector_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/item_puzzle_selector_preview_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/item_rv_photos_easy_photos.xml create mode 100644 easyphotos/src/main/res/layout/item_text_sticker_easy_photos.xml create mode 100644 easyphotos/src/main/res/values-v19/styles.xml create mode 100644 easyphotos/src/main/res/values-v21/styles.xml create mode 100644 easyphotos/src/main/res/values-w480dp/integers.xml create mode 100644 easyphotos/src/main/res/values-w640dp/integers.xml create mode 100644 easyphotos/src/main/res/values/attrs.xml create mode 100644 easyphotos/src/main/res/values/colors.xml create mode 100644 easyphotos/src/main/res/values/dimens.xml create mode 100644 easyphotos/src/main/res/values/integers.xml create mode 100644 easyphotos/src/main/res/values/strings.xml create mode 100644 easyphotos/src/main/res/values/styles.xml diff --git a/android_crop_lib/build.gradle b/android_crop_lib/build.gradle index 0cd97952f..cd0cadb4f 100644 --- a/android_crop_lib/build.gradle +++ b/android_crop_lib/build.gradle @@ -2,11 +2,11 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 32 + compileSdkVersion COMPILE_SDK_VERSION.toInteger() defaultConfig { - minSdkVersion 21 - targetSdkVersion 32 + minSdkVersion MIN_SDK_VERSION.toInteger() + targetSdkVersion TARGET_SDK_VERSION.toInteger() testApplicationId 'com.soundcloud.android.crop.test' testInstrumentationRunner 'android.test.InstrumentationTestRunner' diff --git a/app/build.gradle b/app/build.gradle index d3dae3a50..44583fbdd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,12 +8,12 @@ apply from: '../mob.gradle' def onlyArm64 = Boolean.parseBoolean(only_arm64) android { - compileSdkVersion 32 + compileSdkVersion COMPILE_SDK_VERSION.toInteger() defaultConfig { applicationId "com.vele.peko" - minSdkVersion 21 - targetSdkVersion 32 + minSdkVersion MIN_SDK_VERSION.toInteger() + targetSdkVersion TARGET_SDK_VERSION.toInteger() versionCode Integer.valueOf(version_code) versionName version_name @@ -177,9 +177,9 @@ def Lombok = "1.18.18" dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation fileTree(dir: 'aliyun-libs', include: ['*.jar', '*.aar']) - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.4.+' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.appcompat:appcompat:1.4.2' + implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' @@ -195,7 +195,7 @@ dependencies { api "com.darsh.multipleimageselect:multipleimageselect:1.0.4" api "me.shaohui.advancedluban:library:1.3.5" - api "pl.droidsonroids.gif:android-gif-drawable:1.2.7" + api "pl.droidsonroids.gif:android-gif-drawable:1.2.24" api "com.makeramen:roundedimageview:2.3.0" api "com.jzxiang.pickerview:TimePickerDialog:1.0.1" api "com.github.zyyoona7:EasyPopup:1.0.2" @@ -261,6 +261,12 @@ dependencies { //Google Play Referrer API implementation 'com.android.installreferrer:installreferrer:2.2' + // 引入easyphotos,方便修改 + api project(':easyphotos') + + //mmkv + implementation 'com.tencent:mmkv:1.2.13' + } channel { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 1d90a1241..fb9ce500a 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -466,4 +466,5 @@ } -keep public class com.android.installreferrer.**{ *; } --keep public class com.android.installreferrer.**{ *; } \ No newline at end of file +## EasyPhotos +-keep class com.huantansheng.easyphotos.models.** { *; } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b921e9727..e77ec1818 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -80,7 +80,6 @@ android:icon="@mipmap/app_logo" android:label="@string/app_name" android:largeHeap="true" - android:requestLegacyExternalStorage="true" android:resizeableActivity="true" android:supportsRtl="true" android:theme="@style/MyMaterialTheme" diff --git a/app/src/main/java/com/yizhuan/erban/avroom/activity/AVRoomActivity.java b/app/src/main/java/com/yizhuan/erban/avroom/activity/AVRoomActivity.java index 3f59fd930..2907b8d21 100644 --- a/app/src/main/java/com/yizhuan/erban/avroom/activity/AVRoomActivity.java +++ b/app/src/main/java/com/yizhuan/erban/avroom/activity/AVRoomActivity.java @@ -75,7 +75,7 @@ import com.yizhuan.erban.ui.widget.dialog.AllServiceGiftLevelOneDialog; import com.yizhuan.erban.ui.widget.dialog.AllServiceGiftLevelThreeDialog; import com.yizhuan.erban.ui.widget.dialog.AllServiceGiftLevelTwoDialog; import com.yizhuan.erban.ui.widget.dialog.MonsterDialog; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.utils.UserUtils; import com.yizhuan.xchat_android_core.Constants; import com.yizhuan.xchat_android_core.DemoCache; diff --git a/app/src/main/java/com/yizhuan/erban/avroom/activity/RecordForPKActivity.java b/app/src/main/java/com/yizhuan/erban/avroom/activity/RecordForPKActivity.java index 7416a0461..ec2fa0bb5 100644 --- a/app/src/main/java/com/yizhuan/erban/avroom/activity/RecordForPKActivity.java +++ b/app/src/main/java/com/yizhuan/erban/avroom/activity/RecordForPKActivity.java @@ -16,7 +16,7 @@ import com.yizhuan.erban.avroom.adapter.RecordForPKAdapter; import com.yizhuan.erban.avroom.presenter.RecordForPKPresenter; import com.yizhuan.erban.avroom.view.IRecordForPKView; import com.yizhuan.erban.base.BaseMvpActivity; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration; import com.yizhuan.xchat_android_core.room.pk.bean.PKRecordInfo; import com.yizhuan.xchat_android_library.base.factory.CreatePresenter; diff --git a/app/src/main/java/com/yizhuan/erban/avroom/dialog/MicQueueDialog.java b/app/src/main/java/com/yizhuan/erban/avroom/dialog/MicQueueDialog.java index d1640bbfa..6e9602b7d 100644 --- a/app/src/main/java/com/yizhuan/erban/avroom/dialog/MicQueueDialog.java +++ b/app/src/main/java/com/yizhuan/erban/avroom/dialog/MicQueueDialog.java @@ -26,7 +26,7 @@ import com.yizhuan.erban.R; import com.yizhuan.erban.avroom.adapter.MicQueueAdapter; import com.yizhuan.erban.home.helper.LoadPageDataHelper; import com.yizhuan.erban.ui.widget.dialog.BaseDialog; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration; import com.yizhuan.xchat_android_core.Constants; import com.yizhuan.xchat_android_core.auth.AuthModel; diff --git a/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKMicQueueDialog.java b/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKMicQueueDialog.java index 9dcf6db9b..6136ea213 100644 --- a/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKMicQueueDialog.java +++ b/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKMicQueueDialog.java @@ -25,7 +25,7 @@ import com.yizhuan.erban.avroom.adapter.PKMicQueueAdapter; import com.yizhuan.erban.home.helper.LoadPageDataHelper; import com.yizhuan.erban.ui.widget.dialog.BaseDialog; import com.yizhuan.erban.ui.widget.dialog.CommonLoadingDialog; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration; import com.yizhuan.xchat_android_core.Constants; import com.yizhuan.xchat_android_core.auth.AuthModel; diff --git a/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKResultDialog.java b/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKResultDialog.java index 1aee4d8f5..90c6dcf00 100644 --- a/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKResultDialog.java +++ b/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKResultDialog.java @@ -21,7 +21,7 @@ import com.netease.nim.uikit.support.glide.GlideApp; import com.yizhuan.erban.R; import com.yizhuan.erban.common.widget.CircleImageView; import com.yizhuan.erban.ui.widget.dialog.BaseDialog; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration; import com.yizhuan.xchat_android_core.auth.AuthModel; import com.yizhuan.xchat_android_core.room.pk.bean.PKTeamInfo; diff --git a/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKSelectPeopleDialog.java b/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKSelectPeopleDialog.java index 7c29b6f53..d46bc17ce 100644 --- a/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKSelectPeopleDialog.java +++ b/app/src/main/java/com/yizhuan/erban/avroom/dialog/PKSelectPeopleDialog.java @@ -18,7 +18,7 @@ import com.netease.nim.uikit.support.glide.GlideApp; import com.yizhuan.erban.R; import com.yizhuan.erban.common.widget.CircleImageView; import com.yizhuan.erban.ui.widget.dialog.BaseDialog; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.xchat_android_core.bean.RoomQueueInfo; import com.yizhuan.xchat_android_core.manager.AvRoomDataManager; import com.yizhuan.xchat_android_core.room.pk.bean.PKMemberInfo; diff --git a/app/src/main/java/com/yizhuan/erban/avroom/dialog/RoomImposeDialog.java b/app/src/main/java/com/yizhuan/erban/avroom/dialog/RoomImposeDialog.java index 7420cad76..b03a9d1c1 100644 --- a/app/src/main/java/com/yizhuan/erban/avroom/dialog/RoomImposeDialog.java +++ b/app/src/main/java/com/yizhuan/erban/avroom/dialog/RoomImposeDialog.java @@ -19,7 +19,7 @@ import com.yizhuan.erban.avroom.activity.CpRoomInviteActivity; import com.yizhuan.erban.avroom.widget.EditRoomTitleDialog; import com.yizhuan.erban.databinding.DialogRoomImposeBinding; import com.yizhuan.erban.ui.widget.magicindicator.buildins.UIUtil; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.xchat_android_core.auth.AuthModel; import com.yizhuan.xchat_android_core.manager.AvRoomDataManager; import com.yizhuan.xchat_android_core.room.bean.RoomInfo; diff --git a/app/src/main/java/com/yizhuan/erban/avroom/dialog/RoomOperationDialog.java b/app/src/main/java/com/yizhuan/erban/avroom/dialog/RoomOperationDialog.java index 1a84928c8..e782f9a2e 100644 --- a/app/src/main/java/com/yizhuan/erban/avroom/dialog/RoomOperationDialog.java +++ b/app/src/main/java/com/yizhuan/erban/avroom/dialog/RoomOperationDialog.java @@ -24,7 +24,7 @@ import com.yizhuan.erban.avroom.anotherroompk.RoomPKCreateActivity; import com.yizhuan.erban.avroom.giftvalue.GiftValueDialogUiHelper; import com.yizhuan.erban.avroom.singleroompk.SingleRoomPKCreateActivity; import com.yizhuan.erban.common.widget.dialog.DialogManager; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration; import com.yizhuan.erban.ui.widget.recyclerview.layoutmanager.FullyGridLayoutManager; import com.yizhuan.erban.vip.VipBroadcastDialog; diff --git a/app/src/main/java/com/yizhuan/erban/avroom/widget/MessageView.java b/app/src/main/java/com/yizhuan/erban/avroom/widget/MessageView.java index 3c9c22abc..85d8c9c60 100644 --- a/app/src/main/java/com/yizhuan/erban/avroom/widget/MessageView.java +++ b/app/src/main/java/com/yizhuan/erban/avroom/widget/MessageView.java @@ -64,7 +64,7 @@ import com.yizhuan.erban.ui.widget.MyItemAnimator; import com.yizhuan.erban.ui.widget.RecyclerViewNoViewpagerScroll; import com.yizhuan.erban.ui.widget.UserInfoDialog; import com.yizhuan.erban.ui.widget.magicindicator.buildins.UIUtil; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.utils.RegexUtil; import com.yizhuan.xchat_android_constants.XChatConstants; import com.yizhuan.xchat_android_core.DemoCache; diff --git a/app/src/main/java/com/yizhuan/erban/base/BaseDialogFragment.java b/app/src/main/java/com/yizhuan/erban/base/BaseDialogFragment.java new file mode 100644 index 000000000..cc6cba3ad --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/BaseDialogFragment.java @@ -0,0 +1,233 @@ +package com.yizhuan.erban.base; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; + +import androidx.annotation.CallSuper; +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; +import androidx.viewbinding.ViewBinding; +import com.yizhuan.erban.base.fragmentation.ISupportActivity; +import com.yizhuan.erban.base.fragmentation.ISupportFragment; +import com.yizhuan.erban.base.fragmentation.SupportFragmentDelegate; +import com.yizhuan.erban.base.fragmentation.windowcallback.WindowCallbackProxyUtil; +import com.yizhuan.erban.base.util.ViewBindingUtil; +import com.yizhuan.erban.common.util.ActivityHelper; +import com.yizhuan.xchat_android_core.utils.Logger; + +public abstract class BaseDialogFragment extends DialogFragment implements ISupportFragment { + private final String TAG = getClass().getSimpleName(); + private final SupportFragmentDelegate mFragmentDelegate = new SupportFragmentDelegate(this); + private View mContentView; + @Nullable + private VB mViewBinding; + private boolean isLoaded = false; + + @Override + public void onStart() { + super.onStart(); + if (getDialog() != null && getDialog().getWindow() != null) { + Window window = getDialog().getWindow(); + Window.Callback callback = WindowCallbackProxyUtil.createWindowCallBack(window.getCallback(), true, this.getClass().getSimpleName()); + if (callback != null) { + window.setCallback(callback); + } + } + } + + @CallSuper + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (getActivity() == null) { + //这里需要关联一个Activity而不是Context + throw new IllegalStateException("Current class " + getClass().getName() + " not attached to an activity."); + } + if (!(getActivity() instanceof ISupportActivity)) { + //Activity必须是实现了ISupportActivity接口才可以 + throw new IllegalStateException(getActivity().getClass().getName() + " must impl ISupportActivity! Current class is " + getClass().getName()); + } + this.mFragmentDelegate.onAttach(); + } + + @CallSuper + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.mFragmentDelegate.onCreate(savedInstanceState); + this.initBefore(savedInstanceState); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + mViewBinding = ViewBindingUtil.inflateWithFragment(this, inflater, container); + if (mViewBinding != null) { + this.mContentView = mViewBinding.getRoot(); + this.mContentView.setTag(BaseViewTag.TAG_NAME, this); + } + this.findView(); + return this.mContentView; + } + + @CallSuper + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + this.mFragmentDelegate.onViewCreated(); + this.setView();//重写该方法即可实现预加载数据,即当Fragment创建布局以后还没有真正可见时就会加载数据,不同与onResume()方法和onLazyLoad()方法 + this.setListener(); + } + + @CallSuper + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + this.mFragmentDelegate.onSaveInstanceState(outState); + } + + /** + * 重写该方法即可实现每次Fragment可见时都能加载数据,不同与setView()方法和onLazyLoad()方法 + */ + @CallSuper + @Override + public void onResume() { + super.onResume(); + if (!isLoaded && !isHidden()) { + onLazyLoad(); + isLoaded = true; + } + } + + /** + * 重写该方法可实现懒加载数据,即当前Fragment第一次可见时才会执行一次,不同与setView()方法和onResume()方法 + */ + protected void onLazyLoad() { + + } + + /** + * 重写该方法即可监听当前Fragment变为不可见时 + */ + @CallSuper + @Override + public void onPause() { + super.onPause(); + } + + @CallSuper + @Override + public void onDestroyView() { + super.onDestroyView(); + this.mFragmentDelegate.onDestroyView(); + isLoaded = false; + this.onWillDestroy(); + } + + /** + * 建议子类各种释放操作,放到onWillDestroy()方法中执行,因为Fragment#onDestroyView()方法一定会执行所以也会触发该方法,在其他方法中处理有可能会导致内存泄露。 + */ + @CallSuper + public void onWillDestroy() { + this.mContentView = null; + } + + @CallSuper + @Override + public void show(@NonNull FragmentManager manager, @Nullable String tag) { + try { + if (!manager.isDestroyed() && !manager.isStateSaved() && !isAdded()) { + super.show(manager, tag); + } + } catch (Exception e) { + Logger.error(TAG, "show", e); + } + } + + @Override + public final void dismiss() { + dismissAllowingStateLoss(); + } + + @Override + public final void dismissAllowingStateLoss() { + if (ActivityHelper.isCanUse(getActivity())) { + super.dismissAllowingStateLoss(); + } + } + + @Nullable + @Override + public final View getView() { + return this.mContentView; + } + + protected final T findViewById(@IdRes int id) { + if (this.getView() != null) { + return this.getView().findViewById(id); + } + return null; + } + + @Override + public final SupportFragmentDelegate getSupportDelegate() { + return this.mFragmentDelegate; + } + + @Override + public final void setFragmentResult(int resultCode, Bundle bundle) { + this.mFragmentDelegate.setFragmentResult(resultCode, bundle); + } + + @Override + public void onFragmentResult(int requestCode, int resultCode, Bundle data) { + this.mFragmentDelegate.onFragmentResult(requestCode, resultCode, data); + } + + @Override + public boolean onBackPressedSupport() { + return this.mFragmentDelegate.onBackPressedSupport(); + } + + @Nullable + protected final VB getBinding() { + return mViewBinding; + } + + ////////////////////////////////////////以下是提供给子类复写的方法//////////////////////////////////////// + + /** + * 该方法是在onCreate()方法里执行,在onCreateView()方法被调用之前触发,可用于处理解析Fragment#getArguments()中的数据时的场景 + */ + protected void initBefore(@Nullable Bundle savedInstanceState) { + + } + + /** + * 该方法是在onCreateView()方法里触发,可用于处理控件的初始化 + */ + protected void findView() { + + } + + /** + * 该方法是在onViewCreated()方法里触发,重写该方法即可实现预加载数据,即当Fragment创建布局以后还没有真正可见时就会加载数据,不同与onResume()方法和onLazyLoad()方法 + */ + protected void setView() { + + } + + /** + * 该方法是在onViewCreated()方法里触发,可用于处理控件的设置监听器 + */ + protected void setListener() { + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/yizhuan/erban/base/BaseViewTag.java b/app/src/main/java/com/yizhuan/erban/base/BaseViewTag.java new file mode 100644 index 000000000..057f544fe --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/BaseViewTag.java @@ -0,0 +1,118 @@ +package com.yizhuan.erban.base; + +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import com.yizhuan.erban.R; +import com.yizhuan.xchat_android_core.utils.Logger; + +/** + * author: wushaocheng + * time: 2022/11/15 + * desc:将View关联上Activity或者Fragment的生命周期 + */ +public class BaseViewTag { + private static final String TAG = "BaseViewTag"; + public static final int TAG_NAME = R.id.baseViewTagID; + + /** + * 注册Activity或者Fragment生命周期监听接口 + * + * @param view 需要关联Activity或者Fragment生命周期的控件,注意该控件需要实现LifecycleObserver接口 + */ + public static void registerLifecycle(View view) { + if (view == null) { + Logger.warn(TAG, "registerLifecycle view is null"); + return; + } + if (!(view instanceof LifecycleObserver)) { + Logger.warn(TAG, "registerLifecycle view: " + view.getClass().getSimpleName() + ", view must implements LifecycleObserver"); + return; + } + view.post(new Runnable() { + @Override + public void run() { + try { + Object tag = view.getTag(BaseViewTag.TAG_NAME); + if (tag != null) { + register(view, tag); + return; + } + ViewParent viewParent = view.getParent(); + ViewGroup parentView = (ViewGroup) viewParent; + while (parentView != null) { + tag = parentView.getTag(BaseViewTag.TAG_NAME); + if (tag instanceof LifecycleOwner) { + register(view, tag); + return; + } + viewParent = parentView.getParent(); + if (viewParent instanceof ViewGroup) { + parentView = (ViewGroup) viewParent; + } else { + break; + } + } + register(view, view.getContext()); + } catch (Exception e) { + Logger.error(TAG, "registerLifecycle view: " + view.getClass().getSimpleName() + ", error", e); + } + } + }); + } + + /** + * 真正将view关联到Activity或者Fragment的生命周期 + * + * @param view 需要关联Activity或者Fragment生命周期的控件,注意该控件需要实现LifecycleObserver接口 + * @param owner 生命周期提供者,一般是Activity或Fragment + */ + private static void register(View view, Object owner) { + if (view == null) { + Logger.warn(TAG, "registerLifecycle view is null"); + return; + } + if (owner == null) { + Logger.warn(TAG, "registerLifecycle owner is null"); + return; + } + try { + if (owner instanceof LifecycleOwner) { + LifecycleOwner lifecycleOwner = (LifecycleOwner) owner; + lifecycleOwner.getLifecycle().addObserver((LifecycleObserver) view);//核心代码 + view.setTag(BaseViewTag.TAG_NAME, owner); + Logger.debug(TAG, "registerLifecycle view: " + view.getClass().getSimpleName() + ", owner: " + owner.getClass().getSimpleName()); + } else { + Logger.warn(TAG, "registerLifecycle view:" + view.getClass().getSimpleName() + ", owner is not instanceof LifecycleOwner"); + } + } catch (Exception e) { + Logger.error(TAG, "registerLifecycle view: " + view.getClass().getSimpleName() + ", owner: " + owner.getClass().getSimpleName() + ", error", e); + } + } + + /** + * 解绑Activity或者Fragment生命周期监听接口 + * + * @param view 需要关联Activity或者Fragment生命周期的控件,注意该控件需要实现LifecycleObserver接口 + * @param owner 生命周期提供者,一般是Activity或Fragment + */ + public static void unRegisterLifecycle(View view, LifecycleOwner owner) { + if (view == null) { + Logger.warn(TAG, "unRegisterLifecycle view is null"); + return; + } + if (owner == null) { + Logger.warn(TAG, "unRegisterLifecycle owner is null"); + return; + } + try { + view.setTag(BaseViewTag.TAG_NAME, null); + owner.getLifecycle().removeObserver((LifecycleObserver) view); + Logger.debug(TAG, "unRegisterLifecycle view: " + view.getClass().getSimpleName() + ", owner: " + owner.getClass().getSimpleName()); + } catch (Exception e) { + Logger.error(TAG, "unRegisterLifecycle view: " + view.getClass().getSimpleName() + ", owner: " + owner.getClass().getSimpleName() + ", error", e); + } + } +} diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/ISupportActivity.java b/app/src/main/java/com/yizhuan/erban/base/fragmentation/ISupportActivity.java new file mode 100644 index 000000000..609637c29 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/ISupportActivity.java @@ -0,0 +1,13 @@ +package com.yizhuan.erban.base.fragmentation; + +/** + * Created by YoKey on 17/6/13. + */ +public interface ISupportActivity { + SupportActivityDelegate getSupportDelegate(); + + void onBackPressed(); + + void onBackPressedSupport(); + +} diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/ISupportFragment.java b/app/src/main/java/com/yizhuan/erban/base/fragmentation/ISupportFragment.java new file mode 100644 index 000000000..bb7311eb3 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/ISupportFragment.java @@ -0,0 +1,36 @@ +package com.yizhuan.erban.base.fragmentation; + +import android.os.Bundle; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by YoKey on 17/6/23. + */ + +public interface ISupportFragment { + // LaunchMode + int STANDARD = 0; + int SINGLETOP = 1; + int SINGLETASK = 2; + + // ResultCode + int RESULT_CANCELED = 0; + int RESULT_OK = -1; + + @IntDef({STANDARD, SINGLETOP, SINGLETASK}) + @Retention(RetentionPolicy.SOURCE) + @interface LaunchMode { + } + + SupportFragmentDelegate getSupportDelegate(); + + void setFragmentResult(int resultCode, Bundle bundle); + + void onFragmentResult(int requestCode, int resultCode, Bundle data); + + boolean onBackPressedSupport(); +} diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportActivityDelegate.java b/app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportActivityDelegate.java new file mode 100644 index 000000000..46dec7a68 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportActivityDelegate.java @@ -0,0 +1,99 @@ +package com.yizhuan.erban.base.fragmentation; + +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import com.yizhuan.erban.base.fragmentation.queue.Action; + +public class SupportActivityDelegate { + private final ISupportActivity mSupport; + private final FragmentActivity mActivity; + + private TransactionDelegate mTransactionDelegate; + + public SupportActivityDelegate(ISupportActivity support) { + if (!(support instanceof FragmentActivity)) + throw new RuntimeException("Must extends FragmentActivity/AppCompatActivity"); + this.mSupport = support; + this.mActivity = (FragmentActivity) support; + } + + public void onCreate() { + mTransactionDelegate = getTransactionDelegate(); + } + + public TransactionDelegate getTransactionDelegate() { + if (mTransactionDelegate == null) { + mTransactionDelegate = new TransactionDelegate(); + } + return mTransactionDelegate; + } + + /** + * 不建议复写该方法,请使用 {@link #onBackPressedSupport} 代替 + */ + public void onBackPressed() { + mTransactionDelegate.mActionQueue.enqueue(new Action(Action.ACTION_BACK, getSupportFragmentManager()) { + @Override + public void run() { + // 获取activeFragment:即从栈顶开始 状态为show的那个Fragment + ISupportFragment activeFragment = SupportHelper.getAddedFragment(getSupportFragmentManager()); + if (mTransactionDelegate.dispatchBackPressedEvent(activeFragment)) return; + + mSupport.onBackPressedSupport(); + } + }); + } + + /** + * 该方法回调时机为,Activity回退栈内Fragment的数量 小于等于1 时,默认finish Activity + * 请尽量复写该方法,避免复写onBackPress(),以保证SupportFragment内的onBackPressedSupport()回退事件正常执行 + */ + public void onBackPressedSupport() { + if (getSupportFragmentManager().getBackStackEntryCount() > 1) { + pop(); + } else { + ActivityCompat.finishAfterTransition(mActivity); + } + } + + //**********************************************************************************************// + + /** + * 加载根Fragment, 即Activity内的第一个Fragment 或 Fragment内的第一个子Fragment + */ + public void loadRootFragment(int containerId, ISupportFragment toFragment) { + loadRootFragment(containerId, toFragment, true); + } + + public void loadRootFragment(int containerId, ISupportFragment toFragment, boolean addToBackStack) { + mTransactionDelegate.loadRootTransaction(getSupportFragmentManager(), containerId, toFragment, addToBackStack); + } + + public void start(ISupportFragment toFragment) { + start(toFragment, ISupportFragment.STANDARD); + } + + /** + * @param launchMode Similar to Activity's LaunchMode. + */ + public void start(ISupportFragment toFragment, @ISupportFragment.LaunchMode int launchMode) { + mTransactionDelegate.dispatchStartTransaction(getSupportFragmentManager(), getTopFragment(), toFragment, 0, launchMode, TransactionDelegate.TYPE_ADD); + } + + /** + * Pop the child fragment. + */ + public void pop() { + mTransactionDelegate.pop(getSupportFragmentManager()); + } + + private FragmentManager getSupportFragmentManager() { + return mActivity.getSupportFragmentManager(); + } + + private ISupportFragment getTopFragment() { + return SupportHelper.getTopFragment(getSupportFragmentManager()); + } +} diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportFragmentDelegate.java b/app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportFragmentDelegate.java new file mode 100644 index 000000000..b693a1a0f --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportFragmentDelegate.java @@ -0,0 +1,172 @@ +package com.yizhuan.erban.base.fragmentation; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import com.yizhuan.erban.base.fragmentation.internal.ResultRecord; +import com.yizhuan.erban.base.fragmentation.internal.TransactionRecord; + +public class SupportFragmentDelegate { + int mContainerId; + + private TransactionDelegate mTransactionDelegate; + TransactionRecord mTransactionRecord; + + private final ISupportFragment mSupportF; + private final Fragment mFragment; + + public SupportFragmentDelegate(ISupportFragment support) { + if (!(support instanceof Fragment)) + throw new RuntimeException("Must extends Fragment"); + this.mSupportF = support; + this.mFragment = (Fragment) support; + } + + public void onAttach() { + FragmentActivity activity = mFragment.getActivity(); + if (activity instanceof ISupportActivity) { + ISupportActivity mSupportA = (ISupportActivity) activity; + mTransactionDelegate = mSupportA.getSupportDelegate().getTransactionDelegate(); + } else { + if (activity != null) { + throw new RuntimeException(activity.getClass().getSimpleName() + " must impl ISupportActivity!"); + } else { + throw new RuntimeException("fragment attached activity must not be null"); + } + } + } + + public void onCreate(@Nullable Bundle savedInstanceState) { + Bundle bundle = mFragment.getArguments(); + if (bundle != null) { + mContainerId = bundle.getInt(TransactionDelegate.FRAGMENTATION_ARG_CONTAINER); + } + + if (savedInstanceState != null) { + savedInstanceState.setClassLoader(getClass().getClassLoader()); + mContainerId = savedInstanceState.getInt(TransactionDelegate.FRAGMENTATION_ARG_CONTAINER); + } + } + + public void onSaveInstanceState(Bundle outState) { + outState.putInt(TransactionDelegate.FRAGMENTATION_ARG_CONTAINER, mContainerId); + } + + public void onViewCreated() { + View view = mFragment.getView(); + if (view != null) { + view.setClickable(true); + setBackground(view); + } + } + + public void onDestroyView() { + mTransactionDelegate.handleResultRecord(mFragment); + } + + /** + * 类似 {@link Activity#setResult(int, Intent)} + *

+ * Similar to {@link Activity#setResult(int, Intent)} + * + * @see #startForResult(ISupportFragment, int) + */ + public void setFragmentResult(int resultCode, Bundle bundle) { + Bundle args = mFragment.getArguments(); + if (args == null || !args.containsKey(TransactionDelegate.FRAGMENTATION_ARG_RESULT_RECORD)) { + return; + } + + ResultRecord resultRecord = args.getParcelable(TransactionDelegate.FRAGMENTATION_ARG_RESULT_RECORD); + if (resultRecord != null) { + resultRecord.resultCode = resultCode; + resultRecord.resultBundle = bundle; + } + } + + /** + * 类似Activity的onActivityResult(int, int, Intent) + * + * @see #startForResult(ISupportFragment, int) + */ + public void onFragmentResult(int requestCode, int resultCode, Bundle data) { + } + + /** + * Back Event + * + * @return false则继续向上传递, true则消费掉该事件 + */ + public boolean onBackPressedSupport() { + return false; + } + + //**********************************************************************************************// + + /** + * 加载根Fragment, 即Activity内的第一个Fragment 或 Fragment内的第一个子Fragment + */ + public void loadRootFragment(int containerId, ISupportFragment toFragment) { + loadRootFragment(containerId, toFragment, true); + } + + public void loadRootFragment(int containerId, ISupportFragment toFragment, boolean addToBackStack) { + mTransactionDelegate.loadRootTransaction(getChildFragmentManager(), containerId, toFragment, addToBackStack); + } + + public void start(ISupportFragment toFragment) { + start(toFragment, ISupportFragment.STANDARD); + } + + /** + * @param launchMode Similar to Activity's LaunchMode. + */ + public void start(final ISupportFragment toFragment, @ISupportFragment.LaunchMode int launchMode) { + mTransactionDelegate.dispatchStartTransaction(mFragment.getFragmentManager(), mSupportF, toFragment, 0, launchMode, TransactionDelegate.TYPE_ADD); + } + + /** + * Launch an fragment for which you would like a result when it poped. + */ + public void startForResult(ISupportFragment toFragment, int requestCode) { + mTransactionDelegate.dispatchStartTransaction(mFragment.getFragmentManager(), mSupportF, toFragment, requestCode, ISupportFragment.STANDARD, TransactionDelegate.TYPE_ADD_RESULT); + } + + public void startChild(ISupportFragment toFragment) { + startChild(toFragment, ISupportFragment.STANDARD); + } + + public void startChild(final ISupportFragment toFragment, @ISupportFragment.LaunchMode int launchMode) { + mTransactionDelegate.dispatchStartTransaction(getChildFragmentManager(), getTopFragment(), toFragment, 0, launchMode, TransactionDelegate.TYPE_ADD); + } + + public void pop() { + mTransactionDelegate.pop(mFragment.getFragmentManager()); + } + + private FragmentManager getChildFragmentManager() { + return mFragment.getChildFragmentManager(); + } + + private ISupportFragment getTopFragment() { + return SupportHelper.getTopFragment(getChildFragmentManager()); + } + + public void setBackground(View view) { + if ((mFragment.getTag() != null && mFragment.getTag().startsWith("android:switcher:")) || + view.getBackground() != null) { + return; + } + + view.setBackgroundColor(Color.TRANSPARENT); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportHelper.java b/app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportHelper.java new file mode 100644 index 000000000..136d0ec0a --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/SupportHelper.java @@ -0,0 +1,209 @@ +package com.yizhuan.erban.base.fragmentation; + +import android.content.Context; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by YoKey on 17/6/13. + */ + +public class SupportHelper { + private static final long SHOW_SPACE = 200L; + + private SupportHelper() { + } + + /** + * 显示软键盘 + */ + public static void showSoftInput(final View view) { + if (view == null || view.getContext() == null) return; + final InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + view.requestFocus(); + view.postDelayed(new Runnable() { + @Override + public void run() { + imm.showSoftInput(view, InputMethodManager.SHOW_FORCED); + } + }, SHOW_SPACE); + } + + /** + * 隐藏软键盘 + */ + public static void hideSoftInput(View view) { + if (view == null || view.getContext() == null) return; + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + /** + * 获得栈顶SupportFragment + */ + public static ISupportFragment getTopFragment(FragmentManager fragmentManager) { + return getTopFragment(fragmentManager, 0); + } + + public static ISupportFragment getTopFragment(FragmentManager fragmentManager, int containerId) { + List fragmentList = fragmentManager.getFragments(); + + for (int i = fragmentList.size() - 1; i >= 0; i--) { + Fragment fragment = fragmentList.get(i); + if (fragment instanceof ISupportFragment) { + ISupportFragment iFragment = (ISupportFragment) fragment; + if (containerId == 0) return iFragment; + + if (containerId == iFragment.getSupportDelegate().mContainerId) { + return iFragment; + } + } + } + return null; + } + + /** + * Same as fragmentManager.findFragmentByTag(fragmentClass.getName()); + * find Fragment from FragmentStack + */ + public static T findFragment(FragmentManager fragmentManager, Class fragmentClass) { + return findAddedFragment(fragmentClass, null, fragmentManager); + } + + /** + * Same as fragmentManager.findFragmentByTag(fragmentTag); + *

+ * find Fragment from FragmentStack + */ + public static T findFragment(FragmentManager fragmentManager, String fragmentTag) { + return findAddedFragment(null, fragmentTag, fragmentManager); + } + + /** + * 从栈顶开始,寻找FragmentManager以及其所有子栈, 直到找到状态为show & userVisible的Fragment + */ + public static ISupportFragment getAddedFragment(FragmentManager fragmentManager) { + return getAddedFragment(fragmentManager, null); + } + + static T findAddedFragment(Class fragmentClass, String toFragmentTag, FragmentManager fragmentManager) { + Fragment fragment = null; + + if (toFragmentTag == null) { + List fragmentList = fragmentManager.getFragments(); + + int sizeChildFrgList = fragmentList.size(); + + for (int i = sizeChildFrgList - 1; i >= 0; i--) { + Fragment brotherFragment = fragmentList.get(i); + if (brotherFragment instanceof ISupportFragment && brotherFragment.getClass().getName().equals(fragmentClass.getName())) { + fragment = brotherFragment; + break; + } + } + } else { + fragment = fragmentManager.findFragmentByTag(toFragmentTag); + if (fragment == null) return null; + } + return (T) fragment; + } + + private static ISupportFragment getAddedFragment(FragmentManager fragmentManager, ISupportFragment parentFragment) { + List fragmentList = fragmentManager.getFragments(); + if (fragmentList.size() == 0) { + return parentFragment; + } + for (int i = fragmentList.size() - 1; i >= 0; i--) { + Fragment fragment = fragmentList.get(i); + if (fragment instanceof ISupportFragment) { + if (fragment.isResumed() && !fragment.isHidden() && fragment.getUserVisibleHint()) { + return getAddedFragment(fragment.getChildFragmentManager(), (ISupportFragment) fragment); + } + } + } + return parentFragment; + } + + /** + * Get the topFragment from BackStack + */ + public static ISupportFragment getBackStackTopFragment(FragmentManager fragmentManager) { + return getBackStackTopFragment(fragmentManager, 0); + } + + /** + * Get the topFragment from BackStack + */ + public static ISupportFragment getBackStackTopFragment(FragmentManager fragmentManager, int containerId) { + int count = fragmentManager.getBackStackEntryCount(); + + for (int i = count - 1; i >= 0; i--) { + FragmentManager.BackStackEntry entry = fragmentManager.getBackStackEntryAt(i); + Fragment fragment = fragmentManager.findFragmentByTag(entry.getName()); + if (fragment instanceof ISupportFragment) { + ISupportFragment supportFragment = (ISupportFragment) fragment; + if (containerId == 0) return supportFragment; + + if (containerId == supportFragment.getSupportDelegate().mContainerId) { + return supportFragment; + } + } + } + return null; + } + + static T findBackStackFragment(Class fragmentClass, String toFragmentTag, FragmentManager fragmentManager) { + int count = fragmentManager.getBackStackEntryCount(); + + if (toFragmentTag == null) { + toFragmentTag = fragmentClass.getName(); + } + + for (int i = count - 1; i >= 0; i--) { + FragmentManager.BackStackEntry entry = fragmentManager.getBackStackEntryAt(i); + + if (toFragmentTag.equals(entry.getName())) { + Fragment fragment = fragmentManager.findFragmentByTag(entry.getName()); + if (fragment instanceof ISupportFragment) { + return (T) fragment; + } + } + } + return null; + } + + static List getWillPopFragments(FragmentManager fm, String targetTag) { + Fragment target = fm.findFragmentByTag(targetTag); + List willPopFragments = new ArrayList<>(); + + List fragmentList = fm.getFragments(); + + int size = fragmentList.size(); + + int startIndex = -1; + for (int i = size - 1; i >= 0; i--) { + if (target == fragmentList.get(i)) { + if (i + 1 < size) { + startIndex = i + 1; + } + break; + } + } + + if (startIndex == -1) return willPopFragments; + + for (int i = size - 1; i >= startIndex; i--) { + Fragment fragment = fragmentList.get(i); + if (fragment != null && fragment.getView() != null) { + willPopFragments.add(fragment); + } + } + return willPopFragments; + } +} diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/TransactionDelegate.java b/app/src/main/java/com/yizhuan/erban/base/fragmentation/TransactionDelegate.java new file mode 100644 index 000000000..8e9efddfb --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/TransactionDelegate.java @@ -0,0 +1,305 @@ +package com.yizhuan.erban.base.fragmentation; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +import com.yizhuan.erban.base.fragmentation.internal.ResultRecord; +import com.yizhuan.erban.base.fragmentation.internal.TransactionRecord; +import com.yizhuan.erban.base.fragmentation.queue.Action; +import com.yizhuan.erban.base.fragmentation.queue.ActionQueue; + +import java.util.List; + + +/** + * Controller + * Created by YoKeyword on 16/1/22. + */ +class TransactionDelegate { + private static final String TAG = "Fragmentation"; + + static final String FRAGMENTATION_ARG_RESULT_RECORD = "fragment_arg_result_record"; + static final String FRAGMENTATION_ARG_CONTAINER = "fragmentation_arg_container"; + static final String FRAGMENTATION_ARG_REPLACE = "fragmentation_arg_replace"; + + private static final String FRAGMENTATION_STATE_SAVE_RESULT = "fragmentation_state_save_result"; + + static final int TYPE_ADD = 0; + static final int TYPE_ADD_RESULT = 1; + static final int TYPE_ADD_WITHOUT_HIDE = 2; + static final int TYPE_ADD_RESULT_WITHOUT_HIDE = 3; + static final int TYPE_REPLACE = 10; + static final int TYPE_REPLACE_DONT_BACK = 11; + + ActionQueue mActionQueue; + + TransactionDelegate() { + Handler mHandler = new Handler(Looper.getMainLooper()); + mActionQueue = new ActionQueue(mHandler); + } + + void loadRootTransaction(final FragmentManager fm, final int containerId, final ISupportFragment to, final boolean addToBackStack) { + enqueue(fm, new Action(Action.ACTION_LOAD, fm) { + @Override + public void run() { + bindContainerId(containerId, to); + + String toFragmentTag = to.getClass().getName(); + TransactionRecord transactionRecord = to.getSupportDelegate().mTransactionRecord; + if (transactionRecord != null) { + if (transactionRecord.tag != null) { + toFragmentTag = transactionRecord.tag; + } + } + + start(fm, null, to, toFragmentTag, !addToBackStack, TYPE_REPLACE); + } + }); + } + + + private void start(FragmentManager fm, final ISupportFragment from, ISupportFragment to, String toFragmentTag, boolean dontAddToBackStack, int type) { + FragmentTransaction ft = fm.beginTransaction(); + boolean addMode = (type == TYPE_ADD || type == TYPE_ADD_RESULT || type == TYPE_ADD_WITHOUT_HIDE || type == TYPE_ADD_RESULT_WITHOUT_HIDE); + Fragment fromF = (Fragment) from; + Fragment toF = (Fragment) to; + Bundle args = getArguments(toF); + args.putBoolean(FRAGMENTATION_ARG_REPLACE, !addMode); + + if (from == null) { + ft.replace(args.getInt(FRAGMENTATION_ARG_CONTAINER), toF, toFragmentTag); + if (!addMode) { + ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + } + } else { + if (addMode) { + ft.add(from.getSupportDelegate().mContainerId, toF, toFragmentTag); + if (type != TYPE_ADD_WITHOUT_HIDE && type != TYPE_ADD_RESULT_WITHOUT_HIDE) { + ft.hide(fromF); + } + } else { + ft.replace(from.getSupportDelegate().mContainerId, toF, toFragmentTag); + } + } + + if (!dontAddToBackStack && type != TYPE_REPLACE_DONT_BACK) { + ft.addToBackStack(toFragmentTag); + } + supportCommit(ft); + } + + /** + * Pop + */ + void pop(final FragmentManager fm) { + enqueue(fm, new Action(Action.ACTION_POP, fm) { + @Override + public void run() { + removeTopFragment(fm); + fm.popBackStackImmediate(); + } + }); + } + + private void removeTopFragment(FragmentManager fm) { + try { // Safe popBackStack() + ISupportFragment top = SupportHelper.getBackStackTopFragment(fm); + if (top != null) { + fm.beginTransaction() + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE) + .remove((Fragment) top) + .commitAllowingStateLoss(); + } + } catch (Exception ignored) { + + } + } + + /** + * Dispatch the start transaction. + */ + void dispatchStartTransaction(final FragmentManager fm, final ISupportFragment from, final ISupportFragment to, final int requestCode, final int launchMode, final int type) { + enqueue(fm, new Action(launchMode == ISupportFragment.SINGLETASK ? Action.ACTION_POP_MOCK : Action.ACTION_NORMAL, fm) { + @Override + public void run() { + doDispatchStartTransaction(fm, from, to, requestCode, launchMode, type); + } + }); + } + + private void doDispatchStartTransaction(FragmentManager fm, ISupportFragment from, ISupportFragment to, int requestCode, int launchMode, int type) { + if (to == null) { + throw new NullPointerException("toFragment == null"); + } + + if ((type == TYPE_ADD_RESULT || type == TYPE_ADD_RESULT_WITHOUT_HIDE) && from != null) { + if (!((Fragment) from).isAdded()) { + Log.w(TAG, ((Fragment) from).getClass().getSimpleName() + " has not been attached yet! startForResult() converted to start()"); + } else { + saveRequestCode(fm, (Fragment) from, (Fragment) to, requestCode); + } + } + + from = getTopFragmentForStart(from, fm); + + int containerId = getArguments((Fragment) to).getInt(FRAGMENTATION_ARG_CONTAINER, 0); + if (from == null && containerId == 0) { + Log.e(TAG, "There is no Fragment in the FragmentManager, maybe you need to call loadRootFragment()!"); + return; + } + + if (from != null && containerId == 0) { + bindContainerId(from.getSupportDelegate().mContainerId, to); + } + + // process ExtraTransaction + String toFragmentTag = to.getClass().getName(); + boolean dontAddToBackStack = false; + TransactionRecord transactionRecord = to.getSupportDelegate().mTransactionRecord; + if (transactionRecord != null) { + if (transactionRecord.tag != null) { + toFragmentTag = transactionRecord.tag; + } + dontAddToBackStack = transactionRecord.dontAddToBackStack; + } + + if (handleLaunchMode(fm, from, to, toFragmentTag, launchMode)) return; + + start(fm, from, to, toFragmentTag, dontAddToBackStack, type); + } + + private void doPopTo(final String targetFragmentTag, FragmentManager fm) { + Fragment targetFragment = fm.findFragmentByTag(targetFragmentTag); + + if (targetFragment == null) { + Log.e(TAG, "Pop failure! Can't find FragmentTag:" + targetFragmentTag + " in the FragmentManager's Stack."); + return; + } + + List willPopFragments = SupportHelper.getWillPopFragments(fm, targetFragmentTag); + if (willPopFragments.size() <= 0) return; + safePopTo(targetFragmentTag, fm, willPopFragments); + } + + private void safePopTo(String fragmentTag, final FragmentManager fm, List willPopFragments) { + // 批量删除fragment ,static final int OP_REMOVE = 3; + FragmentTransaction transaction = fm.beginTransaction() + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE); + for (Fragment fragment : willPopFragments) { + transaction.remove(fragment); + } + transaction.commitAllowingStateLoss(); + + // 弹栈到指定fragment,从数据上来看,和上面的效果完全一样,把栈中所有的backStackRecord 包含多个记录,每个记录包含多个操作 , + // 在每个记录中,对操作索引 按照从大到小的顺序,逐个进行反操作。 + // 除了第一个记录,其余每个记录都有两个操作,一个是添加OP_ADD = 1;(反操作是remove) 一个是OP_HIDE = 4;(反操作是show)(这是在start中设定的) + // 之所以有上面的批量删除,在执行动画的时候,发现f.mView == null 就不去执行show动画。 + fm.popBackStackImmediate(fragmentTag, 0); + } + + private ISupportFragment getTopFragmentForStart(ISupportFragment from, FragmentManager fm) { + ISupportFragment top; + if (from == null) { + top = SupportHelper.getTopFragment(fm); + } else { + if (from.getSupportDelegate().mContainerId == 0) { + Fragment fromF = (Fragment) from; + if (fromF.getTag() != null && !fromF.getTag().startsWith("android:switcher:")) { + throw new IllegalStateException("Can't find container, please call loadRootFragment() first!"); + } + } + top = SupportHelper.getTopFragment(fm, from.getSupportDelegate().mContainerId); + } + return top; + } + + /** + * Dispatch the pop-event. Priority of the top of the stack of Fragment + */ + boolean dispatchBackPressedEvent(ISupportFragment activeFragment) { + if (activeFragment != null) { + boolean result = activeFragment.onBackPressedSupport(); + if (result) { + return true; + } + + Fragment parentFragment = ((Fragment) activeFragment).getParentFragment(); + return dispatchBackPressedEvent((ISupportFragment) parentFragment); + } + return false; + } + + void handleResultRecord(Fragment from) { + try { + Bundle args = from.getArguments(); + if (args == null) return; + final ResultRecord resultRecord = args.getParcelable(FRAGMENTATION_ARG_RESULT_RECORD); + if (resultRecord == null) return; + + ISupportFragment targetFragment = (ISupportFragment) from.getFragmentManager().getFragment(from.getArguments(), FRAGMENTATION_STATE_SAVE_RESULT); + targetFragment.onFragmentResult(resultRecord.requestCode, resultRecord.resultCode, resultRecord.resultBundle); + } catch (IllegalStateException ignored) { + // Fragment no longer exists + } + } + + private void enqueue(FragmentManager fm, Action action) { + if (fm == null) { + Log.w(TAG, "FragmentManager is null, skip the action!"); + return; + } + mActionQueue.enqueue(action); + } + + private void bindContainerId(int containerId, ISupportFragment to) { + Bundle args = getArguments((Fragment) to); + args.putInt(FRAGMENTATION_ARG_CONTAINER, containerId); + } + + private Bundle getArguments(Fragment fragment) { + Bundle bundle = fragment.getArguments(); + if (bundle == null) { + bundle = new Bundle(); + fragment.setArguments(bundle); + } + return bundle; + } + + private void supportCommit(FragmentTransaction transaction) { + transaction.commitAllowingStateLoss(); + } + + private boolean handleLaunchMode(FragmentManager fm, ISupportFragment topFragment, final ISupportFragment to, String toFragmentTag, int launchMode) { + if (topFragment == null) return false; + final ISupportFragment stackToFragment = SupportHelper.findBackStackFragment(to.getClass(), toFragmentTag, fm); + if (stackToFragment == null) return false; + + if (launchMode == ISupportFragment.SINGLETOP) { + if (to == topFragment || to.getClass().getName().equals(topFragment.getClass().getName())) { + return true; + } + } else if (launchMode == ISupportFragment.SINGLETASK) { + doPopTo(toFragmentTag, fm); + return true; + } + return false; + } + + /** + * save requestCode + */ + private void saveRequestCode(FragmentManager fm, Fragment from, Fragment to, int requestCode) { + Bundle bundle = getArguments(to); + ResultRecord resultRecord = new ResultRecord(); + resultRecord.requestCode = requestCode; + bundle.putParcelable(FRAGMENTATION_ARG_RESULT_RECORD, resultRecord); + fm.putFragment(bundle, FRAGMENTATION_STATE_SAVE_RESULT, from); + } + +} diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/internal/ResultRecord.java b/app/src/main/java/com/yizhuan/erban/base/fragmentation/internal/ResultRecord.java new file mode 100644 index 000000000..d5d736b72 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/internal/ResultRecord.java @@ -0,0 +1,48 @@ +package com.yizhuan.erban.base.fragmentation.internal; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @Hide Result 记录 + * Created by YoKeyword on 16/6/2. + */ +public final class ResultRecord implements Parcelable { + public int requestCode; + public int resultCode = 0; + public Bundle resultBundle; + + public ResultRecord() { + } + + protected ResultRecord(Parcel in) { + requestCode = in.readInt(); + resultCode = in.readInt(); + resultBundle = in.readBundle(getClass().getClassLoader()); + } + + public static final Creator CREATOR = new Creator() { + @Override + public ResultRecord createFromParcel(Parcel in) { + return new ResultRecord(in); + } + + @Override + public ResultRecord[] newArray(int size) { + return new ResultRecord[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(requestCode); + dest.writeInt(resultCode); + dest.writeBundle(resultBundle); + } +} diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/internal/TransactionRecord.java b/app/src/main/java/com/yizhuan/erban/base/fragmentation/internal/TransactionRecord.java new file mode 100644 index 000000000..72ec5ef0b --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/internal/TransactionRecord.java @@ -0,0 +1,9 @@ +package com.yizhuan.erban.base.fragmentation.internal; + +/** + * @hide Created by YoKey on 16/11/25. + */ +public final class TransactionRecord { + public String tag; + public boolean dontAddToBackStack = false; +} diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/queue/Action.java b/app/src/main/java/com/yizhuan/erban/base/fragmentation/queue/Action.java new file mode 100644 index 000000000..f6fadfd54 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/queue/Action.java @@ -0,0 +1,35 @@ +package com.yizhuan.erban.base.fragmentation.queue; + +import androidx.fragment.app.FragmentManager; + +/** + * Created by YoKey on 17/12/28. + */ +public abstract class Action { + public static final long DEFAULT_POP_TIME = 300L; + + public static final int ACTION_NORMAL = 0; + public static final int ACTION_POP = 1; + public static final int ACTION_POP_MOCK = 2; + public static final int ACTION_BACK = 3; + public static final int ACTION_LOAD = 4; + + public FragmentManager fragmentManager; + public int action = ACTION_NORMAL; + public long duration = 0; + + public Action(int action) { + this.action = action; + } + + public Action(int action, FragmentManager fragmentManager) { + this(action); + this.fragmentManager = fragmentManager; + } + + public Action(FragmentManager fragmentManager) { + this.fragmentManager = fragmentManager; + } + + public abstract void run(); +} diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/queue/ActionQueue.java b/app/src/main/java/com/yizhuan/erban/base/fragmentation/queue/ActionQueue.java new file mode 100644 index 000000000..f489579f1 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/queue/ActionQueue.java @@ -0,0 +1,81 @@ +package com.yizhuan.erban.base.fragmentation.queue; + +import android.os.Handler; +import android.os.Looper; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * The queue of perform action. + *

+ * Created by YoKey on 17/12/29. + */ +public class ActionQueue { + private Queue mQueue = new LinkedList<>(); + private Handler mMainHandler; + + public ActionQueue(Handler mainHandler) { + this.mMainHandler = mainHandler; + } + + public void enqueue(final Action action) { + if (isThrottleBACK(action)) return; + + if (action.action == Action.ACTION_LOAD && mQueue.isEmpty() + && Thread.currentThread() == Looper.getMainLooper().getThread()) { + action.run(); + return; + } + + mMainHandler.post(new Runnable() { + @Override + public void run() { + enqueueAction(action); + } + }); + } + + private void enqueueAction(Action action) { + mQueue.add(action); + //第一次进来的时候,执行完上局,队列只有一个,一旦进入handleAction,就会一直执行,直到队列为空 + if (mQueue.size() == 1) { + handleAction(); + } + } + + private void handleAction() { + if (mQueue.isEmpty()) return; + + Action action = mQueue.peek(); + if (action == null || action.fragmentManager.isStateSaved()) { + mQueue.clear(); + return; + } + action.run(); + + executeNextAction(action); + } + + private void executeNextAction(Action action) { + if (action.action == Action.ACTION_POP) { + action.duration = Action.DEFAULT_POP_TIME; + } + + mMainHandler.postDelayed(new Runnable() { + @Override + public void run() { + mQueue.poll(); + handleAction(); + } + }, action.duration); + } + + private boolean isThrottleBACK(Action action) { + if (action.action == Action.ACTION_BACK) { + Action head = mQueue.peek(); + return head != null && head.action == Action.ACTION_POP; + } + return false; + } +} diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/windowcallback/IWindowCallbackProxy.kt b/app/src/main/java/com/yizhuan/erban/base/fragmentation/windowcallback/IWindowCallbackProxy.kt new file mode 100644 index 000000000..634084183 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/windowcallback/IWindowCallbackProxy.kt @@ -0,0 +1,14 @@ +package com.yizhuan.erban.base.fragmentation.windowcallback + +import android.view.Window + +/** + * author: wushaocheng + * time: 2022/11/15 + * desc: + */ +interface IWindowCallbackProxy { + fun createCallBack( + callback: Window.Callback?, isDialog: Boolean, tag: String? + ): Window.Callback? +} \ No newline at end of file diff --git a/app/src/main/java/com/yizhuan/erban/base/fragmentation/windowcallback/WindowCallbackProxyUtil.kt b/app/src/main/java/com/yizhuan/erban/base/fragmentation/windowcallback/WindowCallbackProxyUtil.kt new file mode 100644 index 000000000..5b2da09a5 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/fragmentation/windowcallback/WindowCallbackProxyUtil.kt @@ -0,0 +1,25 @@ +package com.yizhuan.erban.base.fragmentation.windowcallback + +import android.view.Window + +/** + * author: wushaocheng + * time: 2022/11/15 + * desc: WindowCallback代理类工具 + */ +object WindowCallbackProxyUtil { + var mCallbackProxy: IWindowCallbackProxy? = null + + @JvmStatic + fun setWindowCallbackProxy(proxy: IWindowCallbackProxy?) { + mCallbackProxy = proxy + } + + @JvmStatic + @JvmOverloads + fun createWindowCallBack( + callback: Window.Callback?, isDialog: Boolean = false, tag: String? = null + ): Window.Callback? { + return mCallbackProxy?.createCallBack(callback, isDialog, tag) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/yizhuan/erban/base/util/ViewBindingUtil.kt b/app/src/main/java/com/yizhuan/erban/base/util/ViewBindingUtil.kt new file mode 100644 index 000000000..4106c93b3 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/base/util/ViewBindingUtil.kt @@ -0,0 +1,97 @@ +package com.yizhuan.erban.base.util + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding +import com.yizhuan.xchat_android_core.utils.Logger +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.ParameterizedType + +/** + * author: wushaocheng + * time: 2022/11/15 + * desc: viewBinding fragment、activity绑定布局 + * @see 参考 https://github.com/DylanCaiCoding/ViewBindingKTX/blob/master/viewbinding-base/src/main/java/com/dylanc/viewbinding/base/ViewBindingUtil.kt + */ +object ViewBindingUtil { + private const val TAG = "ViewBindingUtil" + + + @JvmStatic + fun inflateWithActivity( + genericOwner: Any, layoutInflater: LayoutInflater + ): VB? = withGenericBindingClass(genericOwner) { clazz -> + clazz.getMethod("inflate", LayoutInflater::class.java).invoke(null, layoutInflater) as? VB + } + + @JvmStatic + fun inflateWithFragment( + genericOwner: Any, + layoutInflater: LayoutInflater, + parent: ViewGroup?, + ): VB? = withGenericBindingClass(genericOwner) { clazz -> + clazz.getMethod( + "inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java + ).invoke(null, layoutInflater, parent, false) as? VB + } + + + @JvmStatic + fun inflateWithView( + genericOwner: Any, + layoutInflater: LayoutInflater, + parent: ViewGroup?, + ): VB? = withGenericBindingClass(genericOwner) { clazz -> + var vb = try { + clazz.getMethod( + "inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java + ).invoke(null, layoutInflater, parent, true) as? VB + } catch (e: Exception) { + //merge 标签会找不到 + Logger.debug( + TAG, + "inflateWithView => maybe use merge: " + genericOwner.javaClass.simpleName + ", genericSuperclass: " + genericOwner.javaClass.genericSuperclass + ) + null + } + if (vb == null) { + //merge 标签的只有该方法 + vb = clazz.getMethod( + "inflate", LayoutInflater::class.java, ViewGroup::class.java + ).invoke(null, layoutInflater, parent) as? VB + } + return@withGenericBindingClass vb + } + + private fun withGenericBindingClass( + genericOwner: Any, block: (Class) -> VB? + ): VB? { + var genericSuperclass = genericOwner.javaClass.genericSuperclass + var superclass = genericOwner.javaClass.superclass + while (superclass != null) { + if (genericSuperclass is ParameterizedType) { + genericSuperclass.actualTypeArguments.forEach { + try { + return block.invoke(it as Class) + } catch (e: NoSuchMethodException) { + } catch (e: ClassCastException) { + } catch (e: InvocationTargetException) { + Logger.error( + TAG, + "withGenericBindingClass => ${e.message}" + ", class: " + genericOwner.javaClass.simpleName + ) + return null + } + } + } + genericSuperclass = superclass.genericSuperclass + superclass = superclass.superclass + } + Logger.error( + TAG, + "withGenericBindingClass: " + genericOwner.javaClass.simpleName + ", genericSuperclass: " + genericOwner.javaClass.genericSuperclass + ) + return null + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/yizhuan/erban/bills/adapter/WithdrawBillsIndicatorAdapter.java b/app/src/main/java/com/yizhuan/erban/bills/adapter/WithdrawBillsIndicatorAdapter.java index d0841c9dd..99a261e79 100644 --- a/app/src/main/java/com/yizhuan/erban/bills/adapter/WithdrawBillsIndicatorAdapter.java +++ b/app/src/main/java/com/yizhuan/erban/bills/adapter/WithdrawBillsIndicatorAdapter.java @@ -10,7 +10,7 @@ import com.yizhuan.erban.R; import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.CommonNavigatorAdapter; import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.IPagerIndicator; import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.IPagerTitleView; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import java.util.List; diff --git a/app/src/main/java/com/yizhuan/erban/common/delegate/SpDelegate.kt b/app/src/main/java/com/yizhuan/erban/common/delegate/SpDelegate.kt new file mode 100644 index 000000000..582ce13b5 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/common/delegate/SpDelegate.kt @@ -0,0 +1,50 @@ +package com.yizhuan.erban.common.delegate + +import com.yizhuan.erban.application.XChatApplication +import com.yizhuan.erban.common.util.SPUtils +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * author: wushaocheng + * time: 2022/11/15 + * desc: sp存储和取出委托 + */ +class SpDelegate(private val key: String, private val default: T) : ReadWriteProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + val value = when (default) { + is Boolean -> SPUtils.getBoolean(key, default) + is String -> SPUtils.getString(key, default) + is Long -> SPUtils.getLong(key, default) + is Int -> SPUtils.getInt(key, default) + is Float -> SPUtils.getFloat(key, default) + is Double -> SPUtils.getDouble(key, default) + is ByteArray -> SPUtils.getBytes(key, default) + else -> { + if (XChatApplication.isDebug()) { + throw IllegalArgumentException("SpDelegate: this type is no supported") + } else { + null + } + } + } + return (value as? T) ?: default + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + when (value) { + is Boolean -> SPUtils.putBoolean(key, value) + is String -> SPUtils.putString(key, value) + is Long -> SPUtils.putLong(key, value) + is Int -> SPUtils.putInt(key, value) + is Float -> SPUtils.putFloat(key, value) + is Double -> SPUtils.putDouble(key, value) + is ByteArray -> SPUtils.putBytes(key, value) + else -> { + if (XChatApplication.isDebug()) { + throw IllegalArgumentException("SpDelegate: this type is no supported") + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/yizhuan/erban/common/file/FileHelper.java b/app/src/main/java/com/yizhuan/erban/common/file/FileHelper.java new file mode 100644 index 000000000..d2ae9b5cd --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/common/file/FileHelper.java @@ -0,0 +1,917 @@ +package com.yizhuan.erban.common.file; + +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Environment; +import android.text.TextUtils; + +import com.yizhuan.erban.application.XChatApplication; +import com.yizhuan.erban.common.util.StringUtils; +import com.yizhuan.xchat_android_core.utils.Logger; +import com.yizhuan.xchat_android_library.utils.FP; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * 文件工具类 + */ +public class FileHelper { + private static final String TAG = "FileHelper"; + private static final String NO_MEDIA = ".nomedia"; + private static File rootCacheDir; + private static HashMap rootFileDirMap = new HashMap<>(); + private static final String[] mCs = new String[]{"/", "\\", "?", "*", ":", "<", ">", "|", "\""}; + private static final char UNICODE_SURROGATE_START_CHAR = '\ud800'; + private static final char UNICODE_SURROGATE_END_CHAR = '\udfff'; + + /** + * 创建.nomedia文件 + * + * @param parentFile 上级目录 + */ + private static void createNoMediaFile(File parentFile) { + File no_media = new File(parentFile, NO_MEDIA); + if (!no_media.exists()) { + try { + no_media.createNewFile(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** + * 获取Android/data/当前应用包名/cache文件夹 + * + * @return 当前应用缓存根目录 + */ + public static File getRootCacheDir() { + if (rootCacheDir != null) { + //因为频繁调用getExternalCacheDir方法会在某些机器上面出现ANR,所以这里降低频率调用。 + return rootCacheDir; + } + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + File file = XChatApplication.gContext.getExternalCacheDir(); + if (file != null) { + createNoMediaFile(file); + //因为频繁调用getExternalCacheDir方法会在某些机器上面出现ANR,所以这里降低频率调用。 + rootCacheDir = file; + return file; + } + } + File file = XChatApplication.gContext.getCacheDir(); + createNoMediaFile(file); + //因为频繁调用getExternalCacheDir方法会在某些机器上面出现ANR,所以这里降低频率调用。 + rootCacheDir = file; + return file; + } + + /** + * 获取Android/data/当前应用包名/files文件夹 + * + * @param type 类型从以下方式中选择,也可以为null: + * {@link Environment#DIRECTORY_MUSIC}, + * {@link Environment#DIRECTORY_PODCASTS}, + * {@link Environment#DIRECTORY_RINGTONES}, + * {@link Environment#DIRECTORY_ALARMS}, + * {@link Environment#DIRECTORY_NOTIFICATIONS}, + * {@link Environment#DIRECTORY_PICTURES}, + * {@link Environment#DIRECTORY_MOVIES}, + * {@link Environment#DIRECTORY_DOWNLOADS}, + * {@link Environment#DIRECTORY_DCIM}, + * {@link Environment#DIRECTORY_DOCUMENTS}, + * {@link Environment#DIRECTORY_AUDIOBOOKS} + * @return 当前应用文件根目录 + */ + public static File getRootFilesDir(@androidx.annotation.Nullable String type) { + String dirName = (type != null && type.length() > 0) ? type.trim() : null; + if (TextUtils.isEmpty(dirName)) { + //因为频繁调用getExternalFilesDir方法会在某些机器上面出现ANR,所以这里降低频率调用。 + File file = rootFileDirMap.get("empty"); + if (file != null) { + return file; + } + } else { + //因为频繁调用getExternalFilesDir方法会在某些机器上面出现ANR,所以这里降低频率调用。 + File file = rootFileDirMap.get(dirName); + if (file != null) { + return file; + } + } + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + File file = XChatApplication.gContext.getExternalFilesDir(dirName); + if (file != null) { + createNoMediaFile(file); + //因为频繁调用getExternalFilesDir方法会在某些机器上面出现ANR,所以这里降低频率调用。 + if (TextUtils.isEmpty(dirName)) { + rootFileDirMap.put("empty", file); + } else { + rootFileDirMap.put(dirName, file); + } + return file; + } + } + File file = XChatApplication.gContext.getFilesDir(); + createNoMediaFile(file); + //因为频繁调用getExternalFilesDir方法会在某些机器上面出现ANR,所以这里降低频率调用。 + rootFileDirMap.put("empty", file); + return file; + } + + /** + * 创建临时文件 + * + * @param dir 文件夹 + * @return 临时文件 + */ + public static File createTempFile(File dir) { + try { + File file = File.createTempFile("pic", null, dir); + file.deleteOnExit(); + return file; + } catch (IOException e) { + return null; + } + } + + /** + * 判断文件或文件夹是否存在 + * + * @param filePath 文件路径 + * @return 是否存在这个文件或文件夹 + */ + public static boolean isFileExist(String filePath) { + if (TextUtils.isEmpty(filePath)) { + return false; + } + + File file = new File(filePath); + return file.exists(); + } + + /** + * 确保文件夹存在,不存在的时候就创建该目录 + * + * @param dirPath 文件夹路径 + * @return 确保是否已存在这个文件夹 + */ + public static boolean ensureDirExists(String dirPath) { + File dirFile = new File(dirPath); + if (!dirFile.exists()) { + return dirFile.mkdirs(); + } + return true; + } + + /** + * 确保该文件的文件夹存在,不存在的时候就创建该文件的文件夹 + * + * @param filePath 文件路径 + * @return 确保是否已存在该文件的文件夹 + */ + public static boolean ensureFileDirExists(String filePath) { + String dir = getDirOfFilePath(filePath); + if (TextUtils.isEmpty(dir)) { + return false; + } + ensureDirExists(dir); + return true; + } + + /** + * 确保该文件存在,不存在的时候就创建该文件 + * + * @param filePath 文件路径 + * @return 确保是否已存在该文件 + */ + public static File ensureFileExists(String filePath) { + if (!ensureFileDirExists(filePath)) { + return null; + } + File file = new File(filePath); + if (file.exists()) { + return file; + } + try { + if (!file.exists() && !file.createNewFile()) { + file = null; + } + } catch (IOException e) { + e.printStackTrace(); + file = null; + } + return file; + } + + /** + * 从文件路径里提取文件夹的路径 + * + * @param filePath 文件路径 + * @return 文件夹的路径 + */ + public static String getDirOfFilePath(String filePath) { + if (TextUtils.isEmpty(filePath)) { + return null; + } + int sepPos = filePath.lastIndexOf(File.separatorChar); + if (sepPos == -1) { + return null; + } + return filePath.substring(0, sepPos); + } + + /** + * 删除单个文件 + * + * @param filePath 文件路径 + */ + public static void removeFile(String filePath) { + if (!TextUtils.isEmpty(filePath)) { + try { + File file = new File(filePath); + file.delete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** + * 删除文件或文件夹下面所有的文件 + * + * @param filePath 文件路径 + * @return 是否删除成功 + */ + public static boolean removeAllFile(String filePath) { + if (TextUtils.isEmpty(filePath)) { + return true; + } + + File file = new File(filePath); + if (!file.exists()) { + return true; + } + if (file.isFile()) { + try { + return file.delete(); + } catch (Exception e) { + return false; + } + } + if (!file.isDirectory()) { + return false; + } + File[] files = file.listFiles(); + if (files != null && files.length > 0) { + for (File f : files) { + if (f.isFile()) { + try { + f.delete(); + } catch (Exception e) { + e.printStackTrace(); + } + } else if (f.isDirectory()) { + removeAllFile(f.getAbsolutePath()); + } + } + } + try { + return file.delete(); + } catch (Exception e) { + return false; + } + } + + /** + * 将数据存储到缓存文件夹里 + * + * @param data 数据 + * @param filePath 文件路径 + */ + public static void saveByteArrayIntoFile(byte[] data, String filePath) { + try { + File f = new File(filePath); + if (f.createNewFile()) { + FileOutputStream fos = new FileOutputStream(f); + fos.write(data); + fos.flush(); + fos.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 将Bitmap保存为JPEG图片 + */ + public static void saveBitmapAsJPEG(Bitmap bmp, String filePath) { + if (bmp == null) { + return; + } + FileOutputStream fos = null; + try { + if (!ensureFileDirExists(filePath)) { + return; + } + File file = new File(filePath); + if (file.exists()) { + file.delete(); + } + fos = new FileOutputStream(file); + bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos); + fos.flush(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 将Bitmap保存为PNG图片 + * + * @param bmp 图片 + * @param filePath 文件路径 + * @return 文件路径 + */ + public static String saveBitmapAsPNG(Bitmap bmp, String filePath) { + if (bmp == null) { + return ""; + } + FileOutputStream fos = null; + try { + if (!ensureFileDirExists(filePath)) { + return ""; + } + File file = new File(filePath); + if (file.exists()) { + file.delete(); + } + fos = new FileOutputStream(file); + bmp.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.flush(); + return filePath; + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return ""; + } + + /** + * 解压zip到指定的路径 + * + * @param zipFileString ZIP的名称 + * @param outPathString 要解压缩路径 + */ + public static boolean unzipFile(String zipFileString, String outPathString) { + ZipInputStream inZip = null; + try { + inZip = new ZipInputStream(new FileInputStream(zipFileString)); + ZipEntry zipEntry; + while ((zipEntry = inZip.getNextEntry()) != null) { + String szName = zipEntry.getName(); + if (szName.contains("/")) { + szName = szName.substring(szName.indexOf("/") + 1); + if (TextUtils.isEmpty(szName)) { + continue; + } + } + if (!zipEntry.isDirectory()) { + File file = new File(outPathString + File.separator + szName); + if (!file.exists()) { + if (file.getParentFile() != null) { + file.getParentFile().mkdirs(); + } + file.createNewFile(); + } + FileOutputStream out = new FileOutputStream(file); + int len; + byte[] buffer = new byte[1024]; + while ((len = inZip.read(buffer)) != -1) { + out.write(buffer, 0, len); + out.flush(); + } + out.close(); + } else { + szName = szName.substring(0, szName.length() - 1); + File folder = new File(outPathString + File.separator + szName); + folder.mkdirs(); + } + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + try { + if (inZip != null) { + inZip.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return true; + } + + /** + * 解压压缩包 + * + * @param srcZipPath 原压缩包路径 + * @param destFilePath 目标文件夹路径 + * @return 解压之后的文件组 + * @throws IOException 抛出异常 + */ + public static File[] unzip(String srcZipPath, String destFilePath) throws IOException { + if (TextUtils.isEmpty(destFilePath)) { + throw new IOException(); + } + if (!destFilePath.endsWith(File.separator)) { + destFilePath = destFilePath + File.separator; + } + File destDir = new File(destFilePath); + if (!destDir.isDirectory() || !destDir.exists()) { + destDir.mkdir(); + } + ArrayList extractedFileList = new ArrayList<>(); + ZipInputStream inZip = null; + try { + ZipEntry zipEntry; + inZip = new ZipInputStream(new FileInputStream(srcZipPath)); + String szName; + while ((zipEntry = inZip.getNextEntry()) != null) { + szName = zipEntry.getName(); + if (zipEntry.isDirectory()) { + szName = szName.substring(0, szName.length() - 1); + File folder = new File(destDir.getAbsolutePath() + File.separator + szName); + folder.mkdirs(); + continue; + } + FileOutputStream out = null; + try { + int len; + File file = new File(destDir.getAbsolutePath() + File.separator + szName); + file = ensureFileExists(file.getAbsolutePath()); + if (file != null) { + out = new FileOutputStream(file); + byte[] buffer = new byte[1024]; + while ((len = inZip.read(buffer)) != -1) { + out.write(buffer, 0, len); + out.flush(); + } + out.close(); + extractedFileList.add(file); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (out != null) { + out.close(); + } + } + } + } finally { + if (inZip != null) { + inZip.close(); + } + } + File[] extractedFiles = new File[extractedFileList.size()]; + extractedFileList.toArray(extractedFiles); + return extractedFiles; + } + + /** + * 从文件路径里面提取文件的后缀 + * + * @param filePath 文件路径 + * @return 后缀,如.mp3 + */ + public static String getFileExtension(String filePath) { + String fileName = getFileName(filePath); + if (TextUtils.isEmpty(fileName)) { + return null; + } + int index = fileName.lastIndexOf("."); + if (index != -1) { + return fileName.substring(index); + } + return null; + } + + /** + * 从文件路径里面提取文件名 + * + * @param filePath 文件路径 + * @return 文件名称 + */ + public static String getFileName(String filePath) { + if (filePath != null) { + String slash = "/"; + int pos = filePath.lastIndexOf(slash) + 1; + if (pos > 0) { + String name = filePath.substring(pos); + if (!TextUtils.isEmpty(name)) { + name = name.replace("?", ""); + } + return name; + } + } + return null; + } + + /** + * 从URL里面提取版本名 + * + * @param url 链接 + * @return 文件名 + */ + public static String getFileNameWithVer(String url) { + String versionName = ""; + String subName = getFileName(url); + if (subName != null && subName.contains("?")) { + String[] tempArr = subName.split("\\?"); + if (tempArr.length > 1) { + subName = tempArr[0]; + versionName = tempArr[1]; + } + } + + String fileName = dropExt(subName); + fileName = fileName + versionName; + return fileName; + } + + /** + * 从文件名里面踢出点得到可用的文件名 + * + * @param fileName 文件名 + * @return 文件名 + */ + public static String dropExt(String fileName) { + if (!TextUtils.isEmpty(fileName)) { + int pos = fileName.lastIndexOf("."); + if (pos != -1) { + return FP.take(pos, fileName); + } + } + return fileName; + } + + /** + * 读取文件内容转换为字符串 + * + * @param filePath 文件路径 + */ + public static String getStringFromFile(String filePath) { + String result = ""; + InputStream is = null; + try { + is = new FileInputStream(filePath); + int length = is.available(); + byte[] buffer = new byte[length]; + is.read(buffer); + result = new String(buffer, StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (is != null) { + is.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return result; + } + + /** + * 读取Assets内容转换为字符串 + * + * @param fileName 文件名 + * @return 字符串内容 + */ + public static String getStringFromAssets(String fileName) { + String result = ""; + try { + InputStream is = XChatApplication.gContext.getAssets().open(fileName); + int length = is.available(); + byte[] buffer = new byte[length]; + is.read(buffer); + result = new String(buffer, StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + /** + * 从Assets里面读取内容覆盖本地文件 + * + * @param dir 文件夹路径 + * @param fileName 文件名称 + * @param overwrite 是否覆盖,即删除旧文件重新创建新文件 + * @return 操作是否成功 + */ + public static boolean copyFileFromAssets(String dir, String fileName, boolean overwrite) { + String path = dir + File.separator + fileName; + File file = new File(path); + if (file.exists() && overwrite) { + file.delete(); + } + if (!file.exists()) { + try { + if (!ensureDirExists(dir)) { + return false; + } + file.createNewFile(); + InputStream in = XChatApplication.gContext.getAssets().open(fileName); + OutputStream out = new FileOutputStream(file); + byte[] buffer = new byte[4096]; + int n; + while ((n = in.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + out.flush(); + in.close(); + out.close(); + } catch (Exception e) { + try { + file.delete(); + } catch (Exception ex) { + ex.printStackTrace(); + } + return false; + } + } + return true; + } + + /** + * 将字节数组写入文件中 + * + * @param buffer 字节数组 + * @param folderPath 文件夹路径 + * @param fileName 文件名称 + */ + public static void saveDataToFile(byte[] buffer, String folderPath, String fileName) { + File fileDir = new File(folderPath); + if (!fileDir.exists()) { + fileDir.mkdirs(); + } + + File file = new File(folderPath + File.separator + fileName); + FileOutputStream out = null; + try { + out = new FileOutputStream(file); + out.write(buffer); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (out != null) { + out.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 格式化大小,带单位 + */ + public static String formatSize(long size) { + //获取到的size为:1705230 + int GB = 1024 * 1024 * 1024;//定义GB的计算常量 + int MB = 1024 * 1024;//定义MB的计算常量 + int KB = 1024;//定义KB的计算常量 + DecimalFormat df = new DecimalFormat("0.0");//格式化小数 + String resultSize = ""; + if (size / GB >= 1) { + //如果当前Byte的值大于等于1GB + resultSize = df.format(size / (float) GB) + "GB"; + } else if (size / MB >= 1) { + //如果当前Byte的值大于等于1MB + resultSize = df.format(size / (float) MB) + "MB"; + } else if (size / KB >= 1) { + //如果当前Byte的值大于等于1KB + resultSize = df.format(size / (float) KB) + "KB"; + } else { + resultSize = size + "B"; + } + return resultSize; + } + + /** + * @param fileName 文件名 + * @return 文件名是否正确 + */ + public static boolean isFileNameCorrect(String fileName) { + if (null == fileName) { + return false; + } else { + fileName = fileName.trim(); + if (fileName.length() == 0) { + return false; + } else { + for (String c : mCs) { + if (fileName.contains(c)) { + return false; + } + } + return !containsSurrogateChar(fileName); + } + } + } + + /** + * @param string 字符串 + * @return 是否包含需要替代的字符串 + */ + public static boolean containsSurrogateChar(String string) { + if (TextUtils.isEmpty(string)) { + return false; + } else { + int length = string.length(); + boolean hasSurrogateChar = false; + for (int i = 0; i < length; ++i) { + char c = string.charAt(i); + if (UNICODE_SURROGATE_START_CHAR <= c && c <= UNICODE_SURROGATE_END_CHAR) { + hasSurrogateChar = true; + break; + } + } + return hasSurrogateChar; + } + } + + /** + * 把Uri转换为文件 + * + * @param uri 源文件的Uri的路径 + * @param path 要存入的文件的路径 + * @param overwrite 是否需要复写 + * @return 是否已经把Uri转换成功为文件了 + */ + public static boolean copyFileFromUri(Uri uri, String path, boolean overwrite) { + File file = new File(path); + if (file.exists() && overwrite) { + file.delete(); + } + if (!file.exists()) { + try { + if (!FileHelper.ensureDirExists(file.getParentFile().getAbsolutePath())) { + return false; + } + InputStream stream = XChatApplication.gContext.getContentResolver().openInputStream(uri); + if (stream != null) { + file.createNewFile(); + OutputStream out = new FileOutputStream(file); + byte buffer[] = new byte[4096]; + int n; + while ((n = stream.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + out.flush(); + stream.close(); + out.close(); + } + } catch (Exception e) { + try { + file.delete(); + } catch (Exception ex) { + ex.printStackTrace(); + } + return false; + } + } + return true; + } + + /** + * 将文件转换成字节数组 + * + * @param file 文件 + * @return 字节数组 + */ + public static byte[] fileToByteArray(File file) { + if (file.exists() && file.canRead()) { + try { + return streamToBytes(new FileInputStream(file)); + } catch (Exception e) { + Logger.error(TAG, String.valueOf(e)); + } + } + return null; + } + + /** + * 将文件流转换成字节数组 + * + * @param inputStream 输入流 + * @return 字节数组 + */ + public static byte[] streamToBytes(InputStream inputStream) { + byte[] content = null; + ByteArrayOutputStream baos = null; + BufferedInputStream bis = null; + try { + baos = new ByteArrayOutputStream(); + bis = new BufferedInputStream(inputStream); + byte[] buffer = new byte[1024]; + int length; + while ((length = bis.read(buffer)) != -1) { + baos.write(buffer, 0, length); + } + content = baos.toByteArray(); + if (content.length == 0) { + content = null; + } + } catch (IOException e) { + Logger.error(TAG, String.valueOf(e)); + } finally { + if (baos != null) { + try { + baos.close(); + } catch (IOException e) { + Logger.error(TAG, String.valueOf(e)); + } + } + if (bis != null) { + try { + bis.close(); + } catch (IOException e) { + Logger.error(TAG, String.valueOf(e)); + } + } + } + return content; + } + + /** + * 文件转换成字符串 + * + * @param filePath 文件路径 + * @return 字符串内容 + */ + public static String getTxtFileContent(String filePath) { + String content = ""; + if (!StringUtils.isNullOrEmpty(filePath)) { + File file = new File(filePath); + if (file.isFile()) { + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream(file); + String line; + StringBuilder sb = new StringBuilder(); + BufferedReader buffReader = new BufferedReader(new InputStreamReader(inputStream)); + while ((line = buffReader.readLine()) != null) { + sb.append(line).append("\n"); + } + content = sb.toString(); + } catch (Exception e) { + Logger.error(TAG, "getTxtFileContent read fail, e = " + e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + } + return content; + } + +} diff --git a/app/src/main/java/com/yizhuan/erban/common/glide/GlideEngine.java b/app/src/main/java/com/yizhuan/erban/common/glide/GlideEngine.java new file mode 100644 index 000000000..d7abf143c --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/common/glide/GlideEngine.java @@ -0,0 +1,73 @@ +package com.yizhuan.erban.common.glide; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.widget.ImageView; +import androidx.annotation.NonNull; +import com.huantansheng.easyphotos.engine.ImageEngine; + +/** + * Created by wushaocheng on 2021/3/29 15:18. + * Desc:Glide实现类 + */ +public class GlideEngine implements ImageEngine { + /** + * 加载图片到ImageView + * + * @param context 上下文 + * @param uri 图片路径Uri + * @param imageView 加载到的ImageView + */ + //安卓10推荐uri,并且path的方式不再可用 + @Override + public void loadPhoto(@NonNull Context context, @NonNull Uri uri, @NonNull ImageView imageView) { + GlideUtils.instance().loadUriCrossFade(uri, imageView); + } + + /** + * 加载gif动图图片到ImageView,gif动图不动 + * + * @param context 上下文 + * @param gifUri gif动图路径Uri + * @param imageView 加载到的ImageView + *

+ * 备注:不支持动图显示的情况下可以不写 + */ + //安卓10推荐uri,并且path的方式不再可用 + @Override + public void loadGifAsBitmap(@NonNull Context context, @NonNull Uri gifUri, @NonNull ImageView imageView) { + GlideUtils.instance().loadUriGift(gifUri, imageView); + } + + /** + * 加载gif动图到ImageView,gif动图动 + * + * @param context 上下文 + * @param gifUri gif动图路径Uri + * @param imageView 加载动图的ImageView + *

+ * 备注:不支持动图显示的情况下可以不写 + */ + //安卓10推荐uri,并且path的方式不再可用 + @Override + public void loadGif(@NonNull Context context, @NonNull Uri gifUri, @NonNull ImageView imageView) { + GlideUtils.instance().loadUriGiftAndCrossFade(gifUri, imageView); + } + + /** + * 获取图片加载框架中的缓存Bitmap,不用拼图功能可以直接返回null + * + * @param context 上下文 + * @param uri 图片路径 + * @param width 图片宽度 + * @param height 图片高度 + * @return Bitmap + * @throws Exception 异常直接抛出,EasyPhotos内部处理 + */ + //安卓10推荐uri,并且path的方式不再可用 + @Override + public Bitmap getCacheBitmap(@NonNull Context context, @NonNull Uri uri, int width, int height) throws Exception { + return null; + } +} diff --git a/app/src/main/java/com/yizhuan/erban/common/glide/GlideUtils.kt b/app/src/main/java/com/yizhuan/erban/common/glide/GlideUtils.kt new file mode 100644 index 000000000..b25e2ce0e --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/common/glide/GlideUtils.kt @@ -0,0 +1,1518 @@ +package com.yizhuan.erban.common.glide + +import android.app.Activity +import android.app.Application +import android.content.Context +import android.content.ContextWrapper +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.text.TextUtils +import android.widget.ImageView +import androidx.annotation.DrawableRes +import androidx.annotation.WorkerThread +import com.bumptech.glide.GenericTransitionOptions +import com.bumptech.glide.Glide +import com.bumptech.glide.Priority +import com.bumptech.glide.RequestManager +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.DecodeFormat +import com.bumptech.glide.load.Transformation +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.load.resource.bitmap.BitmapTransitionOptions +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.CircleCrop +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.DrawableImageViewTarget +import com.bumptech.glide.request.target.Target +import com.yizhuan.erban.common.transform.AssignScaleTransformation +import com.yizhuan.erban.common.transform.ComplexTransformation +import com.yizhuan.erban.common.util.ActivityHelper +import com.yizhuan.erban.common.util.Utils +import com.yizhuan.xchat_android_core.utils.Logger +import jp.wasabeef.glide.transformations.RoundedCornersTransformation +import java.io.File + +/** + * author: create by Even on 2022/3/29 + * 图片加载工具类 + */ +class GlideUtils { + + /** + * 加载字节图片 + * @param bytes 图片字节 + */ + fun loadBytes( + bytes: ByteArray, + imageView: ImageView?, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "loadBytes imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this + .asBitmap() + .load(bytes) + .transform(*transformation) + .into(imageView) + } + } + + /** + * 预加载图片到缓存中,无需设置到目标空间 + */ + fun loadNoInto(context: Context, url: String?) { + getGlideConfig(context)?.apply { + this + .load(url) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .preload() + } + } + + /** + * 》》》》》》》》需在子线程中调用《《《《《《《《 + * 加载图片并返回bitmap图片 + */ + @WorkerThread + fun loadGetBitmap(context: Context, url: String?): Bitmap? { + if (url.isNullOrEmpty()) return null + val glideConfig = getGlideConfig(context) ?: return null + return glideConfig.asBitmap().load(url).submit().get() + } + + /** + * 》》》》》》》》需在子线程中调用《《《《《《《《 + * 加载图片并返回bitmap图片 + * @param width 图片宽 + * @param height 图片高 + */ + @WorkerThread + fun loadGetBitmap( + context: Context, + url: String?, + isCircle: Boolean? = false, + width: Int, + height: Int + ): Bitmap? { + if (url.isNullOrEmpty()) return null + val glideConfig = getGlideConfig(context) ?: return null + val load = glideConfig.asBitmap().load(url) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + if (isCircle == true) { + load.circleCrop() + } + return load.submit(width, height).get() + } + + /** + * 》》》》》》》》需在子线程中调用《《《《《《《《 + * 获取图片的file文件,并指定大小 + * @param width 图片宽 + * @param height 图片高 + */ + @WorkerThread + fun loadGetFile(context: Context, url: String?, width: Int, height: Int): File? { + val glideConfig = getGlideConfig(context) ?: return null + return glideConfig.asFile().load(url).submit(width, height).get() + } + + /** + * 》》》》》》》》需在子线程中调用《《《《《《《《 + * 获取图片的file文件 + */ + @WorkerThread + fun loadGetFile(context: Context, url: String?): File? { + val glideConfig = getGlideConfig(context) ?: return null + return glideConfig.asFile().load(url).submit().get() + } + + /** + * 加载图片并设置format以及是否跳过内存缓存,无占位图 + */ + fun loadFormat( + path: String, + imageView: ImageView?, + format: DecodeFormat + ) { + if (imageView == null) { + Logger.error(TAG, "loadFormat imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.asBitmap().load(path).format(format).into(imageView) + } + } + + /** + * 加载图片并设置format以及是否跳过内存缓存,加载失败占位图 + */ + fun loadFormat( + path: String, + imageView: ImageView?, + @DrawableRes errorRes: Int, + format: DecodeFormat + ) { + if (imageView == null) { + Logger.error(TAG, "loadFormat imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this + .asBitmap().load(path).error(errorRes).format(format).into(imageView) + } + } + + /** + * 加载object类型图片并拉满全屏,设置加载错误占位图 + * @param path object 类型图片 + * @param errorRes 加载错误占位图 + */ + fun loadObjectFitCenter(path: Any, @DrawableRes errorRes: Int, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "loadObjectFitCenter imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(path).error(errorRes).fitCenter().dontAnimate() + .into(imageView) + } + } + + + /** + * 加载object类型图片,并设置错误资源占位图 + */ + fun loadObject(path: Any, imageView: ImageView?, @DrawableRes errorRes: Int) { + if (imageView == null) { + Logger.error(TAG, "loadObject imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this + .load(path) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .error(errorRes) + .into(imageView) + } + } + + /** + * 加载object类型图片,无占位图 + */ + fun loadObject(context: Context, path: Any, target: Target) { + getGlideConfig(context)?.apply { + this.asBitmap() + .load(path) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .dontAnimate() + .into(target) + } + } + + /** + * 加载object类型图片,错误占位图,设置缩略图 + */ + fun loadObject( + context: Context, + path: Any, + @DrawableRes errorRes: Int, + thumbnail: Float, + target: Target + ) { + val config = + getGlideConfig(context)?.load(path)?.diskCacheStrategy(DiskCacheStrategy.RESOURCE) + ?.error(errorRes) + if (thumbnail > 0) { + config?.thumbnail(thumbnail) + } + config?.into(target) + } + + /** + * 加载图片,返回Drawable + */ + fun loadForDrawable( + context: Context, + url: String, + @DrawableRes errorRes: Int, + target: Target + ) { + val config = + getGlideConfig(context)?.load(url)?.diskCacheStrategy(DiskCacheStrategy.RESOURCE) + ?.error(errorRes) + config?.into(target) + } + + /** + * 加载object类型图片,无占位图 + */ + fun loadObject( + path: Any, + imageView: ImageView?, + @DrawableRes defaultRes: Int, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "loadObject imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.asBitmap() + .load(path) + .transform(*transformation) + .placeholder(defaultRes) + .error(defaultRes) + .into(imageView) + } + } + + /** + * 加载Uri图片时显示过度动画 + * @param uri 图片路径Uri + * + */ + fun loadUriCrossFade(uri: Uri, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "loadUriCrossFade imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(uri) + .transition(DrawableTransitionOptions.withCrossFade()).into(imageView) + } + } + + /** + * 加载本地图片并添加过度动画以及图片变换 + */ + fun loadResCrossFade( + @DrawableRes resId: Int, + imageView: ImageView?, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "loadResCrossFade imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(resId).diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .transform(*transformation).transition(DrawableTransitionOptions.withCrossFade()) + .into(imageView) + } + } + + /** + * 加载本地图片并添加过度动画以及图片变换 + */ + fun loadResCrossFade( + @DrawableRes resId: Int, + imageView: ImageView?, + format: DecodeFormat, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "loadResCrossFade imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(resId).diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .transition(DrawableTransitionOptions.withCrossFade()).transform(*transformation) + .format(format) + .into(imageView) + } + } + + /** + * 加载Uri Gift图片,无默认图无动画 + * @param uri gift路径 + */ + fun loadUriGift(uri: Uri, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "loadUriGift imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.asGif().load(uri).into(imageView) + } + } + + /** + * 加载Uri Gift图片并添加图片过度动画,无默认图无动画 + * @param uri gift路径 + */ + fun loadUriGiftAndCrossFade(uri: Uri, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "loadUriGift imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.asGif().load(uri) + .transition(DrawableTransitionOptions.withCrossFade()).into(imageView) + } + } + + + /** + * 加载图片时显示过度动画并设置图形变换,缓存模式,无占位图 + * @param url 图片地址 + */ + fun loadCrossFade( + url: String?, + imageView: ImageView?, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "loadCrossFade imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url) + .transition(DrawableTransitionOptions.withCrossFade()) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE).dontAnimate() + .transform(*transformation).into(imageView) + } + } + + /** + * 加载图片时显示过度动画并设置图形变换,缓存模式 + * @param url 图片地址 + */ + fun loadCrossFade( + url: String?, + imageView: ImageView?, + @DrawableRes defaultRes: Int, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "loadCrossFade imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url) + .transition(DrawableTransitionOptions.withCrossFade()) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE).dontAnimate() + .placeholder(defaultRes).error(defaultRes) + .transform(*transformation).into(imageView) + } + } + + /** + * 加载图片时显示过度动画并设置图形变换,有占位图 + * @param url 图片地址 + * @param defaultRes 占位图 + * @param listener 请求监听 + */ + fun loadCrossFade( + url: String?, + imageView: ImageView?, + @DrawableRes defaultRes: Int, + listener: RequestListener + ) { + if (imageView == null) { + Logger.error(TAG, "loadCrossFade imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url).placeholder(defaultRes).error(defaultRes) + .transition(DrawableTransitionOptions.withCrossFade()).listener(listener) + .into(imageView) + } + } + + /** + * 加载图片时显示过度动画并设置请求优先级,加载失败占位图 + * @param url 图片地址 + * @param errorRes 占位图 + * @param priority 优先级 + */ + fun loadCrossFade( + url: String?, + imageView: ImageView?, + @DrawableRes errorRes: Int, + priority: Priority + ) { + if (imageView == null) { + Logger.error(TAG, "loadCrossFade imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url).error(errorRes) + .transition(DrawableTransitionOptions.withCrossFade()).priority(priority) + .into(imageView) + } + } + + /** + * 加载图片时显示过度动画并设置请求优先级,加载失败占位图 + * @param url 图片地址 + * @param errorRes 占位图 + * @param priority 优先级 + * @param transformation 图形变换 + */ + fun loadCrossFade( + url: String?, + imageView: ImageView?, + @DrawableRes errorRes: Int, + priority: Priority, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "loadCrossFade imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url).error(errorRes) + .transition(DrawableTransitionOptions.withCrossFade()).priority(priority) + .transform(*transformation) + .into(imageView) + } + } + + /** + * 加载图片时显示过度动画并设置图形变换,无占位图 + * @param url 图片地址 + */ + fun loadCrossFade( + context: Context, + url: String?, + target: Target, + vararg transformation: Transformation? + ) { + + getGlideConfig(context)?.apply { + this.asBitmap().load(url) + .transition(BitmapTransitionOptions.withCrossFade()) + .transform(*transformation) + .into(target) + } + } + + /** + * 加载需要优先级的图片并添加缓存模式,默认有动画,无占位图 + * @param priority 加载优先级 + */ + fun loadPriority( + url: String?, + imageView: ImageView?, + priority: Priority + ) { + if (imageView == null) { + Logger.error(TAG, "loadPriority imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url).diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .dontAnimate().priority(priority).into(imageView) + } + } + + /** + * 加载需要优先级的图片,并设置磁盘缓存,无默认动画,无占位图, + * @param priority 加载有玄机 + */ + fun loadPriority( + context: Context, + url: String?, + priority: Priority, + target: Target + ) { + getGlideConfig(context)?.apply { + this.asBitmap().load(url).dontAnimate().priority(priority) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(target) + } + } + + /** + * 加载需要优先级的图片,并设置磁盘缓存,无默认动画,有加载失败占位图, + * @param priority 加载优先级 + */ + fun loadPriority( + context: Context, + url: String?, + @DrawableRes errorRes: Int, + priority: Priority, + target: Target + ) { + getGlideConfig(context)?.apply { + this.asBitmap().load(url) + .dontAnimate().priority(priority) + .error(errorRes) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(target) + } + } + + /** + * 加载需要优先级的图片并添加缓存模式,默认有动画,有占位图 + * @param priority 加载优先级 + * @param transformation 图形变换模式 + * @param defaultRes 占位图 + */ + fun loadPriority( + url: String?, + imageView: ImageView?, + @DrawableRes defaultRes: Int, + priority: Priority, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "loadPriority imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url).placeholder(defaultRes).error(defaultRes) + .dontAnimate().priority(priority).transform(*transformation).into(imageView) + } + } + + /** + * 加载需要优先级的图片并添加缓存模式,默认有动画,有占位图 + * @param priority 加载优先级 + * @param defaultRes 占位图 + */ + fun loadPriority( + url: String?, + imageView: ImageView?, + @DrawableRes defaultRes: Int, + priority: Priority + ) { + if (imageView == null) { + Logger.error(TAG, "loadPriority imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url).placeholder(defaultRes).error(defaultRes) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .dontAnimate().priority(priority).into(imageView) + } + } + + /** + * 加载广告页 + * @param defaultRes 加载错误显示图片 + */ + fun loadAd( + context: Context, + url: String?, + @DrawableRes defaultRes: Int, + target: DrawableImageViewTarget + ) { + getGlideConfig(context)?.apply { + this.load(url).diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .error(defaultRes).dontTransform().dontAnimate().into(target) + } + } + + /** + * 加载指定大小的资源图片 + * @param width 图片宽 + * @param height 图片高 + */ + fun loadResBySize(imageView: ImageView?, @DrawableRes resId: Int, width: Int, height: Int) { + if (imageView == null) { + Logger.error(TAG, "loadSquareIcon imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(resId).override(width, height).into(imageView) + } + } + + /** + * 加载图片并设置优先级, + * @param priority 加载优先级 + */ + fun load( + context: Context, + url: String?, + @DrawableRes defaultRes: Int, + priority: Priority, + target: Target + ) { + getGlideConfig(context)?.apply { + this + .asBitmap() + .load(url) + .error(defaultRes) + .dontAnimate() + .priority(priority) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(target) + } + } + + + /** + * 加载图片返回到target中,无占位图 + */ + fun load( + context: Context, + url: String?, + target: Target, + vararg transformation: Transformation? + ) { + getGlideConfig(context)?.apply { + this + .asBitmap() + .load(url) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .transform(*transformation) + .into(target) + } + } + + /** + * 加载图片返回到target中,无占位图 + */ + fun load(context: Context, url: String?, target: Target) { + getGlideConfig(context)?.apply { + this + .asBitmap() + .load(url) + .into(target) + } + } + + /** + * 加载图片并设置缩略图,占位图以及图形变换 + */ + fun load( + context: Context, + url: String?, + @DrawableRes errorRes: Int, + thumbnail: Float, + target: Target, + vararg transformation: Transformation? + ) { + getGlideConfig(context)?.apply { + this.load(url) + .placeholder(errorRes) + .thumbnail(thumbnail) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .transform(*transformation) + .into(target) + } + } + + fun load( + context: Context, + url: String?, + @DrawableRes defaultRes: Int, + target: Target + ) { + getGlideConfig(context)?.apply { + this + .asBitmap() + .load(url) + .placeholder(defaultRes) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(target) + } + } + + fun load( + context: Context, + mrcImageUrl: Any?, + @DrawableRes defaultDrawable: Int, + target: Target + ) { + getGlideConfig(context)?.apply { + this + .asBitmap() + .load(mrcImageUrl) + .placeholder(defaultDrawable) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(target) + } + } + + /** + * 加载本地文件,并设置宽高以及图形变换 + */ + fun loadLocalFile( + filePath: String, + @DrawableRes errorRes: Int, + imageView: ImageView?, + width: Int, + height: Int, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "loadLocalFile imageView is null") + return + } + val file = File(filePath) + getGlideConfig(imageView.context)?.apply { + this + .load(file) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .error(errorRes) + .override(width, height) + .dontAnimate() + .transform(*transformation) + .into(imageView) + } + } + + /** + * 加载本地文件 + */ + fun loadLocalFile( + filePath: String, + @DrawableRes errorRes: Int, + imageView: ImageView?, + width: Int, + height: Int + ) { + if (imageView == null) { + Logger.error(TAG, "loadLocalFile imageView is null") + return + } + val file = File(filePath) + getGlideConfig(imageView.context)?.apply { + this + .load(file) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .error(errorRes) + .override(width, height) + .dontAnimate() + .into(imageView) + } + } + + //加载文件,并传递target + fun loadLocalFile( + context: Context, + filePath: String, + @DrawableRes defaultDrawable: Int, + target: Target + ) { + val file = File(filePath) + getGlideConfig(context)?.apply { + this + .asBitmap() + .load(file) + .placeholder(defaultDrawable) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(target) + } + } + + //加载文件 + fun loadLocalFile( + filePath: String?, + @DrawableRes defaultDrawable: Int, + imageView: ImageView? + ) { + if (imageView == null) + return + if (TextUtils.isEmpty(filePath)) { + Logger.error(TAG, "loadLocalFile url is empty.") + getGlideConfig(imageView.context)?.apply { + this + .load(defaultDrawable) + .placeholder(defaultDrawable) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(imageView) + } + } else { + val file = File(filePath) + getGlideConfig(imageView.context)?.apply { + this + .load(file) + .placeholder(defaultDrawable) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(imageView) + } + } + } + + // 加载圆形头像--小图 + fun loadRoundIcon( + icon: String?, + imageView: ImageView?, + @DrawableRes defaultRes: Int, + ) { + if (imageView == null) { + Logger.error(TAG, "loadRoundIcon imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this + .load(icon) + .dontAnimate() + .placeholder(defaultRes) + .error(defaultRes) + .priority(Priority.IMMEDIATE) + .transform(CircleCrop()) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(imageView) + } + } + + // 加载圆形本地头像 + fun loadRoundIcon(resId: Int, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "loadRoundIcon imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this + .load(resId) + .dontAnimate() + .priority(Priority.IMMEDIATE) + .transform(CircleCrop()) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(imageView) + } + } + + // 加载本地图片,加载到Target + fun loadTargetLocalRes(context: Context, resId: Int, target: Target) { + getGlideConfig(context)?.apply { + this.asBitmap() + .load(resId) + .placeholder(resId) + .dontAnimate() + .into(target) + } + } + + //加载指定大小的图片 + fun loadFixSizeImage( + context: Context, + url: String?, + width: Int, + height: Int, + target: Target + ) { + getGlideConfig(context)?.apply { + this + .asBitmap() + .load(url) + .fitCenter() + .override(width, height) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(target) + } + } + + /** + * 加载本地资源GIF图 + */ + fun loadGiftRes(@DrawableRes resId: Int, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "loadGiftRes imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.asGif().load(resId).into(imageView) + } + } + + /** + * 加载本地资源GIF图,并设置填充模式 + */ + fun loadGiftRes( + @DrawableRes resId: Int, + imageView: ImageView?, + anim: Int + ) { + if (imageView == null) { + Logger.error(TAG, "loadGiftRes imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.asGif().load(resId).centerCrop() + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .transition(GenericTransitionOptions.with(anim)).into(imageView) + } + } + + /** + * 加载GIF图片,无占位图和动画 + */ + fun loadGift(url: String?, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "loadGif imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.asGif().load(url).into(imageView) + } + } + + /** + * 加载GIF图 + * @param resId 占位图 + * @param anim 显示动画 + */ + fun loadGif( + url: String?, + @DrawableRes resId: Int, + anim: Int, + imageView: ImageView? + ) { + if (imageView == null) { + Logger.error(TAG, "loadGif imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this + .asGif() + .load(url).placeholder(resId).error(resId) + .transition(GenericTransitionOptions.with(anim)) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .centerCrop().into(imageView) + } + } + + fun loadGifImage( + imageView: ImageView?, + url: Any, + isCenterCrop: Boolean, + isScale: Boolean, + corner: Float, + maxWidth: Int, + maxHeight: Int, + callBack: IGlideLoaderCallBack? + ) { + if (imageView == null) { + Logger.error(TAG, "loadGifImage imageView is null") + return + } + val builder = ComplexTransformation.ComplexParamsBuilder() + .setMaxHeight(maxHeight) + .setMaxWidth(maxWidth) + .setIsNeedCropCenter(isCenterCrop) + .setCorner(corner) + .setIsNeedCorner(true) + .setIsNeedScale(isScale) + getGlideConfig(imageView.context)?.apply { + this + .load(url) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .transform(ComplexTransformation(builder)) + .override(maxWidth, maxHeight) + .addListener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any, + target: Target, + isFirstResource: Boolean + ): Boolean { + Logger.error(TAG, "loadGifImage url:$url, onException:$e") + callBack?.onError() + return false + } + + override fun onResourceReady( + resource: Drawable?, + model: Any, + target: Target, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { + callBack?.onSuccess() + return false + } + }) + .into(imageView) + } + } + + /** + * 原图片宽高短的一边设置为指定目标值,长的自适应拉伸,按照指定宽高比例和截取方式进行截取 + */ + fun loadAssignScaleImage( + imageView: ImageView?, + url: Any, + targetSize: Int, + whRadio: Float, + clipType: AssignScaleTransformation.ClipType?, + callBack: IGlideLoaderCallBack? + ) { + if (imageView == null) { + Logger.error(TAG, "loadAssignScaleImage imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this + .load(url) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .thumbnail(0.1f) + .transform(AssignScaleTransformation(targetSize, whRadio, clipType!!)) + .listener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any, + target: Target, + isFirstResource: Boolean + ): Boolean { + Logger.error( + TAG, + "loadAssignScaleImage url:$url, onException:$e" + ) + callBack?.onError() + return false + } + + override fun onResourceReady( + resource: Drawable?, + model: Any, + target: Target, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { + callBack?.onSuccess() + return false + } + }) + .into(imageView) + } + } + + + /** + * 加载圆形图片,并设置默认图 + */ + fun loadCircleImage(url: String?, @DrawableRes resId: Int, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "loadCircleImage imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this + .load(url) + .placeholder(resId) + .dontAnimate() + .transform(CircleCrop()) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(imageView) + } + } + + //加载上半圆角图片 + fun loadHalfConnerImage( + url: String?, + @DrawableRes resId: Int, + radius: Float, + imageView: ImageView? + ) { + if (imageView == null) { + Logger.error(TAG, "loadHalfConnerImage imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this + .asBitmap() + .load(url) + .placeholder(resId) + .error(resId) + .transform( + CenterCrop(), RoundedCornersTransformation( + Utils.dip2px(imageView.context, radius), + 0, RoundedCornersTransformation.CornerType.TOP + ) + ).into(imageView) + } + } + + + // 加载圆角图片 + fun loadConnerImage( + url: String?, + @DrawableRes resId: Int, + radius: Float, + imageView: ImageView? + ) { + if (imageView == null) { + Logger.error(TAG, "ImageLoader ImageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.asBitmap().load(url) + .placeholder(resId) + .error(resId) + .transform( + CenterCrop(), RoundedCornersTransformation( + Utils.dip2px(imageView.context, radius), + 0, RoundedCornersTransformation.CornerType.ALL + ) + ).into(imageView) + } + } + + + // 加载本地资源图片 + fun loadLocalRes(@DrawableRes resId: Int, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "loadLocalRes imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(resId) + .dontAnimate() + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(imageView) + } + } + + /** + * 加载本地资源,设置缓存模式,设置加载格式,无占位图 + * @param format 图片加载格式 + */ + fun loadLocalRes( + @DrawableRes resId: Int, + imageView: ImageView?, + format: DecodeFormat + ) { + if (imageView == null) { + Logger.error(TAG, "loadLocalRes imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(resId) + .format(format) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(imageView) + } + + } + + /** + * 加载本地资源 + * @param resId 资源ID + * @param transformation 图片转换器 + */ + fun loadLocalRes( + @DrawableRes resId: Int, + imageView: ImageView?, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "loadLocalRes imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(resId).diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .transform(*transformation).into(imageView) + } + + } + + + // 加载Assets图片 + fun loadAssetsImg(path: String?, imageView: ImageView?) { + if (imageView == null || TextUtils.isEmpty(path)) { + Logger.error(TAG, "loadAssetsImg path is invalid or imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(path) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(imageView) + } + } + + /** + * 加载res资源,并设置居中裁剪 + */ + fun loadResCenterCrop(@DrawableRes resId: Int, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "loadResScale imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(resId) + .centerCrop() + .into(imageView) + } + } + + /** + * 加载res资源,并设置居中裁剪 + */ + fun loadResFitCenter(@DrawableRes resId: Int, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "loadResFitCenter imageView is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(resId) + .fitCenter() + .into(imageView) + } + } + + + /** + * 加载url图片并拉满全屏,无占位图,添加磁盘缓存 + */ + fun loadFitCenter(url: String, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, " loadFitCenter imageViw is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url) + .dontAnimate() + .fitCenter() + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(imageView) + } + } + + /** + * 加载url图片并拉满全屏,添加加载错误占位图 + */ + fun loadFitCenter(url: String?, @DrawableRes errorRes: Int, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, " loadFitCenter imageViw is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url) + .error(errorRes).fitCenter().dontAnimate().into(imageView) + } + } + + /** + * 加载url图片并拉满全屏,添加加载错误占位图 + */ + fun loadFitCenter( + context: Context, + url: String?, + width: Int, + height: Int, + listener: RequestListener + ) { + + getGlideConfig(context)?.apply { + this.load(url) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .addListener(listener) + .fitCenter().dontAnimate().into(width, height) + } + } + + + /** + * 通过target加载图片,并撑满全屏 + */ + fun loadFitCenterIntoTarget(context: Context, url: String, target: Target) { + getGlideConfig(context)?.apply { + this.asBitmap().load(url).dontAnimate().fitCenter() + .into(target) + } + } + + /** + * 加载图片,是否跳过缓存,以及设置图片变换,无占位图 + */ + fun load( + url: String?, + imageView: ImageView?, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "load imageViw is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url) + .transform(*transformation) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .dontAnimate() + .into(imageView) + } + } + + /** + * 加载图片,是否跳过缓存,以及设置图片变换,无占位图 + */ + fun load( + url: String?, + imageView: ImageView?, + @DrawableRes resId: Int, + vararg transformation: Transformation? + ) { + if (imageView == null) { + Logger.error(TAG, "load imageViw is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url) + .placeholder(resId) + .error(resId) + .transform(*transformation) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .dontAnimate() + .into(imageView) + } + } + + + /** + * 使用Glide下载图片,返回File + */ + fun downloadFromUrl( + context: Context?, + url: String?, + listener: RequestListener? + ) { + if (context == null) { + Logger.error(TAG, "load context is null") + return + } + + val glideConfig = getGlideConfig(context) + glideConfig?.downloadOnly()?.load(url)?.listener(listener)?.preload() + } + + /** + * 加载图片,并添加RequestListener ,无占位图 ,listener需使用addListener方法添加 + */ + fun load(url: String?, imageView: ImageView?, listener: RequestListener) { + if (imageView == null || TextUtils.isEmpty(url)) { + Logger.error(TAG, "load imageViw is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url) + .addListener(listener) + .into(imageView) + } + } + + /** + * 加载图片,并添加RequestListener ,无占位图 + */ + fun load( + url: String?, + imageView: ImageView?, + format: DecodeFormat, + listener: RequestListener + ) { + if (imageView == null || TextUtils.isEmpty(url)) { + Logger.error(TAG, "load imageViw is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.asBitmap().load(url).addListener(listener).format(format) + .into(imageView) + } + } + + //加载图片,无默认图片 + fun load(url: String?, imageView: ImageView?) { + if (imageView == null || TextUtils.isEmpty(url)) { + Logger.error(TAG, "load url is invalid or imageViw is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url) + .dontAnimate() + .into(imageView) + } + } + + //加载图片,无默认图片 + fun loadWithError(url: String?, errorDrawable: Int, imageView: ImageView?) { + if (imageView == null || TextUtils.isEmpty(url)) { + Logger.error(TAG, "load url imageViw is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url) + .dontAnimate() + .error(errorDrawable) + .into(imageView) + } + } + + /** + * 加载图片病设置占位图 + */ + fun load(url: String?, @DrawableRes defaultRes: Int, imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "load url is invalid or imageViw is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url) + .placeholder(defaultRes).error(defaultRes) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(imageView) + } + } + fun load(url: String?, @DrawableRes defaultRes: Int, @DrawableRes errorRes: Int,imageView: ImageView?) { + if (imageView == null) { + Logger.error(TAG, "load url is invalid or imageViw is null") + return + } + getGlideConfig(imageView.context)?.apply { + this.load(url) + .placeholder(defaultRes).error(errorRes) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(imageView) + } + } + private fun getGlideConfig(context: Context): RequestManager? { + if (checkActivityIsDestroy(context)) return null + + return Glide.with(context) + } + + /** + * 判断activity是否回收 + * @return true 被回收 ,false 没有被回收 + */ + private fun checkActivityIsDestroy(context: Context): Boolean { + if (context is Application) { + return false + } else if (context is Activity) { + return !ActivityHelper.isCanUse(context) + } else if (context is ContextWrapper) { + val baseContext = context.baseContext + if (baseContext is Activity) { + return !ActivityHelper.isCanUse(baseContext) + } + } + return true + } + + interface IGlideLoaderCallBack { + fun onSuccess() + fun onError() + } + + + companion object { + private const val TAG = "GlideUtils" + + // 0小图(圆形)0=120*120; + const val SMALL_CIRCLE = 0 + + // 1大图(长方形)800*600 + const val BIG_RECTANGLE = 1 + + // 2(正方形)300*300 + const val BIG_SQUARE = 2 + + private val glideUtils by lazy { GlideUtils() } + + @JvmStatic + fun instance(): GlideUtils { + return glideUtils + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/yizhuan/erban/common/photo/PhotoProvider.kt b/app/src/main/java/com/yizhuan/erban/common/photo/PhotoProvider.kt new file mode 100644 index 000000000..c29603dbf --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/common/photo/PhotoProvider.kt @@ -0,0 +1,224 @@ +package com.yizhuan.erban.common.photo + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Environment +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import com.huantansheng.easyphotos.EasyPhotos +import com.huantansheng.easyphotos.constant.Type.* +import com.huantansheng.easyphotos.models.album.entity.Photo +import com.yizhuan.erban.application.XChatApplication +import com.yizhuan.erban.common.delegate.SpDelegate +import com.yizhuan.erban.common.file.FileHelper +import com.yizhuan.erban.common.glide.GlideEngine +import com.yizhuan.xchat_android_core.utils.Logger +import com.yizhuan.xchat_android_library.utils.TimeUtils +import com.yizhuan.xchat_android_library.utils.TimeUtils.TIME_FORMAT +import kotlinx.coroutines.* +import java.io.File + +/** + * Created by wushaocheng on 2022/11/15 + * Desc:图片选择二次封装 + */ +object PhotoProvider { + private const val TAG = "PhotoProvider" + + //上一次选择的时间,避免用户连续进入选择图片页导致删除缓存 + private var mLastSelectTime: Long by SpDelegate("PhotoProvider_last_select_time", 0L) + + /** + * easyPhoto库选择文件copy到内部的目录名 + */ + private const val FOLD_EASY_PHOTO_INTERNAL = "selectPhotoTemp" + private var mPhotoJob: Job? = null + + @JvmStatic + @JvmOverloads + fun photoCamera(fragment: Fragment, resultCode: Int, isClearCache: Boolean = true) { + cancelJop() + mPhotoJob = MainScope().launch { + if (isClearCache && isClearByTime()) { + withContext(Dispatchers.IO) { clearCache() } + } + EasyPhotos.createCamera(fragment, false)//参数说明:上下文,是否使用宽高数据(false时宽高数据为0,扫描速度更快) + .setFileProviderAuthority("${XChatApplication.getApplication().packageName}.fileprovider")//参数说明:见下方`FileProvider的配置` + .start(resultCode) + } + } + + /** + * 喵圈发布动态专用,去掉bmp格式的图片 + */ + @JvmStatic + @JvmOverloads + fun photoProviderPublish(activity: Activity, maxSelect: Int = 1, canChooseGif: Boolean = false, resultCode: Int, isClearCache: Boolean = true) { + cancelJop() + mPhotoJob = MainScope().launch { + if (isClearCache && isClearByTime()) { + withContext(Dispatchers.IO) { clearCache() } + } + EasyPhotos.createAlbum(activity, false, false, GlideEngine())//参数说明:上下文,是否显示相机按钮,是否使用宽高数据(false时宽高数据为0,扫描速度更快),[配置Glide为图片加载引擎](https://github.com/HuanTanSheng/EasyPhotos/wiki/12-%E9%85%8D%E7%BD%AEImageEngine%EF%BC%8C%E6%94%AF%E6%8C%81%E6%89%80%E6%9C%89%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93) + .setCount(maxSelect)//参数说明:最大可选数,默认1 + .setGif(canChooseGif) + .filter(JPEG, JPG, PNG, WEBP) + .setPuzzleMenu(false) + .setCleanMenu(false) + .start(resultCode) + } + } + + @JvmStatic + @JvmOverloads + fun videoProvider(activity: Activity, maxSelect: Int = 1, resultCode: Int, isClearCache: Boolean = true) { + cancelJop() + mPhotoJob = MainScope().launch { + if (isClearCache && isClearByTime()) { + withContext(Dispatchers.IO) { clearCache() } + } + EasyPhotos.createAlbum(activity, false, false, GlideEngine()) + .setCount(maxSelect)//参数说明:最大可选数,默认1 + .setPuzzleMenu(false) + .onlyVideo() + .setCleanMenu(false) + .start(resultCode) + } + } + + @JvmStatic + @JvmOverloads + fun photoProvider(activity: Activity, maxSelect: Int = 1, canChooseGif: Boolean = false, resultCode: Int, isClearCache: Boolean = true) { + cancelJop() + mPhotoJob = MainScope().launch { + if (isClearCache && isClearByTime()) { + withContext(Dispatchers.IO) { clearCache() } + } + EasyPhotos.createAlbum(activity, false, false, GlideEngine())//参数说明:上下文,是否显示相机按钮,是否使用宽高数据(false时宽高数据为0,扫描速度更快),[配置Glide为图片加载引擎](https://github.com/HuanTanSheng/EasyPhotos/wiki/12-%E9%85%8D%E7%BD%AEImageEngine%EF%BC%8C%E6%94%AF%E6%8C%81%E6%89%80%E6%9C%89%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93) + .setCount(maxSelect)//参数说明:最大可选数,默认1 + .setGif(canChooseGif) + .setPuzzleMenu(false) + .setCleanMenu(false) + .start(resultCode) + } + } + + @JvmStatic + @JvmOverloads + fun photoProvider(activity: FragmentActivity, maxSelect: Int = 1, canChooseGif: Boolean = false, resultCode: Int, isClearCache: Boolean = true) { + cancelJop() + mPhotoJob = MainScope().launch { + if (isClearCache && isClearByTime()) { + withContext(Dispatchers.IO) { clearCache() } + } + EasyPhotos.createAlbum(activity, false, false, GlideEngine())//参数说明:上下文,是否显示相机按钮,是否使用宽高数据(false时宽高数据为0,扫描速度更快),[配置Glide为图片加载引擎](https://github.com/HuanTanSheng/EasyPhotos/wiki/12-%E9%85%8D%E7%BD%AEImageEngine%EF%BC%8C%E6%94%AF%E6%8C%81%E6%89%80%E6%9C%89%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93) + .setCount(maxSelect)//参数说明:最大可选数,默认1 + .setGif(canChooseGif) + .setPuzzleMenu(false) + .setCleanMenu(false) + .start(resultCode) + } + } + + @JvmStatic + @JvmOverloads + fun photoProvider(fragment: Fragment, maxSelect: Int = 1, canChooseGif: Boolean = false, resultCode: Int, isClearCache: Boolean = true, minFileSize: Long = 0L) { + cancelJop() + mPhotoJob = MainScope().launch { + if (isClearCache && isClearByTime()) { + withContext(Dispatchers.IO) { clearCache() } + } + EasyPhotos.createAlbum(fragment, false, false, GlideEngine())//参数说明:上下文,是否显示相机按钮,是否使用宽高数据(false时宽高数据为0,扫描速度更快),[配置Glide为图片加载引擎](https://github.com/HuanTanSheng/EasyPhotos/wiki/12-%E9%85%8D%E7%BD%AEImageEngine%EF%BC%8C%E6%94%AF%E6%8C%81%E6%89%80%E6%9C%89%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93) + .setCount(maxSelect)//参数说明:最大可选数,默认1 + .setGif(canChooseGif) + .setMinFileSize(minFileSize) + .setPuzzleMenu(false) + .setCleanMenu(false) + .start(resultCode) + } + } + + @JvmStatic + fun getResultUriList(data: Intent?): List? { + val list: List? = data?.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS) + return list?.takeIf { it.isNotEmpty() }?.map { it.uri } + } + + @JvmStatic + fun getResultPhotoList(data: Intent?): List? { + return data?.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS) + } + + @JvmStatic + fun getResultPathListAsync(data: Intent?, resultListener: ((List?) -> Unit)) { + cancelJop() + mPhotoJob = MainScope().launch { + val list: List? = data?.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS) + val result = withContext(Dispatchers.IO) { copyToInternalCache(list) } + resultListener.invoke(result) + } + } + + /** + * 外部的文件复制到项目目录下,再获取对应路径 + * 修改方案原因: + * 1. android Q 外部文件path变更为类似这种结构:/external/images/media/{文件id},导致无法通过path读取文件信息,文件名字及格式 + * 2. android Q 支持Uri获取文件,但uri获取不到文件类型及不少自身或者sdk的函数从参数需要用到path + * 3. 原本项目功能逻辑很多用到了path(包括不仅仅文件大小,文件类型,作为参数传给其他函数使用(比如BitmapFactory.decodeFile)),直接全局替换为Uri,影响面过大,直接copy一份到自己内部,返回内部的路径,使得外部调用无感知 + * + * 发现几个重点问题: + * 1. 项目使用到BitmapFactory.decodeFile(imgPath, options)之类的方法,该方法在android Q直接使用外部path测试中发现,获取图片信息失败 + * + */ + private fun copyToInternalCache(photos: List?): List? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val foldPath = getInternalPath() + File.separator + val newPaths = ArrayList() + photos?.forEach { + if (it.uri != null && !it.name.isNullOrEmpty()) { + val path = "$foldPath${it.name}" + if (FileHelper.copyFileFromUri(it.uri, path, true)) { + newPaths.add(path) + Logger.debug(TAG, "path: ${it.path} , displayName: ${it.name} , newPath: $path ") + } + } + } + newPaths + } else { + photos?.takeIf { it.isNotEmpty() }?.map { it.path } + } + } + + /** + * 清除复制缓存 + */ + fun clearCache() { + Logger.debug(TAG, "clearCache => mLastSelectTime: ${TimeUtils.getDateTimeString(mLastSelectTime, TIME_FORMAT)}") + FileHelper.removeAllFile(getInternalPath() + File.separator) + } + + /** + * 检查时间,判断是否要删除缓冲 + */ + private fun isClearByTime(): Boolean { + val currentTime = System.currentTimeMillis() + val isClear = currentTime - mLastSelectTime > 10 * 60 * 1000 + mLastSelectTime = currentTime + return isClear + } + + private fun cancelJop() { + if (mPhotoJob?.isActive == true) { + mPhotoJob?.cancel() + } + } + + /** + * easyPhoto内部复制缓存的路径 + */ + private fun getInternalPath(): String { + return ("${FileHelper.getRootFilesDir(Environment.DIRECTORY_PICTURES).absolutePath}${File.separator}$FOLD_EASY_PHOTO_INTERNAL") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/yizhuan/erban/common/transform/AssignScaleTransformation.kt b/app/src/main/java/com/yizhuan/erban/common/transform/AssignScaleTransformation.kt new file mode 100644 index 000000000..c10cb6a53 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/common/transform/AssignScaleTransformation.kt @@ -0,0 +1,96 @@ +package com.yizhuan.erban.common.transform + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.RectF +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import java.security.MessageDigest + +/** + * author: lishangming + * e-mail: lishangming@miya818.com + * time: 2021/09/17 + * desc: glide转化器,原图片宽高短的一边设置为指定目标值,长的自适应拉伸,按照指定宽高比例和截取方式进行截取 + */ +class AssignScaleTransformation(private val targetSize: Int, private val whRadio: Float, private val clipType: ClipType) : BitmapTransformation() { + override fun updateDiskCacheKey(messageDigest: MessageDigest) { + messageDigest.update(("AssignScaleTransformation(${targetSize}_${whRadio}_${clipType})").toByteArray()) + } + + override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap { + return crop(pool, toTransform) ?: toTransform + } + + private fun crop(pool: BitmapPool, source: Bitmap?): Bitmap? { + if (targetSize <= 0 || whRadio <= 0) return null + if (source == null) return null + //计算截取原文件的宽高 + var clipWidth = 0 + var clipHeight = 0 + when { + source.width / whRadio <= source.height -> { + //用宽度计算,按比例拉伸高度比原本的小,故宽度不变,高度按比例设置 + clipWidth = source.width + clipHeight = (source.width / whRadio).toInt() + } + source.height * whRadio <= source.width -> { + //用高度计算,按比例拉伸宽度比原本的小,故高度不变,宽度按比例设置 + clipWidth = (source.height * whRadio).toInt() + clipHeight = source.height + + } + else -> { + clipWidth = source.width + clipHeight = source.height + } + } + + //需要生成图片的宽高 + val resultWidth = targetSize + val resultHeight = (targetSize / whRadio).toInt() + //截取比例 + val left = if (clipWidth < source.width) (source.width - clipWidth) / 2 else 0 + val right = left + clipWidth + var result: Bitmap? = pool[resultWidth, resultHeight, Bitmap.Config.ARGB_8888] + if (result == null) { + result = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ARGB_8888) + } + val targetRect = RectF(0f, 0f, resultWidth.toFloat(), resultHeight.toFloat()) + val sourceRect = when (clipType) { + ClipType.TOP -> { + //顶部截取,固定截取高度 + Rect(left, 0, right, clipHeight) + } + else -> { + //默认中间截取 + val top = if (clipHeight < source.height) (source.height - clipHeight) / 2 else 0 + val bottom = top + clipHeight + Rect(left, top, right, bottom) + } + } + if (result != null) { + val canvas = Canvas(result) + if (clipWidth < source.width || clipHeight < source.height) { + canvas.drawBitmap(source, sourceRect, targetRect, null) + } else { + canvas.drawBitmap(source, null, targetRect, null) + } + } + return result + } + + override fun equals(obj: Any?): Boolean { + return obj is AssignScaleTransformation + } + + override fun hashCode(): Int { + return javaClass.name.hashCode() + } + + enum class ClipType { + TOP,//截取顶部 + CENTER,//截取中间 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/yizhuan/erban/common/transform/ComplexTransformation.java b/app/src/main/java/com/yizhuan/erban/common/transform/ComplexTransformation.java new file mode 100644 index 000000000..7e27e4dc5 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/common/transform/ComplexTransformation.java @@ -0,0 +1,152 @@ +package com.yizhuan.erban.common.transform; + +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; + +import java.security.MessageDigest; + +/** + * Created by zhl on 2020/1/3. + */ +public class ComplexTransformation extends BitmapTransformation { + + private ComplexParamsBuilder mBuilder; + + public ComplexTransformation(@NonNull ComplexParamsBuilder builder) { + mBuilder = builder; + } + + @Override + protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { + return crop(pool, toTransform, mBuilder); + } + + private static Bitmap crop(BitmapPool pool, Bitmap source, ComplexParamsBuilder builder) { + if (source == null) return null; + + int resultWidth = builder.mMaxWidth == 0 ? source.getWidth() : builder.mMaxWidth; + int resultHeight = builder.mMaxHeight == 0 ? source.getHeight() : builder.mMaxHeight; + + if (builder.mIsNeedScale) { + if ((source.getWidth() > resultWidth || source.getHeight() > resultHeight)) { + float scaleX = (float) resultWidth / source.getWidth(); + float scaleY = (float) resultHeight / source.getHeight(); + float scale = Math.min(scaleX, scaleY); + resultWidth = (int) (scale * source.getWidth()); + resultHeight = (int) (scale * source.getHeight()); + } else { + resultWidth = source.getWidth(); + resultHeight = source.getHeight(); + } + } + + Bitmap result = pool.get(resultWidth, resultHeight, Bitmap.Config.ARGB_8888); + + RectF targetRect = new RectF(0, 0, resultWidth, resultHeight); + Canvas canvas = new Canvas(result); + if (builder.mIsNeedCorner && source.getWidth() == resultWidth && source.getHeight() == resultHeight) { + //该图没有过缩放,可直接绘制圆角 + Paint paint = new Paint(); + paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP)); + paint.setAntiAlias(true); + canvas.drawRoundRect(targetRect, builder.mCorner, builder.mCorner, paint); + return result; + } + if (builder.mIsNeedCropCenter && source.getHeight() != source.getWidth()) { + int min = Math.min(source.getWidth(), source.getHeight()); + Rect sourceRect = new Rect((source.getWidth() - min) / 2, + (source.getHeight() - min) / 2, + (source.getWidth() - min) / 2 + min, + (source.getHeight() - min) / 2 + min); + canvas.drawBitmap(source, sourceRect, targetRect, null); + } else { + canvas.drawBitmap(source, null, targetRect, null); + } + if (builder.mIsNeedCorner) { + Paint paint = new Paint(); + paint.setShader(new BitmapShader(result, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP)); + paint.setAntiAlias(true); + Bitmap cornerResult = pool.get(resultWidth, resultHeight, Bitmap.Config.ARGB_8888); + Canvas cornerCanvas = new Canvas(cornerResult); + cornerCanvas.drawRoundRect(targetRect, builder.mCorner, builder.mCorner, paint); + return cornerResult; + } + return result; + } + + @Override + public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { + messageDigest.update(("ComplexTransformation(" + mBuilder.toString() + ")").getBytes()); + } + + public static class ComplexParamsBuilder { + int mMaxWidth; + int mMaxHeight; + boolean mIsNeedScale; + boolean mIsNeedCropCenter; + boolean mIsNeedCorner; + float mCorner; + + public ComplexParamsBuilder setMaxWidth(int mMaxWidth) { + this.mMaxWidth = mMaxWidth; + return this; + } + + public ComplexParamsBuilder setMaxHeight(int mMaxHeight) { + this.mMaxHeight = mMaxHeight; + return this; + } + + public ComplexParamsBuilder setIsNeedScale(boolean mIsNeedScale) { + this.mIsNeedScale = mIsNeedScale; + return this; + } + + public ComplexParamsBuilder setIsNeedCropCenter(boolean mIsNeedCropCenter) { + this.mIsNeedCropCenter = mIsNeedCropCenter; + return this; + } + + public ComplexParamsBuilder setIsNeedCorner(boolean mIsNeedCorner) { + this.mIsNeedCorner = mIsNeedCorner; + return this; + } + + public ComplexParamsBuilder setCorner(float mCorner) { + this.mCorner = mCorner; + return this; + } + + @Override + public String toString() { + return "ComplexParamsBuilder{" + + "mMaxWidth=" + mMaxWidth + + ", mMaxHeight=" + mMaxHeight + + ", mIsNeedScale=" + mIsNeedScale + + ", mIsNeedCropCenter=" + mIsNeedCropCenter + + ", mIsNeedCorner=" + mIsNeedCorner + + ", mCorner=" + mCorner + + '}'; + } + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ComplexTransformation; + } + + @Override + public int hashCode() { + return getClass().getName().hashCode(); + } +} diff --git a/app/src/main/java/com/yizhuan/erban/common/util/ActivityHelper.java b/app/src/main/java/com/yizhuan/erban/common/util/ActivityHelper.java new file mode 100644 index 000000000..949f47648 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/common/util/ActivityHelper.java @@ -0,0 +1,48 @@ +package com.yizhuan.erban.common.util; + +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; +import android.view.ContextThemeWrapper; +import android.view.View; + +import androidx.appcompat.widget.TintContextWrapper; + +/** + * author: wushaocheng + * time: 2022/11/15 + * desc: 判断Activity是否存在 + */ +public class ActivityHelper { + + /** + * 检查Activity是否可用 + */ + public static boolean isCanUse(Activity activity) { + return activity != null && !activity.isFinishing() && !activity.isDestroyed(); + } + + /** + * 从View里面获取Activity对象 + */ + public static Activity getActivityFromView(View view) { + Context context = view.getContext(); + return getActivityFromContext(context); + } + + /** + * 从Context里面获取Activity对象 + */ + public static Activity getActivityFromContext(Context context) { + if (context instanceof Activity) { + return (Activity) context; + } else if (context instanceof ContextThemeWrapper && ((ContextThemeWrapper) context).getBaseContext() instanceof Activity) { + return (Activity) ((ContextWrapper) context).getBaseContext(); + } else if (context instanceof TintContextWrapper) { + return (Activity) ((ContextWrapper) context).getBaseContext(); + } else { + return null; + } + } + +} diff --git a/app/src/main/java/com/yizhuan/erban/common/util/Config.java b/app/src/main/java/com/yizhuan/erban/common/util/Config.java new file mode 100644 index 000000000..10b1c879f --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/common/util/Config.java @@ -0,0 +1,195 @@ +package com.yizhuan.erban.common.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Parcelable; + +import com.tencent.mmkv.MMKV; + +import java.util.Set; + +/** + * author: wushaocheng + * time: 2022/11/15 + * desc: 使用腾讯MMKV框架替代SharedPreference,参考文档:https://github.com/Tencent/MMKV/wiki/android_tutorial_cn + */ +public class Config { + private volatile static Config instance = null; + private MMKV mmkv; + + public static Config getInstance(Context context) { + if (instance == null) { + synchronized (Config.class) { + if (instance == null) { + instance = new Config(context); + } + } + } + return instance; + } + + private Config(Context context) { + try { + if (context == null) { + return; + } + MMKV.initialize(context); + mmkv = MMKV.mmkvWithID("config", MMKV.MULTI_PROCESS_MODE); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void setOnChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) { + if (listener != null && mmkv != null) { + mmkv.registerOnSharedPreferenceChangeListener(listener); + } + } + + /************************************** 编码方法 **************************************/ + + public boolean putBytes(String key, byte[] bytes) { + if (mmkv == null) { + return false; + } + return mmkv.encode(key, bytes); + } + + public boolean putInt(String key, int value) { + if (mmkv == null) { + return false; + } + return mmkv.encode(key, value); + } + + public boolean putLong(String key, long value) { + if (mmkv == null) { + return false; + } + return mmkv.encode(key, value); + } + + public boolean putFloat(String key, float value) { + if (mmkv == null) { + return false; + } + return mmkv.encode(key, value); + } + + public boolean putDouble(String key, double value) { + if (mmkv == null) { + return false; + } + return mmkv.encode(key, value); + } + + public boolean putBoolean(String key, boolean value) { + if (mmkv == null) { + return false; + } + return mmkv.encode(key, value); + } + + public boolean putString(String key, String value) { + if (mmkv == null) { + return false; + } + return mmkv.encode(key, value); + } + + public boolean putStringSet(String key, Set value) { + if (mmkv == null) { + return false; + } + return mmkv.encode(key, value); + } + + public boolean putParcelable(String key, Parcelable value) { + if (mmkv == null) { + return false; + } + return mmkv.encode(key, value); + } + + /************************************** 解码方法 **************************************/ + + public byte[] getBytes(String key, byte[] defaultValue) { + if (mmkv == null) { + return defaultValue; + } + return mmkv.decodeBytes(key, defaultValue); + } + + public int getInt(String key, int defaultValue) { + if (mmkv == null) { + return defaultValue; + } + return mmkv.decodeInt(key, defaultValue); + } + + public long getLong(String key, long defaultValue) { + if (mmkv == null) { + return defaultValue; + } + return mmkv.decodeLong(key, defaultValue); + } + + public float getFloat(String key, float defaultValue) { + if (mmkv == null) { + return defaultValue; + } + return mmkv.decodeFloat(key, defaultValue); + } + + public double getDouble(String key, double defaultValue) { + if (mmkv == null) { + return defaultValue; + } + return mmkv.decodeDouble(key, defaultValue); + } + + public boolean getBoolean(String key, boolean defaultValue) { + if (mmkv == null) { + return defaultValue; + } + return mmkv.decodeBool(key, defaultValue); + } + + public String getString(String key, String defaultValue) { + if (mmkv == null) { + return defaultValue; + } + return mmkv.decodeString(key, defaultValue); + } + + public Set getStringSet(String key, Set defaultValue) { + if (mmkv == null) { + return defaultValue; + } + return mmkv.decodeStringSet(key, defaultValue); + } + + public T getParcelable(String key, Class tClass, T defaultValue) { + if (mmkv == null) { + return defaultValue; + } + return mmkv.decodeParcelable(key, tClass, defaultValue); + } + + /************************************** 清理方法 **************************************/ + + public void remove(String key) { + if (mmkv == null) { + return; + } + mmkv.remove(key); + } + + public void clearAll() { + if (mmkv == null) { + return; + } + mmkv.clearAll(); + } + +} diff --git a/app/src/main/java/com/yizhuan/erban/common/util/SPUtils.java b/app/src/main/java/com/yizhuan/erban/common/util/SPUtils.java new file mode 100644 index 000000000..4e9340d5c --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/common/util/SPUtils.java @@ -0,0 +1,107 @@ +package com.yizhuan.erban.common.util; + +import android.os.Parcelable; + +import com.yizhuan.erban.application.XChatApplication; +import com.yizhuan.xchat_android_library.utils.TimeUtils; + +import java.util.Date; +import java.util.Set; + +/** + * author: wushaocheng + * time: 2022/11/15 + * desc: 封装底层com.tcloud.core.util.Config,方便使用 + */ +public class SPUtils { + + private static Config sConfig = Config.getInstance(XChatApplication.gContext); + + public static void putBytes(String key, byte[] bytes) { + sConfig.putBytes(key, bytes); + } + + public static void putInt(String key, int value) { + sConfig.putInt(key, value); + } + + public static void putLong(String key, long value) { + sConfig.putLong(key, value); + } + + public static void putFloat(String key, float value) { + sConfig.putFloat(key, value); + } + + public static void putDouble(String key, double value) { + sConfig.putDouble(key, value); + } + + public static void putBoolean(String key, boolean value) { + sConfig.putBoolean(key, value); + } + + public static void putString(String key, String value) { + sConfig.putString(key, value); + } + + public static void putStringSet(String key, Set value) { + sConfig.putStringSet(key, value); + } + + public static void putParcelable(String key, Parcelable value) { + sConfig.putParcelable(key, value); + } + + public static byte[] getBytes(String key, byte[] defaultValue) { + return sConfig.getBytes(key, defaultValue); + } + + public static int getInt(String key, int defaultValue) { + return sConfig.getInt(key, defaultValue); + } + + public static long getLong(String key, long defaultValue) { + return sConfig.getLong(key, defaultValue); + } + + public static float getFloat(String key, float defaultValue) { + return sConfig.getFloat(key, defaultValue); + } + + public static double getDouble(String key, double defaultValue) { + return sConfig.getDouble(key, defaultValue); + } + + public static boolean getBoolean(String key, boolean defaultValue) { + return sConfig.getBoolean(key, defaultValue); + } + + public static String getString(String key, String defaultValue) { + return sConfig.getString(key, defaultValue); + } + + public static Set getStringSet(String key, Set defaultValue) { + return sConfig.getStringSet(key, defaultValue); + } + + public static T getParcelable(String key, Class tClass, T defaultValue) { + return sConfig.getParcelable(key, tClass, defaultValue); + } + + public static void remove(String key) { + sConfig.remove(key); + } + + public static void clearAll() { + sConfig.clearAll(); + } + + public static String getSharedDataKey(String constants, long playerId) { + String toDayStr = TimeUtils.date2Str(new Date(), "yyyy-MM-dd"); + return getAccountKey(constants, playerId) + toDayStr; + } + public static String getAccountKey(String key, long playerId) { + return key + playerId ; + } +} diff --git a/app/src/main/java/com/yizhuan/erban/common/util/StringUtils.java b/app/src/main/java/com/yizhuan/erban/common/util/StringUtils.java new file mode 100644 index 000000000..ad9f829f4 --- /dev/null +++ b/app/src/main/java/com/yizhuan/erban/common/util/StringUtils.java @@ -0,0 +1,106 @@ +package com.yizhuan.erban.common.util; + +import com.yizhuan.xchat_android_library.utils.FP; + +public class StringUtils { + + public static boolean isNullOrEmpty(String str) { + return FP.empty(str); + } + + public static boolean isNotNullOrEmpty(String str) { + return !StringUtils.isNullOrEmpty(str); + } + + public static boolean equal(String s1, String s2) { + return StringUtils.equal(s1, s2, false); + } + + public static boolean equal(String s1, String s2, boolean ignoreCase) { + if (s1 != null && s2 != null) { + if (ignoreCase) { + return s1.equalsIgnoreCase(s2); + } + return s1.equals(s2); + } + return s1 == null && s2 == null; + } + + public static int find(String pattern, String s) { + return StringUtils.find(pattern, s, false); + } + + public static int find(String pattern, String s, boolean ignoreCase) { + return StringUtils.find(pattern, s, ignoreCase, false); + } + + public static int find(String pattern, String s, boolean ignoreCase, boolean ignoreWidth) { + if (FP.empty(s)) { + return -1; + } else { + pattern = FP.ref(pattern); + if (ignoreCase) { + pattern = pattern.toLowerCase(); + s = s.toLowerCase(); + } + + if (ignoreWidth) { + pattern = narrow(pattern); + s = narrow(s); + } + + return s.indexOf(pattern); + } + } + + public static String narrow(String s) { + if (FP.empty(s)) { + return ""; + } else { + char[] cs = s.toCharArray(); + + for (int i = 0; i < cs.length; ++i) { + cs[i] = narrow(cs[i]); + } + + return new String(cs); + } + } + + public static char narrow(char c) { + if (c >= '!' && c <= '}') { + return (char) (c - 'ﻠ'); + } else if (c == 12288) { + return (char) (c - 12288 + 32); + } else if (c == '。') { + return '。'; + } else if (c == 12539) { + return '·'; + } else { + return c == 8226 ? '·' : c; + } + } + + public static int ord(char c) { + if ('a' <= c && c <= 'z') { + return c; + } else { + return 'A' <= c && c <= 'Z' ? c - 65 + 97 : 0; + } + } + + public static int compare(String x, String y) { + return FP.ref(x).compareTo(FP.ref(y)); + } + + public static long parseLong(String s) { + long l = 0L; + try { + l = Long.parseLong(s.trim()); + } catch (Exception e) { + return l; + } + return l; + } + +} diff --git a/app/src/main/java/com/yizhuan/erban/ui/widget/marqueeview/Utils.java b/app/src/main/java/com/yizhuan/erban/common/util/Utils.java similarity index 98% rename from app/src/main/java/com/yizhuan/erban/ui/widget/marqueeview/Utils.java rename to app/src/main/java/com/yizhuan/erban/common/util/Utils.java index df76e386f..9d5e0520f 100644 --- a/app/src/main/java/com/yizhuan/erban/ui/widget/marqueeview/Utils.java +++ b/app/src/main/java/com/yizhuan/erban/common/util/Utils.java @@ -1,4 +1,4 @@ -package com.yizhuan.erban.ui.widget.marqueeview; +package com.yizhuan.erban.common.util; import android.annotation.TargetApi; import android.content.Context; @@ -10,7 +10,9 @@ import android.view.WindowManager; import java.util.List; /** - * Created by sunfusheng on 17/8/8. + * author: wushaocheng + * time: 2022/11/15 + * desc: 转换帮助类 */ public class Utils { diff --git a/app/src/main/java/com/yizhuan/erban/common/widget/CircleImageSpan.java b/app/src/main/java/com/yizhuan/erban/common/widget/CircleImageSpan.java index 7a6d7f78a..ea35f0189 100644 --- a/app/src/main/java/com/yizhuan/erban/common/widget/CircleImageSpan.java +++ b/app/src/main/java/com/yizhuan/erban/common/widget/CircleImageSpan.java @@ -16,7 +16,7 @@ import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import com.netease.nim.uikit.support.glide.GlideApp; import com.yizhuan.erban.ui.gift.widget.GlideCircleTransform; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.xchat_android_library.utils.config.BasicConfig; public class CircleImageSpan extends ImageSpan { diff --git a/app/src/main/java/com/yizhuan/erban/common/widget/CustomAutoWidthImageSpan.java b/app/src/main/java/com/yizhuan/erban/common/widget/CustomAutoWidthImageSpan.java index a32dafc9a..00efe54fd 100644 --- a/app/src/main/java/com/yizhuan/erban/common/widget/CustomAutoWidthImageSpan.java +++ b/app/src/main/java/com/yizhuan/erban/common/widget/CustomAutoWidthImageSpan.java @@ -16,7 +16,7 @@ import com.bumptech.glide.load.engine.GlideException; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import com.netease.nim.uikit.support.glide.GlideApp; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.xchat_android_library.utils.config.BasicConfig; import java.lang.ref.WeakReference; diff --git a/app/src/main/java/com/yizhuan/erban/common/widget/CustomImageSpan.java b/app/src/main/java/com/yizhuan/erban/common/widget/CustomImageSpan.java index 5bdef1a32..6622f2732 100644 --- a/app/src/main/java/com/yizhuan/erban/common/widget/CustomImageSpan.java +++ b/app/src/main/java/com/yizhuan/erban/common/widget/CustomImageSpan.java @@ -20,7 +20,7 @@ import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import com.netease.nim.uikit.support.glide.GlideApp; import com.yizhuan.erban.ui.widget.magicindicator.buildins.UIUtil; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.xchat_android_library.utils.SizeUtils; import com.yizhuan.xchat_android_library.utils.config.BasicConfig; diff --git a/app/src/main/java/com/yizhuan/erban/common/widget/dialog/CommonPopupDialog.java b/app/src/main/java/com/yizhuan/erban/common/widget/dialog/CommonPopupDialog.java index d88bf7ce4..89fa4344c 100644 --- a/app/src/main/java/com/yizhuan/erban/common/widget/dialog/CommonPopupDialog.java +++ b/app/src/main/java/com/yizhuan/erban/common/widget/dialog/CommonPopupDialog.java @@ -17,7 +17,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetDialog; import com.yizhuan.erban.R; import com.yizhuan.erban.ui.widget.ButtonItem; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import java.util.List; diff --git a/app/src/main/java/com/yizhuan/erban/family/view/activity/FamilyHomeActivity.java b/app/src/main/java/com/yizhuan/erban/family/view/activity/FamilyHomeActivity.java index 317e149e9..2033d84d0 100644 --- a/app/src/main/java/com/yizhuan/erban/family/view/activity/FamilyHomeActivity.java +++ b/app/src/main/java/com/yizhuan/erban/family/view/activity/FamilyHomeActivity.java @@ -42,7 +42,7 @@ import com.yizhuan.erban.team.view.NimTeamMessageActivity; import com.yizhuan.erban.ui.user.UserInfoActivity; import com.yizhuan.erban.ui.webview.CommonWebViewActivity; import com.yizhuan.erban.ui.widget.ShareDialog; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration; import com.yizhuan.erban.ui.widget.recyclerview.layoutmanager.FullyLinearLayoutManager; import com.yizhuan.xchat_android_core.family.bean.FamilyGameInfo; diff --git a/app/src/main/java/com/yizhuan/erban/family/view/adapter/FamilyMemberAdapter.java b/app/src/main/java/com/yizhuan/erban/family/view/adapter/FamilyMemberAdapter.java index 8fc1211e3..0cdb2956c 100644 --- a/app/src/main/java/com/yizhuan/erban/family/view/adapter/FamilyMemberAdapter.java +++ b/app/src/main/java/com/yizhuan/erban/family/view/adapter/FamilyMemberAdapter.java @@ -14,7 +14,7 @@ import com.chad.library.adapter.base.BaseViewHolder; import com.netease.nim.uikit.support.glide.GlideApp; import com.yizhuan.erban.R; import com.yizhuan.erban.common.widget.CircleImageView; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.xchat_android_core.family.bean.FamilyMemberInfo; import java.util.List; diff --git a/app/src/main/java/com/yizhuan/erban/home/activity/NewUserListActivity.java b/app/src/main/java/com/yizhuan/erban/home/activity/NewUserListActivity.java index 322887d28..374a9e829 100644 --- a/app/src/main/java/com/yizhuan/erban/home/activity/NewUserListActivity.java +++ b/app/src/main/java/com/yizhuan/erban/home/activity/NewUserListActivity.java @@ -14,7 +14,7 @@ import com.yizhuan.erban.base.BaseMvpActivity; import com.yizhuan.erban.home.adapter.FindNewUserListAdapter; import com.yizhuan.erban.home.presenter.NewUserListPresenter; import com.yizhuan.erban.home.view.INewUserListActivityView; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.ui.widget.recyclerview.decoration.SpacingDecoration; import com.yizhuan.xchat_android_core.user.bean.UserInfo; import com.yizhuan.xchat_android_library.base.factory.CreatePresenter; diff --git a/app/src/main/java/com/yizhuan/erban/home/adapter/ContactsIndicatorAdapter.java b/app/src/main/java/com/yizhuan/erban/home/adapter/ContactsIndicatorAdapter.java index cd9fd92b1..3f2853ea8 100644 --- a/app/src/main/java/com/yizhuan/erban/home/adapter/ContactsIndicatorAdapter.java +++ b/app/src/main/java/com/yizhuan/erban/home/adapter/ContactsIndicatorAdapter.java @@ -14,7 +14,7 @@ import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.C import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.IPagerIndicator; import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.IPagerTitleView; import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.indicators.LinePagerIndicator; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import java.util.List; diff --git a/app/src/main/java/com/yizhuan/erban/ui/anim/FlowFaceDrawable.java b/app/src/main/java/com/yizhuan/erban/ui/anim/FlowFaceDrawable.java index 3c7dac48b..5a3fb349b 100644 --- a/app/src/main/java/com/yizhuan/erban/ui/anim/FlowFaceDrawable.java +++ b/app/src/main/java/com/yizhuan/erban/ui/anim/FlowFaceDrawable.java @@ -15,7 +15,7 @@ import androidx.annotation.Nullable; import com.bumptech.glide.request.FutureTarget; import com.netease.nim.uikit.support.glide.GlideApp; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/com/yizhuan/erban/ui/anim/OverlayFaceDrawable.java b/app/src/main/java/com/yizhuan/erban/ui/anim/OverlayFaceDrawable.java index c4b91bddb..f2ae6ff30 100644 --- a/app/src/main/java/com/yizhuan/erban/ui/anim/OverlayFaceDrawable.java +++ b/app/src/main/java/com/yizhuan/erban/ui/anim/OverlayFaceDrawable.java @@ -15,7 +15,7 @@ import androidx.annotation.Nullable; import com.bumptech.glide.request.FutureTarget; import com.netease.nim.uikit.support.glide.GlideApp; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/com/yizhuan/erban/ui/widget/UserInfoDialog.java b/app/src/main/java/com/yizhuan/erban/ui/widget/UserInfoDialog.java index ed4b0d0e1..940c69cf9 100644 --- a/app/src/main/java/com/yizhuan/erban/ui/widget/UserInfoDialog.java +++ b/app/src/main/java/com/yizhuan/erban/ui/widget/UserInfoDialog.java @@ -1067,7 +1067,6 @@ public class UserInfoDialog extends AppCompatDialog implements View.OnClickListe private void handleInviteMicItem(List buttonItems) { if (!SuperAdminUtil.isSuperAdmin() && !(AvRoomDataManager.get().isSingleRoom() && AvRoomDataManager.get().isOpenAnotherPKMode())) { -// buttonItems.add(createGiveGiftMicItem()); buttonItems.add(createInviteMicItem()); } } diff --git a/app/src/main/java/com/yizhuan/erban/ui/widget/dynamicface/DynamicFaceDialog.java b/app/src/main/java/com/yizhuan/erban/ui/widget/dynamicface/DynamicFaceDialog.java index 345a0d0f5..35e124999 100644 --- a/app/src/main/java/com/yizhuan/erban/ui/widget/dynamicface/DynamicFaceDialog.java +++ b/app/src/main/java/com/yizhuan/erban/ui/widget/dynamicface/DynamicFaceDialog.java @@ -20,7 +20,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetDialog; import com.yizhuan.erban.R; import com.yizhuan.erban.common.widget.dialog.DialogManager; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.vip.VipMainActivity; import com.yizhuan.xchat_android_core.market_verify.MarketVerifyModel; import com.yizhuan.xchat_android_core.room.event.FaceIsReadyEvent; diff --git a/app/src/main/java/com/yizhuan/erban/ui/widget/marqueeview/MarqueeView.java b/app/src/main/java/com/yizhuan/erban/ui/widget/marqueeview/MarqueeView.java index 9d8e135f3..d93a55154 100644 --- a/app/src/main/java/com/yizhuan/erban/ui/widget/marqueeview/MarqueeView.java +++ b/app/src/main/java/com/yizhuan/erban/ui/widget/marqueeview/MarqueeView.java @@ -16,6 +16,7 @@ import android.widget.ViewFlipper; import androidx.annotation.AnimRes; import com.yizhuan.erban.R; +import com.yizhuan.erban.common.util.Utils; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/res/drawable/photo_dialog_bg.xml b/app/src/main/res/drawable/photo_dialog_bg.xml new file mode 100644 index 000000000..7b9ea1937 --- /dev/null +++ b/app/src/main/res/drawable/photo_dialog_bg.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/photo_dialog.xml b/app/src/main/res/layout/photo_dialog.xml new file mode 100644 index 000000000..48fea34ab --- /dev/null +++ b/app/src/main/res/layout/photo_dialog.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 587d479d3..f00b48287 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -25,4 +25,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e3cc8c6e3..c91661825 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -114,7 +114,7 @@ 實名認證 關聯房間 - 僅%s或%s的用於可發起聊天 + 僅%s或%s的用戶可發起聊天 房主已下線 更多好玩的房間在Peko等您喲!去看看! diff --git a/app/src/module_labour_union/java/com/yizhuan/erban/module_hall/income/IncomeDetailActivity.java b/app/src/module_labour_union/java/com/yizhuan/erban/module_hall/income/IncomeDetailActivity.java index 4ea796d90..32e0dfe64 100644 --- a/app/src/module_labour_union/java/com/yizhuan/erban/module_hall/income/IncomeDetailActivity.java +++ b/app/src/module_labour_union/java/com/yizhuan/erban/module_hall/income/IncomeDetailActivity.java @@ -16,7 +16,7 @@ import com.yizhuan.erban.module_hall.income.adapter.IncomeDetailAdapter; import com.yizhuan.erban.module_hall.income.presenter.IncomeDetailPresenter; import com.yizhuan.erban.module_hall.income.view.IIncomeDetailView; import com.yizhuan.erban.ui.utils.ImageLoadUtils; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.ui.widget.recyclerview.decoration.SpacingDecoration; import com.yizhuan.xchat_android_core.module_hall.income.bean.IncomeGiftInfo; import com.yizhuan.xchat_android_core.module_hall.income.bean.IncomeInfo; diff --git a/app/src/module_music/java/com/yizhuan/tutu/music/widget/VoiceSeekDialog.java b/app/src/module_music/java/com/yizhuan/tutu/music/widget/VoiceSeekDialog.java index 5f82ca0dc..22c410f63 100644 --- a/app/src/module_music/java/com/yizhuan/tutu/music/widget/VoiceSeekDialog.java +++ b/app/src/module_music/java/com/yizhuan/tutu/music/widget/VoiceSeekDialog.java @@ -12,7 +12,7 @@ import androidx.annotation.NonNull; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetDialog; import com.yizhuan.erban.R; -import com.yizhuan.erban.ui.widget.marqueeview.Utils; +import com.yizhuan.erban.common.util.Utils; import com.yizhuan.xchat_android_core.music.model.PlayerModel; diff --git a/core/build.gradle b/core/build.gradle index 9fb90080a..e02ec20c3 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -5,10 +5,10 @@ apply plugin: 'kotlin-android-extensions' apply from: '../mob.gradle' android { - compileSdkVersion 32 + compileSdkVersion COMPILE_SDK_VERSION.toInteger() defaultConfig { - minSdkVersion 21 - targetSdkVersion 32 + minSdkVersion MIN_SDK_VERSION.toInteger() + targetSdkVersion TARGET_SDK_VERSION.toInteger() testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/easyphotos/.gitignore b/easyphotos/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/easyphotos/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/easyphotos/build.gradle b/easyphotos/build.gradle new file mode 100644 index 000000000..ccb6b998a --- /dev/null +++ b/easyphotos/build.gradle @@ -0,0 +1,38 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdk COMPILE_SDK_VERSION.toInteger() + + defaultConfig { + targetSdk TARGET_SDK_VERSION.toInteger() + minSdk MIN_SDK_VERSION.toInteger() + + testApplicationId 'com.soundcloud.android.crop.test' + testInstrumentationRunner 'android.test.InstrumentationTestRunner' + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + buildToolsVersion = '30.0.3' +} + +dependencies { + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.appcompat:appcompat:1.4.2' + implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' + implementation 'com.github.chrisbanes:PhotoView:2.3.0' +} \ No newline at end of file diff --git a/easyphotos/src/main/AndroidManifest.xml b/easyphotos/src/main/AndroidManifest.xml new file mode 100644 index 000000000..157ddb6ea --- /dev/null +++ b/easyphotos/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/EasyPhotos.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/EasyPhotos.java new file mode 100644 index 000000000..f659710e4 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/EasyPhotos.java @@ -0,0 +1,345 @@ +package com.huantansheng.easyphotos; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.graphics.Bitmap; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; + +import com.huantansheng.easyphotos.builder.AlbumBuilder; +import com.huantansheng.easyphotos.callback.PuzzleCallback; +import com.huantansheng.easyphotos.engine.ImageEngine; +import com.huantansheng.easyphotos.models.ad.AdListener; +import com.huantansheng.easyphotos.models.album.AlbumModel; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.models.sticker.StickerModel; +import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData; +import com.huantansheng.easyphotos.ui.PuzzleActivity; +import com.huantansheng.easyphotos.utils.bitmap.BitmapUtils; +import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack; +import com.huantansheng.easyphotos.utils.media.MediaScannerConnectionUtils; +import com.huantansheng.easyphotos.utils.result.EasyResult; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * EasyPhotos的启动管理器 + * Created by huan on 2017/10/18. + */ +public class EasyPhotos { + + //easyPhotos的返回数据Key + public static final String RESULT_PHOTOS = "keyOfEasyPhotosResult"; + public static final String RESULT_SELECTED_ORIGINAL = "keyOfEasyPhotosResultSelectedOriginal"; + + /** + * 预加载 + * 调不调用该方法都可以,不调用不影响EasyPhotos正常使用 + * 第一次扫描媒体库可能会慢,调用预加载会使真正打开相册的速度加快 + * 若调用该方法,建议自行判断代码书写位置,建议在用户打开相册的3秒前调用,比如app主页面或调用相册的上一页 + * 该方法如果没有授权读取权限的话,是无效的,所以外部加不加权限控制都可以,加的话保证执行,不加也不影响程序正常使用 + * + * @param cxt 上下文 + */ + public static void preLoad(Context cxt) { + AlbumModel.getInstance().query(cxt, null); + } + + /** + * 预加载 + * 调不调用该方法都可以,不调用不影响EasyPhotos正常使用 + * 第一次扫描媒体库可能会慢,调用预加载会使真正打开相册的速度加快 + * 若调用该方法,建议自行判断代码书写位置,建议在用户打开相册的3秒前调用,比如app主页面或调用相册的上一页 + * 该方法如果没有授权读取权限的话,是无效的,所以外部加不加权限控制都可以,加的话保证执行,不加也不影响程序正常使用 + * + * @param cxt 上下文 + * @param callBack 预加载完成的回调,若进行UI操作,需自行切回主线程。 + */ + public static void preLoad(Context cxt, AlbumModel.CallBack callBack) { + AlbumModel.getInstance().query(cxt, callBack); + } + + + /** + * 创建相机 + * + * @param activity 上下文 + * @param useWidth 是否使用宽高数据 + * @return AlbumBuilder + */ + public static AlbumBuilder createCamera(Activity activity, + boolean useWidth) { + return AlbumBuilder.createCamera(activity).setUseWidth(useWidth); + } + + public static AlbumBuilder createCamera(Fragment fragment, + boolean useWidth) { + return AlbumBuilder.createCamera(fragment).setUseWidth(useWidth); + } + + public static AlbumBuilder createCamera(FragmentActivity activity, + boolean useWidth) { + return AlbumBuilder.createCamera(activity).setUseWidth(useWidth); + } + + public static AlbumBuilder createCamera(androidx.fragment.app.Fragment fragmentV, + boolean useWidth) { + return AlbumBuilder.createCamera(fragmentV).setUseWidth(useWidth); + } + + /** + * 创建相册 + * + * @param activity 上下文 + * @param isShowCamera 是否显示相机按钮 + * @param useWidth 是否使用宽高数据。 + * true:会保证宽高数据的正确性,返回速度慢,耗时,尤其在华为mate30上,可能点击完成后会加载三四秒才能返回。 + * false:有宽高数据但不保证正确性,点击完成后秒回,但可能有因旋转问题导致的宽高相反的情况,以及极少数的宽高为0情况。 + * @param imageEngine 图片加载引擎的具体实现 + * @return AlbumBuilder 建造者模式配置其他选项 + */ + public static AlbumBuilder createAlbum(Activity activity, boolean isShowCamera, + boolean useWidth, @NonNull ImageEngine imageEngine) { + return AlbumBuilder.createAlbum(activity, isShowCamera, imageEngine).setUseWidth(useWidth); + } + + public static AlbumBuilder createAlbum(Fragment fragment, boolean isShowCamera, + boolean useWidth, @NonNull ImageEngine imageEngine) { + return AlbumBuilder.createAlbum(fragment, isShowCamera, imageEngine).setUseWidth(useWidth); + } + + public static AlbumBuilder createAlbum(FragmentActivity activity, boolean isShowCamera, + boolean useWidth, @NonNull ImageEngine imageEngine) { + return AlbumBuilder.createAlbum(activity, isShowCamera, imageEngine).setUseWidth(useWidth); + } + + public static AlbumBuilder createAlbum(androidx.fragment.app.Fragment fragmentV, + boolean isShowCamera, boolean useWidth, + @NonNull ImageEngine imageEngine) { + return AlbumBuilder.createAlbum(fragmentV, isShowCamera, imageEngine).setUseWidth(useWidth); + } + + +//*********************AD************************************ + + + /** + * 设置广告监听 + * 内部使用,无需关心 + * + * @param adListener 广告监听 + */ + public static void setAdListener(AdListener adListener) { + AlbumBuilder.setAdListener(adListener); + } + + /** + * 刷新图片列表广告数据 + */ + public static void notifyPhotosAdLoaded() { + AlbumBuilder.notifyPhotosAdLoaded(); + } + + /** + * 刷新专辑项目列表广告 + */ + public static void notifyAlbumItemsAdLoaded() { + AlbumBuilder.notifyAlbumItemsAdLoaded(); + } + + +//*************************bitmap功能***********************************/ + + /** + * 回收bitmap + * + * @param bitmap 要回收的bitmap + */ + public static void recycle(Bitmap bitmap) { + BitmapUtils.recycle(bitmap); + } + + /** + * 回收bitmap数组中的所有图片 + * + * @param bitmaps 要回收的bitmap数组 + */ + public static void recycle(Bitmap... bitmaps) { + BitmapUtils.recycle(bitmaps); + } + + /** + * 回收bitmap集合中的所有图片 + * + * @param bitmaps 要回收的bitmap集合 + */ + public static void recycle(List bitmaps) { + BitmapUtils.recycle(bitmaps); + } + + /** + * 给图片添加水印,水印会根据图片宽高自动缩放处理 + * + * @param watermark 水印 + * @param image 添加水印的图片 + * @param srcImageWidth 水印对应的原图片宽度,即ui制作水印时参考的要添加水印的图片的宽度 + * @param offsetX 添加水印的X轴偏移量 + * @param offsetY 添加水印的Y轴偏移量 + * @param addInLeft true 在左下角添加水印,false 在右下角添加水印 + * @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap + * 不是用户手机内图片,填0即可。 + * @return 添加水印后的bitmap + */ + public static Bitmap addWatermark(Bitmap watermark, Bitmap image, int srcImageWidth, + int offsetX, int offsetY, boolean addInLeft, + int orientation) { + return BitmapUtils.addWatermark(watermark, image, srcImageWidth, offsetX, offsetY, + addInLeft, orientation); + } + + /** + * 给图片添加带文字和图片的水印,水印会根据图片宽高自动缩放处理 + * + * @param watermark 水印图片 + * @param image 要加水印的图片 + * @param srcImageWidth 水印对应的原图片宽度,即ui制作水印时参考的要添加水印的图片的宽度 + * @param text 要添加的文字 + * @param offsetX 添加水印的X轴偏移量 + * @param offsetY 添加水印的Y轴偏移量 + * @param addInLeft true 在左下角添加水印,false 在右下角添加水印 + * @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap + * 不是用户手机内图片,填0即可。 + * @return 添加水印后的bitmap + */ + public static Bitmap addWatermarkWithText(Bitmap watermark, Bitmap image, int srcImageWidth, + @NonNull String text, int offsetX, int offsetY, + boolean addInLeft, int orientation) { + return BitmapUtils.addWatermarkWithText(watermark, image, srcImageWidth, text, offsetX, + offsetY, + addInLeft, orientation); + } + + /** + * 保存Bitmap到指定文件夹 + * + * @param act 上下文 + * @param dirPath 文件夹全路径 + * @param bitmap bitmap + * @param namePrefix 保存文件的前缀名,文件最终名称格式为:前缀名+自动生成的唯一数字字符+.png + * @param notifyMedia 是否更新到媒体库 + * @param callBack 保存图片后的回调,回调已经处于UI线程 + */ + public static void saveBitmapToDir(Activity act, String dirPath, String namePrefix, + Bitmap bitmap, boolean notifyMedia, + SaveBitmapCallBack callBack) { + BitmapUtils.saveBitmapToDir(act, dirPath, namePrefix, bitmap, notifyMedia, callBack); + } + + + /** + * 把View画成Bitmap + * + * @param view 要处理的View + * @return Bitmap + */ + public static Bitmap createBitmapFromView(View view) { + return BitmapUtils.createBitmapFromView(view); + } + + /** + * 启动拼图(最多对9张图片进行拼图) + * + * @param act 上下文 + * @param photos 图片集合(最多对9张图片进行拼图) + * @param puzzleSaveDirPath 拼图完成保存的文件夹全路径 + * @param puzzleSaveNamePrefix 拼图完成保存的文件名前缀,最终格式:前缀+默认生成唯一数字标识+.png + * @param requestCode 请求code + * @param replaceCustom 单击替换拼图中的某张图片时,是否以startForResult的方式启动你的自定义界面,该界面与传进来的act + * 为同一界面。false则在EasyPhotos内部完成,正常需求直接写false即可。 + * true的情况适用于:用于拼图的图片集合中包含网络图片,是在你的act界面中获取并下载的(也可以直接用网络地址,不用下载后的本地地址,也就是可以不下载下来),而非单纯本地相册。举例:你的act中有两个按钮,一个指向本地相册,一个指向网络相册,用户在该界面任意选择,选择好图片后跳转到拼图界面,用户在拼图界面点击替换按钮,将会启动一个新的act界面,这时,act只让用户在网络相册和本地相册选择一张图片,选择好执行 + * Intent intent = new Intent(); + * intent.putParcelableArrayListExtra(AlbumBuilder.RESULT_PHOTOS + * , photos); + * act.setResult(RESULT_OK,intent); 并关闭act,回到拼图界面,完成替换。 + * @param imageEngine 图片加载引擎的具体实现 + */ + + public static void startPuzzleWithPhotos(Activity act, ArrayList photos, + String puzzleSaveDirPath, + String puzzleSaveNamePrefix, int requestCode, + boolean replaceCustom, + @NonNull ImageEngine imageEngine) { + act.setResult(Activity.RESULT_OK); + PuzzleActivity.startWithPhotos(act, photos, puzzleSaveDirPath, puzzleSaveNamePrefix, + requestCode, replaceCustom, imageEngine); + } + + public static void startPuzzleWithPhotos(FragmentActivity act, ArrayList photos, + String puzzleSaveDirPath, + String puzzleSaveNamePrefix, boolean replaceCustom, + @NonNull ImageEngine imageEngine, + PuzzleCallback callback) { + act.setResult(Activity.RESULT_OK); + EasyResult.get(act).startPuzzleWithPhotos(photos, puzzleSaveDirPath, puzzleSaveNamePrefix + , replaceCustom, imageEngine, callback); + } + + + //**************更新媒体库*********************** + + /** + * 更新媒体文件到媒体库 + * + * @param cxt 上下文 + * @param filePaths 更新的文件地址 + */ + public static void notifyMedia(Context cxt, String... filePaths) { + MediaScannerConnectionUtils.refresh(cxt, filePaths); + } + + /** + * 更新媒体文件到媒体库 + * + * @param cxt 上下文 + * @param files 更新的文件 + */ + public static void notifyMedia(Context cxt, File... files) { + MediaScannerConnectionUtils.refresh(cxt, files); + } + + /** + * 更新媒体文件到媒体库 + * + * @param cxt 上下文 + * @param fileList 更新的文件地址集合 + */ + public static void notifyMedia(Context cxt, List fileList) { + MediaScannerConnectionUtils.refresh(cxt, fileList); + } + + + //*********************************贴纸*************************** + + + /** + * 添加文字贴纸的文字数据 + * + * @param textStickerData 文字贴纸的文字数据 + */ + public static void addTextStickerData(TextStickerData... textStickerData) { + StickerModel.textDataList.addAll(Arrays.asList(textStickerData)); + } + + /** + * 清空文字贴纸的数据 + */ + public static void clearTextStickerDataList() { + StickerModel.textDataList.clear(); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/builder/AlbumBuilder.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/builder/AlbumBuilder.java new file mode 100644 index 000000000..f3f446666 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/builder/AlbumBuilder.java @@ -0,0 +1,618 @@ +package com.huantansheng.easyphotos.builder; + +import android.app.Activity; +import android.net.Uri; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; + +import com.huantansheng.easyphotos.callback.SelectCallback; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.engine.ImageEngine; +import com.huantansheng.easyphotos.models.ad.AdListener; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.EasyPhotosActivity; +import com.huantansheng.easyphotos.utils.result.EasyResult; +import com.huantansheng.easyphotos.utils.uri.UriUtils; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * EasyPhotos的启动管理器 + * Created by huan on 2017/10/18. + */ +public class AlbumBuilder { + + /** + * 启动模式 + * CAMERA-相机 + * ALBUM-相册专辑 + * ALBUM_CAMERA-带有相机按钮的相册专辑 + */ + private enum StartupType { + CAMERA, + ALBUM, + ALBUM_CAMERA + } + + private static AlbumBuilder instance; + private WeakReference mActivity; + private WeakReference mFragmentV; + private WeakReference mFragment; + private StartupType startupType; + private WeakReference adListener; + + //私有构造函数,不允许外部调用,真正实例化通过静态方法实现 + private AlbumBuilder(Activity activity, StartupType startupType) { + mActivity = new WeakReference<>(activity); + this.startupType = startupType; + } + + private AlbumBuilder(android.app.Fragment fragment, StartupType startupType) { + mFragment = new WeakReference<>(fragment); + this.startupType = startupType; + } + + private AlbumBuilder(FragmentActivity activity, StartupType startupType) { + mActivity = new WeakReference<>(activity); + this.startupType = startupType; + } + + private AlbumBuilder(Fragment fragment, StartupType startupType) { + mFragmentV = new WeakReference<>(fragment); + this.startupType = startupType; + } + + /** + * 内部处理相机和相册的实例 + * + * @param activity Activity的实例 + * @return AlbumBuilder EasyPhotos的实例 + */ + + private static AlbumBuilder with(Activity activity, StartupType startupType) { + clear(); + instance = new AlbumBuilder(activity, startupType); + return instance; + } + + + private static AlbumBuilder with(android.app.Fragment fragment, StartupType startupType) { + clear(); + instance = new AlbumBuilder(fragment, startupType); + return instance; + } + + private static AlbumBuilder with(FragmentActivity activity, StartupType startupType) { + clear(); + instance = new AlbumBuilder(activity, startupType); + return instance; + } + + private static AlbumBuilder with(Fragment fragmentV, StartupType startupType) { + clear(); + instance = new AlbumBuilder(fragmentV, startupType); + return instance; + } + + + /** + * 创建相机 + * + * @param activity 上下文 + * @return AlbumBuilder + */ + + public static AlbumBuilder createCamera(Activity activity) { + return AlbumBuilder.with(activity, StartupType.CAMERA); + } + + + public static AlbumBuilder createCamera(android.app.Fragment fragment) { + return AlbumBuilder.with(fragment, StartupType.CAMERA); + } + + public static AlbumBuilder createCamera(FragmentActivity activity) { + return AlbumBuilder.with(activity, StartupType.CAMERA); + } + + public static AlbumBuilder createCamera(Fragment fragmentV) { + return AlbumBuilder.with(fragmentV, StartupType.CAMERA); + } + + /** + * 创建相册 + * + * @param activity 上下文 + * @param isShowCamera 是否显示相机按钮 + * @param imageEngine 图片加载引擎的具体实现 + */ + public static AlbumBuilder createAlbum(Activity activity, boolean isShowCamera, + @NonNull ImageEngine imageEngine) { + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + if (isShowCamera) { + return AlbumBuilder.with(activity, StartupType.ALBUM_CAMERA); + } else { + return AlbumBuilder.with(activity, StartupType.ALBUM); + } + } + + public static AlbumBuilder createAlbum(android.app.Fragment fragment, boolean isShowCamera, + @NonNull ImageEngine imageEngine) { + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + if (isShowCamera) { + return AlbumBuilder.with(fragment, StartupType.ALBUM_CAMERA); + } else { + return AlbumBuilder.with(fragment, StartupType.ALBUM); + } + } + + public static AlbumBuilder createAlbum(FragmentActivity activity, boolean isShowCamera, + @NonNull ImageEngine imageEngine) { + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + if (isShowCamera) { + return AlbumBuilder.with(activity, StartupType.ALBUM_CAMERA); + } else { + return AlbumBuilder.with(activity, StartupType.ALBUM); + } + } + + public static AlbumBuilder createAlbum(Fragment fragmentV, boolean isShowCamera, + @NonNull ImageEngine imageEngine) { + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + if (isShowCamera) { + return AlbumBuilder.with(fragmentV, StartupType.ALBUM_CAMERA); + } else { + return AlbumBuilder.with(fragmentV, StartupType.ALBUM); + } + } + + /** + * 设置fileProvider字段 + * + * @param fileProviderAuthority fileProvider字段 + * @return AlbumBuilder + */ + public AlbumBuilder setFileProviderAuthority(String fileProviderAuthority) { + Setting.fileProviderAuthority = fileProviderAuthority; + return AlbumBuilder.this; + } + + /** + * 设置选择数 + * + * @param selectorMaxCount 最大选择数 + * @return AlbumBuilder + */ + public AlbumBuilder setCount(int selectorMaxCount) { + if (Setting.complexSelector) return AlbumBuilder.this; + Setting.count = selectorMaxCount; + return AlbumBuilder.this; + } + + /** + * 设置是否使用宽高数据 + * + * @param useWidth 是否使用宽高数据,需要使用写true,不用写false。 + * true:会保证宽高数据的正确性,返回速度慢,耗时。 + * false:宽高数据为0。 + * @return AlbumBuilder + */ + public AlbumBuilder setUseWidth(boolean useWidth) { + Setting.useWidth = useWidth; + return AlbumBuilder.this; + } + + /** + * 支持复杂选择情况 + * + * @param singleType 是否只能选择一种文件类型,如用户选择视频后不可以选择图片,若false则可以同时选择 + * @param videoCount 可选择视频类型文件的最大数 + * @param pictureCount 可选择图片类型文件的最大数 + */ + public AlbumBuilder complexSelector(boolean singleType, int videoCount, int pictureCount) { + Setting.complexSelector = true; + Setting.complexSingleType = singleType; + Setting.complexVideoCount = videoCount; + Setting.complexPictureCount = pictureCount; + Setting.count = videoCount + pictureCount; + Setting.showVideo = true; + return AlbumBuilder.this; + } + + /** + * 设置相机按钮位置 + * + * @param cLocation 使用Material Design风格相机按钮 默认 BOTTOM_RIGHT + * @return AlbumBuilder + */ + public AlbumBuilder setCameraLocation(@Setting.Location int cLocation) { + Setting.cameraLocation = cLocation; + return AlbumBuilder.this; + } + + /** + * 设置显示照片的最小文件大小 + * + * @param minFileSize 最小文件大小,单位Bytes + * @return AlbumBuilder + */ + public AlbumBuilder setMinFileSize(long minFileSize) { + Setting.minSize = minFileSize; + return AlbumBuilder.this; + } + + /** + * 设置显示照片的最小宽度 + * + * @param minWidth 照片的最小宽度,单位Px + * @return AlbumBuilder + */ + public AlbumBuilder setMinWidth(int minWidth) { + Setting.minWidth = minWidth; + return AlbumBuilder.this; + } + + /** + * 设置显示照片的最小高度 + * + * @param minHeight 显示照片的最小高度,单位Px + * @return AlbumBuilder + */ + public AlbumBuilder setMinHeight(int minHeight) { + Setting.minHeight = minHeight; + return AlbumBuilder.this; + } + + /** + * 设置默认选择图片集合 + * + * @param selectedPhotos 默认选择图片集合 + * @return AlbumBuilder + */ + public AlbumBuilder setSelectedPhotos(ArrayList selectedPhotos) { + Setting.selectedPhotos.clear(); + if (selectedPhotos.isEmpty()) { + return AlbumBuilder.this; + } + Setting.selectedPhotos.addAll(selectedPhotos); + Setting.selectedOriginal = selectedPhotos.get(0).selectedOriginal; + return AlbumBuilder.this; + } + + /** + * 设置默认选择图片地址集合 + * + * @param selectedPhotoPaths 默认选择图片地址集合 + * @return AlbumBuilder + * @Deprecated android 10 不推荐使用直接使用Path方式,推荐使用Photo类 + */ + @Deprecated + public AlbumBuilder setSelectedPhotoPaths(ArrayList selectedPhotoPaths) { + Setting.selectedPhotos.clear(); + ArrayList selectedPhotos = new ArrayList<>(); + for (String path : selectedPhotoPaths) { + File file = new File(path); + Uri uri = null; + if (null != mActivity && null != mActivity.get()) { + uri = UriUtils.getUri(mActivity.get(), file); + } + if (null != mFragment && null != mFragment.get()) { + uri = UriUtils.getUri(mFragment.get().getActivity(), file); + } + if (null != mFragmentV && null != mFragmentV.get()) { + uri = UriUtils.getUri(mFragmentV.get().getActivity(), file); + } + if (uri == null) { + uri = Uri.fromFile(file); + } + Photo photo = new Photo(null, uri, path, 0, 0, 0, 0, 0, 0, null); + selectedPhotos.add(photo); + } + Setting.selectedPhotos.addAll(selectedPhotos); + return AlbumBuilder.this; + } + + + /** + * 原图按钮设置,不调用该方法不显示原图按钮 + * + * @param isChecked 原图选项默认状态是否为选中状态 + * @param usable 原图按钮是否可使用 + * @param unusableHint 原图按钮不可使用时给用户的文字提示 + * @return AlbumBuilder + */ + public AlbumBuilder setOriginalMenu(boolean isChecked, boolean usable, String unusableHint) { + Setting.showOriginalMenu = true; + Setting.selectedOriginal = isChecked; + Setting.originalMenuUsable = usable; + Setting.originalMenuUnusableHint = unusableHint; + return AlbumBuilder.this; + } + + + /** + * 是否显示拼图按钮 + * + * @param shouldShow 是否显示 + * @return AlbumBuilder + */ + public AlbumBuilder setPuzzleMenu(boolean shouldShow) { + Setting.showPuzzleMenu = shouldShow; + return AlbumBuilder.this; + } + + /** + * 只显示Video + * + * @return @return AlbumBuilder + */ + + public AlbumBuilder onlyVideo() { + return filter(Type.VIDEO); + } + + /** + * 过滤 + * + * @param types {@link Type} + * @return @return AlbumBuilder + */ + public AlbumBuilder filter(String... types) { + Setting.filterTypes = Arrays.asList(types); + return AlbumBuilder.this; + } + + /** + * 是否显示gif图 + * + * @param shouldShow 是否显示 + * @return @return AlbumBuilder + */ + public AlbumBuilder setGif(boolean shouldShow) { + Setting.showGif = shouldShow; + return AlbumBuilder.this; + } + + /** + * 是否显示video + * + * @param shouldShow 是否显示 + * @return @return AlbumBuilder + */ + public AlbumBuilder setVideo(boolean shouldShow) { + Setting.showVideo = shouldShow; + return AlbumBuilder.this; + } + + /** + * 显示最少多少秒的视频 + * + * @param second 秒 + * @return @return AlbumBuilder + */ + public AlbumBuilder setVideoMinSecond(int second) { + Setting.videoMinSecond = second * 1000; + return AlbumBuilder.this; + } + + /** + * 显示最多多少秒的视频 + * + * @param second 秒 + * @return @return AlbumBuilder + */ + public AlbumBuilder setVideoMaxSecond(int second) { + Setting.videoMaxSecond = second * 1000; + return AlbumBuilder.this; + } + + /** + * 相册选择页是否显示清空按钮 + */ + public AlbumBuilder setCleanMenu(boolean shouldShow) { + Setting.showCleanMenu = shouldShow; + return AlbumBuilder.this; + } + + private void setSettingParams() { + switch (startupType) { + case CAMERA: + Setting.onlyStartCamera = true; + Setting.isShowCamera = true; + break; + case ALBUM: + Setting.isShowCamera = false; + break; + case ALBUM_CAMERA: + Setting.isShowCamera = true; + break; + } + if (!Setting.filterTypes.isEmpty()) { + if (Setting.isFilter(Type.GIF)) { + Setting.showGif = true; + } + if (Setting.isFilter(Type.VIDEO)) { + Setting.showVideo = true; + } + } + if (Setting.isOnlyVideo()) { + //只选择视频 暂不支持拍照/拼图等 + Setting.isShowCamera = false; + Setting.showPuzzleMenu = false; + Setting.showGif = false; + Setting.showVideo = true; + } + } + + /** + * 启动,onActivityResult方式 + * + * @param requestCode startActivityForResult的请求码 + */ + public void start(int requestCode) { + setSettingParams(); + launchEasyPhotosActivity(requestCode); + } + + /** + * 启动,链式调用 + */ + public void start(SelectCallback callback) { + setSettingParams(); + if (null != mActivity && null != mActivity.get() && mActivity.get() instanceof FragmentActivity) { + EasyResult.get((FragmentActivity) mActivity.get()).startEasyPhoto(callback); + return; + } + if (null != mFragmentV && null != mFragmentV.get()) { + EasyResult.get(mFragmentV.get()).startEasyPhoto(callback); + return; + } + throw new RuntimeException("mActivity or mFragmentV maybe null, you can not use this " + + "method... "); + } + + /** + * 正式启动 + * + * @param requestCode startActivityForResult的请求码 + */ + private void launchEasyPhotosActivity(int requestCode) { + if (null != mActivity && null != mActivity.get()) { + EasyPhotosActivity.start(mActivity.get(), requestCode); + return; + } + if (null != mFragment && null != mFragment.get()) { + EasyPhotosActivity.start(mFragment.get(), requestCode); + return; + } + if (null != mFragmentV && null != mFragmentV.get()) { + EasyPhotosActivity.start(mFragmentV.get(), requestCode); + } + } + + /** + * 清除所有数据 + */ + private static void clear() { + Result.clear(); + Setting.clear(); + instance = null; + } + +//*********************AD************************************ + + /** + * 设置广告(不设置该选项则表示不使用广告) + * + * @param photosAdView 使用图片列表的广告View + * @param photosAdIsLoaded 图片列表广告是否加载完毕 + * @param albumItemsAdView 使用专辑项目列表的广告View + * @param albumItemsAdIsLoaded 专辑项目列表广告是否加载完毕 + * @return AlbumBuilder + */ + public AlbumBuilder setAdView(View photosAdView, boolean photosAdIsLoaded, + View albumItemsAdView, boolean albumItemsAdIsLoaded) { + Setting.photosAdView = new WeakReference(photosAdView); + Setting.albumItemsAdView = new WeakReference(albumItemsAdView); + Setting.photoAdIsOk = photosAdIsLoaded; + Setting.albumItemsAdIsOk = albumItemsAdIsLoaded; + return AlbumBuilder.this; + } + + /** + * 设置广告监听 + * 内部使用,无需关心 + * + * @param adListener 广告监听 + */ + public static void setAdListener(AdListener adListener) { + if (null == instance) return; + if (instance.startupType == StartupType.CAMERA) return; + instance.adListener = new WeakReference(adListener); + } + + /** + * 刷新图片列表广告数据 + */ + public static void notifyPhotosAdLoaded() { + if (Setting.photoAdIsOk) { + return; + } + if (null == instance) { + return; + } + if (instance.startupType == StartupType.CAMERA) { + return; + } + if (null == instance.adListener) { + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (null != instance && null != instance.adListener) { + Setting.photoAdIsOk = true; + instance.adListener.get().onPhotosAdLoaded(); + } + } + }).start(); + return; + } + Setting.photoAdIsOk = true; + instance.adListener.get().onPhotosAdLoaded(); + } + + /** + * 刷新专辑项目列表广告 + */ + public static void notifyAlbumItemsAdLoaded() { + if (Setting.albumItemsAdIsOk) { + return; + } + if (null == instance) { + return; + } + if (instance.startupType == StartupType.CAMERA) { + return; + } + if (null == instance.adListener) { + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (null != instance && null != instance.adListener) { + Setting.albumItemsAdIsOk = true; + instance.adListener.get().onAlbumItemsAdLoaded(); + } + } + }).start(); + return; + } + Setting.albumItemsAdIsOk = true; + instance.adListener.get().onAlbumItemsAdLoaded(); + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/callback/PuzzleCallback.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/callback/PuzzleCallback.java new file mode 100644 index 000000000..eeae7597f --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/callback/PuzzleCallback.java @@ -0,0 +1,22 @@ +package com.huantansheng.easyphotos.callback; + +import com.huantansheng.easyphotos.models.album.entity.Photo; + +/** + * PuzzleCallback + * + * @author joker + * @date 2019/4/9. + */ +public abstract class PuzzleCallback { + /** + * 选择结果回调 + * + * @param photo 返回对象:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个 + */ + public abstract void onResult(Photo photo); + /** + * 什么都没选,取消选择回调 + */ + public abstract void onCancel(); +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/callback/SelectCallback.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/callback/SelectCallback.java new file mode 100644 index 000000000..c91b3a7fa --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/callback/SelectCallback.java @@ -0,0 +1,26 @@ +package com.huantansheng.easyphotos.callback; + +import com.huantansheng.easyphotos.models.album.entity.Photo; + +import java.util.ArrayList; + +/** + * SelectCallback + * + * @author joker + * @date 2019/4/9. + */ +public abstract class SelectCallback { + /** + * 选择结果回调 + * + * @param photos 返回对象集合:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个 + * @param isOriginal 返回图片地址集合时如果你需要知道用户选择图片时是否选择了原图选项,用如下方法获取 + */ + public abstract void onResult(ArrayList photos, boolean isOriginal); + + /** + * 什么都没选,取消选择回调 + */ + public abstract void onCancel(); +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Code.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Code.java new file mode 100644 index 000000000..92629f10f --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Code.java @@ -0,0 +1,21 @@ +package com.huantansheng.easyphotos.constant; + +/** + * Code常量 + * Created by huan on 2017/10/19. + */ + +public class Code { + //相机请求码 + public static final int REQUEST_CAMERA = 11; + //权限请求码 + public static final int REQUEST_PERMISSION = 12; + //预览activity请求码 + public static final int REQUEST_PREVIEW_ACTIVITY = 13; + //请求应用详情 + public static final int REQUEST_SETTING_APP_DETAILS = 14; + //拼图activity请求吗 + public static final int REQUEST_PUZZLE = 15; + //拼图选择activity请求吗 + public static final int REQUEST_PUZZLE_SELECTOR = 16; +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Key.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Key.java new file mode 100644 index 000000000..4502f6a7e --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Key.java @@ -0,0 +1,23 @@ +package com.huantansheng.easyphotos.constant; + +/** + * key的常量 + * Created by huan on 2017/10/19. + */ + +public class Key { + //预览图片的当前角标 + public static final String PREVIEW_PHOTO_INDEX = "keyOfPreviewPhotoIndex"; + //当前预览界面的专辑index + public static final String PREVIEW_ALBUM_ITEM_INDEX = "keyOfPreviewAlbumItemIndex"; + //预览界面是否点击完成 + public static final String PREVIEW_CLICK_DONE = "keyOfPreviewClickDone"; + //拼图界面图片类型,true-Photo,false-String + public static final String PUZZLE_FILE_IS_PHOTO = "keyOfPuzzleFilesTypeIsPhoto"; + //拼图界面图片结合 + public static final String PUZZLE_FILES = "keyOfPuzzleFiles"; + //拼图界面图片保存文件夹地址 + public static final String PUZZLE_SAVE_DIR = "keyOfPuzzleSaveDir"; + //拼图界面图片保存文件名前缀 + public static final String PUZZLE_SAVE_NAME_PREFIX = "keyOfPuzzleSaveNamePrefix"; +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Type.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Type.java new file mode 100644 index 000000000..f6e39cff9 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/constant/Type.java @@ -0,0 +1,15 @@ +package com.huantansheng.easyphotos.constant; + +/** + * Created by huan on 2018/1/9. + */ + +public class Type { + public static final String GIF = "gif"; + public static final String VIDEO = "video"; + public static final String JPEG = "jpeg"; + public static final String JPG = "jpg"; + public static final String PNG = "png"; + public static final String BMP = "bmp"; + public static final String WEBP = "webp"; +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/engine/ImageEngine.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/engine/ImageEngine.java new file mode 100644 index 000000000..49226d426 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/engine/ImageEngine.java @@ -0,0 +1,63 @@ +package com.huantansheng.easyphotos.engine; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.widget.ImageView; + +import androidx.annotation.NonNull; + +/** + * 自定义图片加载方式 + * Created by huan on 2018/1/15. + */ +public interface ImageEngine { + /** + * 加载图片到ImageView + * + * @param context 上下文 + * @param uri 图片Uri + * @param imageView 加载到的ImageView + */ + //安卓10推荐uri,并且path的方式不再可用 + void loadPhoto(@NonNull Context context, @NonNull Uri uri,@NonNull ImageView imageView); + + /** + * 加载gif动图图片到ImageView,gif动图不动 + * + * @param context 上下文 + * @param gifUri gif动图路径Uri + * @param imageView 加载到的ImageView + *

+ * 备注:不支持动图显示的情况下可以不写 + */ + //安卓10推荐uri,并且path的方式不再可用 + void loadGifAsBitmap(@NonNull Context context,@NonNull Uri gifUri,@NonNull ImageView imageView); + + /** + * 加载gif动图到ImageView,gif动图动 + * + * @param context 上下文 + * @param gifUri gif动图路径Uri + * @param imageView 加载动图的ImageView + *

+ * 备注:不支持动图显示的情况下可以不写 + */ + //安卓10推荐uri,并且path的方式不再可用 + void loadGif(@NonNull Context context,@NonNull Uri gifUri,@NonNull ImageView imageView); + + /** + * 获取图片加载框架中的缓存Bitmap,不用拼图功能可以直接返回null + * + * @param context 上下文 + * @param uri 图片路径 + * @param width 图片宽度 + * @param height 图片高度 + * @return Bitmap + * @throws Exception 异常直接抛出,EasyPhotos内部处理 + */ + //安卓10推荐uri,并且path的方式不再可用 + Bitmap getCacheBitmap(@NonNull Context context,@NonNull Uri uri, int width, int height) throws Exception; + + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdEntity.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdEntity.java new file mode 100644 index 000000000..0d624c57c --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdEntity.java @@ -0,0 +1,18 @@ +package com.huantansheng.easyphotos.models.ad; + +import android.view.View; + +/** + * 广告实体 + * Created by huan on 2017/10/24. + */ + +public class AdEntity { + public View adView; + public int lineIndex; + + public AdEntity(View adView, int lineIndex) { + this.adView = adView; + this.lineIndex = lineIndex; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdListener.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdListener.java new file mode 100644 index 000000000..ab5ed7687 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdListener.java @@ -0,0 +1,11 @@ +package com.huantansheng.easyphotos.models.ad; + +/** + * 广告监听 + * Created by huan on 2017/10/24. + */ + +public interface AdListener { + void onPhotosAdLoaded(); + void onAlbumItemsAdLoaded(); +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdViewHolder.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdViewHolder.java new file mode 100644 index 000000000..9b96e3d00 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdViewHolder.java @@ -0,0 +1,20 @@ +package com.huantansheng.easyphotos.models.ad; + +import androidx.recyclerview.widget.RecyclerView; +import android.view.View; +import android.widget.FrameLayout; + +import com.huantansheng.easyphotos.R; + +/** + * 广告viewolder + * Created by huan on 2017/10/28. + */ + +public class AdViewHolder extends RecyclerView.ViewHolder { + public FrameLayout adFrame; + public AdViewHolder(View itemView) { + super(itemView); + adFrame = (FrameLayout) itemView.findViewById(R.id.ad_frame_easy_photos); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/AlbumModel.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/AlbumModel.java new file mode 100644 index 000000000..1b1aff250 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/AlbumModel.java @@ -0,0 +1,361 @@ +package com.huantansheng.easyphotos.models.album; + +import android.Manifest; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.provider.MediaStore; +import android.text.TextUtils; + +import androidx.core.content.PermissionChecker; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.album.entity.Album; +import com.huantansheng.easyphotos.models.album.entity.AlbumItem; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.utils.string.StringUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * 专辑模型 + * Created by huan on 2017/10/20. + *

+ * Modified by Eagle on 2018/08/31. + * 修改内容:将AlbumModel的实例化与数据查询分开 + */ +public class AlbumModel { + public static AlbumModel instance; + public Album album; + private String[] projections; + + private AlbumModel() { + album = new Album(); + } + + public static AlbumModel getInstance() { + if (null == instance) { + synchronized (AlbumModel.class) { + if (null == instance) { + instance = new AlbumModel(); + } + } + } + return instance; + } + + /** + * 专辑查询 + */ + public volatile boolean canRun = true; + + public void query(Context context, final CallBack callBack) { + final Context appCxt = context.getApplicationContext(); + if (PermissionChecker.checkSelfPermission(context, + Manifest.permission.READ_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED) { + if (null != callBack) callBack.onAlbumWorkedCallBack(); + return; + } + canRun = true; + new Thread(new Runnable() { + @Override + public void run() { + try { + initAlbum(appCxt); + if (null != callBack) callBack.onAlbumWorkedCallBack(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + } + + public void stopQuery() { + canRun = false; + } + + private synchronized void initAlbum(Context context) { + album.clear(); + if (Setting.selectedPhotos.size() > Setting.count) { + throw new RuntimeException("AlbumBuilder: 默认勾选的图片张数不能大于设置的选择数!" + "|默认勾选图片张数:" + Setting.selectedPhotos.size() + "|设置的选择数:" + Setting.count); + } + final String sortOrder = MediaStore.MediaColumns.DATE_MODIFIED + " DESC"; + + Uri contentUri; + String selection = null; + String[] selectionAllArgs = null; + + if (Setting.isOnlyVideo()) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if (!Setting.showVideo) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else { + contentUri = MediaStore.Files.getContentUri("external"); + selection = "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + " OR " + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"; + selectionAllArgs = new String[]{String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE), + String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)}; + } + + ContentResolver contentResolver = context.getContentResolver(); + + List projectionList = new ArrayList<>(); + projectionList.add(MediaStore.MediaColumns._ID); + projectionList.add(MediaStore.MediaColumns.DATA); + projectionList.add(MediaStore.MediaColumns.DISPLAY_NAME); + projectionList.add(MediaStore.MediaColumns.DATE_MODIFIED); + projectionList.add(MediaStore.MediaColumns.MIME_TYPE); + projectionList.add(MediaStore.MediaColumns.SIZE); + projectionList.add(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME); + + if (!Setting.useWidth) { + if (Setting.minWidth != 1 && Setting.minHeight != 1) + Setting.useWidth = true; + } + if (Setting.useWidth) { + projectionList.add(MediaStore.MediaColumns.WIDTH); + projectionList.add(MediaStore.MediaColumns.HEIGHT); + if (!Setting.isOnlyVideo()) + projectionList.add(MediaStore.MediaColumns.ORIENTATION); + } + + if (Setting.showVideo) { + projectionList.add(MediaStore.MediaColumns.DURATION); + } + + projections = projectionList.toArray(new String[0]); + + Cursor cursor = contentResolver.query(contentUri, projections, selection, selectionAllArgs, sortOrder); + if (cursor != null && !cursor.isClosed() && cursor.moveToFirst()) { + String albumItem_all_name = getAllAlbumName(context); + String albumItem_video_name = context.getString(R.string.selector_folder_video_easy_photos); + + int albumNameCol = cursor.getColumnIndex(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME); + int durationCol = cursor.getColumnIndex(MediaStore.MediaColumns.DURATION); + int WidthCol = 0; + int HeightCol = 0; + int orientationCol = -1; + if (Setting.useWidth) { + WidthCol = cursor.getColumnIndex(MediaStore.MediaColumns.WIDTH); + HeightCol = cursor.getColumnIndex(MediaStore.MediaColumns.HEIGHT); + orientationCol = cursor.getColumnIndex(MediaStore.MediaColumns.ORIENTATION); + } + boolean hasTime = durationCol > 0; + + do { + long id = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns._ID)); + String path = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DATA)); + String name = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)); + long dateTime = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED)); + String type = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)); + long size = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.SIZE)); + long duration = 0; + + if (TextUtils.isEmpty(path) || TextUtils.isEmpty(type)) { + continue; + } + + if (size < Setting.minSize) { + continue; + } + + boolean isVideo = type.contains(Type.VIDEO);// 是否是视频 + + int width = 0; + int height = 0; + int orientation = 0; + if (isVideo) { + if (hasTime) + duration = cursor.getLong(durationCol); + if (duration <= Setting.videoMinSecond || duration >= Setting.videoMaxSecond) { + continue; + } + } else { + if (orientationCol != -1) { + orientation = cursor.getInt(orientationCol); + } + boolean showGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF); + if (!Setting.showGif) { + if (showGif) { + continue; + } + } + if (!Setting.filterTypes.isEmpty()) { + List list = Setting.filterTypes; + boolean hasFilter = false; + for (String filterType : list) { + if (path.endsWith(filterType) || type.endsWith(filterType)) { + hasFilter = true; + break; + } + } + if (Setting.showGif) { + if (showGif) { + hasFilter = true; + } + } + if (!hasFilter) { + continue; + } + } + if (Setting.useWidth) { + width = cursor.getInt(WidthCol); + height = cursor.getInt(HeightCol); + if (width == 0 || height == 0) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, options); + width = options.outWidth; + height = options.outHeight; + } + + if (orientation == 90 || orientation == 270) { + int temp = width; + width = height; + height = temp; + } + + if (width < Setting.minWidth || height < Setting.minHeight) { + continue; + } + + } + } + + Uri uri = ContentUris.withAppendedId(isVideo ? + MediaStore.Video.Media.EXTERNAL_CONTENT_URI : + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id); + + //某些机型,特定情况下三方应用或用户操作删除媒体文件时,没有通知媒体库, + // 导致媒体库表中还有其数据,但真实文件已经不存在 + File file = new File(path); + if (!file.isFile()) { + continue; + } + + Photo imageItem = new Photo(name, uri, path, dateTime, width, height, orientation + , size, duration, type); + if (!Setting.selectedPhotos.isEmpty()) { + int selectSize = Setting.selectedPhotos.size(); + for (int i = 0; i < selectSize; i++) { + Photo selectedPhoto = Setting.selectedPhotos.get(i); + if (path.equals(selectedPhoto.path)) { + imageItem.selectedOriginal = Setting.selectedOriginal; + Result.addPhoto(imageItem); + } + } + } + + // 初始化“全部”专辑 + if (album.isEmpty()) { + // 用第一个图片作为专辑的封面 + album.addAlbumItem(albumItem_all_name, "", path, uri); + } + // 把图片全部放进“全部”专辑 + if (album.getAlbumItem(albumItem_all_name) != null) { + album.getAlbumItem(albumItem_all_name).addImageItem(imageItem); + } + + if (Setting.showVideo && isVideo && !albumItem_video_name.equals(albumItem_all_name)) { + album.addAlbumItem(albumItem_video_name, "", path, uri); + album.getAlbumItem(albumItem_video_name).addImageItem(imageItem); + } + + // 添加当前图片的专辑到专辑模型实体中 + String albumName; + String folderPath; + if (albumNameCol > 0) { + albumName = cursor.getString(albumNameCol); + folderPath = albumName; + } else { + File parentFile = new File(path).getParentFile(); + if (null == parentFile) { + continue; + } + folderPath = parentFile.getAbsolutePath(); + albumName = StringUtils.getLastPathSegment(folderPath); + } + + album.addAlbumItem(albumName, folderPath, path, uri); + album.getAlbumItem(albumName).addImageItem(imageItem); + } while (!cursor.isClosed() && cursor.moveToNext() && canRun); + cursor.close(); + } + } + + /** + * 获取全部专辑名 + * + * @return 专辑名 + */ + public String getAllAlbumName(Context context) { + String albumItem_all_name = context.getString(R.string.selector_folder_all_video_photo_easy_photos); + if (Setting.isOnlyVideo()) { + albumItem_all_name = context.getString(R.string.selector_folder_video_easy_photos); + } else if (!Setting.showVideo) { + //不显示视频 + albumItem_all_name = context.getString(R.string.selector_folder_all_easy_photos); + } + return albumItem_all_name; + } + + /** + * 获取当前专辑项目的图片集 + * + * @return 当前专辑项目的图片集 + */ + public ArrayList getCurrAlbumItemPhotos(int currAlbumItemIndex) { + return album.getAlbumItem(currAlbumItemIndex).photos; + } + + /** + * 获取专辑项目集 + * + * @return 专辑项目集 + */ + public ArrayList getAlbumItems() { + return album.albumItems; + } + + public interface CallBack { + void onAlbumWorkedCallBack(); + } + + + /** + * 获取projections + */ + public String[] getProjections() { + if (null == projections || projections.length == 0) { + if (Setting.useWidth) { + projections = new String[]{MediaStore.MediaColumns._ID, + MediaStore.MediaColumns.DATA, + MediaStore.MediaColumns.DISPLAY_NAME, + MediaStore.MediaColumns.DATE_MODIFIED, + MediaStore.MediaColumns.MIME_TYPE, + MediaStore.MediaColumns.SIZE, + MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, + MediaStore.MediaColumns.WIDTH, + MediaStore.MediaColumns.HEIGHT, + MediaStore.MediaColumns.ORIENTATION}; + } else { + projections = new String[]{MediaStore.MediaColumns._ID, + MediaStore.MediaColumns.DATA, + MediaStore.MediaColumns.DISPLAY_NAME, + MediaStore.MediaColumns.DATE_MODIFIED, + MediaStore.MediaColumns.MIME_TYPE, + MediaStore.MediaColumns.SIZE, + MediaStore.MediaColumns.BUCKET_DISPLAY_NAME}; + } + } + return projections; + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Album.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Album.java new file mode 100644 index 000000000..d3aa004ed --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Album.java @@ -0,0 +1,49 @@ +package com.huantansheng.easyphotos.models.album.entity; + +import android.net.Uri; + +import java.util.ArrayList; +import java.util.LinkedHashMap; + +/** + * 专辑模型实体类 + * Created by huan on 2017/10/20. + */ + +public class Album { + public ArrayList albumItems; + private LinkedHashMap hasAlbumItems;//用于记录专辑项目 + + public Album() { + albumItems = new ArrayList<>(); + hasAlbumItems = new LinkedHashMap<>(); + } + + private void addAlbumItem(AlbumItem albumItem) { + this.hasAlbumItems.put(albumItem.name, albumItem); + this.albumItems.add(albumItem); + } + + public void addAlbumItem(String name, String folderPath, String coverImagePath, Uri coverImageUri) { + if (null == hasAlbumItems.get(name)) { + addAlbumItem(new AlbumItem(name, folderPath, coverImagePath,coverImageUri)); + } + } + + public AlbumItem getAlbumItem(String name) { + return hasAlbumItems.get(name); + } + + public AlbumItem getAlbumItem(int currIndex) { + return albumItems.get(currIndex); + } + + public boolean isEmpty() { + return albumItems.isEmpty(); + } + + public void clear() { + albumItems.clear(); + hasAlbumItems.clear(); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/AlbumItem.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/AlbumItem.java new file mode 100644 index 000000000..2208275fe --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/AlbumItem.java @@ -0,0 +1,34 @@ +package com.huantansheng.easyphotos.models.album.entity; + +import android.net.Uri; + +import java.util.ArrayList; + +/** + * 专辑项目实体类 + * Created by huan on 2017/10/20. + */ + +public class AlbumItem { + public String name; + public String folderPath; + public String coverImagePath; + public Uri coverImageUri; + public ArrayList photos; + + AlbumItem(String name, String folderPath, String coverImagePath, Uri coverImageUri) { + this.name = name; + this.folderPath = folderPath; + this.coverImagePath = coverImagePath; + this.coverImageUri = coverImageUri; + this.photos = new ArrayList<>(); + } + + public void addImageItem(Photo imageItem) { + this.photos.add(imageItem); + } + + public void addImageItem(int index, Photo imageItem) { + this.photos.add(index, imageItem); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Photo.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Photo.java new file mode 100644 index 000000000..0d3d6ed7d --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Photo.java @@ -0,0 +1,114 @@ +package com.huantansheng.easyphotos.models.album.entity; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +/** + * 图片item实体类 + * Created by huan on 2017/10/20. + */ + +public class Photo implements Parcelable { + private static final String TAG = "Photo"; + public Uri uri;//图片Uri + public String name;//图片名称 + public String path;//图片全路径 + public String type;//图片类型 + public int width;//图片宽度 + public int height;//图片高度 + public int orientation;//图片旋转角度 + public long size;//图片文件大小,单位:Bytes + public long duration;//视频时长,单位:毫秒 + public long time;//图片拍摄的时间戳,单位:毫秒 + public boolean selected;//是否被选中,内部使用,无需关心 + public boolean selectedOriginal;//用户选择时是否选择了原图选项 + + public Photo(String name, Uri uri, String path, long time, int width, int height, int orientation, long size, long duration, String type) { + this.name = name; + this.uri = uri; + this.path = path; + this.time = time; + this.width = width; + this.height = height; + this.orientation = orientation; + this.type = type; + this.size = size; + this.duration = duration; + this.selected = false; + this.selectedOriginal = false; + } + + @Override + public boolean equals(Object o) { + try { + Photo other = (Photo) o; + return this.path.equalsIgnoreCase(other.path); + } catch (ClassCastException e) { + Log.e(TAG, "equals: " + Log.getStackTraceString(e)); + } + return super.equals(o); + } + + @Override + public String toString() { + return "Photo{" + + "name='" + name + '\'' + + ", uri='" + uri.toString() + '\'' + + ", path='" + path + '\'' + + ", time=" + time + '\'' + + ", minWidth=" + width + '\'' + + ", minHeight=" + height + + ", orientation=" + orientation + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(this.uri, flags); + dest.writeString(this.name); + dest.writeString(this.path); + dest.writeString(this.type); + dest.writeInt(this.width); + dest.writeInt(this.height); + dest.writeInt(this.orientation); + dest.writeLong(this.size); + dest.writeLong(this.duration); + dest.writeLong(this.time); + dest.writeByte(this.selected ? (byte) 1 : (byte) 0); + dest.writeByte(this.selectedOriginal ? (byte) 1 : (byte) 0); + } + + protected Photo(Parcel in) { + this.uri = in.readParcelable(Uri.class.getClassLoader()); + this.name = in.readString(); + this.path = in.readString(); + this.type = in.readString(); + this.width = in.readInt(); + this.height = in.readInt(); + this.orientation = in.readInt(); + this.size = in.readLong(); + this.duration = in.readLong(); + this.time = in.readLong(); + this.selected = in.readByte() != 0; + this.selectedOriginal = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public Photo createFromParcel(Parcel source) { + return new Photo(source); + } + + @Override + public Photo[] newArray(int size) { + return new Photo[size]; + } + }; +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Area.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Area.java new file mode 100644 index 000000000..6f48cda25 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Area.java @@ -0,0 +1,65 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.graphics.Path; +import android.graphics.PointF; +import android.graphics.RectF; + +import java.util.List; + +/** + * @author wupanjie + */ +public interface Area { + float left(); + + float top(); + + float right(); + + float bottom(); + + float centerX(); + + float centerY(); + + float width(); + + float height(); + + PointF getCenterPoint(); + + boolean contains(PointF point); + + boolean contains(float x, float y); + + boolean contains(Line line); + + Path getAreaPath(); + + RectF getAreaRect(); + + List getLines(); + + PointF[] getHandleBarPoints(Line line); + + float radian(); + + void setRadian(float radian); + + float getPaddingLeft(); + + float getPaddingTop(); + + float getPaddingRight(); + + float getPaddingBottom(); + + void setPadding(float padding); + + void setPadding(float paddingLeft, float paddingTop, float paddingRight, float paddingBottom); +} + + + + + diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/DegreeSeekBar.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/DegreeSeekBar.java new file mode 100644 index 000000000..cfd7b5bb7 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/DegreeSeekBar.java @@ -0,0 +1,354 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import androidx.core.content.ContextCompat; + +import com.huantansheng.easyphotos.R; + +/** + * @author wupanjie + */ +public class DegreeSeekBar extends View { + private static final String TAG = "DegreeSeekBar"; + private Paint mTextPaint; + private Paint mCirclePaint; + private Paint.FontMetricsInt mFontMetrics; + private int mBaseLine; + private float[] mTextWidths; + + private final Rect mCanvasClipBounds = new Rect(); + + private ScrollingListener mScrollingListener; + private float mLastTouchedPosition; + + private Paint mPointPaint; + private float mPointMargin; + + private boolean mScrollStarted; + private int mTotalScrollDistance; + + private Path mIndicatorPath = new Path(); + + private int mCurrentDegrees = 0; + private int mPointCount = 51; + + private int mPointColor; + private int mTextColor; + private int mCenterTextColor; + + //阻尼系数的倒数 + private float mDragFactor = 2.1f; + + private int mMinReachableDegrees = -45; + private int mMaxReachableDegrees = 45; + + private String suffix = ""; + + public DegreeSeekBar(Context context) { + this(context, null); + } + + public DegreeSeekBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DegreeSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public DegreeSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + mPointColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary); + mTextColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary); + mCenterTextColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent); + + mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPointPaint.setStyle(Paint.Style.STROKE); + mPointPaint.setColor(mPointColor); + mPointPaint.setStrokeWidth(2); + + mTextPaint = new Paint(); + mTextPaint.setColor(mTextColor); + mTextPaint.setStyle(Paint.Style.STROKE); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(24f); + mTextPaint.setTextAlign(Paint.Align.LEFT); + mTextPaint.setAlpha(100); + + mFontMetrics = mTextPaint.getFontMetricsInt(); + + mTextWidths = new float[1]; + mTextPaint.getTextWidths("0", mTextWidths); + + mCirclePaint = new Paint(); + mCirclePaint.setStyle(Paint.Style.FILL); + mCirclePaint.setAlpha(255); + mCirclePaint.setAntiAlias(true); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mPointMargin = (float) w / mPointCount; + + mBaseLine = (h - mFontMetrics.bottom + mFontMetrics.top) / 2 - mFontMetrics.top; + + mIndicatorPath.moveTo(w / 2, h / 2 + mFontMetrics.top / 2 - 18); + mIndicatorPath.rLineTo(-8, -8); + mIndicatorPath.rLineTo(16, 0); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mLastTouchedPosition = event.getX(); + if (!mScrollStarted) { + mScrollStarted = true; + if (mScrollingListener != null) { + mScrollingListener.onScrollStart(); + } + } + break; + case MotionEvent.ACTION_UP: + mScrollStarted = false; + if (mScrollingListener != null) { + mScrollingListener.onScrollEnd(); + } + invalidate(); + break; + case MotionEvent.ACTION_MOVE: + float distance = event.getX() - mLastTouchedPosition; + if (mCurrentDegrees >= mMaxReachableDegrees && distance < 0) { + mCurrentDegrees = mMaxReachableDegrees; + invalidate(); + break; + } + if (mCurrentDegrees <= mMinReachableDegrees && distance > 0) { + mCurrentDegrees = mMinReachableDegrees; + invalidate(); + break; + } + if (distance != 0) { + onScrollEvent(event, distance); + } + break; + } + return true; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.getClipBounds(mCanvasClipBounds); + + int zeroIndex = mPointCount / 2 + (0 - mCurrentDegrees) / 2; + mPointPaint.setColor(mPointColor); + for (int i = 0; i < mPointCount; i++) { + + if (i > zeroIndex - Math.abs(mMinReachableDegrees) / 2 + && i < zeroIndex + Math.abs(mMaxReachableDegrees) / 2 + && mScrollStarted) { + mPointPaint.setAlpha(255); + } else { + mPointPaint.setAlpha(100); + } + + if (i > mPointCount / 2 - 8 + && i < mPointCount / 2 + 8 + && i > zeroIndex - Math.abs(mMinReachableDegrees) / 2 + && i < zeroIndex + Math.abs(mMaxReachableDegrees) / 2) { + if (mScrollStarted) { + mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 255 / 8); + } else { + mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 100 / 8); + } + } + + canvas.drawPoint(mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin, + mCanvasClipBounds.centerY(), mPointPaint); + + if (mCurrentDegrees != 0 && i == zeroIndex) { + if (mScrollStarted) { + mTextPaint.setAlpha(255); + } else { + mTextPaint.setAlpha(192); + } + mPointPaint.setStrokeWidth(4); + canvas.drawPoint((mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin), + mCanvasClipBounds.centerY(), mPointPaint); + mPointPaint.setStrokeWidth(2); + mTextPaint.setAlpha(100); + } + } + + for (int i = -180; i <= 180; i += 15) { + if (i >= mMinReachableDegrees && i <= mMaxReachableDegrees) { + drawDegreeText(i, canvas, true); + } else { + drawDegreeText(i, canvas, false); + } + } + + mTextPaint.setTextSize(28f); + mTextPaint.setAlpha(255); + mTextPaint.setColor(mCenterTextColor); + + if (mCurrentDegrees >= 10) { + canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0], mBaseLine, + mTextPaint); + } else if (mCurrentDegrees <= -10) { + canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0] / 2 * 3, mBaseLine, + mTextPaint); + } else if (mCurrentDegrees < 0) { + canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0], mBaseLine, + mTextPaint); + } else { + canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0] / 2, mBaseLine, + mTextPaint); + } + + mTextPaint.setAlpha(100); + mTextPaint.setTextSize(24f); + mTextPaint.setColor(mTextColor); + //画中心三角 + mCirclePaint.setColor(mCenterTextColor); + canvas.drawPath(mIndicatorPath, mCirclePaint); + mCirclePaint.setColor(mCenterTextColor); + } + + private void drawDegreeText(int degrees, Canvas canvas, boolean canReach) { + if (canReach) { + if (mScrollStarted) { + mTextPaint.setAlpha(Math.min(255, Math.abs(degrees - mCurrentDegrees) * 255 / 15)); + if (Math.abs(degrees - mCurrentDegrees) <= 7) { + mTextPaint.setAlpha(0); + } + } else { + mTextPaint.setAlpha(100); + if (Math.abs(degrees - mCurrentDegrees) <= 7) { + mTextPaint.setAlpha(0); + } + } + } else { + mTextPaint.setAlpha(100); + } + if (degrees == 0) { + if (Math.abs(mCurrentDegrees) >= 15 && !mScrollStarted) { + mTextPaint.setAlpha(180); + } + canvas.drawText("0°", + getWidth() / 2 - mTextWidths[0] / 2 - mCurrentDegrees / 2 * mPointMargin, + getHeight() / 2 - 10, mTextPaint); + } else { + canvas.drawText(degrees + suffix, getWidth() / 2 + mPointMargin * degrees / 2 + - mTextWidths[0] / 2 * 3 + - mCurrentDegrees / 2 * mPointMargin, getHeight() / 2 - 10, mTextPaint); + } + } + + private void onScrollEvent(MotionEvent event, float distance) { + mTotalScrollDistance -= distance; + postInvalidate(); + mLastTouchedPosition = event.getX(); + mCurrentDegrees = (int) ((mTotalScrollDistance * mDragFactor) / mPointMargin); + if (mScrollingListener != null) { + mScrollingListener.onScroll(mCurrentDegrees); + } + } + + public void setDegreeRange(int min, int max) { + if (min > max) { + Log.e(TAG, "setDegreeRange: error, max must greater than min"); + } else { + mMinReachableDegrees = min; + mMaxReachableDegrees = max; + + if (mCurrentDegrees > mMaxReachableDegrees || mCurrentDegrees < mMinReachableDegrees) { + mCurrentDegrees = (mMinReachableDegrees + mMaxReachableDegrees) / 2; + } + mTotalScrollDistance = (int) (mCurrentDegrees * mPointMargin / mDragFactor); + invalidate(); + } + } + + public void setCurrentDegrees(int degrees) { + if (degrees <= mMaxReachableDegrees && degrees >= mMinReachableDegrees) { + mCurrentDegrees = degrees; + mTotalScrollDistance = (int) (degrees * mPointMargin / mDragFactor); + invalidate(); + } + } + + public void setScrollingListener(ScrollingListener scrollingListener) { + mScrollingListener = scrollingListener; + } + + public int getPointColor() { + return mPointColor; + } + + public void setPointColor(int pointColor) { + mPointColor = pointColor; + mPointPaint.setColor(mPointColor); + postInvalidate(); + } + + public int getTextColor() { + return mTextColor; + } + + public void setTextColor(int textColor) { + mTextColor = textColor; + mTextPaint.setColor(textColor); + postInvalidate(); + } + + public int getCenterTextColor() { + return mCenterTextColor; + } + + public void setCenterTextColor(int centerTextColor) { + mCenterTextColor = centerTextColor; + postInvalidate(); + } + + public float getDragFactor() { + return mDragFactor; + } + + public void setDragFactor(float dragFactor) { + mDragFactor = dragFactor; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + + public interface ScrollingListener { + + void onScrollStart(); + + void onScroll(int currentDegrees); + + void onScrollEnd(); + } +} + diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Line.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Line.java new file mode 100644 index 000000000..79ab74fcc --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Line.java @@ -0,0 +1,52 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.graphics.PointF; + +/** + * @author wupanjie + */ +public interface Line { + enum Direction { + HORIZONTAL, VERTICAL + } + + float length(); + + PointF startPoint(); + + PointF endPoint(); + + Line lowerLine(); + + Line upperLine(); + + Line attachStartLine(); + + Line attachEndLine(); + + void setLowerLine(Line lowerLine); + + void setUpperLine(Line upperLine); + + Direction direction(); + + float slope(); + + boolean contains(float x, float y, float extra); + + void prepareMove(); + + boolean move(float offset, float extra); + + void update(float layoutWidth, float layoutHeight); + + float minX(); + + float maxX(); + + float minY(); + + float maxY(); + + void offset(float x, float y); +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/MatrixUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/MatrixUtils.java new file mode 100644 index 000000000..52e89a614 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/MatrixUtils.java @@ -0,0 +1,167 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.graphics.Matrix; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; + +import java.util.Arrays; + +import static java.lang.Math.round; + +/** + * some useful matrix operation methods + * + * @author wupanjie + */ +public class MatrixUtils { + private MatrixUtils() { + //no instance + } + + private static final float[] sMatrixValues = new float[9]; + private static final Matrix sTempMatrix = new Matrix(); + + /** + * This method calculates scale value for given Matrix object. + */ + public static float getMatrixScale(Matrix matrix) { + return (float) Math.sqrt(Math.pow(getMatrixValue(matrix, Matrix.MSCALE_X), 2) + Math.pow( + getMatrixValue(matrix, Matrix.MSKEW_Y), 2)); + } + + /** + * This method calculates rotation angle for given Matrix object. + */ + public static float getMatrixAngle(Matrix matrix) { + return (float) -(Math.atan2(getMatrixValue(matrix, Matrix.MSKEW_X), + getMatrixValue(matrix, Matrix.MSCALE_X)) * (180 / Math.PI)); + } + + public static float getMatrixValue(Matrix matrix, int valueIndex) { + matrix.getValues(sMatrixValues); + return sMatrixValues[valueIndex]; + } + + public static float getMinMatrixScale(PuzzlePiece piece) { + if (piece != null) { + + sTempMatrix.reset(); + sTempMatrix.setRotate(-piece.getMatrixAngle()); + + float[] unrotatedCropBoundsCorners = getCornersFromRect(piece.getArea().getAreaRect()); + + sTempMatrix.mapPoints(unrotatedCropBoundsCorners); + + RectF unrotatedCropRect = trapToRect(unrotatedCropBoundsCorners); + + return Math.max(unrotatedCropRect.width() / piece.getWidth(), + unrotatedCropRect.height() / piece.getHeight()); + } + + return 1f; + } + + //判断剪裁框是否在图片内 + static boolean judgeIsImageContainsBorder(PuzzlePiece piece, float rotateDegrees) { + sTempMatrix.reset(); + sTempMatrix.setRotate(-rotateDegrees); + float[] unrotatedWrapperCorner = new float[8]; + float[] unrotateBorderCorner = new float[8]; + sTempMatrix.mapPoints(unrotatedWrapperCorner, piece.getCurrentDrawablePoints()); + sTempMatrix.mapPoints(unrotateBorderCorner, getCornersFromRect(piece.getArea().getAreaRect())); + + return trapToRect(unrotatedWrapperCorner).contains(trapToRect(unrotateBorderCorner)); + } + + static float[] calculateImageIndents(PuzzlePiece piece) { + if (piece == null) return new float[]{0, 0, 0, 0, 0, 0, 0, 0}; + + sTempMatrix.reset(); + sTempMatrix.setRotate(-piece.getMatrixAngle()); + + final float[] currentImageCorners = piece.getCurrentDrawablePoints(); + final float[] unrotatedImageCorners = + Arrays.copyOf(currentImageCorners, currentImageCorners.length); + final float[] unrotatedCropBoundsCorners = getCornersFromRect(piece.getArea().getAreaRect()); + + sTempMatrix.mapPoints(unrotatedImageCorners); + sTempMatrix.mapPoints(unrotatedCropBoundsCorners); + + RectF unrotatedImageRect = trapToRect(unrotatedImageCorners); + RectF unrotatedCropRect = trapToRect(unrotatedCropBoundsCorners); + + float deltaLeft = unrotatedImageRect.left - unrotatedCropRect.left; + float deltaTop = unrotatedImageRect.top - unrotatedCropRect.top; + float deltaRight = unrotatedImageRect.right - unrotatedCropRect.right; + float deltaBottom = unrotatedImageRect.bottom - unrotatedCropRect.bottom; + + float indents[] = new float[4]; + + indents[0] = (deltaLeft > 0) ? deltaLeft : 0; + indents[1] = (deltaTop > 0) ? deltaTop : 0; + indents[2] = (deltaRight < 0) ? deltaRight : 0; + indents[3] = (deltaBottom < 0) ? deltaBottom : 0; + + sTempMatrix.reset(); + sTempMatrix.setRotate(piece.getMatrixAngle()); + sTempMatrix.mapPoints(indents); + + return indents; + } + + //计算包含给出点的最小矩形 + public static RectF trapToRect(float[] array) { + RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY); + int length = array.length; + for (int i = 1; i < length; i += 2) { + float x = round(array[i - 1] * 10) / 10.f; + float y = round(array[i] * 10) / 10.f; + r.left = (x < r.left) ? x : r.left; + r.top = (y < r.top) ? y : r.top; + r.right = (x > r.right) ? x : r.right; + r.bottom = (y > r.bottom) ? y : r.bottom; + } + r.sort(); + return r; + } + + public static float[] getCornersFromRect(RectF r) { + return new float[]{ + r.left, r.top, r.right, r.top, r.right, r.bottom, r.left, r.bottom + }; + } + + public static Matrix generateMatrix(PuzzlePiece piece, float extra) { + return generateMatrix(piece.getArea(), piece.getDrawable(), extra); + } + + public static Matrix generateMatrix(Area area, Drawable drawable, float extraSize) { + return generateCenterCropMatrix(area, drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), extraSize); + } + + private static Matrix generateCenterCropMatrix(Area area, int width, int height, + float extraSize) { + final RectF rectF = area.getAreaRect(); + + Matrix matrix = new Matrix(); + + float offsetX = rectF.centerX() - width / 2; + float offsetY = rectF.centerY() - height / 2; + + matrix.postTranslate(offsetX, offsetY); + + float scale; + + if (width * rectF.height() > rectF.width() * height) { + scale = (rectF.height() + extraSize) / height; + } else { + scale = (rectF.width() + extraSize) / width; + } + + matrix.postScale(scale, scale, rectF.centerX(), rectF.centerY()); + + return matrix; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleLayout.java new file mode 100644 index 000000000..615e4426e --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleLayout.java @@ -0,0 +1,88 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.graphics.RectF; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public interface PuzzleLayout { + void setOuterBounds(RectF bounds); + + void layout(); + + int getAreaCount(); + + List getOuterLines(); + + List getLines(); + + Area getOuterArea(); + + void update(); + + void reset(); + + Area getArea(int position); + + float width(); + + float height(); + + void setPadding(float padding); + + float getPadding(); + + float getRadian(); + + void setRadian(float radian); + + Info generateInfo(); + + void setColor(int color); + + int getColor(); + + class Info { + public static final int TYPE_STRAIGHT = 0; + public static final int TYPE_SLANT = 1; + + public int type; + public ArrayList steps; + public ArrayList lineInfos; + public float padding; + public float radian; + public int color; + } + + class Step { + public static final int ADD_LINE = 0; + public static final int ADD_CROSS = 1; + public static final int CUT_EQUAL_PART_ONE = 2; + public static final int CUT_EQUAL_PART_TWO = 3; + public static final int CUT_SPIRAL = 4; + + public int type; + public int direction; + public int position; + public int part; + public int hSize; + public int vSize; + } + + class LineInfo { + public float startX; + public float startY; + public float endX; + public float endY; + + public LineInfo(Line line) { + startX = line.startPoint().x; + startY = line.startPoint().y; + endX = line.endPoint().x; + endY = line.endPoint().y; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzlePiece.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzlePiece.java new file mode 100644 index 000000000..ff76f25a8 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzlePiece.java @@ -0,0 +1,433 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.animation.ValueAnimator; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Xfermode; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.DecelerateInterpolator; + +import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.calculateImageIndents; +import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.getMinMatrixScale; +import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.judgeIsImageContainsBorder; + + +/** + * @author wupanjie + */ +@SuppressWarnings("WeakerAccess") +public class PuzzlePiece { + private static Xfermode SRC_IN = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); + + private Drawable drawable; + private Matrix matrix; + private Matrix previousMatrix; + private Area area; + private Rect drawableBounds; + private float[] drawablePoints; + private float[] mappedDrawablePoints; + + private float previousMoveX; + private float previousMoveY; + + private final RectF mappedBounds; + private final PointF centerPoint; + private final PointF mappedCenterPoint; + + private ValueAnimator animator; + private int duration = 300; + private Matrix tempMatrix; + + PuzzlePiece(Drawable drawable, Area area, Matrix matrix) { + this.drawable = drawable; + this.area = area; + this.matrix = matrix; + this.previousMatrix = new Matrix(); + this.drawableBounds = new Rect(0, 0, getWidth(), getHeight()); + this.drawablePoints = new float[]{ + 0f, 0f, getWidth(), 0f, getWidth(), getHeight(), 0f, getHeight() + }; + this.mappedDrawablePoints = new float[8]; + + this.mappedBounds = new RectF(); + this.centerPoint = new PointF(area.centerX(), area.centerY()); + this.mappedCenterPoint = new PointF(); + + this.animator = ValueAnimator.ofFloat(0f, 1f); + this.animator.setInterpolator(new DecelerateInterpolator()); + + this.tempMatrix = new Matrix(); + } + + void draw(Canvas canvas) { + draw(canvas, 255, true); + } + + void draw(Canvas canvas, int alpha) { + draw(canvas, alpha, false); + } + + private void draw(Canvas canvas, int alpha, boolean needClip) { + if (drawable instanceof BitmapDrawable) { + int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG); + + Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); + Paint paint = ((BitmapDrawable) drawable).getPaint(); + + paint.setColor(Color.WHITE); + paint.setAlpha(alpha); + if (needClip) { + canvas.drawPath(area.getAreaPath(), paint); + paint.setXfermode(SRC_IN); + } + canvas.drawBitmap(bitmap, matrix, paint); + paint.setXfermode(null); + + canvas.restoreToCount(saved); + } else { + canvas.save(); + if (needClip) { + canvas.clipPath(area.getAreaPath()); + } + canvas.concat(matrix); + drawable.setBounds(drawableBounds); + drawable.setAlpha(alpha); + drawable.draw(canvas); + + canvas.restore(); + } + } + + public Area getArea() { + return area; + } + + public void setDrawable(Drawable drawable) { + this.drawable = drawable; + this.drawableBounds = new Rect(0, 0, getWidth(), getHeight()); + this.drawablePoints = new float[]{ + 0f, 0f, getWidth(), 0f, getWidth(), getHeight(), 0f, getHeight() + }; + } + + public Drawable getDrawable() { + return drawable; + } + + public int getWidth() { + return drawable.getIntrinsicWidth(); + } + + public int getHeight() { + return drawable.getIntrinsicHeight(); + } + + public boolean contains(float x, float y) { + return area.contains(x, y); + } + + public boolean contains(Line line) { + return area.contains(line); + } + + public Rect getDrawableBounds() { + return drawableBounds; + } + + void setPreviousMoveX(float previousMoveX) { + this.previousMoveX = previousMoveX; + } + + void setPreviousMoveY(float previousMoveY) { + this.previousMoveY = previousMoveY; + } + + private RectF getCurrentDrawableBounds() { + matrix.mapRect(mappedBounds, new RectF(drawableBounds)); + return mappedBounds; + } + + private PointF getCurrentDrawableCenterPoint() { + getCurrentDrawableBounds(); + mappedCenterPoint.x = mappedBounds.centerX(); + mappedCenterPoint.y = mappedBounds.centerY(); + return mappedCenterPoint; + } + + public PointF getAreaCenterPoint() { + centerPoint.x = area.centerX(); + centerPoint.y = area.centerY(); + return centerPoint; + } + + private float getMatrixScale() { + return MatrixUtils.getMatrixScale(matrix); + } + + float getMatrixAngle() { + return MatrixUtils.getMatrixAngle(matrix); + } + + float[] getCurrentDrawablePoints() { + matrix.mapPoints(mappedDrawablePoints, drawablePoints); + return mappedDrawablePoints; + } + + boolean isFilledArea() { + RectF bounds = getCurrentDrawableBounds(); + return !(bounds.left > area.left() + || bounds.top > area.top() + || bounds.right < area.right() + || bounds.bottom < area.bottom()); + } + + boolean canFilledArea() { + float scale = MatrixUtils.getMatrixScale(matrix); + float minScale = getMinMatrixScale(this); + return scale >= minScale; + } + + void record() { + previousMatrix.set(matrix); + } + + void translate(float offsetX, float offsetY) { + matrix.set(previousMatrix); + postTranslate(offsetX, offsetY); + } + + private void zoom(float scaleX, float scaleY, PointF midPoint) { + matrix.set(previousMatrix); + postScale(scaleX, scaleY, midPoint); + } + + void zoomAndTranslate(float scaleX, float scaleY, PointF midPoint, float offsetX, float offsetY) { + matrix.set(previousMatrix); + postTranslate(offsetX, offsetY); + postScale(scaleX, scaleY, midPoint); + } + + void set(Matrix matrix) { + this.matrix.set(matrix); + moveToFillArea(null); + } + + void postTranslate(float x, float y) { + this.matrix.postTranslate(x, y); + } + + void postScale(float scaleX, float scaleY, PointF midPoint) { + this.matrix.postScale(scaleX, scaleY, midPoint.x, midPoint.y); + } + + void postFlipVertically() { + this.matrix.postScale(1, -1, area.centerX(), area.centerY()); + } + + void postFlipHorizontally() { + this.matrix.postScale(-1, 1, area.centerX(), area.centerY()); + } + + void postRotate(float degree) { + this.matrix.postRotate(degree, area.centerX(), area.centerY()); + + float minScale = getMinMatrixScale(this); + if (getMatrixScale() < minScale) { + final PointF midPoint = new PointF(); + midPoint.set(getCurrentDrawableCenterPoint()); + + postScale(minScale / getMatrixScale(), minScale / getMatrixScale(), midPoint); + } + + if (!judgeIsImageContainsBorder(this, getMatrixAngle())) { + final float[] imageIndents = calculateImageIndents(this); + float deltaX = -(imageIndents[0] + imageIndents[2]); + float deltaY = -(imageIndents[1] + imageIndents[3]); + + postTranslate(deltaX, deltaY); + } + } + + private void animateTranslate(final View view, final float translateX, final float translateY) { + animator.end(); + animator.removeAllUpdateListeners(); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float x = translateX * (float) animation.getAnimatedValue(); + float y = translateY * (float) animation.getAnimatedValue(); + + translate(x, y); + view.invalidate(); + } + }); + animator.setDuration(duration); + animator.start(); + } + + void moveToFillArea(final View view) { + if (isFilledArea()) return; + record(); + + RectF rectF = getCurrentDrawableBounds(); + float offsetX = 0f; + float offsetY = 0f; + + if (rectF.left > area.left()) { + offsetX = area.left() - rectF.left; + } + + if (rectF.top > area.top()) { + offsetY = area.top() - rectF.top; + } + + if (rectF.right < area.right()) { + offsetX = area.right() - rectF.right; + } + + if (rectF.bottom < area.bottom()) { + offsetY = area.bottom() - rectF.bottom; + } + + if (view == null) { + postTranslate(offsetX, offsetY); + } else { + animateTranslate(view, offsetX, offsetY); + } + } + + void fillArea(final View view, boolean quick) { + if (isFilledArea()) return; + record(); + + final float startScale = getMatrixScale(); + final float endScale = getMinMatrixScale(this); + + final PointF midPoint = new PointF(); + midPoint.set(getCurrentDrawableCenterPoint()); + + tempMatrix.set(matrix); + tempMatrix.postScale(endScale / startScale, endScale / startScale, midPoint.x, midPoint.y); + + RectF rectF = new RectF(drawableBounds); + tempMatrix.mapRect(rectF); + + float offsetX = 0f; + float offsetY = 0f; + + if (rectF.left > area.left()) { + offsetX = area.left() - rectF.left; + } + + if (rectF.top > area.top()) { + offsetY = area.top() - rectF.top; + } + + if (rectF.right < area.right()) { + offsetX = area.right() - rectF.right; + } + + if (rectF.bottom < area.bottom()) { + offsetY = area.bottom() - rectF.bottom; + } + + final float translateX = offsetX; + final float translateY = offsetY; + + animator.end(); + animator.removeAllUpdateListeners(); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float value = (float) animation.getAnimatedValue(); + float scale = (startScale + (endScale - startScale) * value) / startScale; + float x = translateX * value; + float y = translateY * value; + + zoom(scale, scale, midPoint); + postTranslate(x, y); + view.invalidate(); + } + }); + + if (quick) { + animator.setDuration(0); + } else { + animator.setDuration(duration); + } + animator.start(); + } + + void updateWith(final MotionEvent event, final Line line) { + float offsetX = (event.getX() - previousMoveX) / 2; + float offsetY = (event.getY() - previousMoveY) / 2; + + if (!canFilledArea()) { + final Area area = getArea(); + float deltaScale = getMinMatrixScale(this) / getMatrixScale(); + postScale(deltaScale, deltaScale, area.getCenterPoint()); + record(); + + previousMoveX = event.getX(); + previousMoveY = event.getY(); + } + + if (line.direction() == Line.Direction.HORIZONTAL) { + translate(0, offsetY); + } else if (line.direction() == Line.Direction.VERTICAL) { + translate(offsetX, 0); + } + + final RectF rectF = getCurrentDrawableBounds(); + final Area area = getArea(); + float moveY = 0f; + + if (rectF.top > area.top()) { + moveY = area.top() - rectF.top; + } + + if (rectF.bottom < area.bottom()) { + moveY = area.bottom() - rectF.bottom; + } + + float moveX = 0f; + + if (rectF.left > area.left()) { + moveX = area.left() - rectF.left; + } + + if (rectF.right < area.right()) { + moveX = area.right() - rectF.right; + } + + if (moveX != 0 || moveY != 0) { + previousMoveX = event.getX(); + previousMoveY = event.getY(); + postTranslate(moveX, moveY); + record(); + } + } + + public void setArea(Area area) { + this.area = area; + } + + boolean isAnimateRunning() { + return animator.isRunning(); + } + + void setAnimateDuration(int duration) { + this.duration = duration; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleUtils.java new file mode 100644 index 000000000..c80982bd3 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleUtils.java @@ -0,0 +1,93 @@ +package com.huantansheng.easyphotos.models.puzzle; + + +import com.huantansheng.easyphotos.models.puzzle.template.slant.OneSlantLayout; +import com.huantansheng.easyphotos.models.puzzle.template.slant.SlantLayoutHelper; +import com.huantansheng.easyphotos.models.puzzle.template.slant.ThreeSlantLayout; +import com.huantansheng.easyphotos.models.puzzle.template.slant.TwoSlantLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.EightStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.FiveStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.FourStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.NineStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.OneStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.SevenStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.SixStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.StraightLayoutHelper; +import com.huantansheng.easyphotos.models.puzzle.template.straight.ThreeStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.TwoStraightLayout; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public class PuzzleUtils { + + private PuzzleUtils() { + //no instance + } + + public static PuzzleLayout getPuzzleLayout(int type, int borderSize, int themeId) { + if (type == 0) { + switch (borderSize) { + case 1: + return new OneSlantLayout(themeId); + case 2: + return new TwoSlantLayout(themeId); + case 3: + return new ThreeSlantLayout(themeId); + default: + return new OneSlantLayout(themeId); + } + } else { + switch (borderSize) { + case 1: + return new OneStraightLayout(themeId); + case 2: + return new TwoStraightLayout(themeId); + case 3: + return new ThreeStraightLayout(themeId); + case 4: + return new FourStraightLayout(themeId); + case 5: + return new FiveStraightLayout(themeId); + case 6: + return new SixStraightLayout(themeId); + case 7: + return new SevenStraightLayout(themeId); + case 8: + return new EightStraightLayout(themeId); + case 9: + return new NineStraightLayout(themeId); + default: + return new OneStraightLayout(themeId); + } + } + } + + public static List getAllPuzzleLayouts() { + List puzzleLayouts = new ArrayList<>(); + //slant layout + puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(2)); + puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(3)); + + // straight layout + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(2)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(3)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(4)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(5)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(6)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(7)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(8)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(9)); + return puzzleLayouts; + } + + public static List getPuzzleLayouts(int pieceCount) { + List puzzleLayouts = new ArrayList<>(); + puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(pieceCount)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(pieceCount)); + return puzzleLayouts; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleView.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleView.java new file mode 100644 index 000000000..44e82c08c --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleView.java @@ -0,0 +1,792 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import androidx.core.content.ContextCompat; + +import com.huantansheng.easyphotos.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public class PuzzleView extends View { + private static final String TAG = "SlantPuzzleView"; + + private enum ActionMode { + NONE, + DRAG, + ZOOM, + MOVE, + SWAP + } + + private ActionMode currentMode = ActionMode.NONE; + + private List puzzlePieces = new ArrayList<>(); + + private List needChangePieces = new ArrayList<>(); + private PuzzleLayout puzzleLayout; + + private RectF bounds; + private int lineSize; + + private int duration; + private Line handlingLine; + + private PuzzlePiece handlingPiece; + private PuzzlePiece replacePiece; + private PuzzlePiece previousHandlingPiece; + + private Paint linePaint; + private Paint selectedAreaPaint; + private Paint handleBarPaint; + + private float downX; + private float downY; + private float previousDistance; + private PointF midPoint; + private boolean needDrawLine; + + private boolean needDrawOuterLine; + private boolean touchEnable = true; + private int lineColor; + + private int selectedLineColor; + private int handleBarColor; + private float piecePadding; + private float pieceRadian; + + private boolean needResetPieceMatrix = true; + + private OnPieceSelectedListener onPieceSelectedListener; + + private Runnable switchToSwapAction = new Runnable() { + @Override + public void run() { + currentMode = ActionMode.SWAP; + invalidate(); + } + }; + + public PuzzleView(Context context) { + this(context, null); + } + + public PuzzleView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PuzzleView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + if (attrs != null) { + TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PuzzleView); + lineSize = ta.getInt(R.styleable.PuzzleView_line_size, 4); + lineColor = ta.getColor(R.styleable.PuzzleView_line_color, + ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary)); + selectedLineColor = + ta.getColor(R.styleable.PuzzleView_selected_line_color, + ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent)); + handleBarColor = + ta.getColor(R.styleable.PuzzleView_handle_bar_color, + ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent)); + piecePadding = ta.getDimensionPixelSize(R.styleable.PuzzleView_piece_padding, 0); + needDrawLine = ta.getBoolean(R.styleable.PuzzleView_need_draw_line, false); + needDrawOuterLine = ta.getBoolean(R.styleable.PuzzleView_need_draw_outer_line, false); + duration = ta.getInt(R.styleable.PuzzleView_animation_duration, 300); + pieceRadian = ta.getFloat(R.styleable.PuzzleView_radian, 0f); + ta.recycle(); + } + + bounds = new RectF(); + + // init some paint + linePaint = new Paint(); + linePaint.setAntiAlias(true); + linePaint.setColor(lineColor); + linePaint.setStrokeWidth(lineSize); + linePaint.setStyle(Paint.Style.STROKE); + linePaint.setStrokeJoin(Paint.Join.ROUND); + linePaint.setStrokeCap(Paint.Cap.SQUARE); + + selectedAreaPaint = new Paint(); + selectedAreaPaint.setAntiAlias(true); + selectedAreaPaint.setStyle(Paint.Style.STROKE); + selectedAreaPaint.setStrokeJoin(Paint.Join.ROUND); + selectedAreaPaint.setStrokeCap(Paint.Cap.ROUND); + selectedAreaPaint.setColor(selectedLineColor); + selectedAreaPaint.setStrokeWidth(lineSize); + + handleBarPaint = new Paint(); + handleBarPaint.setAntiAlias(true); + handleBarPaint.setStyle(Paint.Style.FILL); + handleBarPaint.setColor(handleBarColor); + handleBarPaint.setStrokeWidth(lineSize * 3); + + midPoint = new PointF(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + resetPuzzleBounds(); + + if (puzzlePieces.size() != 0) { + int size = puzzlePieces.size(); + for (int i = 0; i < size; i++) { + PuzzlePiece piece = puzzlePieces.get(i); + piece.setArea(puzzleLayout.getArea(i)); + if (needResetPieceMatrix) { + piece.set(MatrixUtils.generateMatrix(piece, 0f)); + } else { + piece.fillArea(this, true); + } + } + } + invalidate(); + } + + private void resetPuzzleBounds() { + bounds.left = getPaddingLeft(); + bounds.top = getPaddingTop(); + bounds.right = getWidth() - getPaddingRight(); + bounds.bottom = getHeight() - getPaddingBottom(); + + if (puzzleLayout != null) { + puzzleLayout.reset(); + puzzleLayout.setOuterBounds(bounds); + puzzleLayout.layout(); + puzzleLayout.setPadding(piecePadding); + puzzleLayout.setRadian(pieceRadian); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (puzzleLayout == null) { + return; + } + + linePaint.setStrokeWidth(lineSize); + selectedAreaPaint.setStrokeWidth(lineSize); + handleBarPaint.setStrokeWidth(lineSize * 3); + + // draw pieces + int count = puzzleLayout.getAreaCount(); + for (int i = 0; i < count; i++) { + if (i >= puzzlePieces.size()) { + break; + } + + PuzzlePiece piece = puzzlePieces.get(i); + + if (piece == handlingPiece && currentMode == ActionMode.SWAP) { + continue; + } + + if (puzzlePieces.size() > i) { + piece.draw(canvas); + } + } + + // draw outer bounds + if (needDrawOuterLine) { + for (Line outerLine : puzzleLayout.getOuterLines()) { + drawLine(canvas, outerLine); + } + } + + // draw slant lines + if (needDrawLine) { + for (Line line : puzzleLayout.getLines()) { + drawLine(canvas, line); + } + } + + // draw selected area + if (handlingPiece != null && currentMode != ActionMode.SWAP) { + drawSelectedArea(canvas, handlingPiece); + } + + // draw swap piece + if (handlingPiece != null && currentMode == ActionMode.SWAP) { + handlingPiece.draw(canvas, 128); + if (replacePiece != null) { + drawSelectedArea(canvas, replacePiece); + } + } + } + + private void drawSelectedArea(Canvas canvas, PuzzlePiece piece) { + final Area area = piece.getArea(); + // draw select area + canvas.drawPath(area.getAreaPath(), selectedAreaPaint); + + // draw handle bar + for (Line line : area.getLines()) { + if (puzzleLayout.getLines().contains(line)) { + PointF[] handleBarPoints = area.getHandleBarPoints(line); + canvas.drawLine(handleBarPoints[0].x, handleBarPoints[0].y, handleBarPoints[1].x, + handleBarPoints[1].y, handleBarPaint); + canvas.drawCircle(handleBarPoints[0].x, handleBarPoints[0].y, lineSize * 3 / 2, + handleBarPaint); + canvas.drawCircle(handleBarPoints[1].x, handleBarPoints[1].y, lineSize * 3 / 2, + handleBarPaint); + } + } + } + + private void drawLine(Canvas canvas, Line line) { + canvas.drawLine(line.startPoint().x, line.startPoint().y, line.endPoint().x, + line.endPoint().y, + linePaint); + } + + public void setPuzzleLayout(PuzzleLayout puzzleLayout) { + clearPieces(); + + this.puzzleLayout = puzzleLayout; + + this.puzzleLayout.setOuterBounds(bounds); + this.puzzleLayout.layout(); + + invalidate(); + } + + public PuzzleLayout getPuzzleLayout() { + return this.puzzleLayout; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!touchEnable) { + return super.onTouchEvent(event); + } + switch (event.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + downX = event.getX(); + downY = event.getY(); + + decideActionMode(event); + prepareAction(event); + break; + + case MotionEvent.ACTION_POINTER_DOWN: + previousDistance = calculateDistance(event); + calculateMidPoint(event, midPoint); + + decideActionMode(event); + break; + + case MotionEvent.ACTION_MOVE: + performAction(event); + + if ((Math.abs(event.getX() - downX) > 10 || Math.abs(event.getY() - downY) > 10) + && currentMode != ActionMode.SWAP) { + removeCallbacks(switchToSwapAction); + } + + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + finishAction(event); + currentMode = ActionMode.NONE; + removeCallbacks(switchToSwapAction); + break; + } + + invalidate(); + return true; + } + + // 决定应该执行什么Action + private void decideActionMode(MotionEvent event) { + for (PuzzlePiece piece : puzzlePieces) { + if (piece.isAnimateRunning()) { + currentMode = ActionMode.NONE; + return; + } + } + + if (event.getPointerCount() == 1) { + handlingLine = findHandlingLine(); + if (handlingLine != null) { + currentMode = ActionMode.MOVE; + } else { + handlingPiece = findHandlingPiece(); + + if (handlingPiece != null) { + currentMode = ActionMode.DRAG; + + postDelayed(switchToSwapAction, 500); + } + } + } else if (event.getPointerCount() > 1) { + if (handlingPiece != null + && handlingPiece.contains(event.getX(1), event.getY(1)) + && currentMode == ActionMode.DRAG) { + currentMode = ActionMode.ZOOM; + } + } + } + + // 执行Action前的准备工作 + @SuppressWarnings("unused") + private void prepareAction(MotionEvent event) { + switch (currentMode) { + case NONE: + break; + case DRAG: + handlingPiece.record(); + break; + case ZOOM: + handlingPiece.record(); + break; + case MOVE: + handlingLine.prepareMove(); + needChangePieces.clear(); + needChangePieces.addAll(findNeedChangedPieces()); + for (PuzzlePiece piece : needChangePieces) { + piece.record(); + piece.setPreviousMoveX(downX); + piece.setPreviousMoveY(downY); + } + break; + } + } + + // 执行Action + private void performAction(MotionEvent event) { + switch (currentMode) { + case NONE: + break; + case DRAG: + dragPiece(handlingPiece, event); + break; + case ZOOM: + zoomPiece(handlingPiece, event); + break; + case SWAP: + dragPiece(handlingPiece, event); + replacePiece = findReplacePiece(event); + break; + case MOVE: + moveLine(handlingLine, event); + break; + } + } + + // 结束Action + private void finishAction(MotionEvent event) { + switch (currentMode) { + case NONE: + break; + case DRAG: + if (handlingPiece != null && !handlingPiece.isFilledArea()) { + handlingPiece.moveToFillArea(this); + } + + if (previousHandlingPiece == handlingPiece + && Math.abs(downX - event.getX()) < 3 + && Math.abs(downY - event.getY()) < 3) { + + handlingPiece = null; + } + + // trigger listener + if (onPieceSelectedListener != null) { + onPieceSelectedListener.onPieceSelected(handlingPiece, + puzzlePieces.indexOf(handlingPiece)); + } + + previousHandlingPiece = handlingPiece; + break; + case ZOOM: + if (handlingPiece != null && !handlingPiece.isFilledArea()) { + if (handlingPiece.canFilledArea()) { + handlingPiece.moveToFillArea(this); + } else { + handlingPiece.fillArea(this, false); + } + } + previousHandlingPiece = handlingPiece; + break; + case MOVE: + break; + case SWAP: + if (handlingPiece != null && replacePiece != null) { + Drawable temp = handlingPiece.getDrawable(); + + handlingPiece.setDrawable(replacePiece.getDrawable()); + replacePiece.setDrawable(temp); + + handlingPiece.fillArea(this, true); + replacePiece.fillArea(this, true); + + handlingPiece = null; + replacePiece = null; + previousHandlingPiece = null; + // trigger listener + if (onPieceSelectedListener != null) { + onPieceSelectedListener.onPieceSelected(null, + 0); + } + } + break; + } + + handlingLine = null; + needChangePieces.clear(); + } + + private void moveLine(Line line, MotionEvent event) { + if (line == null || event == null) return; + + boolean needUpdate; + if (line.direction() == Line.Direction.HORIZONTAL) { + needUpdate = line.move(event.getY() - downY, 80); + } else { + needUpdate = line.move(event.getX() - downX, 80); + } + + if (needUpdate) { + puzzleLayout.update(); + updatePiecesInArea(line, event); + } + } + + private void updatePiecesInArea(Line line, MotionEvent event) { + int size = needChangePieces.size(); + for (int i = 0; i < size; i++) { + needChangePieces.get(i).updateWith(event, line); + } + } + + private void zoomPiece(PuzzlePiece piece, MotionEvent event) { + if (piece == null || event == null || event.getPointerCount() < 2) return; + float scale = calculateDistance(event) / previousDistance; + piece.zoomAndTranslate(scale, scale, midPoint, event.getX() - downX, event.getY() - downY); + } + + private void dragPiece(PuzzlePiece piece, MotionEvent event) { + if (piece == null || event == null) return; + piece.translate(event.getX() - downX, event.getY() - downY); + } + + public void replace(Bitmap bitmap) { + replace(new BitmapDrawable(getResources(), bitmap)); + } + + public void replace(final Drawable bitmapDrawable) { + post(new Runnable() { + @Override + public void run() { + if (handlingPiece == null) { + return; + } + + handlingPiece.setDrawable(bitmapDrawable); + handlingPiece.set(MatrixUtils.generateMatrix(handlingPiece, 0f)); + + postInvalidate(); + } + }); + } + + public void flipVertically() { + if (handlingPiece == null) { + return; + } + + handlingPiece.postFlipVertically(); + handlingPiece.record(); + + invalidate(); + } + + public void flipHorizontally() { + if (handlingPiece == null) { + return; + } + + handlingPiece.postFlipHorizontally(); + handlingPiece.record(); + + invalidate(); + } + + public void rotate(float degree) { + if (handlingPiece == null) { + return; + } + + handlingPiece.postRotate(degree); + handlingPiece.record(); + + invalidate(); + } + + private PuzzlePiece findHandlingPiece() { + for (PuzzlePiece piece : puzzlePieces) { + if (piece.contains(downX, downY)) { + return piece; + } + } + return null; + } + + private Line findHandlingLine() { + for (Line line : puzzleLayout.getLines()) { + if (line.contains(downX, downY, 40)) { + return line; + } + } + return null; + } + + private PuzzlePiece findReplacePiece(MotionEvent event) { + for (PuzzlePiece piece : puzzlePieces) { + if (piece.contains(event.getX(), event.getY())) { + return piece; + } + } + return null; + } + + private List findNeedChangedPieces() { + if (handlingLine == null) return new ArrayList<>(); + + List needChanged = new ArrayList<>(); + + for (PuzzlePiece piece : puzzlePieces) { + if (piece.contains(handlingLine)) { + needChanged.add(piece); + } + } + + return needChanged; + } + + private float calculateDistance(MotionEvent event) { + float x = event.getX(0) - event.getX(1); + float y = event.getY(0) - event.getY(1); + + return (float) Math.sqrt(x * x + y * y); + } + + private void calculateMidPoint(MotionEvent event, PointF point) { + point.x = (event.getX(0) + event.getX(1)) / 2; + point.y = (event.getY(0) + event.getY(1)) / 2; + } + + public void reset() { + clearPieces(); + if (puzzleLayout != null) { + puzzleLayout.reset(); + } + } + + public void clearPieces() { + handlingLine = null; + handlingPiece = null; + replacePiece = null; + needChangePieces.clear(); + puzzlePieces.clear(); + } + + public void addPieces(List bitmaps) { + for (Bitmap bitmap : bitmaps) { + addPiece(bitmap); + } + + postInvalidate(); + } + + public void addPiece(Bitmap bitmap) { + BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap); + bitmapDrawable.setAntiAlias(true); + bitmapDrawable.setFilterBitmap(true); + + addPiece(bitmapDrawable); + } + + public void addPiece(Drawable drawable) { + int position = puzzlePieces.size(); + + if (position >= puzzleLayout.getAreaCount()) { + Log.e(TAG, "addPiece: can not add more. the current puzzle layout can contains " + + puzzleLayout.getAreaCount() + + " puzzle piece."); + return; + } + + final Area area = puzzleLayout.getArea(position); + area.setPadding(piecePadding); + + PuzzlePiece piece = new PuzzlePiece(drawable, area, new Matrix()); + + final Matrix matrix = MatrixUtils.generateMatrix(area, drawable, 0f); + piece.set(matrix); + + piece.setAnimateDuration(duration); + + puzzlePieces.add(piece); + + setPiecePadding(piecePadding); + setPieceRadian(pieceRadian); + + invalidate(); + } + + public void setAnimateDuration(int duration) { + this.duration = duration; + for (PuzzlePiece piece : puzzlePieces) { + piece.setAnimateDuration(duration); + } + } + + public boolean isNeedDrawLine() { + return needDrawLine; + } + + public void setNeedDrawLine(boolean needDrawLine) { + this.needDrawLine = needDrawLine; + handlingPiece = null; + previousHandlingPiece = null; + invalidate(); + } + + public boolean isNeedDrawOuterLine() { + return needDrawOuterLine; + } + + public void setNeedDrawOuterLine(boolean needDrawOuterLine) { + this.needDrawOuterLine = needDrawOuterLine; + invalidate(); + } + + public int getLineColor() { + return lineColor; + } + + public void setLineColor(int lineColor) { + this.lineColor = lineColor; + this.linePaint.setColor(lineColor); + invalidate(); + } + + public int getLineSize() { + return lineSize; + } + + public void setLineSize(int lineSize) { + this.lineSize = lineSize; + invalidate(); + } + + public int getSelectedLineColor() { + return selectedLineColor; + } + + public void setSelectedLineColor(int selectedLineColor) { + this.selectedLineColor = selectedLineColor; + this.selectedAreaPaint.setColor(selectedLineColor); + invalidate(); + } + + public int getHandleBarColor() { + return handleBarColor; + } + + public void setHandleBarColor(int handleBarColor) { + this.handleBarColor = handleBarColor; + this.handleBarPaint.setColor(handleBarColor); + invalidate(); + } + + public boolean isTouchEnable() { + return touchEnable; + } + + public void setTouchEnable(boolean touchEnable) { + this.touchEnable = touchEnable; + } + + public void clearHandling() { + handlingPiece = null; + handlingLine = null; + replacePiece = null; + previousHandlingPiece = null; + needChangePieces.clear(); + } + + public void setPiecePadding(float padding) { + this.piecePadding = padding; + if (puzzleLayout != null) { + puzzleLayout.setPadding(padding); + } + + invalidate(); + } + + public void setPieceRadian(float radian) { + this.pieceRadian = radian; + if (puzzleLayout != null) { + puzzleLayout.setRadian(radian); + } + + invalidate(); + } + + @Override + public void setBackgroundColor(int color) { + super.setBackgroundColor(color); + if (puzzleLayout != null) { + puzzleLayout.setColor(color); + } + } + + public void setNeedResetPieceMatrix(boolean needResetPieceMatrix) { + this.needResetPieceMatrix = needResetPieceMatrix; + } + + public float getPiecePadding() { + return piecePadding; + } + + public float getPieceRadian() { + return pieceRadian; + } + + public void setOnPieceSelectedListener(OnPieceSelectedListener onPieceSelectedListener) { + this.onPieceSelectedListener = onPieceSelectedListener; + } + + public interface OnPieceSelectedListener { + void onPieceSelected(PuzzlePiece piece, int position); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/SquarePuzzleView.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/SquarePuzzleView.java new file mode 100644 index 000000000..1c2c4437a --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/SquarePuzzleView.java @@ -0,0 +1,32 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.content.Context; +import android.util.AttributeSet; + +/** + * @author wupanjie + */ +public class SquarePuzzleView extends PuzzleView { + public SquarePuzzleView(Context context) { + super(context); + } + + public SquarePuzzleView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SquarePuzzleView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + int length = width > height ? height : width; + + setMeasuredDimension(length, length); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/CrossoverPointF.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/CrossoverPointF.java new file mode 100644 index 000000000..372b4a59b --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/CrossoverPointF.java @@ -0,0 +1,34 @@ +package com.huantansheng.easyphotos.models.puzzle.slant; + +import android.graphics.PointF; + +/** + * 两条线的交点 + * + * @author wupanjie + */ +class CrossoverPointF extends PointF { + SlantLine horizontal; + SlantLine vertical; + + CrossoverPointF() { + + } + + CrossoverPointF(float x, float y) { + this.x = x; + this.y = y; + } + + CrossoverPointF(SlantLine horizontal, SlantLine vertical) { + this.horizontal = horizontal; + this.vertical = vertical; + } + + void update() { + if (horizontal == null || vertical == null) { + return; + } + SlantUtils.intersectionOfLines(this, horizontal, vertical); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantArea.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantArea.java new file mode 100644 index 000000000..72676c81c --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantArea.java @@ -0,0 +1,294 @@ +package com.huantansheng.easyphotos.models.puzzle.slant; + +import android.graphics.Path; +import android.graphics.PointF; +import android.graphics.RectF; + + +import com.huantansheng.easyphotos.models.puzzle.Area; +import com.huantansheng.easyphotos.models.puzzle.Line; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.distance; +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.getPoint; +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.intersectionOfLines; + + +/** + * @author wupanjie + */ +class SlantArea implements Area { + SlantLine lineLeft; + SlantLine lineTop; + SlantLine lineRight; + SlantLine lineBottom; + + CrossoverPointF leftTop; + CrossoverPointF leftBottom; + CrossoverPointF rightTop; + CrossoverPointF rightBottom; + + private PointF tempPoint; + + private float paddingLeft; + private float paddingTop; + private float paddingRight; + private float paddingBottom; + private float radian; + + private Path areaPath = new Path(); + private RectF areaRect = new RectF(); + private PointF[] handleBarPoints = new PointF[2]; + + SlantArea() { + handleBarPoints[0] = new PointF(); + handleBarPoints[1] = new PointF(); + + leftTop = new CrossoverPointF(); + leftBottom = new CrossoverPointF(); + rightTop = new CrossoverPointF(); + rightBottom = new CrossoverPointF(); + + tempPoint = new PointF(); + } + + SlantArea(SlantArea src) { + this(); + this.lineLeft = src.lineLeft; + this.lineTop = src.lineTop; + this.lineRight = src.lineRight; + this.lineBottom = src.lineBottom; + + this.leftTop = src.leftTop; + this.leftBottom = src.leftBottom; + this.rightTop = src.rightTop; + this.rightBottom = src.rightBottom; + + updateCornerPoints(); + } + + @Override + public float left() { + return Math.min(leftTop.x, leftBottom.x) + paddingLeft; + } + + @Override + public float top() { + return Math.min(leftTop.y, rightTop.y) + paddingTop; + } + + @Override + public float right() { + return Math.max(rightTop.x, rightBottom.x) - paddingRight; + } + + @Override + public float bottom() { + return Math.max(leftBottom.y, rightBottom.y) - paddingBottom; + } + + @Override + public float centerX() { + return (left() + right()) / 2; + } + + @Override + public float centerY() { + return (top() + bottom()) / 2; + } + + @Override + public float width() { + return right() - left(); + } + + @Override + public float height() { + return bottom() - top(); + } + + @Override + public PointF getCenterPoint() { + return new PointF(centerX(), centerY()); + } + + public Path getAreaPath() { + areaPath.reset(); + + if (radian > 0) { + float tempRatio = radian / distance(leftTop, leftBottom); + getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio); + tempPoint.offset(paddingLeft, paddingTop); + areaPath.moveTo(tempPoint.x, tempPoint.y); + + tempRatio = radian / distance(leftTop, rightTop); + getPoint(tempPoint, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio); + tempPoint.offset(paddingLeft, paddingTop); + areaPath.quadTo(leftTop.x + paddingLeft, leftTop.y + paddingTop, tempPoint.x, tempPoint.y); + + tempRatio = 1 - tempRatio; + getPoint(tempPoint, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio); + tempPoint.offset(-paddingRight, paddingTop); + areaPath.lineTo(tempPoint.x, tempPoint.y); + + tempRatio = radian / distance(rightTop, rightBottom); + getPoint(tempPoint, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio); + tempPoint.offset(-paddingRight, paddingTop); + areaPath.quadTo(rightTop.x - paddingLeft, rightTop.y + paddingTop, tempPoint.x, tempPoint.y); + + tempRatio = 1 - tempRatio; + getPoint(tempPoint, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio); + tempPoint.offset(-paddingRight, -paddingBottom); + areaPath.lineTo(tempPoint.x, tempPoint.y); + + tempRatio = 1 - radian / distance(leftBottom, rightBottom); + getPoint(tempPoint, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio); + tempPoint.offset(-paddingRight, -paddingBottom); + areaPath.quadTo(rightBottom.x - paddingRight, rightBottom.y - paddingTop, tempPoint.x, tempPoint.y); + + tempRatio = 1 - tempRatio; + getPoint(tempPoint, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio); + tempPoint.offset(paddingLeft, -paddingBottom); + areaPath.lineTo(tempPoint.x, tempPoint.y); + + tempRatio = 1 - radian / distance(leftTop, leftBottom); + getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio); + tempPoint.offset(paddingLeft, -paddingBottom); + areaPath.quadTo(leftBottom.x + paddingLeft, leftBottom.y - paddingBottom, tempPoint.x, tempPoint.y); + + tempRatio = 1 - tempRatio; + getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio); + tempPoint.offset(paddingLeft, paddingTop); + areaPath.lineTo(tempPoint.x, tempPoint.y); + } else { + areaPath.moveTo(leftTop.x + paddingLeft, leftTop.y + paddingTop); + areaPath.lineTo(rightTop.x - paddingRight, rightTop.y + paddingTop); + areaPath.lineTo(rightBottom.x - paddingRight, rightBottom.y - paddingBottom); + areaPath.lineTo(leftBottom.x + paddingLeft, leftBottom.y - paddingBottom); + areaPath.lineTo(leftTop.x + paddingLeft, leftTop.y + paddingTop); + } + return areaPath; + } + + @Override + public RectF getAreaRect() { + areaRect.set(left(), top(), right(), bottom()); + return areaRect; + } + + public boolean contains(float x, float y) { + return SlantUtils.contains(this, x, y); + } + + @Override + public boolean contains(Line line) { + return lineLeft == line || lineTop == line || lineRight == line || lineBottom == line; + } + + @Override + public boolean contains(PointF point) { + return contains(point.x, point.y); + } + + @Override + public List getLines() { + return Arrays.asList((Line) lineLeft, lineTop, lineRight, lineBottom); + } + + @Override + public PointF[] getHandleBarPoints(Line line) { + if (line == lineLeft) { + getPoint(handleBarPoints[0], leftTop, leftBottom, line.direction(), 0.25f); + getPoint(handleBarPoints[1], leftTop, leftBottom, line.direction(), 0.75f); + handleBarPoints[0].offset(paddingLeft, 0); + handleBarPoints[1].offset(paddingLeft, 0); + } else if (line == lineTop) { + getPoint(handleBarPoints[0], leftTop, rightTop, line.direction(), 0.25f); + getPoint(handleBarPoints[1], leftTop, rightTop, line.direction(), 0.75f); + handleBarPoints[0].offset(0, paddingTop); + handleBarPoints[1].offset(0, paddingTop); + } else if (line == lineRight) { + getPoint(handleBarPoints[0], rightTop, rightBottom, line.direction(), 0.25f); + getPoint(handleBarPoints[1], rightTop, rightBottom, line.direction(), 0.75f); + handleBarPoints[0].offset(-paddingRight, 0); + handleBarPoints[1].offset(-paddingRight, 0); + } else if (line == lineBottom) { + getPoint(handleBarPoints[0], leftBottom, rightBottom, line.direction(), 0.25f); + getPoint(handleBarPoints[1], leftBottom, rightBottom, line.direction(), 0.75f); + handleBarPoints[0].offset(0, -paddingBottom); + handleBarPoints[1].offset(0, -paddingBottom); + } + return handleBarPoints; + } + + @Override + public float radian() { + return radian; + } + + @Override + public void setRadian(float radian) { + this.radian = radian; + } + + @Override + public float getPaddingLeft() { + return paddingLeft; + } + + @Override + public float getPaddingTop() { + return paddingTop; + } + + @Override + public float getPaddingRight() { + return paddingRight; + } + + @Override + public float getPaddingBottom() { + return paddingBottom; + } + + @Override + public void setPadding(float padding) { + setPadding(padding, padding, padding, padding); + } + + @Override + public void setPadding(float paddingLeft, float paddingTop, float paddingRight, float paddingBottom) { + this.paddingLeft = paddingLeft; + this.paddingTop = paddingTop; + this.paddingRight = paddingRight; + this.paddingBottom = paddingBottom; + } + + void updateCornerPoints() { + intersectionOfLines(leftTop, lineLeft, lineTop); + intersectionOfLines(leftBottom, lineLeft, lineBottom); + intersectionOfLines(rightTop, lineRight, lineTop); + intersectionOfLines(rightBottom, lineRight, lineBottom); + } + + static class AreaComparator implements Comparator { + + @Override + public int compare(SlantArea one, SlantArea two) { + if (one.leftTop.y < two.leftTop.y) { + return -1; + } else if (one.leftTop.y == two.leftTop.y) { + if (one.leftTop.x < two.leftTop.x) { + return -1; + } else { + return 1; + } + } else { + return 1; + } + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantLine.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantLine.java new file mode 100644 index 000000000..1032a1b5f --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantLine.java @@ -0,0 +1,173 @@ +package com.huantansheng.easyphotos.models.puzzle.slant; + +import android.graphics.PointF; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.intersectionOfLines; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.lang.Math.pow; +import static java.lang.Math.sqrt; + +/** + * 分为两种斜线,横谢线和竖线线 + * 横斜线-->start为左边的点,end为右边的点 + * 竖斜线-->start为上面的点,end为下面的点 + * + * @author wupanjie + */ +class SlantLine implements Line { + CrossoverPointF start; + CrossoverPointF end; + + // 移动前的点 + private PointF previousStart = new PointF(); + private PointF previousEnd = new PointF(); + + public final Direction direction; + + SlantLine attachLineStart; + SlantLine attachLineEnd; + + Line upperLine; + Line lowerLine; + + SlantLine(Direction direction) { + this.direction = direction; + } + + SlantLine(CrossoverPointF start, CrossoverPointF end, Direction direction) { + this.start = start; + this.end = end; + this.direction = direction; + } + + public float length() { + return (float) sqrt(pow(end.x - start.x, 2) + pow(end.y - start.y, 2)); + } + + @Override + public PointF startPoint() { + return start; + } + + @Override + public PointF endPoint() { + return end; + } + + @Override + public Line lowerLine() { + return lowerLine; + } + + @Override + public Line upperLine() { + return upperLine; + } + + @Override + public Line attachStartLine() { + return attachLineStart; + } + + @Override + public Line attachEndLine() { + return attachLineEnd; + } + + @Override + public void setLowerLine(Line lowerLine) { + this.lowerLine = lowerLine; + } + + @Override + public void setUpperLine(Line upperLine) { + this.upperLine = upperLine; + } + + @Override + public Direction direction() { + return direction; + } + + @Override + public float slope() { + return SlantUtils.calculateSlope(this); + } + + public boolean contains(float x, float y, float extra) { + return SlantUtils.contains(this, x, y, extra); + } + + @Override + public boolean move(float offset, float extra) { + if (direction == Direction.HORIZONTAL) { + if (previousStart.y + offset < lowerLine.maxY() + extra + || previousStart.y + offset > upperLine.minY() - extra + || previousEnd.y + offset < lowerLine.maxY() + extra + || previousEnd.y + offset > upperLine.minY() - extra) { + return false; + } + + start.y = previousStart.y + offset; + end.y = previousEnd.y + offset; + } else { + if (previousStart.x + offset < lowerLine.maxX() + extra + || previousStart.x + offset > upperLine.minX() - extra + || previousEnd.x + offset < lowerLine.maxX() + extra + || previousEnd.x + offset > upperLine.minX() - extra) { + return false; + } + + start.x = previousStart.x + offset; + end.x = previousEnd.x + offset; + } + + return true; + } + + @Override + public void prepareMove() { + previousStart.set(start); + previousEnd.set(end); + } + + @Override + public void update(float layoutWidth, float layoutHeight) { + intersectionOfLines(start, this, attachLineStart); + intersectionOfLines(end, this, attachLineEnd); + } + + @Override + public float minX() { + return min(start.x, end.x); + } + + @Override + public float maxX() { + return max(start.x, end.x); + } + + @Override + public float minY() { + return min(start.y, end.y); + } + + @Override + public float maxY() { + return max(start.y, end.y); + } + + @Override + public void offset(float x, float y) { + start.offset(x, y); + end.offset(x, y); + } + + @Override + public String toString() { + return "start --> " + start.toString() + ",end --> " + end.toString(); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantPuzzleLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantPuzzleLayout.java new file mode 100644 index 000000000..e9c0a6084 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantPuzzleLayout.java @@ -0,0 +1,334 @@ +package com.huantansheng.easyphotos.models.puzzle.slant; + +import android.graphics.RectF; +import android.util.Pair; + +import com.huantansheng.easyphotos.models.puzzle.Area; +import com.huantansheng.easyphotos.models.puzzle.Line; +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.createLine; +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.cutAreaCross; +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.cutAreaWith; + + +/** + * 斜线布局,外围区域为一矩形 + * + * @author wupanjie + */ +public abstract class SlantPuzzleLayout implements PuzzleLayout { + private RectF bounds; + private SlantArea outerArea; + + private List outerLines = new ArrayList<>(4); + private List areas = new ArrayList<>(); + private List lines = new ArrayList<>(); + + private float padding; + private float radian; + private int color; + + private Comparator areaComparator = new SlantArea.AreaComparator(); + + private ArrayList steps = new ArrayList<>(); + + protected SlantPuzzleLayout() { + + } + + @Override + public void setOuterBounds(RectF bounds) { + reset(); + + this.bounds = bounds; + + CrossoverPointF leftTop = new CrossoverPointF(bounds.left, bounds.top); + CrossoverPointF rightTop = new CrossoverPointF(bounds.right, bounds.top); + CrossoverPointF leftBottom = new CrossoverPointF(bounds.left, bounds.bottom); + CrossoverPointF rightBottom = new CrossoverPointF(bounds.right, bounds.bottom); + + SlantLine lineLeft = new SlantLine(leftTop, leftBottom, Line.Direction.VERTICAL); + SlantLine lineTop = new SlantLine(leftTop, rightTop, Line.Direction.HORIZONTAL); + SlantLine lineRight = new SlantLine(rightTop, rightBottom, Line.Direction.VERTICAL); + SlantLine lineBottom = new SlantLine(leftBottom, rightBottom, Line.Direction.HORIZONTAL); + + outerLines.clear(); + + outerLines.add(lineLeft); + outerLines.add(lineTop); + outerLines.add(lineRight); + outerLines.add(lineBottom); + + outerArea = new SlantArea(); + outerArea.lineLeft = lineLeft; + outerArea.lineTop = lineTop; + outerArea.lineRight = lineRight; + outerArea.lineBottom = lineBottom; + + outerArea.updateCornerPoints(); + + areas.clear(); + areas.add(outerArea); + } + + public abstract void layout(); + + private void updateLineLimit() { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line line = lines.get(i); + updateUpperLine(line); + updateLowerLine(line); + } + } + + private void updateLowerLine(final Line line) { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line l = lines.get(i); + if (l.direction() != line.direction()) { + continue; + } + + if (l.attachStartLine() != line.attachStartLine() + || l.attachEndLine() != line.attachEndLine()) { + continue; + } + + if (l.direction() == Line.Direction.HORIZONTAL) { + if (l.minY() > line.lowerLine().maxY() && l.maxY() < line.minY()) { + line.setLowerLine(l); + } + } else { + if (l.minX() > line.lowerLine().maxX() && l.maxX() < line.minX()) { + line.setLowerLine(l); + } + } + } + } + + private void updateUpperLine(final Line line) { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line l = lines.get(i); + if (l.direction() != line.direction()) { + continue; + } + + if (l.attachStartLine() != line.attachStartLine() + || l.attachEndLine() != line.attachEndLine()) { + continue; + } + + if (l.direction() == Line.Direction.HORIZONTAL) { + if (l.maxY() < line.upperLine().minY() && l.minY() > line.maxY()) { + line.setUpperLine(l); + } + } else { + if (l.maxX() < line.upperLine().minX() && l.minX() > line.maxX()) { + line.setUpperLine(l); + } + } + } + } + + @Override + public int getAreaCount() { + return areas.size(); + } + + @Override + public void reset() { + lines.clear(); + areas.clear(); + areas.add(outerArea); + steps.clear(); + } + + @Override + public void update() { + int size = lines.size(); + for (int i = 0; i < size; i++) { + lines.get(i).update(width(), height()); + } + int areasSize = areas.size(); + for (int i = 0; i < areasSize; i++) { + areas.get(i).updateCornerPoints(); + } + } + + @Override + public float width() { + return outerArea == null ? 0 : outerArea.width(); + } + + @Override + public float height() { + return outerArea == null ? 0 : outerArea.height(); + } + + private void sortAreas() { + Collections.sort(areas, areaComparator); + } + + @Override + public List getOuterLines() { + return outerLines; + } + + @Override + public Area getOuterArea() { + return outerArea; + } + + public List getAreas() { + return areas; + } + + @Override + public SlantArea getArea(int position) { + return areas.get(position); + } + + @Override + public List getLines() { + return lines; + } + + @Override + public void setPadding(float padding) { + this.padding = padding; + for (Area area : areas) { + area.setPadding(padding); + } + + outerArea.lineLeft.startPoint().set(bounds.left + padding, bounds.top + padding); + outerArea.lineLeft.endPoint().set(bounds.left + padding, bounds.bottom - padding); + + outerArea.lineRight.startPoint().set(bounds.right - padding, bounds.top + padding); + outerArea.lineRight.endPoint().set(bounds.right - padding, bounds.bottom - padding); + + outerArea.updateCornerPoints(); + update(); + } + + @Override + public float getPadding() { + return padding; + } + + @Override + public float getRadian() { + return radian; + } + + @Override + public void setRadian(float radian) { + this.radian = radian; + for (Area area : areas) { + area.setRadian(radian); + } + } + + @Override + public int getColor() { + return color; + } + + @Override + public void setColor(int color) { + this.color = color; + } + + protected List addLine(int position, Line.Direction direction, float ratio) { + return addLine(position, direction, ratio, ratio); + } + + protected List addLine(int position, Line.Direction direction, float startRatio, + float endRatio) { + SlantArea area = areas.get(position); + areas.remove(area); + SlantLine line = createLine(area, direction, startRatio, endRatio); + lines.add(line); + + List increasedAreas = cutAreaWith(area, line); + + areas.addAll(increasedAreas); + + updateLineLimit(); + sortAreas(); + + Step step = new Step(); + step.type = Step.ADD_LINE; + step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1; + step.position = position; + steps.add(step); + + return increasedAreas; + } + + protected void addCross(int position, float startRatio1, float endRatio1, + float startRatio2, float endRatio2) { + SlantArea area = areas.get(position); + areas.remove(area); + + SlantLine horizontal = createLine(area, Line.Direction.HORIZONTAL, startRatio1, endRatio1); + SlantLine vertical = createLine(area, Line.Direction.VERTICAL, startRatio2, endRatio2); + lines.add(horizontal); + lines.add(vertical); + + List increasedAreas = cutAreaCross(area, horizontal, vertical); + + areas.addAll(increasedAreas); + sortAreas(); + + Step step = new Step(); + step.type = Step.ADD_CROSS; + step.position = position; + steps.add(step); + } + + protected void cutArea(int position, int hSize, int vSize) { + SlantArea area = areas.get(position); + areas.remove(area); + + Pair, List> spilt = + cutAreaWith(area, hSize, vSize); + + lines.addAll(spilt.first); + areas.addAll(spilt.second); + + updateLineLimit(); + sortAreas(); + + Step step = new Step(); + step.type = Step.CUT_EQUAL_PART_ONE; + step.position = position; + step.hSize = hSize; + step.vSize = vSize; + steps.add(step); + } + + @Override + public Info generateInfo() { + Info info = new Info(); + info.type = Info.TYPE_SLANT; + info.padding = padding; + info.radian = radian; + info.color = color; + info.steps = steps; + ArrayList lineInfos = new ArrayList<>(); + for (Line line : lines) { + LineInfo lineInfo = new LineInfo(line); + lineInfos.add(lineInfo); + } + info.lineInfos = lineInfos; + return info; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantUtils.java new file mode 100644 index 000000000..e7c79d0bb --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantUtils.java @@ -0,0 +1,482 @@ +package com.huantansheng.easyphotos.models.puzzle.slant; + +import android.graphics.PointF; +import android.util.Pair; + + +import com.huantansheng.easyphotos.models.puzzle.Line; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +class SlantUtils { + private static final PointF A = new PointF(); + private static final PointF B = new PointF(); + private static final PointF C = new PointF(); + private static final PointF D = new PointF(); + private static final PointF AB = new PointF(); + private static final PointF AM = new PointF(); + private static final PointF BC = new PointF(); + private static final PointF BM = new PointF(); + private static final PointF CD = new PointF(); + private static final PointF CM = new PointF(); + private static final PointF DA = new PointF(); + private static final PointF DM = new PointF(); + + private SlantUtils() { + //no instance + } + + static float distance(PointF one, PointF two) { + return (float) Math.sqrt(Math.pow(two.x - one.x, 2) + Math.pow(two.y - one.y, 2)); + } + + static List cutAreaWith(SlantArea area, SlantLine line) { + List areas = new ArrayList<>(); + SlantArea area1 = new SlantArea(area); + SlantArea area2 = new SlantArea(area); + + if (line.direction == Line.Direction.HORIZONTAL) { + area1.lineBottom = line; + area1.leftBottom = line.start; + area1.rightBottom = line.end; + + area2.lineTop = line; + area2.leftTop = line.start; + area2.rightTop = line.end; + } else { + area1.lineRight = line; + area1.rightTop = line.start; + area1.rightBottom = line.end; + + area2.lineLeft = line; + area2.leftTop = line.start; + area2.leftBottom = line.end; + } + + areas.add(area1); + areas.add(area2); + + return areas; + } + + static SlantLine createLine(SlantArea area, Line.Direction direction, float startratio, + float endratio) { + SlantLine line = new SlantLine(direction); + + if (direction == Line.Direction.HORIZONTAL) { + line.start = getPoint(area.leftTop, area.leftBottom, Line.Direction.VERTICAL, startratio); + line.end = getPoint(area.rightTop, area.rightBottom, Line.Direction.VERTICAL, endratio); + + line.attachLineStart = area.lineLeft; + line.attachLineEnd = area.lineRight; + + line.upperLine = area.lineBottom; + line.lowerLine = area.lineTop; + } else { + line.start = getPoint(area.leftTop, area.rightTop, Line.Direction.HORIZONTAL, startratio); + line.end = getPoint(area.leftBottom, area.rightBottom, Line.Direction.HORIZONTAL, endratio); + + line.attachLineStart = area.lineTop; + line.attachLineEnd = area.lineBottom; + + line.upperLine = area.lineRight; + line.lowerLine = area.lineLeft; + } + + return line; + } + + static Pair, List> cutAreaWith(final SlantArea area, + final int horizontalSize, final int verticalSize) { + List areaList = new ArrayList<>(); + List horizontalLines = new ArrayList<>(horizontalSize); + + SlantArea restArea = new SlantArea(area); + for (int i = horizontalSize + 1; i > 1; i--) { + SlantLine horizontalLine = + createLine(restArea, Line.Direction.HORIZONTAL, (float) (i - 1) / i - 0.025f, (float) (i - 1) / i + 0.025f); + horizontalLines.add(horizontalLine); + restArea.lineBottom = horizontalLine; + restArea.leftBottom = horizontalLine.start; + restArea.rightBottom = horizontalLine.end; + } + List verticalLines = new ArrayList<>(); + + restArea = new SlantArea(area); + for (int i = verticalSize + 1; i > 1; i--) { + SlantLine verticalLine = + createLine(restArea, Line.Direction.VERTICAL, (float) (i - 1) / i + 0.025f, (float) (i - 1) / i - 0.025f); + verticalLines.add(verticalLine); + SlantArea spiltArea = new SlantArea(restArea); + spiltArea.lineLeft = verticalLine; + spiltArea.leftTop = verticalLine.start; + spiltArea.leftBottom = verticalLine.end; + + int size = horizontalLines.size(); + for (int j = 0; j <= size; j++) { + SlantArea blockArea = new SlantArea(spiltArea); + if (j == 0) { + blockArea.lineTop = horizontalLines.get(j); + } else if (j == size) { + blockArea.lineBottom = horizontalLines.get(j - 1); + + CrossoverPointF leftBottom = + new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft); + intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft); + CrossoverPointF rightBottom = + new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight); + intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight); + blockArea.leftBottom = leftBottom; + blockArea.rightBottom = rightBottom; + } else { + blockArea.lineTop = horizontalLines.get(j); + blockArea.lineBottom = horizontalLines.get(j - 1); + } + CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft); + intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft); + CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight); + intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight); + blockArea.leftTop = leftTop; + blockArea.rightTop = rightTop; + areaList.add(blockArea); + } + restArea.lineRight = verticalLine; + restArea.rightTop = verticalLine.start; + restArea.rightBottom = verticalLine.end; + } + int size = horizontalLines.size(); + for (int j = 0; j <= size; j++) { + SlantArea blockArea = new SlantArea(restArea); + if (j == 0) { + blockArea.lineTop = horizontalLines.get(j); + } else if (j == size) { + blockArea.lineBottom = horizontalLines.get(j - 1); + CrossoverPointF leftBottom = new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft); + intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft); + CrossoverPointF rightBottom = + new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight); + intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight); + blockArea.leftBottom = leftBottom; + blockArea.rightBottom = rightBottom; + } else { + blockArea.lineTop = horizontalLines.get(j); + blockArea.lineBottom = horizontalLines.get(j - 1); + } + CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft); + intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft); + CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight); + intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight); + blockArea.leftTop = leftTop; + blockArea.rightTop = rightTop; + areaList.add(blockArea); + } + + List lines = new ArrayList<>(); + lines.addAll(horizontalLines); + lines.addAll(verticalLines); + return new Pair<>(lines, areaList); + } + + static List cutAreaCross(final SlantArea area, final SlantLine horizontal, + final SlantLine vertical) { + List list = new ArrayList<>(); + + CrossoverPointF crossoverPoint = new CrossoverPointF(horizontal, vertical); + intersectionOfLines(crossoverPoint, horizontal, vertical); + + SlantArea one = new SlantArea(area); + one.lineBottom = horizontal; + one.lineRight = vertical; + + one.rightTop = vertical.start; + one.rightBottom = crossoverPoint; + one.leftBottom = horizontal.start; + list.add(one); + + SlantArea two = new SlantArea(area); + two.lineBottom = horizontal; + two.lineLeft = vertical; + + two.leftTop = vertical.start; + two.rightBottom = horizontal.end; + two.leftBottom = crossoverPoint; + list.add(two); + + SlantArea three = new SlantArea(area); + three.lineTop = horizontal; + three.lineRight = vertical; + + three.leftTop = horizontal.start; + three.rightTop = crossoverPoint; + three.rightBottom = vertical.end; + list.add(three); + + SlantArea four = new SlantArea(area); + four.lineTop = horizontal; + four.lineLeft = vertical; + + four.leftTop = crossoverPoint; + four.rightTop = horizontal.end; + four.leftBottom = vertical.end; + list.add(four); + + return list; + } + + private static CrossoverPointF getPoint(final PointF start, final PointF end, + final Line.Direction direction, float ratio) { + CrossoverPointF point = new CrossoverPointF(); + getPoint(point, start, end, direction, ratio); + return point; + } + + static void getPoint(final PointF dst, final PointF start, final PointF end, + final Line.Direction direction, float ratio) { + float deltaY = Math.abs(start.y - end.y); + float deltaX = Math.abs(start.x - end.x); + float maxY = Math.max(start.y, end.y); + float minY = Math.min(start.y, end.y); + float maxX = Math.max(start.x, end.x); + float minX = Math.min(start.x, end.x); + if (direction == Line.Direction.HORIZONTAL) { + dst.x = minX + deltaX * ratio; + if (start.y < end.y) { + dst.y = minY + ratio * deltaY; + } else { + dst.y = maxY - ratio * deltaY; + } + } else { + dst.y = minY + deltaY * ratio; + if (start.x < end.x) { + dst.x = minX + ratio * deltaX; + } else { + dst.x = maxX - ratio * deltaX; + } + } + } + + // 叉乘 + private static float crossProduct(final PointF a, final PointF b) { + return a.x * b.y - b.x * a.y; + } + + /** + * 判断一个斜线区域是否包含(x,y)点 + * + * @param area 斜线区域 + * @param x x + * @param y y + * @return 是否包含 + */ + static boolean contains(SlantArea area, float x, float y) { + AB.x = area.rightTop.x - area.leftTop.x; + AB.y = area.rightTop.y - area.leftTop.y; + + AM.x = x - area.leftTop.x; + AM.y = y - area.leftTop.y; + + BC.x = area.rightBottom.x - area.rightTop.x; + BC.y = area.rightBottom.y - area.rightTop.y; + + BM.x = x - area.rightTop.x; + BM.y = y - area.rightTop.y; + + CD.x = area.leftBottom.x - area.rightBottom.x; + CD.y = area.leftBottom.y - area.rightBottom.y; + + CM.x = x - area.rightBottom.x; + CM.y = y - area.rightBottom.y; + + DA.x = area.leftTop.x - area.leftBottom.x; + DA.y = area.leftTop.y - area.leftBottom.y; + + DM.x = x - area.leftBottom.x; + DM.y = y - area.leftBottom.y; + + return crossProduct(AB, AM) > 0 + && crossProduct(BC, BM) > 0 + && crossProduct(CD, CM) > 0 + && crossProduct(DA, DM) > 0; + } + + static boolean contains(SlantLine line, float x, float y, float extra) { + PointF start = line.start; + PointF end = line.end; + if (line.direction == Line.Direction.VERTICAL) { + A.x = start.x - extra; + A.y = start.y; + B.x = start.x + extra; + B.y = start.y; + C.x = end.x + extra; + C.y = end.y; + D.x = end.x - extra; + D.y = end.y; + } else { + A.x = start.x; + A.y = start.y - extra; + B.x = end.x; + B.y = end.y - extra; + C.x = end.x; + C.y = end.y + extra; + D.x = start.x; + D.y = start.y + extra; + } + + AB.x = B.x - A.x; + AB.y = B.y - A.y; + + AM.x = x - A.x; + AM.y = y - A.y; + + BC.x = C.x - B.x; + BC.y = C.y - B.y; + + BM.x = x - B.x; + BM.y = y - B.y; + + CD.x = D.x - C.x; + CD.y = D.y - C.y; + + CM.x = x - C.x; + CM.y = y - C.y; + + DA.x = A.x - D.x; + DA.y = A.y - D.y; + + DM.x = x - D.x; + DM.y = y - D.y; + + return crossProduct(AB, AM) > 0 + && crossProduct(BC, BM) > 0 + && crossProduct(CD, CM) > 0 + && crossProduct(DA, DM) > 0; + } + + /** + * 计算两线的交点 + * + * @param dst 计算出的交点 + * @param lineOne 线一 + * @param lineTwo 线二 + */ + static void intersectionOfLines(final CrossoverPointF dst, final SlantLine lineOne, + final SlantLine lineTwo) { + dst.horizontal = lineOne; + dst.vertical = lineTwo; + if (isParallel(lineOne, lineTwo)) { + dst.set(0, 0); + return; + } + + if (isHorizontalLine(lineOne) && isVerticalLine(lineTwo)) { + dst.set(lineTwo.start.x, lineOne.start.y); + return; + } + + if (isVerticalLine(lineOne) && isHorizontalLine(lineTwo)) { + dst.set(lineOne.start.x, lineTwo.start.y); + return; + } + + if (isHorizontalLine(lineOne) && !isVerticalLine(lineTwo)) { + float k = calculateSlope(lineTwo); + float b = calculateVerticalIntercept(lineTwo); + + dst.y = lineOne.start.y; + dst.x = (dst.y - b) / k; + return; + } + + if (isVerticalLine(lineOne) && !isHorizontalLine(lineTwo)) { + float k = calculateSlope(lineTwo); + float b = calculateVerticalIntercept(lineTwo); + + dst.x = lineOne.start.x; + dst.y = k * dst.x + b; + return; + } + + if (isHorizontalLine(lineTwo) && !isVerticalLine(lineOne)) { + float k = calculateSlope(lineOne); + float b = calculateVerticalIntercept(lineOne); + + dst.y = lineTwo.start.y; + dst.x = (dst.y - b) / k; + return; + } + + if (isVerticalLine(lineTwo) && !isHorizontalLine(lineOne)) { + float k = calculateSlope(lineOne); + float b = calculateVerticalIntercept(lineOne); + + dst.x = lineTwo.start.x; + dst.y = k * dst.x + b; + return; + } + + final float k1 = calculateSlope(lineOne); + final float b1 = calculateVerticalIntercept(lineOne); + + final float k2 = calculateSlope(lineTwo); + final float b2 = calculateVerticalIntercept(lineTwo); + + dst.x = (b2 - b1) / (k1 - k2); + dst.y = dst.x * k1 + b1; + } + + private static boolean isHorizontalLine(SlantLine line) { + return line.start.y == line.end.y; + } + + private static boolean isVerticalLine(SlantLine line) { + return line.start.x == line.end.x; + } + + /** + * 判断两条线是否平行 + * + * @param lineOne 第一条 + * @param lineTwo 第二条 + * @return 是否平行 + */ + private static boolean isParallel(final SlantLine lineOne, final SlantLine lineTwo) { + return calculateSlope(lineOne) == calculateSlope(lineTwo); + } + + /** + * 计算线的斜率 + * + * @param line 线 + * @return 线的斜率 + */ + static float calculateSlope(final SlantLine line) { + if (isHorizontalLine(line)) { + return 0f; + } else if (isVerticalLine(line)) { + return Float.POSITIVE_INFINITY; + } else { + return (line.start.y - line.end.y) / (line.start.x - line.end.x); + } + } + + /** + * 计算纵截距 + * + * @param line 线 + * @return 纵截距 + */ + private static float calculateVerticalIntercept(final SlantLine line) { + if (isHorizontalLine(line)) { + return line.start.y; + } else if (isVerticalLine(line)) { + return Float.POSITIVE_INFINITY; + } else { + float k = calculateSlope(line); + return line.start.y - k * line.start.x; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightArea.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightArea.java new file mode 100644 index 000000000..2adfcdea2 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightArea.java @@ -0,0 +1,231 @@ +package com.huantansheng.easyphotos.models.puzzle.straight; + +import android.graphics.Path; +import android.graphics.PointF; +import android.graphics.RectF; + +import com.huantansheng.easyphotos.models.puzzle.Area; +import com.huantansheng.easyphotos.models.puzzle.Line; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + + +/** + * @author wupanjie + */ +class StraightArea implements Area { + StraightLine lineLeft; + StraightLine lineTop; + StraightLine lineRight; + StraightLine lineBottom; + + private Path areaPath = new Path(); + private RectF areaRect = new RectF(); + private PointF[] handleBarPoints = new PointF[2]; + + private float paddingLeft; + private float paddingTop; + private float paddingRight; + private float paddingBottom; + private float radian; + + StraightArea() { + handleBarPoints[0] = new PointF(); + handleBarPoints[1] = new PointF(); + } + + StraightArea(RectF baseRect) { + this(); + setBaseRect(baseRect); + } + + private void setBaseRect(RectF baseRect) { + PointF one = new PointF(baseRect.left, baseRect.top); + PointF two = new PointF(baseRect.right, baseRect.top); + PointF three = new PointF(baseRect.left, baseRect.bottom); + PointF four = new PointF(baseRect.right, baseRect.bottom); + + lineLeft = new StraightLine(one, three); + lineTop = new StraightLine(one, two); + lineRight = new StraightLine(two, four); + lineBottom = new StraightLine(three, four); + } + + StraightArea(StraightArea src) { + this.lineLeft = src.lineLeft; + this.lineTop = src.lineTop; + this.lineRight = src.lineRight; + this.lineBottom = src.lineBottom; + + handleBarPoints[0] = new PointF(); + handleBarPoints[1] = new PointF(); + } + + @Override + public float left() { + return lineLeft.minX() + paddingLeft; + } + + @Override + public float top() { + return lineTop.minY() + paddingTop; + } + + @Override + public float right() { + return lineRight.maxX() - paddingRight; + } + + @Override + public float bottom() { + return lineBottom.maxY() - paddingBottom; + } + + @Override + public float centerX() { + return (left() + right()) / 2; + } + + @Override + public float centerY() { + return (top() + bottom()) / 2; + } + + @Override + public float width() { + return right() - left(); + } + + @Override + public float height() { + return bottom() - top(); + } + + @Override + public PointF getCenterPoint() { + return new PointF(centerX(), centerY()); + } + + @Override + public boolean contains(PointF point) { + return contains(point.x, point.y); + } + + @Override + public boolean contains(float x, float y) { + return getAreaRect().contains(x, y); + } + + @Override + public boolean contains(Line line) { + return lineLeft == line || lineTop == line || lineRight == line || lineBottom == line; + } + + @Override + public Path getAreaPath() { + areaPath.reset(); + areaPath.addRoundRect(getAreaRect(), radian, radian, Path.Direction.CCW); + //areaPath.addRect(getAreaRect(), Path.Direction.CCW); + return areaPath; + } + + @Override + public RectF getAreaRect() { + areaRect.set(left(), top(), right(), bottom()); + return areaRect; + } + + @Override + public List getLines() { + return Arrays.asList((Line) lineLeft, lineTop, lineRight, lineBottom); + } + + @Override + public PointF[] getHandleBarPoints(Line line) { + if (line == lineLeft) { + handleBarPoints[0].x = left(); + handleBarPoints[0].y = top() + height() / 4; + handleBarPoints[1].x = left(); + handleBarPoints[1].y = top() + height() / 4 * 3; + } else if (line == lineTop) { + handleBarPoints[0].x = left() + width() / 4; + handleBarPoints[0].y = top(); + handleBarPoints[1].x = left() + width() / 4 * 3; + handleBarPoints[1].y = top(); + } else if (line == lineRight) { + handleBarPoints[0].x = right(); + handleBarPoints[0].y = top() + height() / 4; + handleBarPoints[1].x = right(); + handleBarPoints[1].y = top() + height() / 4 * 3; + } else if (line == lineBottom) { + handleBarPoints[0].x = left() + width() / 4; + handleBarPoints[0].y = bottom(); + handleBarPoints[1].x = left() + width() / 4 * 3; + handleBarPoints[1].y = bottom(); + } + return handleBarPoints; + } + + @Override + public float radian() { + return radian; + } + + @Override + public void setRadian(float radian) { + this.radian = radian; + } + + @Override + public float getPaddingLeft() { + return paddingLeft; + } + + @Override + public float getPaddingTop() { + return paddingTop; + } + + @Override + public float getPaddingRight() { + return paddingRight; + } + + @Override + public float getPaddingBottom() { + return paddingBottom; + } + + @Override + public void setPadding(float padding) { + setPadding(padding, padding, padding, padding); + } + + @Override + public void setPadding(float paddingLeft, float paddingTop, float paddingRight, + float paddingBottom) { + this.paddingLeft = paddingLeft; + this.paddingTop = paddingTop; + this.paddingRight = paddingRight; + this.paddingBottom = paddingBottom; + } + + static class AreaComparator implements Comparator { + @Override + public int compare(StraightArea lhs, StraightArea rhs) { + if (lhs.top() < rhs.top()) { + return -1; + } else if (lhs.top() == rhs.top()) { + if (lhs.left() < rhs.left()) { + return -1; + } else { + return 1; + } + } else { + return 1; + } + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightLine.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightLine.java new file mode 100644 index 000000000..a86e4b3cd --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightLine.java @@ -0,0 +1,215 @@ +package com.huantansheng.easyphotos.models.puzzle.straight; + +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.Log; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +/** + * @author wupanjie + */ +class StraightLine implements Line { + private PointF start; + private PointF end; + + private PointF previousStart = new PointF(); + private PointF previousEnd = new PointF(); + + public Direction direction = Direction.HORIZONTAL; + + StraightLine attachLineStart; + StraightLine attachLineEnd; + + private Line upperLine; + private Line lowerLine; + + private RectF bounds = new RectF(); + + StraightLine(PointF start, PointF end) { + this.start = start; + this.end = end; + + if (start.x == end.x) { + direction = Direction.VERTICAL; + } else if (start.y == end.y) { + direction = Direction.HORIZONTAL; + } else { + Log.d("StraightLine", "StraightLine: current only support two direction"); + } + } + + @Override + public float length() { + return (float) Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2)); + } + + @Override + public PointF startPoint() { + return start; + } + + @Override + public PointF endPoint() { + return end; + } + + @Override + public Line lowerLine() { + return lowerLine; + } + + @Override + public Line upperLine() { + return upperLine; + } + + @Override + public Line attachStartLine() { + return attachLineStart; + } + + @Override + public Line attachEndLine() { + return attachLineEnd; + } + + @Override + public void setLowerLine(Line lowerLine) { + this.lowerLine = lowerLine; + } + + @Override + public void setUpperLine(Line upperLine) { + this.upperLine = upperLine; + } + + void setAttachLineStart(StraightLine attachLineStart) { + this.attachLineStart = attachLineStart; + } + + void setAttachLineEnd(StraightLine attachLineEnd) { + this.attachLineEnd = attachLineEnd; + } + + @Override + public Direction direction() { + return direction; + } + + @Override + public float slope() { + return direction == Direction.HORIZONTAL ? 0 : Float.MAX_VALUE; + } + + @Override + public boolean contains(float x, float y, float extra) { + if (direction == Direction.HORIZONTAL) { + bounds.left = start.x; + bounds.right = end.x; + bounds.top = start.y - extra / 2; + bounds.bottom = start.y + extra / 2; + } else if (direction == Direction.VERTICAL) { + bounds.top = start.y; + bounds.bottom = end.y; + bounds.left = start.x - extra / 2; + bounds.right = start.x + extra / 2; + } + + return bounds.contains(x, y); + } + + @Override + public void prepareMove() { + previousStart.set(start); + previousEnd.set(end); + } + + @Override + public boolean move(float offset, float extra) { + if (direction == Direction.HORIZONTAL) { + if (previousStart.y + offset < lowerLine.maxY() + extra + || previousStart.y + offset > upperLine.minY() - extra + || previousEnd.y + offset < lowerLine.maxY() + extra + || previousEnd.y + offset > upperLine.minY() - extra) { + return false; + } + + start.y = previousStart.y + offset; + end.y = previousEnd.y + offset; + } else { + if (previousStart.x + offset < lowerLine.maxX() + extra + || previousStart.x + offset > upperLine.minX() - extra + || previousEnd.x + offset < lowerLine.maxX() + extra + || previousEnd.x + offset > upperLine.minX() - extra) { + return false; + } + + start.x = previousStart.x + offset; + end.x = previousEnd.x + offset; + } + + return true; + } + + @Override + public void update(float layoutWidth, float layoutHeight) { + if (direction == Direction.HORIZONTAL) { + if (attachLineStart != null) { + start.x = attachLineStart.getPosition(); + } + if (attachLineEnd != null) { + end.x = attachLineEnd.getPosition(); + } + } else if (direction == Direction.VERTICAL) { + if (attachLineStart != null) { + start.y = attachLineStart.getPosition(); + } + if (attachLineEnd != null) { + end.y = attachLineEnd.getPosition(); + } + } + } + + public float getPosition() { + if (direction == Direction.HORIZONTAL) { + return start.y; + } else { + return start.x; + } + } + + @Override + public float minX() { + return min(start.x, end.x); + } + + @Override + public float maxX() { + return max(start.x, end.x); + } + + @Override + public float minY() { + return min(start.y, end.y); + } + + @Override + public float maxY() { + return max(start.y, end.y); + } + + @Override + public void offset(float x, float y) { + start.offset(x, y); + end.offset(x, y); + } + + @Override + public String toString() { + return "start --> " + start.toString() + ",end --> " + end.toString(); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightPuzzleLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightPuzzleLayout.java new file mode 100644 index 000000000..73d3b91ad --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightPuzzleLayout.java @@ -0,0 +1,359 @@ +package com.huantansheng.easyphotos.models.puzzle.straight; + +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.Pair; + +import com.huantansheng.easyphotos.models.puzzle.Area; +import com.huantansheng.easyphotos.models.puzzle.Line; +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.createLine; +import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.cutAreaCross; +import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.cutAreaSpiral; + + +/** + * @author wupanjie + */ +public abstract class StraightPuzzleLayout implements PuzzleLayout { + private RectF bounds; + private StraightArea outerArea; + + private List areas = new ArrayList<>(); + private List lines = new ArrayList<>(); + private List outerLines = new ArrayList<>(4); + + private float padding; + private float radian; + private int color; + + private Comparator areaComparator = new StraightArea.AreaComparator(); + + private ArrayList steps = new ArrayList<>(); + + protected StraightPuzzleLayout() { + + } + + @Override + public void setOuterBounds(RectF bounds) { + reset(); + + this.bounds = bounds; + + PointF one = new PointF(bounds.left, bounds.top); + PointF two = new PointF(bounds.right, bounds.top); + PointF three = new PointF(bounds.left, bounds.bottom); + PointF four = new PointF(bounds.right, bounds.bottom); + + StraightLine lineLeft = new StraightLine(one, three); + StraightLine lineTop = new StraightLine(one, two); + StraightLine lineRight = new StraightLine(two, four); + StraightLine lineBottom = new StraightLine(three, four); + + outerLines.clear(); + + outerLines.add(lineLeft); + outerLines.add(lineTop); + outerLines.add(lineRight); + outerLines.add(lineBottom); + + outerArea = new StraightArea(); + outerArea.lineLeft = lineLeft; + outerArea.lineTop = lineTop; + outerArea.lineRight = lineRight; + outerArea.lineBottom = lineBottom; + + areas.clear(); + areas.add(outerArea); + } + + @Override + public abstract void layout(); + + @Override + public int getAreaCount() { + return areas.size(); + } + + @Override + public List getOuterLines() { + return outerLines; + } + + @Override + public List getLines() { + return lines; + } + + @Override + public void update() { + for (Line line : lines) { + line.update(width(), height()); + } + } + + @Override + public float width() { + return outerArea == null ? 0 : outerArea.width(); + } + + @Override + public float height() { + return outerArea == null ? 0 : outerArea.height(); + } + + @Override + public void reset() { + lines.clear(); + areas.clear(); + areas.add(outerArea); + steps.clear(); + } + + @Override + public Area getArea(int position) { + return areas.get(position); + } + + @Override + + public StraightArea getOuterArea() { + return outerArea; + } + + @Override + public void setPadding(float padding) { + this.padding = padding; + + for (Area area : areas) { + area.setPadding(padding); + } + + outerArea.lineLeft.startPoint().set(bounds.left + padding, bounds.top + padding); + outerArea.lineLeft.endPoint().set(bounds.left + padding, bounds.bottom - padding); + + outerArea.lineRight.startPoint().set(bounds.right - padding, bounds.top + padding); + outerArea.lineRight.endPoint().set(bounds.right - padding, bounds.bottom - padding); + + update(); + } + + @Override + public float getPadding() { + return padding; + } + + protected void addLine(int position, Line.Direction direction, float ratio) { + StraightArea area = areas.get(position); + addLine(area, direction, ratio); + + Step step = new Step(); + step.type = Step.ADD_LINE; + step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1; + step.position = position; + steps.add(step); + } + + private List addLine(StraightArea area, Line.Direction direction, float ratio) { + areas.remove(area); + StraightLine line = createLine(area, direction, ratio); + lines.add(line); + + List increasedArea = StraightUtils.cutArea(area, line); + areas.addAll(increasedArea); + + updateLineLimit(); + sortAreas(); + + return increasedArea; + } + + protected void cutAreaEqualPart(int position, int part, Line.Direction direction) { + StraightArea temp = areas.get(position); + for (int i = part; i > 1; i--) { + temp = addLine(temp, direction, (float) (i - 1) / i).get(0); + } + + Step step = new Step(); + step.type = Step.CUT_EQUAL_PART_TWO; + step.part = part; + step.position = position; + step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1; + steps.add(step); + } + + protected void addCross(int position, float ratio) { + addCross(position, ratio, ratio); + } + + protected void addCross(int position, float horizontalRatio, float verticalRatio) { + StraightArea area = areas.get(position); + areas.remove(area); + StraightLine horizontal = createLine(area, Line.Direction.HORIZONTAL, horizontalRatio); + StraightLine vertical = createLine(area, Line.Direction.VERTICAL, verticalRatio); + lines.add(horizontal); + lines.add(vertical); + + List newAreas = cutAreaCross(area, horizontal, vertical); + areas.addAll(newAreas); + + updateLineLimit(); + sortAreas(); + + Step step = new Step(); + step.type = Step.ADD_CROSS; + step.position = position; + steps.add(step); + } + + protected void cutAreaEqualPart(int position, int hSize, int vSize) { + StraightArea area = areas.get(position); + areas.remove(area); + Pair, List> increased = + StraightUtils.cutArea(area, hSize, vSize); + List newLines = increased.first; + List newAreas = increased.second; + + lines.addAll(newLines); + areas.addAll(newAreas); + + updateLineLimit(); + sortAreas(); + + Step step = new Step(); + step.type = Step.CUT_EQUAL_PART_ONE; + step.position = position; + step.hSize = hSize; + step.vSize = vSize; + steps.add(step); + } + + protected void cutSpiral(int position) { + StraightArea area = areas.get(position); + areas.remove(area); + Pair, List> spilt = cutAreaSpiral(area); + + lines.addAll(spilt.first); + areas.addAll(spilt.second); + + updateLineLimit(); + sortAreas(); + + Step step = new Step(); + step.type = Step.CUT_SPIRAL; + step.position = position; + steps.add(step); + } + + private void sortAreas() { + Collections.sort(areas, areaComparator); + } + + private void updateLineLimit() { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line line = lines.get(i); + updateUpperLine(line); + updateLowerLine(line); + } + } + + private void updateLowerLine(final Line line) { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line l = lines.get(i); + if (l == line) { + continue; + } + + if (l.direction() != line.direction()) { + continue; + } + + if (l.direction() == Line.Direction.HORIZONTAL) { + if (l.maxX() <= line.minX() || line.maxX() <= l.minX()) continue; + if (l.minY() > line.lowerLine().maxY() && l.maxY() < line.minY()) { + line.setLowerLine(l); + } + } else { + if (l.maxY() <= line.minY() || line.maxY() <= l.minY()) continue; + if (l.minX() > line.lowerLine().maxX() && l.maxX() < line.minX()) { + line.setLowerLine(l); + } + } + } + } + + private void updateUpperLine(final Line line) { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line l = lines.get(i); + if (l == line) { + continue; + } + + if (l.direction() != line.direction()) { + continue; + } + + if (l.direction() == Line.Direction.HORIZONTAL) { + if (l.maxX() <= line.minX() || line.maxX() <= l.minX()) continue; + if (l.maxY() < line.upperLine().minY() && l.minY() > line.maxY()) { + line.setUpperLine(l); + } + } else { + if (l.maxY() <= line.minY() || line.maxY() <= l.minY()) continue; + if (l.maxX() < line.upperLine().minX() && l.minX() > line.maxX()) { + line.setUpperLine(l); + } + } + } + } + + @Override + public float getRadian() { + return radian; + } + + @Override + public void setRadian(float radian) { + this.radian = radian; + for (Area area : areas) { + area.setRadian(radian); + } + } + + @Override + public int getColor() { + return color; + } + + @Override + public void setColor(int color) { + this.color = color; + } + + @Override + public Info generateInfo() { + Info info = new Info(); + info.type = Info.TYPE_STRAIGHT; + info.padding = padding; + info.radian = radian; + info.color = color; + info.steps = steps; + ArrayList lineInfos = new ArrayList<>(); + for (Line line : lines) { + LineInfo lineInfo = new LineInfo(line); + lineInfos.add(lineInfo); + } + info.lineInfos = lineInfos; + return info; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightUtils.java new file mode 100644 index 000000000..47d1411b6 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightUtils.java @@ -0,0 +1,236 @@ +package com.huantansheng.easyphotos.models.puzzle.straight; + +import android.graphics.PointF; +import android.util.Pair; + + +import com.huantansheng.easyphotos.models.puzzle.Line; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +class StraightUtils { + static StraightLine createLine(final StraightArea area, final Line.Direction direction, + final float ratio) { + PointF one = new PointF(); + PointF two = new PointF(); + if (direction == Line.Direction.HORIZONTAL) { + one.x = area.left(); + one.y = area.height() * ratio + area.top(); + two.x = area.right(); + two.y = area.height() * ratio + area.top(); + } else if (direction == Line.Direction.VERTICAL) { + one.x = area.width() * ratio + area.left(); + one.y = area.top(); + two.x = area.width() * ratio + area.left(); + two.y = area.bottom(); + } + + StraightLine line = new StraightLine(one, two); + + if (direction == Line.Direction.HORIZONTAL) { + line.attachLineStart = area.lineLeft; + line.attachLineEnd = area.lineRight; + + line.setUpperLine(area.lineBottom); + line.setLowerLine(area.lineTop); + } else if (direction == Line.Direction.VERTICAL) { + line.attachLineStart = area.lineTop; + line.attachLineEnd = area.lineBottom; + + line.setUpperLine(area.lineRight); + line.setLowerLine(area.lineLeft); + } + + return line; + } + + static List cutArea(final StraightArea area, final StraightLine line) { + List list = new ArrayList<>(); + if (line.direction() == Line.Direction.HORIZONTAL) { + StraightArea one = new StraightArea(area); + one.lineBottom = line; + list.add(one); + + StraightArea two = new StraightArea(area); + two.lineTop = line; + list.add(two); + } else if (line.direction() == Line.Direction.VERTICAL) { + StraightArea one = new StraightArea(area); + one.lineRight = line; + list.add(one); + + StraightArea two = new StraightArea(area); + two.lineLeft = line; + list.add(two); + } + + return list; + } + + static Pair, List> cutArea(final StraightArea area, + final int horizontalSize, + final int verticalSize) { + List areaList = new ArrayList<>(); + List horizontalLines = new ArrayList<>(horizontalSize); + + StraightArea restArea = new StraightArea(area); + for (int i = horizontalSize + 1; i > 1; i--) { + StraightLine horizontalLine = + createLine(restArea, Line.Direction.HORIZONTAL, (float) (i - 1) / i); + horizontalLines.add(horizontalLine); + restArea.lineBottom = horizontalLine; + } + List verticalLines = new ArrayList<>(); + + restArea = new StraightArea(area); + for (int i = verticalSize + 1; i > 1; i--) { + StraightLine verticalLine = + createLine(restArea, Line.Direction.VERTICAL, (float) (i - 1) / i); + verticalLines.add(verticalLine); + StraightArea spiltArea = new StraightArea(restArea); + spiltArea.lineLeft = verticalLine; + int size = horizontalLines.size(); + for (int j = 0; j <= size; j++) { + StraightArea blockArea = new StraightArea(spiltArea); + if (j == 0) { + blockArea.lineTop = horizontalLines.get(j); + } else if (j == size) { + blockArea.lineBottom = horizontalLines.get(j - 1); + } else { + blockArea.lineTop = horizontalLines.get(j); + blockArea.lineBottom = horizontalLines.get(j - 1); + } + areaList.add(blockArea); + } + restArea.lineRight = verticalLine; + } + int size = horizontalLines.size(); + for (int j = 0; j <= size; j++) { + StraightArea blockArea = new StraightArea(restArea); + if (j == 0) { + blockArea.lineTop = horizontalLines.get(j); + } else if (j == horizontalLines.size()) { + blockArea.lineBottom = horizontalLines.get(j - 1); + } else { + blockArea.lineTop = horizontalLines.get(j); + blockArea.lineBottom = horizontalLines.get(j - 1); + } + areaList.add(blockArea); + } + + List lines = new ArrayList<>(); + lines.addAll(horizontalLines); + lines.addAll(verticalLines); + return new Pair<>(lines, areaList); + } + + static List cutAreaCross(final StraightArea area, final StraightLine horizontal, + final StraightLine vertical) { + List list = new ArrayList<>(); + + StraightArea one = new StraightArea(area); + one.lineBottom = horizontal; + one.lineRight = vertical; + list.add(one); + + StraightArea two = new StraightArea(area); + two.lineBottom = horizontal; + two.lineLeft = vertical; + list.add(two); + + StraightArea three = new StraightArea(area); + three.lineTop = horizontal; + three.lineRight = vertical; + list.add(three); + + StraightArea four = new StraightArea(area); + four.lineTop = horizontal; + four.lineLeft = vertical; + list.add(four); + + return list; + } + + static Pair, List> cutAreaSpiral(final StraightArea area) { + List lines = new ArrayList<>(); + List areas = new ArrayList<>(); + + float width = area.width(); + float height = area.height(); + + float left = area.left(); + float top = area.top(); + + PointF one = new PointF(left, top + height / 3); + PointF two = new PointF(left + width / 3 * 2, top); + PointF three = new PointF(left + width, top + height / 3 * 2); + PointF four = new PointF(left + width / 3, top + height); + PointF five = new PointF(left + width / 3, top + height / 3); + PointF six = new PointF(left + width / 3 * 2, top + height / 3); + PointF seven = new PointF(left + width / 3 * 2, top + height / 3 * 2); + PointF eight = new PointF(left + width / 3, top + height / 3 * 2); + + StraightLine l1 = new StraightLine(one, six); + StraightLine l2 = new StraightLine(two, seven); + StraightLine l3 = new StraightLine(eight, three); + StraightLine l4 = new StraightLine(five, four); + + l1.setAttachLineStart(area.lineLeft); + l1.setAttachLineEnd(l2); + l1.setLowerLine(area.lineTop); + l1.setUpperLine(l3); + + l2.setAttachLineStart(area.lineTop); + l2.setAttachLineEnd(l3); + l2.setLowerLine(l4); + l2.setUpperLine(area.lineRight); + + l3.setAttachLineStart(l4); + l3.setAttachLineEnd(area.lineRight); + l3.setLowerLine(l1); + l3.setUpperLine(area.lineBottom); + + l4.setAttachLineStart(l1); + l4.setAttachLineEnd(area.lineBottom); + l4.setLowerLine(area.lineLeft); + l4.setUpperLine(l2); + + lines.add(l1); + lines.add(l2); + lines.add(l3); + lines.add(l4); + + StraightArea b1 = new StraightArea(area); + b1.lineRight = l2; + b1.lineBottom = l1; + areas.add(b1); + + StraightArea b2 = new StraightArea(area); + b2.lineLeft = l2; + b2.lineBottom = l3; + areas.add(b2); + + StraightArea b3 = new StraightArea(area); + b3.lineRight = l4; + b3.lineTop = l1; + areas.add(b3); + + StraightArea b4 = new StraightArea(area); + b4.lineTop = l1; + b4.lineRight = l2; + b4.lineLeft = l4; + b4.lineBottom = l3; + areas.add(b4); + + StraightArea b5 = new StraightArea(area); + b5.lineLeft = l4; + b5.lineTop = l3; + areas.add(b5); + + return new Pair<>(lines, areas); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/NumberSlantLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/NumberSlantLayout.java new file mode 100644 index 000000000..6e2cd19f4 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/NumberSlantLayout.java @@ -0,0 +1,33 @@ +package com.huantansheng.easyphotos.models.puzzle.template.slant; + +import android.util.Log; + +import com.huantansheng.easyphotos.models.puzzle.slant.SlantPuzzleLayout; + + +/** + * @author wupanjie + */ + +public abstract class NumberSlantLayout extends SlantPuzzleLayout { + + static final String TAG = "NumberSlantLayout"; + protected int theme; + + public NumberSlantLayout(int theme) { + if (theme >= getThemeCount()) { + Log.e(TAG, "NumberSlantLayout: the most theme count is " + + getThemeCount() + + " ,you should let theme from 0 to " + + (getThemeCount() - 1) + + " ."); + } + this.theme = theme; + } + + public abstract int getThemeCount(); + + public int getTheme() { + return theme; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/OneSlantLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/OneSlantLayout.java new file mode 100644 index 000000000..2f6f06df8 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/OneSlantLayout.java @@ -0,0 +1,36 @@ +package com.huantansheng.easyphotos.models.puzzle.template.slant; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ + +public class OneSlantLayout extends NumberSlantLayout { + public OneSlantLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 4; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f); + break; + case 2: + addCross(0, 0.56f, 0.44f, 0.56f, 0.44f); + break; + case 3: + cutArea(0, 1, 2); + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/SlantLayoutHelper.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/SlantLayoutHelper.java new file mode 100644 index 000000000..2a24f95fb --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/SlantLayoutHelper.java @@ -0,0 +1,69 @@ +package com.huantansheng.easyphotos.models.puzzle.template.slant; + + +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public class SlantLayoutHelper { + private SlantLayoutHelper() { + + } + + public static List getAllThemeLayout(int pieceCount) { + List puzzleLayouts = new ArrayList<>(); + switch (pieceCount) { + case 1: + for (int i = 0; i < 4; i++) { + puzzleLayouts.add(new OneSlantLayout(i)); + } + break; + case 2: + for (int i = 0; i < 2; i++) { + puzzleLayouts.add(new TwoSlantLayout(i)); + } + break; + case 3: + for (int i = 0; i < 6; i++) { + puzzleLayouts.add(new ThreeSlantLayout(i)); + } + break; + //case 4: + // for (int i = 0; i < 8; i++) { + // puzzleLayouts.add(new FourStraightLayout(i)); + // } + // break; + //case 5: + // for (int i = 0; i < 17; i++) { + // puzzleLayouts.add(new FiveStraightLayout(i)); + // } + // break; + //case 6: + // for (int i = 0; i < 12; i++) { + // puzzleLayouts.add(new SixStraightLayout(i)); + // } + // break; + //case 7: + // for (int i = 0; i < 9; i++) { + // puzzleLayouts.add(new SevenStraightLayout(i)); + // } + // break; + //case 8: + // for (int i = 0; i < 11; i++) { + // puzzleLayouts.add(new EightStraightLayout(i)); + // } + // break; + //case 9: + // for (int i = 0; i < 8; i++) { + // puzzleLayouts.add(new NineStraightLayout(i)); + // } + // break; + } + + return puzzleLayouts; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/ThreeSlantLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/ThreeSlantLayout.java new file mode 100644 index 000000000..015f63853 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/ThreeSlantLayout.java @@ -0,0 +1,48 @@ +package com.huantansheng.easyphotos.models.puzzle.template.slant; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ + +public class ThreeSlantLayout extends NumberSlantLayout { + public ThreeSlantLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 6; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 0.5f); + addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f); + break; + case 1: + addLine(0, Line.Direction.HORIZONTAL, 0.5f); + addLine(1, Line.Direction.VERTICAL, 0.56f, 0.44f); + break; + case 2: + addLine(0, Line.Direction.VERTICAL, 0.5f); + addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f); + break; + case 3: + addLine(0, Line.Direction.VERTICAL, 0.5f); + addLine(1, Line.Direction.HORIZONTAL, 0.56f, 0.44f); + break; + case 4: + addLine(0, Line.Direction.HORIZONTAL, 0.44f, 0.56f); + addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f); + break; + case 5: + addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f); + addLine(1, Line.Direction.HORIZONTAL, 0.44f, 0.56f); + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/TwoSlantLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/TwoSlantLayout.java new file mode 100644 index 000000000..951114424 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/TwoSlantLayout.java @@ -0,0 +1,30 @@ +package com.huantansheng.easyphotos.models.puzzle.template.slant; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ + +public class TwoSlantLayout extends NumberSlantLayout { + public TwoSlantLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 2; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f); + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/EightStraightLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/EightStraightLayout.java new file mode 100644 index 000000000..6560547c3 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/EightStraightLayout.java @@ -0,0 +1,89 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class EightStraightLayout extends NumberStraightLayout { + public EightStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 11; + } + + @Override + public void layout() { + switch (theme) { + case 0: + cutAreaEqualPart(0, 3, 1); + break; + case 1: + cutAreaEqualPart(0, 1, 3); + break; + case 2: + cutAreaEqualPart(0, 4, Line.Direction.VERTICAL); + addLine(3, Line.Direction.HORIZONTAL, 4f / 5); + addLine(2, Line.Direction.HORIZONTAL, 3f / 5); + addLine(1, Line.Direction.HORIZONTAL, 2f / 5); + addLine(0, Line.Direction.HORIZONTAL, 1f / 5); + break; + case 3: + cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL); + addLine(3, Line.Direction.VERTICAL, 4f / 5); + addLine(2, Line.Direction.VERTICAL, 3f / 5); + addLine(1, Line.Direction.VERTICAL, 2f / 5); + addLine(0, Line.Direction.VERTICAL, 1f / 5); + break; + case 4: + cutAreaEqualPart(0, 4, Line.Direction.VERTICAL); + addLine(3, Line.Direction.HORIZONTAL, 1f / 5); + addLine(2, Line.Direction.HORIZONTAL, 2f / 5); + addLine(1, Line.Direction.HORIZONTAL, 3f / 5); + addLine(0, Line.Direction.HORIZONTAL, 4f / 5); + break; + case 5: + cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL); + addLine(3, Line.Direction.VERTICAL, 1f / 5); + addLine(2, Line.Direction.VERTICAL, 2f / 5); + addLine(1, Line.Direction.VERTICAL, 3f / 5); + addLine(0, Line.Direction.VERTICAL, 4f / 5); + break; + case 6: + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + cutAreaEqualPart(2, 3, Line.Direction.VERTICAL); + cutAreaEqualPart(1, 2, Line.Direction.VERTICAL); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 7: + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL); + cutAreaEqualPart(1, 2, Line.Direction.HORIZONTAL); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 8: + addLine(0, Line.Direction.HORIZONTAL, 4f / 5); + cutAreaEqualPart(1, 5, Line.Direction.VERTICAL); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.VERTICAL, 1f / 2); + break; + case 9: + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + cutAreaEqualPart(2, 2, Line.Direction.VERTICAL); + cutAreaEqualPart(1, 3, Line.Direction.VERTICAL); + addLine(0, Line.Direction.VERTICAL, 3f / 4); + addLine(0, Line.Direction.VERTICAL, 1f / 3); + break; + case 10: + cutAreaEqualPart(0, 2, 1); + addLine(5, Line.Direction.VERTICAL, 1f / 2); + addLine(4, Line.Direction.VERTICAL, 1f / 2); + break; + default: + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FiveStraightLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FiveStraightLayout.java new file mode 100644 index 000000000..098265145 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FiveStraightLayout.java @@ -0,0 +1,94 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class FiveStraightLayout extends NumberStraightLayout { + + public FiveStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 15; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 2f / 5); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + cutAreaEqualPart(2, 3, Line.Direction.VERTICAL); + break; + case 1: + addLine(0, Line.Direction.HORIZONTAL, 3f / 5); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + addLine(3, Line.Direction.VERTICAL, 1f / 2); + break; + case 2: + addLine(0, Line.Direction.VERTICAL, 2f / 5); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + addLine(1, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 3: + addLine(0, Line.Direction.VERTICAL, 2f / 5); + cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 4: + addLine(0, Line.Direction.HORIZONTAL, 3f / 4); + cutAreaEqualPart(1, 4, Line.Direction.VERTICAL); + break; + case 5: + addLine(0, Line.Direction.HORIZONTAL, 1f / 4); + cutAreaEqualPart(0, 4, Line.Direction.VERTICAL); + break; + case 6: + addLine(0, Line.Direction.VERTICAL, 3f / 4); + cutAreaEqualPart(1, 4, Line.Direction.HORIZONTAL); + break; + case 7: + addLine(0, Line.Direction.VERTICAL, 1f / 4); + cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL); + break; + case 8: + addLine(0, Line.Direction.HORIZONTAL, 1f / 4); + addLine(1, Line.Direction.HORIZONTAL, 2f / 3); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + addLine(3, Line.Direction.VERTICAL, 1f / 2); + break; + case 9: + addLine(0, Line.Direction.VERTICAL, 1f / 4); + addLine(1, Line.Direction.VERTICAL, 2f / 3); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + addLine(2, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 10: + addCross(0, 1f / 3); + addLine(2, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 11: + addCross(0, 2f / 3); + addLine(1, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 12: + addCross(0, 1f / 3, 2f / 3); + addLine(3, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 13: + addCross(0, 2f / 3, 1f / 3); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 14: + cutSpiral(0); + break; + default: + cutAreaEqualPart(0, 5, Line.Direction.HORIZONTAL); + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FourStraightLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FourStraightLayout.java new file mode 100644 index 000000000..6a56173a1 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FourStraightLayout.java @@ -0,0 +1,52 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class FourStraightLayout extends NumberStraightLayout { + private static final String TAG = "FourStraightLayout"; + + public FourStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 6; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addCross(0, 1f / 2); + break; + case 1: + addLine(0, Line.Direction.HORIZONTAL, 1f / 3); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 2: + addLine(0, Line.Direction.HORIZONTAL, 2f / 3); + cutAreaEqualPart(1, 3, Line.Direction.VERTICAL); + break; + case 3: + addLine(0, Line.Direction.VERTICAL, 1f / 3); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 4: + addLine(0, Line.Direction.VERTICAL, 2f / 3); + cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL); + break; + case 5: + addLine(0, Line.Direction.VERTICAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 2f / 3); + addLine(1, Line.Direction.HORIZONTAL, 1f / 3); + break; + default: + cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL); + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NineStraightLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NineStraightLayout.java new file mode 100644 index 000000000..28500d978 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NineStraightLayout.java @@ -0,0 +1,76 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class NineStraightLayout extends NumberStraightLayout { + public NineStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 8; + } + + @Override + public void layout() { + switch (theme) { + case 0: + cutAreaEqualPart(0, 2, 2); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, 3f / 4); + addLine(0, Line.Direction.VERTICAL, 1f / 3); + cutAreaEqualPart(2, 4, Line.Direction.HORIZONTAL); + cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL); + break; + case 2: + addLine(0, Line.Direction.HORIZONTAL, 3f / 4); + addLine(0, Line.Direction.HORIZONTAL, 1f / 3); + cutAreaEqualPart(2, 4, Line.Direction.VERTICAL); + cutAreaEqualPart(0, 4, Line.Direction.VERTICAL); + break; + case 3: + addLine(0, Line.Direction.HORIZONTAL, 3f / 4); + addLine(0, Line.Direction.HORIZONTAL, 1f / 3); + cutAreaEqualPart(2, 3, Line.Direction.VERTICAL); + addLine(1, Line.Direction.VERTICAL, 3f / 4); + addLine(1, Line.Direction.VERTICAL, 1f / 3); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 4: + addLine(0, Line.Direction.VERTICAL, 3f / 4); + addLine(0, Line.Direction.VERTICAL, 1f / 3); + cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL); + addLine(1, Line.Direction.HORIZONTAL, 3f / 4); + addLine(1, Line.Direction.HORIZONTAL, 1f / 3); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 5: + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + addLine(2, Line.Direction.HORIZONTAL, 3f / 4); + addLine(2, Line.Direction.HORIZONTAL, 1f / 3); + cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL); + addLine(0, Line.Direction.HORIZONTAL, 3f / 4); + addLine(0, Line.Direction.HORIZONTAL, 1f / 3); + break; + case 6: + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + addLine(2, Line.Direction.VERTICAL, 3f / 4); + addLine(2, Line.Direction.VERTICAL, 1f / 3); + cutAreaEqualPart(1, 3, Line.Direction.VERTICAL); + addLine(0, Line.Direction.VERTICAL, 3f / 4); + addLine(0, Line.Direction.VERTICAL, 1f / 3); + break; + case 7: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + cutAreaEqualPart(1, 1, 3); + break; + default: + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NumberStraightLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NumberStraightLayout.java new file mode 100644 index 000000000..aa6f4ea52 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NumberStraightLayout.java @@ -0,0 +1,31 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import android.util.Log; + +import com.huantansheng.easyphotos.models.puzzle.straight.StraightPuzzleLayout; + + +/** + * @author wupanjie + */ +public abstract class NumberStraightLayout extends StraightPuzzleLayout { + static final String TAG = "NumberStraightLayout"; + protected int theme; + + public NumberStraightLayout(int theme) { + if (theme >= getThemeCount()) { + Log.e(TAG, "NumberStraightLayout: the most theme count is " + + getThemeCount() + + " ,you should let theme from 0 to " + + (getThemeCount() - 1) + + " ."); + } + this.theme = theme; + } + + public abstract int getThemeCount(); + + public int getTheme() { + return theme; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/OneStraightLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/OneStraightLayout.java new file mode 100644 index 000000000..e742aace4 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/OneStraightLayout.java @@ -0,0 +1,45 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class OneStraightLayout extends NumberStraightLayout { + + public OneStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 6; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, 1f / 2); + break; + case 2: + addCross(0, 1f / 2); + break; + case 3: + cutAreaEqualPart(0, 2, 1); + break; + case 4: + cutAreaEqualPart(0, 1, 2); + break; + case 5: + cutAreaEqualPart(0, 2, 2); + break; + default: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SevenStraightLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SevenStraightLayout.java new file mode 100644 index 000000000..4be036e70 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SevenStraightLayout.java @@ -0,0 +1,77 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class SevenStraightLayout extends NumberStraightLayout { + public SevenStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 9; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + cutAreaEqualPart(1, 4, Line.Direction.VERTICAL); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, 1f / 2); + cutAreaEqualPart(1, 4, Line.Direction.HORIZONTAL); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 2: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + cutAreaEqualPart(1, 1, 2); + break; + case 3: + addLine(0, Line.Direction.HORIZONTAL, 2f / 3); + cutAreaEqualPart(1, 3, Line.Direction.VERTICAL); + addCross(0, 1f / 2); + break; + case 4: + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 5: + addLine(0, Line.Direction.HORIZONTAL, 2f / 3); + addLine(1, Line.Direction.VERTICAL, 3f / 4); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.VERTICAL, 2f / 5); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 6: + addLine(0, Line.Direction.VERTICAL, 2f / 3); + addLine(1, Line.Direction.HORIZONTAL, 3f / 4); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 2f / 5); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 7: + addLine(0, Line.Direction.VERTICAL, 1f / 4); + addLine(1, Line.Direction.VERTICAL, 2f / 3); + addLine(2, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 3f / 4); + addLine(1, Line.Direction.HORIZONTAL, 1f / 3); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 8: + addLine(0, Line.Direction.HORIZONTAL, 1f / 4); + addLine(1, Line.Direction.HORIZONTAL, 2f / 3); + cutAreaEqualPart(2, 3, Line.Direction.VERTICAL); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + default: + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SixStraightLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SixStraightLayout.java new file mode 100644 index 000000000..420a92255 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SixStraightLayout.java @@ -0,0 +1,93 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class SixStraightLayout extends NumberStraightLayout { + + public SixStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 12; + } + + @Override + public void layout() { + switch (theme) { + case 0: + cutAreaEqualPart(0, 2, 1); + break; + + case 1: + cutAreaEqualPart(0, 1, 2); + break; + case 2: + addCross(0, 2f / 3, 1f / 2); + addLine(3, Line.Direction.VERTICAL, 1f / 2); + addLine(2, Line.Direction.VERTICAL, 1f / 2); + break; + case 3: + addCross(0, 1f / 2, 2f / 3); + addLine(3, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 4: + addCross(0, 1f / 2, 1f / 3); + addLine(2, Line.Direction.HORIZONTAL, 1f / 2); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 5: + addCross(0, 1f / 3, 1f / 2); + addLine(1, Line.Direction.VERTICAL, 1f / 2); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + break; + case 6: + addLine(0, Line.Direction.HORIZONTAL, 4f / 5); + cutAreaEqualPart(1, 5, Line.Direction.VERTICAL); + break; + case 7: + addLine(0, Line.Direction.HORIZONTAL, 1f / 4); + addLine(1, Line.Direction.HORIZONTAL, 2f / 3); + addLine(1, Line.Direction.VERTICAL, 1f / 4); + addLine(2, Line.Direction.VERTICAL, 2f / 3); + addLine(4, Line.Direction.VERTICAL, 1f / 2); + break; + case 8: + addCross(0, 1f / 3); + addLine(1, Line.Direction.VERTICAL, 1f / 2); + addLine(4, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 9: + addCross(0, 2f / 3, 1f / 3); + addLine(3, Line.Direction.VERTICAL, 1f / 2); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 10: + addCross(0, 2f / 3); + addLine(2, Line.Direction.VERTICAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 11: + addCross(0, 1f / 3, 2f / 3); + addLine(3, Line.Direction.HORIZONTAL, 1f / 2); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + break; + case 12: + addCross(0, 1f / 3); + addLine(2, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.VERTICAL, 1f / 2); + break; + default: + addCross(0, 2f / 3, 1f / 2); + addLine(3, Line.Direction.VERTICAL, 1f / 2); + addLine(2, Line.Direction.VERTICAL, 1f / 2); + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/StraightLayoutHelper.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/StraightLayoutHelper.java new file mode 100644 index 000000000..32c492034 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/StraightLayoutHelper.java @@ -0,0 +1,69 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + + +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public class StraightLayoutHelper { + private StraightLayoutHelper() { + + } + + public static List getAllThemeLayout(int pieceCount) { + List puzzleLayouts = new ArrayList<>(); + switch (pieceCount) { + case 1: + for (int i = 0; i < 6; i++) { + puzzleLayouts.add(new OneStraightLayout(i)); + } + break; + case 2: + for (int i = 0; i < 6; i++) { + puzzleLayouts.add(new TwoStraightLayout(i)); + } + break; + case 3: + for (int i = 0; i < 6; i++) { + puzzleLayouts.add(new ThreeStraightLayout(i)); + } + break; + case 4: + for (int i = 0; i < 8; i++) { + puzzleLayouts.add(new FourStraightLayout(i)); + } + break; + case 5: + for (int i = 0; i < 17; i++) { + puzzleLayouts.add(new FiveStraightLayout(i)); + } + break; + case 6: + for (int i = 0; i < 12; i++) { + puzzleLayouts.add(new SixStraightLayout(i)); + } + break; + case 7: + for (int i = 0; i < 9; i++) { + puzzleLayouts.add(new SevenStraightLayout(i)); + } + break; + case 8: + for (int i = 0; i < 11; i++) { + puzzleLayouts.add(new EightStraightLayout(i)); + } + break; + case 9: + for (int i = 0; i < 8; i++) { + puzzleLayouts.add(new NineStraightLayout(i)); + } + break; + } + + return puzzleLayouts; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/ThreeStraightLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/ThreeStraightLayout.java new file mode 100644 index 000000000..8c1575ed9 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/ThreeStraightLayout.java @@ -0,0 +1,50 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class ThreeStraightLayout extends NumberStraightLayout { + + public ThreeStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 6; + } + + @Override + public void layout() { + switch (theme) { + case 0: + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 1: + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 2: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + break; + case 3: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.VERTICAL, 1f / 2); + break; + case 4: + addLine(0, Line.Direction.VERTICAL, 1f / 2); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 5: + addLine(0, Line.Direction.VERTICAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 1f / 2); + break; + default: + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/TwoStraightLayout.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/TwoStraightLayout.java new file mode 100644 index 000000000..793ee0b35 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/TwoStraightLayout.java @@ -0,0 +1,58 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import android.util.Log; + +import com.huantansheng.easyphotos.models.puzzle.Line; + + +/** + * @author wupanjie + */ +public class TwoStraightLayout extends NumberStraightLayout { + private float mRadio = 1f / 2; + + public TwoStraightLayout(int theme) { + super(theme); + } + + public TwoStraightLayout(float radio, int theme) { + super(theme); + if (mRadio > 1) { + Log.e(TAG, "CrossLayout: the radio can not greater than 1f"); + mRadio = 1f; + } + mRadio = radio; + } + + @Override + public int getThemeCount() { + return 6; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, mRadio); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, mRadio); + break; + case 2: + addLine(0, Line.Direction.HORIZONTAL, 1f / 3); + break; + case 3: + addLine(0, Line.Direction.HORIZONTAL, 2f / 3); + break; + case 4: + addLine(0, Line.Direction.VERTICAL, 1f / 3); + break; + case 5: + addLine(0, Line.Direction.VERTICAL, 2f / 3); + break; + default: + addLine(0, Line.Direction.HORIZONTAL, mRadio); + break; + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/StickerModel.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/StickerModel.java new file mode 100644 index 000000000..dcca408b0 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/StickerModel.java @@ -0,0 +1,221 @@ +package com.huantansheng.easyphotos.models.sticker; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.view.View; +import android.view.ViewGroup; + +import androidx.fragment.app.FragmentManager; + +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.models.sticker.cache.StickerCache; +import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData; +import com.huantansheng.easyphotos.models.sticker.listener.OnStickerClickListener; +import com.huantansheng.easyphotos.models.sticker.view.BitmapSticker; +import com.huantansheng.easyphotos.models.sticker.view.EditFragment; +import com.huantansheng.easyphotos.models.sticker.view.TextSticker; +import com.huantansheng.easyphotos.utils.bitmap.BitmapUtils; +import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack; + +import java.util.ArrayList; +import java.util.List; + +/** + * 贴图view的管理器,用于module与外部解耦 + * Created by huan on 2017/7/24. + */ + +public class StickerModel { + public static final ArrayList textDataList = new ArrayList<>(); + + public List bitmapStickers; + public List textStickers; + public BitmapSticker currBitmapSticker; + public TextSticker currTextSticker; + + + public StickerModel() { + super(); + this.bitmapStickers = new ArrayList<>(); + this.textStickers = new ArrayList<>(); + } + + public void addBitmapSticker(Context cxt, String imagePath, int imageResourceId, ViewGroup rootgroup) { + + if (bitmapStickers.size() > 0 && !bitmapStickers.get(bitmapStickers.size() - 1).isChecked) { + bitmapStickers.get(bitmapStickers.size() - 1).delete(); + } + final BitmapSticker sticker = new BitmapSticker(cxt, imagePath, imageResourceId, rootgroup.getWidth() / 2, rootgroup.getHeight() / 2); + sticker.setOnStickerClickListener(new OnStickerClickListener() { + @Override + public void onDelete() { + bitmapStickers.remove(sticker); + } + + @Override + public void onEditor() { + + } + + @Override + public void onTop() { + bitmapStickers.remove(sticker); + bitmapStickers.add(sticker); + } + + @Override + public void onUsing() { + if (currBitmapSticker != null && currBitmapSticker != sticker) { + currBitmapSticker.setUsing(false); + currBitmapSticker = sticker; + } + } + }); + if (currBitmapSticker != null) { + currBitmapSticker.setUsing(false); + } + rootgroup.addView(sticker); + currBitmapSticker = sticker; + bitmapStickers.add(sticker); + } + + + public void addTextSticker(final Context cxt, final FragmentManager fragmentManager, String text, ViewGroup rootgroup) { + + if (textStickers.size() > 0 && !textStickers.get(textStickers.size() - 1).isChecked) { + textStickers.get(textStickers.size() - 1).delete(); + } + final TextSticker sticker = new TextSticker(cxt, text, rootgroup.getWidth() / 2, rootgroup.getHeight() / 2); + sticker.setOnStickerClickListener(new OnStickerClickListener() { + @Override + public void onDelete() { + textStickers.remove(sticker); + } + + @Override + public void onEditor() { + EditFragment.show(fragmentManager, sticker); + } + + @Override + public void onTop() { + textStickers.remove(sticker); + textStickers.add(sticker); + } + + @Override + public void onUsing() { + if (currTextSticker != null && currTextSticker != sticker) { + currTextSticker.setUsing(false); + currTextSticker = sticker; + } + } + }); + if (currBitmapSticker != null) { + currBitmapSticker.setUsing(false); + } + rootgroup.addView(sticker); + currTextSticker = sticker; + textStickers.add(sticker); + } + + public void save(Activity act, ViewGroup stickerGroup, View imageGroup, int imageWidth, int imageHeight, final String dirPath, final String namePrefix, final boolean notifyMedia, final SaveBitmapCallBack callBack) { + + if (null != this.currBitmapSticker && this.currBitmapSticker.isUsing()) { + this.currBitmapSticker.setUsing(false); + } + if (null != this.currTextSticker && this.currTextSticker.isUsing()) { + this.currTextSticker.setUsing(false); + } + + for (BitmapSticker bs : bitmapStickers) { + if (bs.isUsing()) { + bs.setUsing(false); + } + } + + for (TextSticker ts : textStickers) { + if (ts.isUsing()) { + ts.setUsing(false); + } + } + + Bitmap srcBitmap = Bitmap.createBitmap(stickerGroup.getWidth(), stickerGroup.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(srcBitmap); + stickerGroup.draw(canvas); + + Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, imageGroup.getLeft(), imageGroup.getTop(), imageGroup.getWidth(), imageGroup.getHeight()); + BitmapUtils.recycle(srcBitmap); + Bitmap saveBitmap = null; + if (imageGroup.getWidth() > imageWidth || imageGroup.getHeight() > imageHeight) { + saveBitmap = Bitmap.createScaledBitmap(cropBitmap, imageWidth, imageHeight, true); + BitmapUtils.recycle(cropBitmap); + } else { + saveBitmap = cropBitmap; + } + + EasyPhotos.saveBitmapToDir(act, dirPath, namePrefix, saveBitmap, notifyMedia, callBack); + + } + + public void setCanvasSize(final Bitmap b, final ViewGroup imageGroup) { + if (imageGroup.getMeasuredWidth() == 0) { + imageGroup.post(new Runnable() { + @Override + public void run() { + setSize(b, imageGroup); + } + }); + } else { + setSize(b, imageGroup); + } + } + + private void setSize(Bitmap b, ViewGroup v) { + int bW = b.getWidth(); + int bH = b.getHeight(); + + int vW = v.getMeasuredWidth(); + int vH = v.getMeasuredHeight(); + + float scalW = (float) vW / (float) bW; + float scalH = (float) vH / (float) bH; + + ViewGroup.LayoutParams params = v.getLayoutParams(); + //如果图片小于viewGroup的宽高则把viewgroup设置为图片宽高 +// if (bW < vW && bH < vH) { +// params.width = bW; +// params.height = bH; +// v.setLayoutParams(params); +// return; +// } + if (bW >= bH) { + params.width = vW; + params.height = (int) (scalW * bH); + } else { + params.width = (int) (scalH * bW); + params.height = vH; + } + if (params.width > vW) { + float tempScaleW = (float) vW / (float) params.width; + params.width = vW; + params.height = (int) (params.height * tempScaleW); + } + if (params.height > vH) { + float tempScaleH = (float) vH / (float) params.height; + params.height = vH; + params.width = (int) (params.width * tempScaleH); + } + v.setLayoutParams(params); + } + + /** + * 释放资源 + */ + public void release() { + StickerCache.get().clear(); + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/cache/StickerCache.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/cache/StickerCache.java new file mode 100644 index 000000000..a88282e78 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/cache/StickerCache.java @@ -0,0 +1,115 @@ +package com.huantansheng.easyphotos.models.sticker.cache; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; + +import androidx.annotation.IdRes; + +import com.huantansheng.easyphotos.EasyPhotos; + +import java.util.LinkedHashMap; + +/** + * 贴纸的图片缓存器 + * Created by huan on 2017/12/8. + */ + +public class StickerCache { + private static StickerCache instance = null; + + public static StickerCache get() { + if (null == instance) { + synchronized (StickerCache.class) { + if (null == instance) { + instance = new StickerCache(); + } + } + } + return instance; + } + + private LinkedHashMap srcBitmapCache = null; + private LinkedHashMap mirrorBitmapCache = null; + private LinkedHashMap bitmapUsedCount = null; + + private StickerCache() { + srcBitmapCache = new LinkedHashMap<>(); + mirrorBitmapCache = new LinkedHashMap<>(); + bitmapUsedCount = new LinkedHashMap<>(); + } + + public Bitmap getSrcBitmap(String path) { + Bitmap bitmap = srcBitmapCache.get(path); + if (null == bitmap) { + bitmap = BitmapFactory.decodeFile(path); + srcBitmapCache.put(path, bitmap); + bitmapUsedCount.put(path, 0); + convertMirror(path, bitmap); + } + + int count = bitmapUsedCount.get(path); + bitmapUsedCount.put(path, ++count); + return bitmap; + } + + public Bitmap getSrcBitmap(Resources resources, @IdRes int resId) { + String path = String.valueOf(resId); + Bitmap bitmap = srcBitmapCache.get(path); + if (null == bitmap) { + bitmap = BitmapFactory.decodeResource(resources, resId); + srcBitmapCache.put(path, bitmap); + bitmapUsedCount.put(path, 0); + convertMirror(path, bitmap); + } + + int count = bitmapUsedCount.get(path); + bitmapUsedCount.put(path, ++count); + return bitmap; + } + + public Bitmap getMirrorBitmap(String key) { + return mirrorBitmapCache.get(key); + } + + + public void clear() { + for (String key : srcBitmapCache.keySet()) { + recycle(key); + } + } + + public void recycle(String key) { + if (!srcBitmapCache.containsKey(key)) { + return; + } + + int count = bitmapUsedCount.get(key); + if (count > 1) { + count--; + bitmapUsedCount.put(key, count); + return; + } + + EasyPhotos.recycle(srcBitmapCache.get(key), mirrorBitmapCache.get(key)); + removeKey(key); + } + + private void convertMirror(String key, Bitmap a) { + int w = a.getWidth(); + int h = a.getHeight(); + + Matrix m = new Matrix(); + m.postScale(-1, 1); //镜像水平翻转 + Bitmap mirrorBitmap = Bitmap.createBitmap(a, 0, 0, w, h, m, true); + mirrorBitmapCache.put(key, mirrorBitmap); + } + + + private void removeKey(String key) { + srcBitmapCache.remove(key); + mirrorBitmapCache.remove(key); + bitmapUsedCount.remove(key); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/entity/TextStickerData.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/entity/TextStickerData.java new file mode 100644 index 000000000..6a362e8f2 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/entity/TextStickerData.java @@ -0,0 +1,48 @@ +package com.huantansheng.easyphotos.models.sticker.entity; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * 文字贴纸数据类 + * Created by huan on 2017/12/14. + */ + +public class TextStickerData implements Parcelable { + + public String stickerName;//文字贴纸的名字 + public String stickerValue;//文字贴纸的文字内容 + + public TextStickerData(String stickerName, String stickerValue) { + this.stickerName = stickerName; + this.stickerValue = stickerValue; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.stickerName); + dest.writeString(this.stickerValue); + } + + protected TextStickerData(Parcel in) { + this.stickerName = in.readString(); + this.stickerValue = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public TextStickerData createFromParcel(Parcel source) { + return new TextStickerData(source); + } + + @Override + public TextStickerData[] newArray(int size) { + return new TextStickerData[size]; + } + }; +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/listener/OnStickerClickListener.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/listener/OnStickerClickListener.java new file mode 100644 index 000000000..f7be75d87 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/listener/OnStickerClickListener.java @@ -0,0 +1,16 @@ +package com.huantansheng.easyphotos.models.sticker.listener; + +/** + * 贴纸的点击监听 + * Created by huan on 2017/12/12. + */ + +public interface OnStickerClickListener { + void onDelete(); + + void onEditor(); + + void onTop(); + + void onUsing(); +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/BitmapSticker.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/BitmapSticker.java new file mode 100644 index 000000000..73c057636 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/BitmapSticker.java @@ -0,0 +1,438 @@ +package com.huantansheng.easyphotos.models.sticker.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.RectF; +import android.graphics.Region; +import android.text.TextUtils; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import androidx.core.content.ContextCompat; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.models.sticker.cache.StickerCache; +import com.huantansheng.easyphotos.models.sticker.listener.OnStickerClickListener; + + +/** + * 自定义贴图view + * Created by huan on 2017/7/24. + */ + +public class BitmapSticker extends View { + + public boolean isChecked = false; + private Bitmap image; + private Bitmap mirrorImage; + private Bitmap srcImage; + private boolean mirror = true; + private int imageWidth; + private int imageHeight; + private Bitmap btDelete; + private Bitmap btScale; + private Bitmap btMirror; + // private Bitmap btRotate; + private int btSize; + private Matrix mMatrix; + private float[] srcPs, dstPs; + private Paint mPaint; + private Paint framePaint; + private boolean isUsing = true; + private float downX1; + private float downY1; + private float downX2; + private float downY2; + private ClickType clickType; + private boolean isOut = false; + private GestureDetector gestureDetector; + private float lastDegree; + private float lastDoubleDegress; + private OnStickerClickListener listener; + private int startX, startY; + private Path path; + + private String cacheKey; + + public BitmapSticker(Context context, String bitmapPath, int bitmapResourceId, int ViewGroupCenterX, int ViewGroupCenterY) { + super(context); + + if (TextUtils.isEmpty(bitmapPath)) { + image = StickerCache.get().getSrcBitmap(getResources(), bitmapResourceId); + cacheKey = String.valueOf(bitmapResourceId); + } else { + image = StickerCache.get().getSrcBitmap(bitmapPath); + cacheKey = bitmapPath; + } + path = new Path(); + srcImage = image; + mirrorImage = StickerCache.get().getMirrorBitmap(cacheKey); + + this.imageWidth = image.getWidth(); + this.imageHeight = image.getHeight(); + this.startX = ViewGroupCenterX - imageWidth / 2; + if (this.startX < 100) { + this.startX = ViewGroupCenterX / 2; + } + this.startY = ViewGroupCenterY - imageHeight / 2; + if (this.startY < 100) { + this.startY = ViewGroupCenterY / 2; + } + initButtons(); + initPs(); + initPaints(); + initMatrix(); + initCanvasPosition(); + lastDegree = computeDegree(new Point(imageWidth, imageHeight), new Point(imageWidth / 2, imageHeight / 2)); + lastDoubleDegress = 1000; + gestureDetector = new GestureDetector(context, new StickerGestureListener()); + + } + + public void setOnStickerClickListener(OnStickerClickListener listener) { + this.listener = listener; + } + + private void initCanvasPosition() { + mMatrix.postTranslate(startX, startY); + mMatrix.mapPoints(dstPs, srcPs); + } + + private void initMatrix() { + mMatrix = new Matrix(); + } + + private void initPaints() { + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setDither(true); + mPaint.setFilterBitmap(true); + + framePaint = new Paint(); + framePaint.setAntiAlias(true); + framePaint.setStrokeWidth(1); + framePaint.setColor(ContextCompat.getColor(getContext(), android.R.color.white)); + } + + private void initPs() { + srcPs = new float[]{0, 0, imageWidth, 0, imageWidth, imageHeight, 0, imageHeight, imageWidth / 2, imageHeight / 2}; + dstPs = srcPs.clone(); + } + + private void initButtons() { + btDelete = BitmapFactory.decodeResource(getResources(), R.drawable.ic_delete_easy_photos); + btMirror = BitmapFactory.decodeResource(getResources(), R.drawable.ic_mirror_easy_photos); + btScale = BitmapFactory.decodeResource(getResources(), R.drawable.ic_controller_easy_photos); + btSize = btDelete.getWidth(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawBitmap(image, mMatrix, mPaint); + if (isUsing) + drawOthers(canvas); + } + + private void drawOthers(Canvas canvas) { + path.reset(); + path.moveTo(dstPs[0], dstPs[1]); + path.lineTo(dstPs[2], dstPs[3]); + path.lineTo(dstPs[4], dstPs[5]); + path.lineTo(dstPs[6], dstPs[7]); + path.lineTo(dstPs[0], dstPs[1]); + for (int i = 0; i < 7; i += 2) { + if (i == 6) { + canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[0], dstPs[1], framePaint); + break; + } + canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[i + 2], dstPs[i + 3], framePaint); + } + + canvas.drawBitmap(btDelete, dstPs[2] - btSize / 2, dstPs[3] - btSize / 2, mPaint); + canvas.drawBitmap(btMirror, dstPs[0] - btSize / 2, dstPs[1] - btSize / 2, mPaint); + canvas.drawBitmap(btScale, dstPs[4] - btSize / 2, dstPs[5] - btSize / 2, mPaint); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + gestureDetector.onTouchEvent(event); + if (MotionEvent.ACTION_UP == event.getAction() || MotionEvent.ACTION_POINTER_UP == event.getAction() || MotionEvent.ACTION_POINTER_1_UP == event.getAction() || MotionEvent.ACTION_POINTER_2_UP == event.getAction()) { + setDoubleDownPoints(0, 0, 0, 0); + lastDoubleDegress = 1000; + lastDegree = computeDegree(new Point((int) dstPs[4], (int) dstPs[5]), new Point((int) dstPs[8], (int) dstPs[9])); + } + + return !isOut; + } + + private void setDoubleDownPoints(float x1, float y1, float x2, float y2) { + downX1 = x1; + downY1 = y1; + downX2 = x2; + downY2 = y2; + } + + private void calculateClickType(int x, int y) { + RectF rectF = new RectF(x - btSize / 2 - 40, y - btSize / 2 - 40, x + btSize / 2 + 40, y + btSize / 2 + 40); + if (rectF.contains(dstPs[2] - 20, dstPs[3])) { + clickType = ClickType.DELETE; + } else if (rectF.contains(dstPs[0], dstPs[1])) { + clickType = ClickType.MIRROR; + } else if (rectF.contains(dstPs[4] + 20, dstPs[5])) { + clickType = ClickType.SCALE; + } else if (rectF.contains(dstPs[6] - 20, dstPs[7])) { + clickType = ClickType.IMAGE; + } else { + + RectF bounds = new RectF(); + path.computeBounds(bounds, true); + Region region = new Region(); + region.setPath(path, new Region((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom)); + + if (region.contains(x, y)) { + if (isOut) { + isOut = false; + } + if (!isUsing) { + isUsing = true; + listener.onUsing(); + postInvalidate(); + } + clickType = ClickType.IMAGE; + } else { + if (isUsing) { + isUsing = false; + postInvalidate(); + } + if (!isOut) { + isOut = true; + } + clickType = ClickType.OUT; + } + } + } + + public void delete() { + if (null == listener) { + throw new NullPointerException("OnStickerClickListener listener is null"); + } + setVisibility(GONE); + StickerCache.get().recycle(cacheKey); + listener.onDelete(); + } + + private void mirror() { + if (mirror) { + image = mirrorImage; + } else { + image = srcImage; + } + mirror = !mirror; + matrixMap(); + } + + private void top() { + bringToFront();//置顶 + invalidate(); + listener.onTop(); + } + + private void move(float distansX, float distansY) { + mMatrix.postTranslate(distansX, distansY); + matrixMap(); + } + + private void controller(MotionEvent event) { + scale(event); + rotate(event); + } + + private void scale(MotionEvent event) { + float originalX1; + float originalY1; + float originalX2; + float originalY2; + float moveX1; + float moveY1; + float moveX2; + float moveY2; + if (event.getPointerCount() == 2) { + originalX2 = downX2; + originalY2 = downY2; + originalX1 = downX1; + originalY1 = downY1; + + moveX2 = event.getX(1); + moveY2 = event.getY(1); + moveX1 = event.getX(0); + moveY1 = event.getY(0); + + } else { + originalX2 = dstPs[4]; + originalY2 = dstPs[5]; + originalX1 = dstPs[0]; + originalY1 = dstPs[1]; + + moveX2 = event.getX(); + moveY2 = event.getY(); + moveX1 = originalX1; + moveY1 = originalY1; + } + + float temp1 = getDistanceOfTwoPoints(originalX2, originalY2, originalX1, originalY1); + float temp2 = getDistanceOfTwoPoints(moveX2, moveY2, moveX1, moveY1); + + float scalValue = temp2 / temp1; + + if (getScaleValue() < (float) 0.3 && scalValue < (float) 1) { + return; + } + mMatrix.postScale(scalValue, scalValue, dstPs[8], dstPs[9]); + matrixMap(); + if (event.getPointerCount() == 2) { + setDoubleDownPoints(moveX1, moveY1, moveX2, moveY2); + } + } + + private void rotate(MotionEvent event) { + if (event.getPointerCount() == 2) { + float preDegree = computeDegree(new Point((int) event.getX(0), (int) event.getY(0)), new Point((int) event.getX(1), (int) event.getY(1))); + if (lastDoubleDegress == 1000) { + lastDoubleDegress = preDegree; + } + mMatrix.postRotate(preDegree - lastDoubleDegress, dstPs[8], dstPs[9]); + matrixMap(); + lastDoubleDegress = preDegree; + + } else { + float preDegree = computeDegree(new Point((int) event.getX(), (int) event.getY()), new Point((int) dstPs[8], (int) dstPs[9])); + mMatrix.postRotate(preDegree - lastDegree, dstPs[8], dstPs[9]); + matrixMap(); + lastDegree = preDegree; + } + } + + // 获取饰品缩放比例(与原图相比) + public float getScaleValue() { + float preDistance = (srcPs[8] - srcPs[0]) * (srcPs[8] - srcPs[0]) + (srcPs[9] - srcPs[1]) * (srcPs[9] - srcPs[1]); + float lastDistance = (dstPs[8] - dstPs[0]) * (dstPs[8] - dstPs[0]) + (dstPs[9] - dstPs[1]) * (dstPs[9] - dstPs[1]); + float scaleValue = (float) Math.sqrt(lastDistance / preDistance); + return scaleValue; + } + + private void matrixMap() { + mMatrix.mapPoints(dstPs, srcPs); + postInvalidate(); + } + + public void setUsing(boolean isUsing) { + this.isUsing = isUsing; + postInvalidate(); + } + + public boolean isUsing() { + return this.isUsing; + } + + private float getDistanceOfTwoPoints(float x1, float y1, float x2, float y2) { + return (float) (Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))); + } + + public float computeDegree(Point p1, Point p2) { + float tran_x = p1.x - p2.x; + float tran_y = p1.y - p2.y; + float degree = 0.0f; + float angle = (float) (Math.asin(tran_x / Math.sqrt(tran_x * tran_x + tran_y * tran_y)) * 180 / Math.PI); + if (!Float.isNaN(angle)) { + if (tran_x >= 0 && tran_y <= 0) {//第一象限 + degree = angle; + } else if (tran_x <= 0 && tran_y <= 0) {//第二象限 + degree = angle; + } else if (tran_x <= 0 && tran_y >= 0) {//第三象限 + degree = -180 - angle; + } else if (tran_x >= 0 && tran_y >= 0) {//第四象限 + degree = 180 - angle; + } + } + return degree; + } + + private enum ClickType { + DELETE, MIRROR, SCALE, ROTATE, IMAGE, OUT + } + + private class StickerGestureListener extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + + switch (clickType) { + case DELETE: + delete(); + break; + case MIRROR: + mirror(); + break; + case SCALE: + break; + case ROTATE: + break; + case IMAGE: + break; + case OUT: + break; + } + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + switch (clickType) { + case DELETE: + break; + case MIRROR: + break; + case SCALE: + if (e2.getPointerCount() > 1) break; + controller(e2); + break; + case ROTATE: + break; + case IMAGE: + if (e2.getPointerCount() == 2) { + if (downX1 + downY1 + downX2 + downY2 == 0) { + setDoubleDownPoints(e2.getX(0), e2.getY(0), e2.getX(1), e2.getY(1)); + } + controller(e2); + } else if (e2.getPointerCount() == 1) { + move(-distanceX, -distanceY); + } + break; + case OUT: + break; + } + return true; + } + + @Override + public boolean onDown(MotionEvent e) { + isChecked = true; + calculateClickType((int) e.getX(), (int) e.getY()); + if (clickType == ClickType.IMAGE) { + top(); + } + return true; + } + + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/EditFragment.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/EditFragment.java new file mode 100644 index 000000000..0d4210919 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/EditFragment.java @@ -0,0 +1,202 @@ +package com.huantansheng.easyphotos.models.sticker.view; + +import android.content.Context; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.annotation.IdRes; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; + +import com.huantansheng.easyphotos.R; + +/** + * 文字贴纸,编辑界面 + * Created by huan on 2017/12/13. + */ + +public class EditFragment extends DialogFragment implements View.OnClickListener { + + private TextView tvSample; + private EditText et; + private SeekBar seekBar; + + private TextSticker textSticker = null; + + private InputMethodManager inputMethodManager; + + public static EditFragment show(FragmentManager fm, TextSticker sticker) { + EditFragment editFragment = new EditFragment(); + editFragment.textSticker = sticker; + editFragment.show(fm, "edit"); + return editFragment; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + + getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); + + View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_text_sticker_easy_photos, container); + + tvSample = (TextView) rootView.findViewById(R.id.tv_sample); + et = (EditText) rootView.findViewById(R.id.et); + seekBar = (SeekBar) rootView.findViewById(R.id.m_seek_bar); + + l(rootView, R.id.iv_red, R.id.iv_orange, R.id.iv_yellow, R.id.iv_green, R.id.iv_cyan, R.id.iv_blue, R.id.iv_purple, R.id.iv_black, R.id.iv_gray, R.id.iv_white, R.id.tv_done, R.id.iv_clear); + + seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + if (b) { + setTextAlpha(i); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + + et.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + tvSample.setText(editable.toString()); + if (null != textSticker) { + textSticker.resetText(editable.toString()); + } + } + }); + return rootView; + } + + private void l(View view, @IdRes int... resIds) { + for (int resId : resIds) { + view.findViewById(resId).setOnClickListener(this); + } + } + + @Override + public void onResume() { + super.onResume(); + bindingSticker(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + Window dialogWindow = getDialog().getWindow(); + if (null != dialogWindow) { + WindowManager.LayoutParams attrs = dialogWindow.getAttributes(); + attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + dialogWindow.setAttributes(attrs); + dialogWindow.requestFeature(Window.FEATURE_NO_TITLE); + } + + super.onActivityCreated(savedInstanceState); + + if (null != dialogWindow) { + dialogWindow.setBackgroundDrawable(new ColorDrawable(0x00000000)); + dialogWindow.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } + } + + + public void bindingSticker() { + String text = textSticker.getText(); + tvSample.setText(text); + et.setText(text); + et.setSelection(text.length()); + int alpha = textSticker.getTextAlpha(); + float alphaF = (float) alpha / (float) 255; + seekBar.setProgress(alpha); + tvSample.setTextColor(textSticker.getTextColor()); + tvSample.setAlpha(alphaF); + inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (null != inputMethodManager) { + inputMethodManager.showSoftInput(et, 0); + } + } + + @Override + public void onClick(View view) { + int id = view.getId(); + if (R.id.iv_red == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_red_easy_photos)); + + } else if (R.id.iv_orange == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_orange_easy_photos)); + + } else if (R.id.iv_yellow == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_yellow_easy_photos)); + + } else if (R.id.iv_green == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_green_easy_photos)); + + } else if (R.id.iv_cyan == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_cyan_easy_photos)); + + } else if (R.id.iv_blue == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_blue_easy_photos)); + + } else if (R.id.iv_purple == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_purple_easy_photos)); + + } else if (R.id.iv_black == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_black_easy_photos)); + + } else if (R.id.iv_gray == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_gray_easy_photos)); + + } else if (R.id.iv_white == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_white_easy_photos)); + + } else if (R.id.tv_done == id) { + dismiss(); + } else if (R.id.iv_clear == id) { + et.setText(null); + } + + } + + private void setTextColor(int color) { + tvSample.setTextColor(color); + textSticker.setTextColor(color); + } + + private void setTextAlpha(int alpha) { + tvSample.setAlpha((float) alpha / (float) 225); + textSticker.setTextAlpha(alpha); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/TextSticker.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/TextSticker.java new file mode 100644 index 000000000..38bf580f8 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/TextSticker.java @@ -0,0 +1,535 @@ +package com.huantansheng.easyphotos.models.sticker.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.RectF; +import android.graphics.Region; +import android.graphics.Typeface; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import androidx.core.content.ContextCompat; + +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.models.sticker.listener.OnStickerClickListener; + + +/** + * 自定义贴图view + * Created by huan on 2017/7/24. + */ + +public class TextSticker extends View { + + public boolean isChecked = false; + private String text; + private float textWidth; + private float textHeight; + private Bitmap bitmap; + private Bitmap btDelete; + private Bitmap btController; + private int btSize; + private Matrix mMatrix; + private float[] srcPs, dstPs; + private TextPaint textPaint; + private Paint bitmapPaint; + private Paint framePaint; + private boolean isUsing = true; + private float downX1; + private float downY1; + private float downX2; + private float downY2; + private ClickType clickType; + private boolean isOut = false; + private GestureDetector gestureDetector; + private float lastDegree; + private float lastDoubleDegress; + private OnStickerClickListener listener; + private int startX, startY; + private int minWidth = 300, minHeight = 100; + private float minScale; + private StaticLayout textLayout; + private int textLayoutWidth; + + private Canvas bitmapConvas; + + private Path path; + + public TextSticker(Context context, String text, int viewGroupCenterX, int viewGroupCenterY) { + super(context); + + this.text = text; + if (TextUtils.isEmpty(this.text)) { + this.text = context.getString(R.string.text_sticker_hint_easy_photos); + } + path = new Path(); + textLayoutWidth = getResources().getDisplayMetrics().widthPixels / 2; + initButtons(); + initPaints(); + resetSize(); + initStartPoint(viewGroupCenterX, viewGroupCenterY); + initPs(); + resetBitmap(); + initMatrix(); + initCanvasPosition(); + lastDegree = computeDegree(new Point((int) textWidth, (int) textHeight), new Point((int) textWidth / 2, (int) textHeight / 2)); + lastDoubleDegress = 1000; + gestureDetector = new GestureDetector(context, new StickerGestureListener()); + + } + + private void resetBitmap() { + EasyPhotos.recycle(bitmap); + bitmap = Bitmap.createBitmap((int) textWidth, (int) textHeight, Bitmap.Config.ARGB_4444); + bitmapConvas = new Canvas(bitmap); + textLayout.draw(bitmapConvas); + } + + private void initStartPoint(int viewGroupCenterX, int viewGroupCenterY) { + this.startX = viewGroupCenterX - (int) textWidth / 2; + if (this.startX < 100) { + this.startX = viewGroupCenterX / 2; + } + this.startY = viewGroupCenterY - (int) textHeight / 2; + if (this.startY < 100) { + this.startY = viewGroupCenterY / 2; + } + } + + public void setOnStickerClickListener(OnStickerClickListener listener) { + this.listener = listener; + } + + private void initCanvasPosition() { + mMatrix.postTranslate(startX, startY); + mMatrix.mapPoints(dstPs, srcPs); + } + + private void initMatrix() { + mMatrix = new Matrix(); + } + + private void initPaints() { + textPaint = new TextPaint(); + textPaint.setAntiAlias(true); + textPaint.setDither(true); + textPaint.setFilterBitmap(true); + textPaint.setTypeface(Typeface.DEFAULT_BOLD); + textPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.sticker_text_size_easy_photos)); + textPaint.setColor(Color.WHITE); + + + bitmapPaint = new Paint(); + bitmapPaint.setAntiAlias(true); + bitmapPaint.setDither(true); + bitmapPaint.setFilterBitmap(true); + + framePaint = new Paint(); + framePaint.setAntiAlias(true); + bitmapPaint.setDither(true); + bitmapPaint.setFilterBitmap(true); + framePaint.setStrokeWidth(1); + framePaint.setColor(ContextCompat.getColor(getContext(), android.R.color.white)); + } + + + private void resetSize() { + textLayout = new StaticLayout(text, textPaint, textLayoutWidth, + Layout.Alignment.ALIGN_CENTER, 1.0F, 0.0F, true); + textWidth = minWidth; + textHeight = minHeight; + if (textWidth < textLayout.getWidth()) { + textWidth = textLayout.getWidth(); + } + if (textHeight < textLayout.getHeight()) { + textHeight = textLayout.getHeight(); + } + minScale = minWidth / textWidth; + + } + + private void initPs() { + srcPs = new float[]{0, 0, textWidth, 0, textWidth, textHeight, 0, textHeight, textWidth / 2, textHeight / 2}; + dstPs = srcPs.clone(); + } + + private void initButtons() { + btDelete = BitmapFactory.decodeResource(getResources(), R.drawable.ic_delete_easy_photos); + btController = BitmapFactory.decodeResource(getResources(), R.drawable.ic_controller_easy_photos); + btSize = btDelete.getWidth(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.drawBitmap(bitmap, mMatrix, bitmapPaint); + if (isUsing) + drawOthers(canvas); + } + + private void drawOthers(Canvas canvas) { + path.reset(); + path.moveTo(dstPs[0], dstPs[1]); + path.lineTo(dstPs[2], dstPs[3]); + path.lineTo(dstPs[4], dstPs[5]); + path.lineTo(dstPs[6], dstPs[7]); + path.lineTo(dstPs[0], dstPs[1]); + for (int i = 0; i < 7; i += 2) { + if (i == 6) { + canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[0], dstPs[1], framePaint); + break; + } + canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[i + 2], dstPs[i + 3], framePaint); + } + + + canvas.drawBitmap(btDelete, dstPs[2] - btSize / 2, dstPs[3] - btSize / 2, bitmapPaint); + canvas.drawBitmap(btController, dstPs[4] - btSize / 2, dstPs[5] - btSize / 2, bitmapPaint); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + gestureDetector.onTouchEvent(event); + if (MotionEvent.ACTION_UP == event.getAction() || MotionEvent.ACTION_POINTER_UP == event.getAction() || MotionEvent.ACTION_POINTER_1_UP == event.getAction() || MotionEvent.ACTION_POINTER_2_UP == event.getAction()) { + setDoubleDownPoints(0, 0, 0, 0); + lastDoubleDegress = 1000; + lastDegree = computeDegree(new Point((int) dstPs[4], (int) dstPs[5]), new Point((int) dstPs[8], (int) dstPs[9])); + } + + return !isOut; + } + + private void setDoubleDownPoints(float x1, float y1, float x2, float y2) { + downX1 = x1; + downY1 = y1; + downX2 = x2; + downY2 = y2; + } + + private void calculateClickType(int x, int y) { + RectF rectF = new RectF(x - btSize / 2 - 40, y - btSize / 2 - 40, x + btSize / 2 + 40, y + btSize / 2 + 40); + + + if (rectF.contains(dstPs[2] - 20, dstPs[3])) { + clickType = ClickType.DELETE; + } + else if (rectF.contains(dstPs[4] + 20, dstPs[5])) { + clickType = ClickType.SCALE; + } + else { + RectF bounds = new RectF(); + path.computeBounds(bounds, true); + Region region = new Region(); + region.setPath(path, new Region((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom)); + + if (region.contains(x, y)) { + if (isOut) { + isOut = false; + } + if (!isUsing) { + isUsing = true; + listener.onUsing(); + postInvalidate(); + } + clickType = ClickType.IMAGE; + } else { + if (isUsing) { + isUsing = false; + postInvalidate(); + } + if (!isOut) { + isOut = true; + } + clickType = ClickType.OUT; + } + } + } + + public void delete() { + if (null == listener) { + throw new NullPointerException("OnStickerClickListener listener is null"); + } + setVisibility(GONE); + EasyPhotos.recycle(bitmap); + listener.onDelete(); + } + + private void editor() { + listener.onEditor(); + } + + private void top() { + bringToFront();//置顶 + invalidate(); + listener.onTop(); + } + + private void move(float distansX, float distansY) { + mMatrix.postTranslate(distansX, distansY); + matrixMap(); + } + + public void moveTo(float x, float y) { + move(x - dstPs[8], y - dstPs[1]); + } + + private void controller(MotionEvent event) { + scale(event); + rotate(event); + } + + private void scale(MotionEvent event) { + float originalX1; + float originalY1; + float originalX2; + float originalY2; + float moveX1; + float moveY1; + float moveX2; + float moveY2; + if (event.getPointerCount() == 2) { + originalX2 = downX2; + originalY2 = downY2; + originalX1 = downX1; + originalY1 = downY1; + + moveX2 = event.getX(1); + moveY2 = event.getY(1); + moveX1 = event.getX(0); + moveY1 = event.getY(0); + + } else { + originalX2 = dstPs[4]; + originalY2 = dstPs[5]; + originalX1 = dstPs[0]; + originalY1 = dstPs[1]; + + moveX2 = event.getX(); + moveY2 = event.getY(); + moveX1 = originalX1; + moveY1 = originalY1; + } + + float temp1 = getDistanceOfTwoPoints(originalX2, originalY2, originalX1, originalY1); + float temp2 = getDistanceOfTwoPoints(moveX2, moveY2, moveX1, moveY1); + + float scalValue = temp2 / temp1; + + if (getScaleValue() < minScale && scalValue < (float) 1) { + return; + } + mMatrix.postScale(scalValue, scalValue, dstPs[8], dstPs[9]); + matrixMap(); + if (event.getPointerCount() == 2) { + setDoubleDownPoints(moveX1, moveY1, moveX2, moveY2); + } + } + + private void rotate(MotionEvent event) { + if (event.getPointerCount() == 2) { + float preDegree = computeDegree(new Point((int) event.getX(0), (int) event.getY(0)), new Point((int) event.getX(1), (int) event.getY(1))); + if (lastDoubleDegress == 1000) { + lastDoubleDegress = preDegree; + } + mMatrix.postRotate(preDegree - lastDoubleDegress, dstPs[8], dstPs[9]); + matrixMap(); + lastDoubleDegress = preDegree; + + } else { + float preDegree = computeDegree(new Point((int) event.getX(), (int) event.getY()), new Point((int) dstPs[8], (int) dstPs[9])); + mMatrix.postRotate(preDegree - lastDegree, dstPs[8], dstPs[9]); + matrixMap(); + lastDegree = preDegree; + } + } + + // 获取饰品缩放比例(与原图相比) + public float getScaleValue() { + float preDistance = (srcPs[8] - srcPs[0]) * (srcPs[8] - srcPs[0]) + (srcPs[9] - srcPs[1]) * (srcPs[9] - srcPs[1]); + float lastDistance = (dstPs[8] - dstPs[0]) * (dstPs[8] - dstPs[0]) + (dstPs[9] - dstPs[1]) * (dstPs[9] - dstPs[1]); + float scaleValue = (float) Math.sqrt(lastDistance / preDistance); + return scaleValue; + } + + private void matrixMap() { + mMatrix.mapPoints(dstPs, srcPs); + postInvalidate(); + } + + public void setUsing(boolean isUsing) { + this.isUsing = isUsing; + postInvalidate(); + } + + private float getDistanceOfTwoPoints(float x1, float y1, float x2, float y2) { + return (float) (Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))); + } + + public float computeDegree(Point p1, Point p2) { + float tran_x = p1.x - p2.x; + float tran_y = p1.y - p2.y; + float degree = 0.0f; + float angle = (float) (Math.asin(tran_x / Math.sqrt(tran_x * tran_x + tran_y * tran_y)) * 180 / Math.PI); + if (!Float.isNaN(angle)) { + if (tran_x >= 0 && tran_y <= 0) {//第一象限 + degree = angle; + } else if (tran_x <= 0 && tran_y <= 0) {//第二象限 + degree = angle; + } else if (tran_x <= 0 && tran_y >= 0) {//第三象限 + degree = -180 - angle; + } else if (tran_x >= 0 && tran_y >= 0) {//第四象限 + degree = 180 - angle; + } + } + return degree; + } + + private enum ClickType { + DELETE, EDITOR, SCALE, ROTATE, IMAGE, OUT + } + + private class StickerGestureListener extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + + switch (clickType) { + case DELETE: + delete(); + break; + case EDITOR: + break; + case SCALE: + break; + case ROTATE: + break; + case IMAGE: + break; + case OUT: + break; + } + return true; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + switch (clickType) { + case DELETE: + break; + case EDITOR: + break; + case SCALE: + break; + case ROTATE: + break; + case IMAGE: + editor(); + break; + case OUT: + break; + } + return super.onDoubleTap(e); + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + switch (clickType) { + case DELETE: + break; + case EDITOR: + break; + case SCALE: + if (e2.getPointerCount() > 1) break; + controller(e2); + break; + case ROTATE: + break; + case IMAGE: + if (e2.getPointerCount() == 2) { + if (downX1 + downY1 + downX2 + downY2 == 0) { + setDoubleDownPoints(e2.getX(0), e2.getY(0), e2.getX(1), e2.getY(1)); + } + controller(e2); + } else if (e2.getPointerCount() == 1) { + move(-distanceX, -distanceY); + } + break; + case OUT: + break; + } + return true; + } + + @Override + public boolean onDown(MotionEvent e) { + isChecked = true; + calculateClickType((int) e.getX(), (int) e.getY()); + if (clickType == ClickType.IMAGE) { + top(); + } + return true; + } + + } + + public void resetText(String text) { + this.text = text; + resetSize(); + resetPoints(); + resetBitmap(); + matrixMap(); + } + + private void resetPoints() { + srcPs = new float[]{0, 0, textWidth, 0, textWidth, textHeight, 0, textHeight, textWidth / 2, textHeight / 2}; + } + + public String getText() { + return this.text; + } + + public void setTextColor(int color) { + textPaint.setColor(color); + resetSize(); + resetPoints(); + resetBitmap(); + matrixMap(); + } + + public int getTextColor() { + return textPaint.getColor(); + } + + public void setTextAlpha(int alpha) { + textPaint.setAlpha(alpha); + resetSize(); + resetPoints(); + resetBitmap(); + matrixMap(); + } + + public int getTextAlpha() { + return textPaint.getAlpha(); + } + + public boolean isUsing() { + return this.isUsing; + } + + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/result/Result.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/result/Result.java new file mode 100644 index 000000000..4fda5e8b6 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/result/Result.java @@ -0,0 +1,134 @@ +package com.huantansheng.easyphotos.result; + +import android.net.Uri; + +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.setting.Setting; + +import java.util.ArrayList; + +/** + * 存储的返回图片集 + * Created by huan on 2017/10/24. + */ + +public class Result { + public static ArrayList photos = new ArrayList<>(); + public static final int ADD_SUCCESS = 0; + public static final int PICTURE_OUT = -1; + public static final int VIDEO_OUT = -2; + public static final int SINGLE_TYPE = -3; + + /** + * @return 0:添加成功 -2:超过视频选择数 -1:超过图片选择数 + */ + public static int addPhoto(Photo photo) { + if (photos.isEmpty()) { + photo.selected = true; + photos.add(photo); + return ADD_SUCCESS; + } + if (Setting.complexSelector) { + if (Setting.complexSingleType) { + if (photos.get(0).type.contains(Type.VIDEO)) { + if (!photo.type.contains(Type.VIDEO)) { + return SINGLE_TYPE; + } + } + if (!photos.get(0).type.contains(Type.VIDEO)) { + if (photo.type.contains(Type.VIDEO)) { + return SINGLE_TYPE; + } + } + + } + int number = getVideoNumber(); + if (photo.type.contains(Type.VIDEO) && number >= Setting.complexVideoCount) { + return VIDEO_OUT; + } + number = photos.size() - number; + if ((!photo.type.contains(Type.VIDEO)) && number >= Setting.complexPictureCount) { + return PICTURE_OUT; + } + } + photo.selected = true; + photos.add(photo); + return ADD_SUCCESS; + } + + public static void removePhoto(Photo photo) { + photo.selected = false; + photos.remove(photo); + } + + public static void removePhoto(int photoIndex) { + removePhoto(photos.get(photoIndex)); + } + + public static void removeAll() { + int size = photos.size(); + for (int i = 0; i < size; i++) { + removePhoto(0); + } + } + + private static int getVideoNumber() { + int count = 0; + for (Photo p : photos) { + if (p.type.contains(Type.VIDEO)) { + count += 1; + } + } + return count; + } + + public static void processOriginal() { + if (Setting.showOriginalMenu) { + if (Setting.originalMenuUsable) { + for (Photo photo : photos) { + photo.selectedOriginal = Setting.selectedOriginal; + } + } + } + } + + public static void clear() { + photos.clear(); + } + + public static boolean isEmpty() { + return photos.isEmpty(); + } + + public static int count() { + return photos.size(); + } + + /** + * 获取选择器应该显示的数字 + * + * @param photo 当前图片 + * @return 选择器应该显示的数字 + */ + public static String getSelectorNumber(Photo photo) { + return String.valueOf(photos.indexOf(photo) + 1); + } + + public static String getPhotoPath(int position) { + return photos.get(position).path; + } + + public static Uri getPhotoUri(int position) { + return photos.get(position).uri; + } + + public static String getPhotoType(int position) { + return photos.get(position).type; + } + + public static long getPhotoDuration(int position) { + return photos.get(position).duration; + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/setting/Setting.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/setting/Setting.java new file mode 100644 index 000000000..ad48c8fc6 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/setting/Setting.java @@ -0,0 +1,121 @@ +package com.huantansheng.easyphotos.setting; + +import android.view.View; + +import androidx.annotation.IntDef; + +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.engine.ImageEngine; +import com.huantansheng.easyphotos.models.album.entity.Photo; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +/** + * EasyPhotos的设置值 + * Created by huan on 2017/10/24. + */ + +public class Setting { + public static int minWidth = 1; + public static int minHeight = 1; + public static long minSize = 1; + public static int count = 1; + + public static WeakReference photosAdView = null; + public static WeakReference albumItemsAdView = null; + public static boolean photoAdIsOk = false; + public static boolean albumItemsAdIsOk = false; + public static boolean useWidth = false; + public static ArrayList selectedPhotos = new ArrayList<>(); + public static boolean showOriginalMenu = false; + public static boolean originalMenuUsable = false; + public static String originalMenuUnusableHint = ""; + public static boolean selectedOriginal = false; + public static String fileProviderAuthority = null; + public static boolean isShowCamera = false; + public static int cameraLocation = 1; + public static boolean onlyStartCamera = false; + public static boolean showPuzzleMenu = true; + public static List filterTypes = new ArrayList<>(); + public static boolean showGif = false; + public static boolean showVideo = false; + public static boolean showCleanMenu = true; + public static long videoMinSecond = 0L; + public static long videoMaxSecond = Long.MAX_VALUE; + public static ImageEngine imageEngine = null; + + public static final int LIST_FIRST = 0; + public static final int BOTTOM_RIGHT = 1; + + public static boolean complexSelector = false; + public static boolean complexSingleType = false; + public static int complexVideoCount = 0; + public static int complexPictureCount = 0; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {LIST_FIRST, BOTTOM_RIGHT}) + public @interface Location { + + } + + public static void clear() { + minWidth = 1; + minHeight = 1; + minSize = 1; + count = 1; + + photosAdView = null; + albumItemsAdView = null; + photoAdIsOk = false; + albumItemsAdIsOk = false; + selectedPhotos.clear(); + showOriginalMenu = false; + originalMenuUsable = false; + originalMenuUnusableHint = ""; + selectedOriginal = false; + cameraLocation = BOTTOM_RIGHT; + isShowCamera = false; + onlyStartCamera = false; + showPuzzleMenu = true; + filterTypes = new ArrayList<>(); + showGif = false; + showVideo = false; + showCleanMenu = true; + videoMinSecond = 0L; + videoMaxSecond = Long.MAX_VALUE; + complexSelector = false; + complexSingleType = false; + complexVideoCount = 0; + complexPictureCount = 0; + } + + public static boolean isFilter(String type) { + type = type.toLowerCase(); + for (String filterType : Setting.filterTypes) { + if (type.contains(filterType)) { + return true; + } + } + return false; + } + + public static boolean isOnlyVideo() { + return filterTypes.size() == 1 && filterTypes.get(0).equals(Type.VIDEO); + } + + public static boolean hasPhotosAd() { + return photosAdView != null && photosAdView.get() != null; + } + + public static boolean hasAlbumItemsAd() { + return albumItemsAdView != null && albumItemsAdView.get() != null; + } + + public static boolean isBottomRightCamera() { + return cameraLocation == BOTTOM_RIGHT; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/EasyPhotosActivity.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/EasyPhotosActivity.java new file mode 100644 index 000000000..ab1767d09 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/EasyPhotosActivity.java @@ -0,0 +1,1091 @@ +package com.huantansheng.easyphotos.ui; + +import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Fragment; +import android.content.ContentValues; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.hardware.Camera; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.provider.MediaStore; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.ScaleAnimation; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.exifinterface.media.ExifInterface; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; + +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Code; +import com.huantansheng.easyphotos.constant.Key; +import com.huantansheng.easyphotos.models.ad.AdListener; +import com.huantansheng.easyphotos.models.album.AlbumModel; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.adapter.AlbumItemsAdapter; +import com.huantansheng.easyphotos.ui.adapter.PhotosAdapter; +import com.huantansheng.easyphotos.ui.dialog.LoadingDialog; +import com.huantansheng.easyphotos.ui.widget.PressedTextView; +import com.huantansheng.easyphotos.utils.color.ColorUtils; +import com.huantansheng.easyphotos.utils.media.DurationUtils; +import com.huantansheng.easyphotos.utils.media.MediaScannerConnectionUtils; +import com.huantansheng.easyphotos.utils.permission.PermissionUtil; +import com.huantansheng.easyphotos.utils.settings.SettingsUtils; +import com.huantansheng.easyphotos.utils.string.StringUtils; +import com.huantansheng.easyphotos.utils.system.SystemUtils; +import com.huantansheng.easyphotos.utils.uri.UriUtils; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Locale; + +public class EasyPhotosActivity extends AppCompatActivity implements AlbumItemsAdapter.OnClickListener, PhotosAdapter.OnClickListener, AdListener, View.OnClickListener { + + private File mTempImageFile; + + private AlbumModel albumModel; + private ArrayList photoList = new ArrayList<>(); + private ArrayList albumItemList = new ArrayList<>(); + + private ArrayList resultList = new ArrayList<>(); + + private RecyclerView rvPhotos; + private PhotosAdapter photosAdapter; + private GridLayoutManager gridLayoutManager; + + private RecyclerView rvAlbumItems; + private AlbumItemsAdapter albumItemsAdapter; + private RelativeLayout rootViewAlbumItems; + + private PressedTextView tvAlbumItems, tvDone, tvPreview; + private TextView tvOriginal; + private AnimatorSet setHide; + private AnimatorSet setShow; + + private int currAlbumItemIndex = 0; + + private ImageView ivCamera; + + private LinearLayout mSecondMenus; + + private RelativeLayout permissionView; + private TextView tvPermission; + private View mBottomBar; + + public static long startTime = 0; + + public static boolean doubleClick() { + long now = System.currentTimeMillis(); + if (now - startTime < 600) { + return true; + } + startTime = now; + return false; + } + + public static void start(Activity activity, int requestCode) { + if (doubleClick()) return; + Intent intent = new Intent(activity, EasyPhotosActivity.class); + activity.startActivityForResult(intent, requestCode); + } + + public static void start(Fragment fragment, int requestCode) { + if (doubleClick()) return; + Intent intent = new Intent(fragment.getActivity(), EasyPhotosActivity.class); + fragment.startActivityForResult(intent, requestCode); + } + + public static void start(androidx.fragment.app.Fragment fragment, int requestCode) { + if (doubleClick()) return; + Intent intent = new Intent(fragment.getContext(), EasyPhotosActivity.class); + fragment.startActivityForResult(intent, requestCode); + } + + LoadingDialog loadingDialog; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_easy_photos); + hideActionBar(); + adaptationStatusBar(); + loadingDialog = LoadingDialog.get(this); + if (!Setting.onlyStartCamera && null == Setting.imageEngine) { + finish(); + return; + } + initSomeViews(); + if (PermissionUtil.checkAndRequestPermissionsInActivity(this, getNeedPermissions())) { + hasPermissions(); + } else { + permissionView.setVisibility(View.VISIBLE); + } + } + + private void adaptationStatusBar() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + int statusColor = getWindow().getStatusBarColor(); + if (statusColor == Color.TRANSPARENT) { + statusColor = ContextCompat.getColor(this, R.color.colorPrimaryDark); + } + if (ColorUtils.isWhiteColor(statusColor)) { + SystemUtils.getInstance().setStatusDark(this, true); + } + } + } + + private void initSomeViews() { + mBottomBar = findViewById(R.id.m_bottom_bar); + permissionView = findViewById(R.id.rl_permissions_view); + tvPermission = findViewById(R.id.tv_permission); + rootViewAlbumItems = findViewById(R.id.root_view_album_items); + TextView tvTitle = findViewById(R.id.tv_title); + if (Setting.isOnlyVideo()) { + tvTitle.setText(R.string.video_selection_easy_photos); + } + findViewById(R.id.iv_second_menu).setVisibility(Setting.showPuzzleMenu || Setting.showCleanMenu || Setting.showOriginalMenu ? View.VISIBLE : View.GONE); + setClick(R.id.iv_back); + } + + private void hasPermissions() { + permissionView.setVisibility(View.GONE); + if (Setting.onlyStartCamera) { + launchCamera(Code.REQUEST_CAMERA); + return; + } + AlbumModel.CallBack albumModelCallBack = new AlbumModel.CallBack() { + @Override + public void onAlbumWorkedCallBack() { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (EasyPhotosActivity.this.isDestroyed()) { // or call isFinishing() if min sdk version < 17 + return; + } + dismissProgressDialog(); + onAlbumWorkedDo(); + } + }); + } + }; + loadingDialog.show(); + albumModel = AlbumModel.getInstance(); + albumModel.query(this, albumModelCallBack); + } + + protected String[] getNeedPermissions() { + if (Setting.isShowCamera) { + return new String[]{Manifest.permission.CAMERA, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE}; + } else { + return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE}; + } + } + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull final String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + PermissionUtil.onPermissionResult(this, permissions, grantResults, + new PermissionUtil.PermissionCallBack() { + @Override + public void onSuccess() { + hasPermissions(); + } + + @Override + public void onShouldShow() { + tvPermission.setText(R.string.permissions_again_easy_photos); + permissionView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (PermissionUtil.checkAndRequestPermissionsInActivity(EasyPhotosActivity.this, getNeedPermissions())) { + hasPermissions(); + } + } + }); + + } + + @Override + public void onFailed() { + tvPermission.setText(R.string.permissions_die_easy_photos); + permissionView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + SettingsUtils.startMyApplicationDetailsForResult(EasyPhotosActivity.this, + getPackageName()); + } + }); + + } + }); + } + + + /** + * 启动相机 + * + * @param requestCode startActivityForResult的请求码 + */ + private void launchCamera(int requestCode) { + if (TextUtils.isEmpty(Setting.fileProviderAuthority)) + throw new RuntimeException("AlbumBuilder" + " : 请执行 setFileProviderAuthority()方法"); + if (!cameraIsCanUse()) { + permissionView.setVisibility(View.VISIBLE); + tvPermission.setText(R.string.permissions_die_easy_photos); + permissionView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + SettingsUtils.startMyApplicationDetailsForResult(EasyPhotosActivity.this, + getPackageName()); + } + }); + return; + } + toAndroidCamera(requestCode); + } + + /** + * 启动系统相机 + * + * @param requestCode 请求相机的请求码 + */ + private Uri photoUri = null; + + private void toAndroidCamera(int requestCode) { + Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + + if (cameraIntent.resolveActivity(getPackageManager()) != null || + this.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + photoUri = createImageUri(); + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); + cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + startActivityForResult(cameraIntent, requestCode); + return; + } + + createCameraTempImageFile(); + if (mTempImageFile != null && mTempImageFile.isFile()) { + + Uri imageUri = UriUtils.getUri(this, mTempImageFile); + + cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //对目标应用临时授权该Uri所代表的文件 + cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); //对目标应用临时授权该Uri所代表的文件 + + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI + startActivityForResult(cameraIntent, requestCode); + } else { + Toast.makeText(getApplicationContext(), R.string.camera_temp_file_error_easy_photos, + Toast.LENGTH_SHORT).show(); + } + } else { + Toast.makeText(getApplicationContext(), R.string.msg_no_camera_easy_photos, Toast.LENGTH_SHORT).show(); + } + } + + + /** + * 创建图片地址uri,用于保存拍照后的照片 Android 10以后使用这种方法 + */ + private Uri createImageUri() { + //设置保存参数到ContentValues中 + ContentValues contentValues = new ContentValues(); + //设置文件名 + contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, String.valueOf(System.currentTimeMillis())); + //兼容Android Q和以下版本 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + //android Q中不再使用DATA字段,而用RELATIVE_PATH代替 + //RELATIVE_PATH是相对路径不是绝对路径;照片存储的地方为:存储/Pictures + contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM); + } + //设置文件类型 + contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/JPEG"); + //执行insert操作,向系统文件夹中添加文件 + //EXTERNAL_CONTENT_URI代表外部存储器,该值不变 + return getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); + } + + + private void createCameraTempImageFile() { + File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); + if (null == dir) { + dir = new File(Environment.getExternalStorageDirectory(), + File.separator + Environment.DIRECTORY_DCIM + File.separator + "Camera" + File.separator); + } + if (!dir.isDirectory()) { + if (!dir.mkdirs()) { + dir = getExternalFilesDir(null); + if (null == dir || !dir.exists()) { + dir = getFilesDir(); + if (null == dir || !dir.exists()) { + dir = getFilesDir(); + if (null == dir || !dir.exists()) { + String cacheDirPath = + File.separator + "data" + File.separator + "data" + File.separator + getPackageName() + File.separator + "cache" + File.separator; + dir = new File(cacheDirPath); + if (!dir.exists()) { + dir.mkdirs(); + } + } + } + } + } + } + + try { + mTempImageFile = File.createTempFile("IMG", ".jpg", dir); + } catch (IOException e) { + e.printStackTrace(); + mTempImageFile = null; + } + + } + + + @SuppressLint("MissingSuperCall") + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { +// super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == Code.REQUEST_SETTING_APP_DETAILS) { + if (PermissionUtil.checkAndRequestPermissionsInActivity(this, getNeedPermissions())) { + hasPermissions(); + } else { + permissionView.setVisibility(View.VISIBLE); + } + return; + } + switch (resultCode) { + case RESULT_OK: + if (Code.REQUEST_CAMERA == requestCode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + onCameraResultForQ(); + return; + } + + if (mTempImageFile == null || !mTempImageFile.isFile()) { + throw new RuntimeException("EasyPhotos拍照保存的图片不存在"); + } + onCameraResult(); + return; + } + + if (Code.REQUEST_PREVIEW_ACTIVITY == requestCode) { + if (data.getBooleanExtra(Key.PREVIEW_CLICK_DONE, false)) { + done(); + return; + } + photosAdapter.change(); + processOriginalMenu(); + shouldShowMenuDone(); + return; + } + + if (Code.REQUEST_PUZZLE_SELECTOR == requestCode) { + Photo puzzlePhoto = data.getParcelableExtra(EasyPhotos.RESULT_PHOTOS); + addNewPhoto(puzzlePhoto); + return; + } + + break; + case RESULT_CANCELED: + if (Code.REQUEST_CAMERA == requestCode) { + // 删除临时文件 + if (mTempImageFile != null && mTempImageFile.exists()) { + mTempImageFile.delete(); + mTempImageFile = null; + } + if (Setting.onlyStartCamera) { + finish(); + } + return; + } + + if (Code.REQUEST_PREVIEW_ACTIVITY == requestCode) { + processOriginalMenu(); + return; + } + break; + default: + break; + } + } + + String folderPath; + String albumName; + + private void addNewPhoto(Photo photo) { + photo.selectedOriginal = Setting.selectedOriginal; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + MediaScannerConnectionUtils.refresh(this, photo.path); + folderPath = new File(photo.path).getParentFile().getAbsolutePath(); + albumName = StringUtils.getLastPathSegment(folderPath); + } + + String albumItem_all_name = albumModel.getAllAlbumName(this); + albumModel.album.getAlbumItem(albumItem_all_name).addImageItem(0, photo); + + albumModel.album.addAlbumItem(albumName, folderPath, photo.path, photo.uri); + albumModel.album.getAlbumItem(albumName).addImageItem(0, photo); + + albumItemList.clear(); + albumItemList.addAll(albumModel.getAlbumItems()); + if (Setting.hasAlbumItemsAd()) { + int albumItemsAdIndex = 2; + if (albumItemList.size() < albumItemsAdIndex + 1) { + albumItemsAdIndex = albumItemList.size() - 1; + } + albumItemList.add(albumItemsAdIndex, Setting.albumItemsAdView); + } + albumItemsAdapter.notifyDataSetChanged(); + + if (Setting.count == 1) { + Result.clear(); + int res = Result.addPhoto(photo); + onSelectorOutOfMax(res); + } else { + if (Result.count() >= Setting.count) { + onSelectorOutOfMax(null); + } else { + int res = Result.addPhoto(photo); + onSelectorOutOfMax(res); + } + } + rvAlbumItems.scrollToPosition(0); + albumItemsAdapter.setSelectedPosition(0); + shouldShowMenuDone(); + } + + private Photo getPhoto(Uri uri) { + Photo p = null; + String path; + String name; + long dateTime; + String type; + long size; + int width = 0; + int height = 0; + int orientation = 0; + String[] projections = AlbumModel.getInstance().getProjections(); + boolean shouldReadWidth = projections.length > 8; + Cursor cursor = getContentResolver().query(uri, projections, null, null, null); + if (cursor == null) { + return null; + } + int albumNameCol = cursor.getColumnIndex(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME); + + if (cursor.moveToFirst()) { + path = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DATA)); + name = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)); + dateTime = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED)); + type = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)); + size = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.SIZE)); + if (shouldReadWidth) { + width = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns.WIDTH)); + height = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns.HEIGHT)); + orientation = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns.ORIENTATION)); + if (90 == orientation || 270 == orientation) { + int temp = width; + width = height; + height = temp; + } + } + if (albumNameCol > 0) { + albumName = cursor.getString(albumNameCol); + folderPath = albumName; + } + p = new Photo(name, uri, path, dateTime, width, height, orientation, size, 0, type); + } + cursor.close(); + + return p; + } + + private void onCameraResultForQ() { + loadingDialog.show(); + new Thread(new Runnable() { + @Override + public void run() { + final Photo photo = getPhoto(photoUri); + if (photo == null) { + Log.e("easyPhotos", "onCameraResultForQ() -》photo = null"); + return; + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + if (EasyPhotosActivity.this.isDestroyed()) { // or call isFinishing() if min sdk version < 17 + return; + } + dismissProgressDialog(); + if (Setting.onlyStartCamera || albumModel.getAlbumItems().isEmpty()) { + Intent data = new Intent(); + photo.selectedOriginal = Setting.selectedOriginal; + resultList.add(photo); + + data.putParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS, resultList); + data.putExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, Setting.selectedOriginal); + setResult(RESULT_OK, data); + finish(); + return; + } + + addNewPhoto(photo); + } + }); + + } + }).start(); + } + + private void onCameraResult() { + LoadingDialog loading = LoadingDialog.get(this); + new Thread(new Runnable() { + @Override + public void run() { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HH_mm_ss", + Locale.getDefault()); + String imageName = "IMG_%s.jpg"; + String filename = String.format(imageName, dateFormat.format(new Date())); + File reNameFile = new File(mTempImageFile.getParentFile(), filename); + if (!reNameFile.exists()) { + if (mTempImageFile.renameTo(reNameFile)) { + mTempImageFile = reNameFile; + } + } + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(mTempImageFile.getAbsolutePath(), options); + MediaScannerConnectionUtils.refresh(EasyPhotosActivity.this, mTempImageFile);//更新媒体库 + + Uri uri = UriUtils.getUri(EasyPhotosActivity.this, mTempImageFile); + int width = 0; + int height = 0; + int orientation = 0; + if (Setting.useWidth) { + width = options.outWidth; + height = options.outHeight; + + ExifInterface exif = null; + try { + exif = new ExifInterface(mTempImageFile); + } catch (IOException e) { + e.printStackTrace(); + } + if (null != exif) { + orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1); + if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) { + width = options.outHeight; + height = options.outWidth; + } + } + } + + final Photo photo = new Photo(mTempImageFile.getName(), uri, + mTempImageFile.getAbsolutePath(), + mTempImageFile.lastModified() / 1000, width, height, orientation, + mTempImageFile.length(), + DurationUtils.getDuration(mTempImageFile.getAbsolutePath()), + options.outMimeType); + + runOnUiThread(new Runnable() { + @Override + public void run() { + if (Setting.onlyStartCamera || albumModel.getAlbumItems().isEmpty()) { + Intent data = new Intent(); + + photo.selectedOriginal = Setting.selectedOriginal; + resultList.add(photo); + + data.putParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS, resultList); + + data.putExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, + Setting.selectedOriginal); + + setResult(RESULT_OK, data); + finish(); + return; + } + + addNewPhoto(photo); + } + }); + } + }).start(); + + } + + + private void onAlbumWorkedDo() { + initView(); + } + + private void initView() { + + if (albumModel.getAlbumItems().isEmpty()) { + if (Setting.isOnlyVideo()) { + Toast.makeText(getApplicationContext(), R.string.no_videos_easy_photos, Toast.LENGTH_LONG).show(); + finish(); + return; + } + Toast.makeText(getApplicationContext(), R.string.no_photos_easy_photos, Toast.LENGTH_LONG).show(); + if (Setting.isShowCamera) { + launchCamera(Code.REQUEST_CAMERA); + } else { + finish(); + } + return; + } + + EasyPhotos.setAdListener(this); + if (Setting.hasPhotosAd()) { + findViewById(R.id.m_tool_bar_bottom_line).setVisibility(View.GONE); + } + ivCamera = findViewById(R.id.fab_camera); + if (Setting.isShowCamera && Setting.isBottomRightCamera()) { + ivCamera.setVisibility(View.VISIBLE); + } + if (!Setting.showPuzzleMenu) { + findViewById(R.id.tv_puzzle).setVisibility(View.GONE); + } + mSecondMenus = findViewById(R.id.m_second_level_menu); + int columns = getResources().getInteger(R.integer.photos_columns_easy_photos); + tvAlbumItems = findViewById(R.id.tv_album_items); + tvAlbumItems.setText(albumModel.getAlbumItems().get(0).name); + tvDone = findViewById(R.id.tv_done); + rvPhotos = findViewById(R.id.rv_photos); + ((SimpleItemAnimator) rvPhotos.getItemAnimator()).setSupportsChangeAnimations(false); + //去除item更新的闪光 + photoList.clear(); + photoList.addAll(albumModel.getCurrAlbumItemPhotos(0)); + int index = 0; + if (Setting.hasPhotosAd()) { + photoList.add(index, Setting.photosAdView); + } + if (Setting.isShowCamera && !Setting.isBottomRightCamera()) { + if (Setting.hasPhotosAd()) index = 1; + photoList.add(index, null); + } + photosAdapter = new PhotosAdapter(this, photoList, this); + + gridLayoutManager = new GridLayoutManager(this, columns); + if (Setting.hasPhotosAd()) { + gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + if (position == 0) { + return gridLayoutManager.getSpanCount();//独占一行 + } else { + return 1;//只占一行中的一列 + } + } + }); + } + rvPhotos.setLayoutManager(gridLayoutManager); + rvPhotos.setAdapter(photosAdapter); + tvOriginal = findViewById(R.id.tv_original); + if (Setting.showOriginalMenu) { + processOriginalMenu(); + } else { + tvOriginal.setVisibility(View.GONE); + } + tvPreview = findViewById(R.id.tv_preview); + + initAlbumItems(); + shouldShowMenuDone(); + setClick(R.id.iv_album_items, R.id.tv_clear, R.id.iv_second_menu, R.id.tv_puzzle); + setClick(tvAlbumItems, rootViewAlbumItems, tvDone, tvOriginal, tvPreview, ivCamera); + + } + + private void hideActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + } + + private void initAlbumItems() { + + rvAlbumItems = findViewById(R.id.rv_album_items); + albumItemList.clear(); + albumItemList.addAll(albumModel.getAlbumItems()); + + if (Setting.hasAlbumItemsAd()) { + int albumItemsAdIndex = 2; + if (albumItemList.size() < albumItemsAdIndex + 1) { + albumItemsAdIndex = albumItemList.size() - 1; + } + albumItemList.add(albumItemsAdIndex, Setting.albumItemsAdView); + } + albumItemsAdapter = new AlbumItemsAdapter(this, albumItemList, 0, this); + rvAlbumItems.setLayoutManager(new LinearLayoutManager(this)); + rvAlbumItems.setAdapter(albumItemsAdapter); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (R.id.tv_album_items == id || R.id.iv_album_items == id) { + showAlbumItems(View.GONE == rootViewAlbumItems.getVisibility()); + } else if (R.id.root_view_album_items == id) { + showAlbumItems(false); + } else if (R.id.iv_back == id) { + onBackPressed(); + } else if (R.id.tv_done == id) { + done(); + } else if (R.id.tv_clear == id) { + if (Result.isEmpty()) { + processSecondMenu(); + return; + } + Result.removeAll(); + photosAdapter.change(); + shouldShowMenuDone(); + processSecondMenu(); + } else if (R.id.tv_original == id) { + if (!Setting.originalMenuUsable) { + Toast.makeText(getApplicationContext(), Setting.originalMenuUnusableHint, Toast.LENGTH_SHORT).show(); + return; + } + Setting.selectedOriginal = !Setting.selectedOriginal; + processOriginalMenu(); + processSecondMenu(); + } else if (R.id.tv_preview == id) { + PreviewActivity.start(EasyPhotosActivity.this, -1, 0); + } else if (R.id.fab_camera == id) { + launchCamera(Code.REQUEST_CAMERA); + } else if (R.id.iv_second_menu == id) { + processSecondMenu(); + } else if (R.id.tv_puzzle == id) { + processSecondMenu(); + PuzzleSelectorActivity.start(this); + } + } + + public void processSecondMenu() { + if (mSecondMenus == null) { + return; + } + if (View.VISIBLE == mSecondMenus.getVisibility()) { + mSecondMenus.setVisibility(View.INVISIBLE); + if (Setting.isShowCamera && Setting.isBottomRightCamera()) { + ivCamera.setVisibility(View.VISIBLE); + } + } else { + mSecondMenus.setVisibility(View.VISIBLE); + if (Setting.isShowCamera && Setting.isBottomRightCamera()) { + ivCamera.setVisibility(View.INVISIBLE); + } + } + } + + private boolean clickDone = false; + + private void done() { + if (clickDone) return; + clickDone = true; + resultFast(); + } + + private void resultFast() { + Intent intent = new Intent(); + Result.processOriginal(); + resultList.addAll(Result.photos); + intent.putParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS, resultList); + intent.putExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, + Setting.selectedOriginal); + setResult(RESULT_OK, intent); + finish(); + } + + private void processOriginalMenu() { + if (!Setting.showOriginalMenu) return; + if (Setting.selectedOriginal) { + tvOriginal.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_accent)); + } else { + if (Setting.originalMenuUsable) { + tvOriginal.setTextColor(ContextCompat.getColor(this, + R.color.easy_photos_fg_primary)); + } else { + tvOriginal.setTextColor(ContextCompat.getColor(this, + R.color.easy_photos_fg_primary_dark)); + } + } + } + + private void showAlbumItems(boolean isShow) { + if (null == setShow) { + newAnimators(); + } + if (isShow) { + rootViewAlbumItems.setVisibility(View.VISIBLE); + setShow.start(); + } else { + setHide.start(); + } + + } + + private void newAnimators() { + newHideAnim(); + newShowAnim(); + } + + private void newShowAnim() { + ObjectAnimator translationShow = ObjectAnimator.ofFloat(rvAlbumItems, "translationY", + mBottomBar.getTop(), 0); + ObjectAnimator alphaShow = ObjectAnimator.ofFloat(rootViewAlbumItems, "alpha", 0.0f, 1.0f); + translationShow.setDuration(300); + setShow = new AnimatorSet(); + setShow.setInterpolator(new AccelerateDecelerateInterpolator()); + setShow.play(translationShow).with(alphaShow); + } + + private void newHideAnim() { + ObjectAnimator translationHide = ObjectAnimator.ofFloat(rvAlbumItems, "translationY", 0, + mBottomBar.getTop()); + ObjectAnimator alphaHide = ObjectAnimator.ofFloat(rootViewAlbumItems, "alpha", 1.0f, 0.0f); + translationHide.setDuration(200); + setHide = new AnimatorSet(); + setHide.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + rootViewAlbumItems.setVisibility(View.GONE); + } + }); + setHide.setInterpolator(new AccelerateInterpolator()); + setHide.play(translationHide).with(alphaHide); + } + + @Override + public void onAlbumItemClick(int position, int realPosition) { + updatePhotos(realPosition); + showAlbumItems(false); + tvAlbumItems.setText(albumModel.getAlbumItems().get(realPosition).name); + } + + private void updatePhotos(int currAlbumItemIndex) { + this.currAlbumItemIndex = currAlbumItemIndex; + photoList.clear(); + photoList.addAll(albumModel.getCurrAlbumItemPhotos(currAlbumItemIndex)); + int index = 0; + if (Setting.hasPhotosAd()) { + photoList.add(index, Setting.photosAdView); + } + if (Setting.isShowCamera && !Setting.isBottomRightCamera()) { + if (Setting.hasPhotosAd()) index = 1; + photoList.add(index, null); + } + photosAdapter.change(); + rvPhotos.scrollToPosition(0); + } + + private void shouldShowMenuDone() { + if (Result.isEmpty()) { + if (View.VISIBLE == tvDone.getVisibility()) { + ScaleAnimation scaleHide = new ScaleAnimation(1f, 0f, 1f, 0f); + scaleHide.setDuration(200); + tvDone.startAnimation(scaleHide); + } + tvDone.setVisibility(View.INVISIBLE); + tvPreview.setVisibility(View.INVISIBLE); + } else { + if (View.INVISIBLE == tvDone.getVisibility()) { + ScaleAnimation scaleShow = new ScaleAnimation(0f, 1f, 0f, 1f); + scaleShow.setDuration(200); + tvDone.startAnimation(scaleShow); + } + tvDone.setVisibility(View.VISIBLE); + tvPreview.setVisibility(View.VISIBLE); + } + tvDone.setText(getString(R.string.selector_action_done_easy_photos, Result.count(), + Setting.count)); + } + + @Override + public void onCameraClick() { + launchCamera(Code.REQUEST_CAMERA); + } + + @Override + public void onPhotoClick(int position, int realPosition) { + PreviewActivity.start(EasyPhotosActivity.this, currAlbumItemIndex, realPosition); + } + + @Override + public void onSelectorOutOfMax(@Nullable Integer result) { + if (result == null) { + if (Setting.isOnlyVideo()) { + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_video_hint_easy_photos + , Setting.count), Toast.LENGTH_SHORT).show(); + + } else if (Setting.showVideo) { + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_hint_easy_photos), Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_image_hint_easy_photos, + Setting.count), Toast.LENGTH_SHORT).show(); + } + return; + } + switch (result) { + case Result.PICTURE_OUT: + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_image_hint_easy_photos + , Setting.complexPictureCount), Toast.LENGTH_SHORT).show(); + break; + case Result.VIDEO_OUT: + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_video_hint_easy_photos + , Setting.complexVideoCount), Toast.LENGTH_SHORT).show(); + break; + case Result.SINGLE_TYPE: + Toast.makeText(getApplicationContext(), getString(R.string.selector_single_type_hint_easy_photos), + Toast.LENGTH_SHORT).show(); + break; + + } + } + + @Override + public void onSelectorChanged() { + shouldShowMenuDone(); + } + + + @Override + public void onBackPressed() { + + if (null != rootViewAlbumItems && rootViewAlbumItems.getVisibility() == View.VISIBLE) { + showAlbumItems(false); + return; + } + + if (null != mSecondMenus && View.VISIBLE == mSecondMenus.getVisibility()) { + processSecondMenu(); + return; + } + if (albumModel != null) albumModel.stopQuery(); + if (Setting.hasPhotosAd()) { + photosAdapter.clearAd(); + } + if (Setting.hasAlbumItemsAd()) { + albumItemsAdapter.clearAd(); + } + setResult(RESULT_CANCELED); + finish(); + } + + private void dismissProgressDialog() { + if (loadingDialog != null && loadingDialog.isShowing()) { + loadingDialog.dismiss(); + } + } + + @Override + protected void onDestroy() { + dismissProgressDialog(); + if (albumModel != null) albumModel.stopQuery(); + super.onDestroy(); + } + + @Override + public void onPhotosAdLoaded() { + runOnUiThread(new Runnable() { + @Override + public void run() { + photosAdapter.change(); + } + }); + } + + @Override + public void onAlbumItemsAdLoaded() { + runOnUiThread(new Runnable() { + @Override + public void run() { + albumItemsAdapter.notifyDataSetChanged(); + } + }); + } + + + private void setClick(@IdRes int... ids) { + for (int id : ids) { + findViewById(id).setOnClickListener(this); + } + } + + private void setClick(View... views) { + for (View v : views) { + v.setOnClickListener(this); + } + } + + /** + * 返回true 表示可以使用 返回false表示不可以使用 + */ + public boolean cameraIsCanUse() { + boolean isCanUse = true; + Camera mCamera = null; + try { + mCamera = Camera.open(); + Camera.Parameters mParameters = mCamera.getParameters(); //针对魅族手机 + mCamera.setParameters(mParameters); + } catch (Exception e) { + isCanUse = false; + } + + if (mCamera != null) { + try { + mCamera.release(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return isCanUse; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewActivity.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewActivity.java new file mode 100644 index 000000000..680d3f925 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewActivity.java @@ -0,0 +1,487 @@ +package com.huantansheng.easyphotos.ui; + +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.text.TextUtils; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.ScaleAnimation; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.PagerSnapHelper; +import androidx.recyclerview.widget.RecyclerView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Code; +import com.huantansheng.easyphotos.constant.Key; +import com.huantansheng.easyphotos.models.album.AlbumModel; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.adapter.PreviewPhotosAdapter; +import com.huantansheng.easyphotos.ui.widget.PressedTextView; +import com.huantansheng.easyphotos.utils.color.ColorUtils; +import com.huantansheng.easyphotos.utils.system.SystemUtils; + +import java.util.ArrayList; + +/** + * 预览页 + */ +public class PreviewActivity extends AppCompatActivity implements PreviewPhotosAdapter.OnClickListener, View.OnClickListener, PreviewFragment.OnPreviewFragmentClickListener { + + public static void start(Activity act, int albumItemIndex, int currIndex) { + Intent intent = new Intent(act, PreviewActivity.class); + intent.putExtra(Key.PREVIEW_ALBUM_ITEM_INDEX, albumItemIndex); + intent.putExtra(Key.PREVIEW_PHOTO_INDEX, currIndex); + act.startActivityForResult(intent, Code.REQUEST_PREVIEW_ACTIVITY); + } + + + /** + * 一些旧设备在UI小部件更新之间需要一个小延迟 + * and a change of the status and navigation bar. + */ + private static final int UI_ANIMATION_DELAY = 300; + private final Handler mHideHandler = new Handler(); + private final Runnable mHidePart2Runnable = new Runnable() { + @Override + public void run() { + SystemUtils.getInstance().systemUiHide(PreviewActivity.this, decorView); + } + }; + private RelativeLayout mBottomBar; + private FrameLayout mToolBar; + private final Runnable mShowPart2Runnable = new Runnable() { + @Override + public void run() { + // 延迟显示UI元素 + mBottomBar.setVisibility(View.VISIBLE); + mToolBar.setVisibility(View.VISIBLE); + } + }; + private boolean mVisible; + View decorView; + private TextView tvOriginal, tvNumber; + private PressedTextView tvDone; + private ImageView ivSelector; + private RecyclerView rvPhotos; + private PreviewPhotosAdapter adapter; + private PagerSnapHelper snapHelper; + private LinearLayoutManager lm; + private int index; + private ArrayList photos = new ArrayList<>(); + private int resultCode = RESULT_CANCELED; + private int lastPosition = 0;//记录recyclerView最后一次角标位置,用于判断是否转换了item + private boolean isSingle = Setting.count == 1; + private boolean unable = Result.count() == Setting.count; + + private FrameLayout flFragment; + private PreviewFragment previewFragment; + private int statusColor; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + decorView = getWindow().getDecorView(); + SystemUtils.getInstance().systemUiInit(this, decorView); + + setContentView(R.layout.activity_preview_easy_photos); + + hideActionBar(); + adaptationStatusBar(); + if (null == AlbumModel.instance) { + finish(); + return; + } + initData(); + initView(); + } + + private void adaptationStatusBar() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + statusColor = ContextCompat.getColor(this, R.color.easy_photos_preview_status_bar); + if (ColorUtils.isWhiteColor(statusColor)) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + } + } + + private void hideActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + } + + + private void initData() { + Intent intent = getIntent(); + int albumItemIndex = intent.getIntExtra(Key.PREVIEW_ALBUM_ITEM_INDEX, 0); + photos.clear(); + + if (albumItemIndex == -1) { + photos.addAll(Result.photos); + } else { + photos.addAll(AlbumModel.instance.getCurrAlbumItemPhotos(albumItemIndex)); + } + index = intent.getIntExtra(Key.PREVIEW_PHOTO_INDEX, 0); + + lastPosition = index; + mVisible = true; + } + + private void toggle() { + if (mVisible) { + hide(); + } else { + show(); + } + } + + private void hide() { + // Hide UI first + AlphaAnimation hideAnimation = new AlphaAnimation(1.0f, 0.0f); + hideAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + mBottomBar.setVisibility(View.GONE); + mToolBar.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }); + hideAnimation.setDuration(UI_ANIMATION_DELAY); + mBottomBar.startAnimation(hideAnimation); + mToolBar.startAnimation(hideAnimation); + mVisible = false; + + // Schedule a runnable to remove the status and navigation bar after a delay + mHideHandler.removeCallbacks(mShowPart2Runnable); + + mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY); + + } + + + private void show() { + // Show the system bar + SystemUtils.getInstance().systemUiShow(this, decorView); + + mVisible = true; + + // Schedule a runnable to display UI elements after a delay + mHideHandler.removeCallbacks(mHidePart2Runnable); + mHideHandler.post(mShowPart2Runnable); + } + + @Override + public void onPhotoClick() { + toggle(); + } + + @Override + public void onPhotoScaleChanged() { + if (mVisible) hide(); + } + + @Override + public void onBackPressed() { + doBack(); + } + + private void doBack() { + Intent intent = new Intent(); + intent.putExtra(Key.PREVIEW_CLICK_DONE, false); + setResult(resultCode, intent); + finish(); + } + + private void initView() { + setClick(R.id.iv_back, R.id.tv_edit, R.id.tv_selector); + + mToolBar = (FrameLayout) findViewById(R.id.m_top_bar_layout); + if (!SystemUtils.getInstance().hasNavigationBar(this)) { + FrameLayout mRootView = (FrameLayout) findViewById(R.id.m_root_view); + mRootView.setFitsSystemWindows(true); + mToolBar.setPadding(0, SystemUtils.getInstance().getStatusBarHeight(this), 0, 0); + if (ColorUtils.isWhiteColor(statusColor)) { + SystemUtils.getInstance().setStatusDark(this, true); + } + } + mBottomBar = (RelativeLayout) findViewById(R.id.m_bottom_bar); + ivSelector = (ImageView) findViewById(R.id.iv_selector); + tvNumber = (TextView) findViewById(R.id.tv_number); + tvDone = (PressedTextView) findViewById(R.id.tv_done); + tvOriginal = (TextView) findViewById(R.id.tv_original); + flFragment = (FrameLayout) findViewById(R.id.fl_fragment); + previewFragment = (PreviewFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_preview); + if (Setting.showOriginalMenu) { + processOriginalMenu(); + } else { + tvOriginal.setVisibility(View.GONE); + } + + setClick(tvOriginal, tvDone, ivSelector); + + initRecyclerView(); + shouldShowMenuDone(); + } + + private void initRecyclerView() { + rvPhotos = (RecyclerView) findViewById(R.id.rv_photos); + adapter = new PreviewPhotosAdapter(this, photos, this); + lm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); + rvPhotos.setLayoutManager(lm); + rvPhotos.setAdapter(adapter); + rvPhotos.scrollToPosition(index); + toggleSelector(); + snapHelper = new PagerSnapHelper(); + snapHelper.attachToRecyclerView(rvPhotos); + rvPhotos.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + + View view = snapHelper.findSnapView(lm); + if (view == null) { + return; + } + int position = lm.getPosition(view); + if (lastPosition == position) { + return; + } + lastPosition = position; + previewFragment.setSelectedPosition(-1); + tvNumber.setText(getString(R.string.preview_current_number_easy_photos, + lastPosition + 1, photos.size())); + toggleSelector(); + } + }); + tvNumber.setText(getString(R.string.preview_current_number_easy_photos, index + 1, + photos.size())); + } + + private boolean clickDone = false; + + @Override + public void onClick(View v) { + int id = v.getId(); + if (R.id.iv_back == id) { + doBack(); + } else if (R.id.tv_selector == id) { + updateSelector(); + } else if (R.id.iv_selector == id) { + updateSelector(); + } else if (R.id.tv_original == id) { + if (!Setting.originalMenuUsable) { + Toast.makeText(getApplicationContext(), Setting.originalMenuUnusableHint, Toast.LENGTH_SHORT).show(); + return; + } + Setting.selectedOriginal = !Setting.selectedOriginal; + processOriginalMenu(); + } else if (R.id.tv_done == id) { + if (clickDone) return; + clickDone = true; + Intent intent = new Intent(); + intent.putExtra(Key.PREVIEW_CLICK_DONE, true); + setResult(RESULT_OK, intent); + finish(); + } + } + + private void processOriginalMenu() { + if (Setting.selectedOriginal) { + tvOriginal.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_accent)); + } else { + if (Setting.originalMenuUsable) { + tvOriginal.setTextColor(ContextCompat.getColor(this, + R.color.easy_photos_preview_fg_primary)); + } else { + tvOriginal.setTextColor(ContextCompat.getColor(this, + R.color.easy_photos_preview_fg_primary_dark)); + } + } + } + + private void toggleSelector() { + if (photos.isEmpty() || lastPosition >= photos.size()) { + return; + } + if (photos.get(lastPosition).selected) { + ivSelector.setImageResource(R.drawable.ic_selector_true_easy_photos); + if (!Result.isEmpty()) { + int count = Result.count(); + for (int i = 0; i < count; i++) { + if (photos.get(lastPosition).path.equals(Result.getPhotoPath(i))) { + previewFragment.setSelectedPosition(i); + break; + } + } + } + } else { + ivSelector.setImageResource(R.drawable.ic_selector_easy_photos); + } + previewFragment.notifyDataSetChanged(); + shouldShowMenuDone(); + } + + private void updateSelector() { + if (photos.isEmpty() || lastPosition >= photos.size()) { + return; + } + resultCode = RESULT_OK; + Photo item = photos.get(lastPosition); + if (isSingle) { + singleSelector(item); + return; + } + if (unable) { + if (item.selected) { + Result.removePhoto(item); + if (unable) { + unable = false; + } + toggleSelector(); + return; + } + if (Setting.isOnlyVideo()) { + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_video_hint_easy_photos + , Setting.count), Toast.LENGTH_SHORT).show(); + } else if (Setting.showVideo) { + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_hint_easy_photos), Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_image_hint_easy_photos, + Setting.count), Toast.LENGTH_SHORT).show(); + } + return; + } + item.selected = !item.selected; + if (item.selected) { + int res = Result.addPhoto(item); + if (res != 0) { + item.selected = false; + switch (res) { + case Result.PICTURE_OUT: + Toast.makeText(getApplicationContext(), + getString(R.string.selector_reach_max_image_hint_easy_photos, + Setting.complexPictureCount), Toast.LENGTH_SHORT).show(); + break; + case Result.VIDEO_OUT: + Toast.makeText(getApplicationContext(), + getString(R.string.selector_reach_max_video_hint_easy_photos, + Setting.complexVideoCount), Toast.LENGTH_SHORT).show(); + break; + case Result.SINGLE_TYPE: + Toast.makeText(getApplicationContext(), getString(R.string.selector_single_type_hint_easy_photos), Toast.LENGTH_SHORT).show(); + break; + } + return; + } + if (Result.count() == Setting.count) { + unable = true; + } + } else { + Result.removePhoto(item); + previewFragment.setSelectedPosition(-1); + if (unable) { + unable = false; + } + } + toggleSelector(); + } + + private void singleSelector(Photo photo) { + if (!Result.isEmpty()) { + if (Result.getPhotoPath(0).equals(photo.path)) { + Result.removePhoto(photo); + } else { + Result.removePhoto(0); + Result.addPhoto(photo); + } + } else { + Result.addPhoto(photo); + } + toggleSelector(); + } + + private void shouldShowMenuDone() { + if (Result.isEmpty()) { + if (View.VISIBLE == tvDone.getVisibility()) { + ScaleAnimation scaleHide = new ScaleAnimation(1f, 0f, 1f, 0f); + scaleHide.setDuration(200); + tvDone.startAnimation(scaleHide); + } + tvDone.setVisibility(View.GONE); + flFragment.setVisibility(View.GONE); + } else { + if (View.GONE == tvDone.getVisibility()) { + ScaleAnimation scaleShow = new ScaleAnimation(0f, 1f, 0f, 1f); + scaleShow.setDuration(200); + tvDone.startAnimation(scaleShow); + } + flFragment.setVisibility(View.VISIBLE); + tvDone.setVisibility(View.VISIBLE); + tvDone.setText(getString(R.string.selector_action_done_easy_photos, Result.count(), + Setting.count)); + } + } + + @Override + public void onPreviewPhotoClick(int position) { + if (Result.isEmpty()) { + return; + } + if (photos.isEmpty()) { + return; + } + int size = photos.size(); + String path = Result.getPhotoPath(position); + for (int i = 0; i < size; i++) { + if (TextUtils.equals(path, photos.get(i).path)) { + rvPhotos.scrollToPosition(i); + lastPosition = i; + tvNumber.setText(getString(R.string.preview_current_number_easy_photos, + lastPosition + 1, photos.size())); + previewFragment.setSelectedPosition(position); + toggleSelector(); + return; + } + } + } + + private void setClick(@IdRes int... ids) { + for (int id : ids) { + findViewById(id).setOnClickListener(this); + } + } + + private void setClick(View... views) { + for (View v : views) { + v.setOnClickListener(this); + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewFragment.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewFragment.java new file mode 100644 index 000000000..fbdc293e6 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewFragment.java @@ -0,0 +1,74 @@ +package com.huantansheng.easyphotos.ui; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.ui.adapter.PreviewPhotosFragmentAdapter; + + +public class PreviewFragment extends Fragment implements PreviewPhotosFragmentAdapter.OnClickListener { + + private OnPreviewFragmentClickListener mListener; + + private RecyclerView rvPhotos; + private PreviewPhotosFragmentAdapter adapter; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_preview_easy_photos, container, false); + rvPhotos = (RecyclerView) rootView.findViewById(R.id.rv_preview_selected_photos); + adapter = new PreviewPhotosFragmentAdapter(getActivity(), this); + rvPhotos.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false)); + rvPhotos.setAdapter(adapter); + return rootView; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnPreviewFragmentClickListener) { + mListener = (OnPreviewFragmentClickListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnPreviewFragmentClickListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + + @Override + public void onPhotoClick(int position) { + mListener.onPreviewPhotoClick(position); + } + + + public interface OnPreviewFragmentClickListener { + void onPreviewPhotoClick(int position); + } + + public void notifyDataSetChanged() { + adapter.notifyDataSetChanged(); + } + + public void setSelectedPosition(int position) { + adapter.setChecked(position); + if (position != -1) { + rvPhotos.smoothScrollToPosition(position); + } + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleActivity.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleActivity.java new file mode 100644 index 000000000..76c8aa8e1 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleActivity.java @@ -0,0 +1,665 @@ +package com.huantansheng.easyphotos.ui; + +import android.Manifest; +import android.app.Activity; +import android.app.Fragment; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Code; +import com.huantansheng.easyphotos.constant.Key; +import com.huantansheng.easyphotos.engine.ImageEngine; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.models.puzzle.Area; +import com.huantansheng.easyphotos.models.puzzle.DegreeSeekBar; +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; +import com.huantansheng.easyphotos.models.puzzle.PuzzlePiece; +import com.huantansheng.easyphotos.models.puzzle.PuzzleUtils; +import com.huantansheng.easyphotos.models.puzzle.PuzzleView; +import com.huantansheng.easyphotos.models.sticker.StickerModel; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.adapter.PuzzleAdapter; +import com.huantansheng.easyphotos.ui.adapter.TextStickerAdapter; +import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack; +import com.huantansheng.easyphotos.utils.media.DurationUtils; +import com.huantansheng.easyphotos.utils.permission.PermissionUtil; +import com.huantansheng.easyphotos.utils.settings.SettingsUtils; +import com.huantansheng.easyphotos.utils.uri.UriUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Locale; + +/** + * 拼图界面 + * Created by huan on 2017/12/4. + */ + +public class PuzzleActivity extends AppCompatActivity implements View.OnClickListener, + PuzzleAdapter.OnItemClickListener, TextStickerAdapter.OnItemClickListener { + + private static WeakReference> toClass; + + + public static void startWithPhotos(Activity act, ArrayList photos, + String puzzleSaveDirPath, String puzzleSaveNamePrefix, + int requestCode, boolean replaceCustom, + @NonNull ImageEngine imageEngine) { + if (null != toClass) { + toClass.clear(); + toClass = null; + } + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + Intent intent = new Intent(act, PuzzleActivity.class); + intent.putExtra(Key.PUZZLE_FILE_IS_PHOTO, true); + intent.putParcelableArrayListExtra(Key.PUZZLE_FILES, photos); + intent.putExtra(Key.PUZZLE_SAVE_DIR, puzzleSaveDirPath); + intent.putExtra(Key.PUZZLE_SAVE_NAME_PREFIX, puzzleSaveNamePrefix); + if (replaceCustom) { + toClass = new WeakReference>(act.getClass()); + } + act.startActivityForResult(intent, requestCode); + } + + public static void startWithPhotos(Fragment fragment, ArrayList photos, + String puzzleSaveDirPath, String puzzleSaveNamePrefix, + int requestCode, boolean replaceCustom, + @NonNull ImageEngine imageEngine) { + if (null != toClass) { + toClass.clear(); + toClass = null; + } + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + Intent intent = new Intent(fragment.getActivity(), PuzzleActivity.class); + intent.putExtra(Key.PUZZLE_FILE_IS_PHOTO, true); + intent.putParcelableArrayListExtra(Key.PUZZLE_FILES, photos); + intent.putExtra(Key.PUZZLE_SAVE_DIR, puzzleSaveDirPath); + intent.putExtra(Key.PUZZLE_SAVE_NAME_PREFIX, puzzleSaveNamePrefix); + if (replaceCustom) { + toClass = + new WeakReference>(fragment.getActivity().getClass()); + } + fragment.startActivityForResult(intent, requestCode); + } + + public static void startWithPhotos(androidx.fragment.app.Fragment fragmentV, + ArrayList photos, String puzzleSaveDirPath, + String puzzleSaveNamePrefix, int requestCode, + boolean replaceCustom, @NonNull ImageEngine imageEngine) { + if (null != toClass) { + toClass.clear(); + toClass = null; + } + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + Intent intent = new Intent(fragmentV.getActivity(), PuzzleActivity.class); + intent.putExtra(Key.PUZZLE_FILE_IS_PHOTO, true); + intent.putParcelableArrayListExtra(Key.PUZZLE_FILES, photos); + intent.putExtra(Key.PUZZLE_SAVE_DIR, puzzleSaveDirPath); + intent.putExtra(Key.PUZZLE_SAVE_NAME_PREFIX, puzzleSaveNamePrefix); + if (replaceCustom) { + if (fragmentV.getActivity() != null) { + toClass = + new WeakReference>(fragmentV.getActivity().getClass()); + } + } + fragmentV.startActivityForResult(intent, requestCode); + } + + ArrayList photos = null; + + ArrayList bitmaps = new ArrayList<>(); + String saveDirPath, saveNamePrefix; + + private PuzzleView puzzleView; + private RecyclerView rvPuzzleTemplet; + private PuzzleAdapter puzzleAdapter; + private ProgressBar progressBar; + private int fileCount = 0; + + private LinearLayout llMenu; + private DegreeSeekBar degreeSeekBar; + private ArrayList ivMenus = new ArrayList<>(); + + private ArrayList degrees = new ArrayList<>(); + private int degreeIndex = -1; + private int controlFlag; + private static final int FLAG_CONTROL_PADDING = 0; + private static final int FLAG_CONTROL_CORNER = 1; + private static final int FLAG_CONTROL_ROTATE = 2; + + private int deviceWidth = 0; + private int deviceHeight = 0; + + private TextView tvTemplate, tvTextSticker; + private RelativeLayout mRootView, mBottomLayout; + private TextStickerAdapter textStickerAdapter; + + private StickerModel stickerModel; + + FloatingActionButton fab; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams attrs = getWindow().getAttributes(); + attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + getWindow().setAttributes(attrs); + setContentView(R.layout.activity_puzzle_easy_photos); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + if (null == Setting.imageEngine) { + finish(); + return; + } + initData(); + initView(); + } + + private void initView() { + initIvMenu(); + initPuzzleView(); + initRecyclerView(); + progressBar = findViewById(R.id.progress); + setClick(R.id.tv_back, R.id.tv_done); + } + + private void initIvMenu() { + fab = (FloatingActionButton) findViewById(R.id.fab); + + tvTemplate = (TextView) findViewById(R.id.tv_template); + tvTextSticker = (TextView) findViewById(R.id.tv_text_sticker); + mRootView = (RelativeLayout) findViewById(R.id.m_root_view); + mBottomLayout = (RelativeLayout) findViewById(R.id.m_bottom_layout); + + llMenu = (LinearLayout) findViewById(R.id.ll_menu); + ImageView ivRotate = (ImageView) findViewById(R.id.iv_rotate); + ImageView ivCorner = (ImageView) findViewById(R.id.iv_corner); + ImageView ivPadding = (ImageView) findViewById(R.id.iv_padding); + + setClick(R.id.iv_replace, R.id.iv_mirror, R.id.iv_flip); + setClick(ivRotate, ivCorner, ivPadding, fab, tvTextSticker, tvTemplate); + + ivMenus.add(ivRotate); + ivMenus.add(ivCorner); + ivMenus.add(ivPadding); + + degreeSeekBar = (DegreeSeekBar) findViewById(R.id.degree_seek_bar); + degreeSeekBar.setScrollingListener(new DegreeSeekBar.ScrollingListener() { + @Override + public void onScrollStart() { + } + + @Override + public void onScroll(int currentDegrees) { + switch (controlFlag) { + case FLAG_CONTROL_PADDING: + puzzleView.setPiecePadding(currentDegrees); + break; + case FLAG_CONTROL_CORNER: + if (currentDegrees < 0) { + currentDegrees = 0; + } + puzzleView.setPieceRadian(currentDegrees); + break; + case FLAG_CONTROL_ROTATE: + puzzleView.rotate(currentDegrees - degrees.get(degreeIndex)); + degrees.remove(degreeIndex); + degrees.add(degreeIndex, currentDegrees); + break; + } + } + + @Override + public void onScrollEnd() { + } + }); + } + + private void initRecyclerView() { + rvPuzzleTemplet = (RecyclerView) findViewById(R.id.rv_puzzle_template); + puzzleAdapter = new PuzzleAdapter(); + puzzleAdapter.setOnItemClickListener(this); + rvPuzzleTemplet.setLayoutManager(new LinearLayoutManager(this, + LinearLayoutManager.HORIZONTAL, false)); + rvPuzzleTemplet.setAdapter(puzzleAdapter); + puzzleAdapter.refreshData(PuzzleUtils.getPuzzleLayouts(fileCount)); + + textStickerAdapter = new TextStickerAdapter(this, this); + } + + private void initPuzzleView() { + int themeType = fileCount > 3 ? 1 : 0; + puzzleView = (PuzzleView) findViewById(R.id.puzzle_view); + puzzleView.setPuzzleLayout(PuzzleUtils.getPuzzleLayout(themeType, fileCount, 0)); + puzzleView.setOnPieceSelectedListener(new PuzzleView.OnPieceSelectedListener() { + @Override + public void onPieceSelected(PuzzlePiece piece, int position) { + + if (null == piece) { + toggleIvMenu(R.id.iv_replace); + llMenu.setVisibility(View.GONE); + degreeSeekBar.setVisibility(View.GONE); + degreeIndex = -1; + controlFlag = -1; + return; + } + + if (degreeIndex != position) { + controlFlag = -1; + toggleIvMenu(R.id.iv_replace); + degreeSeekBar.setVisibility(View.GONE); + } + llMenu.setVisibility(View.VISIBLE); + degreeIndex = position; + } + }); + } + + private void loadPhoto() { + puzzleView.addPieces(bitmaps); + } + + private void initData() { + stickerModel = new StickerModel(); + deviceWidth = getResources().getDisplayMetrics().widthPixels; + deviceHeight = getResources().getDisplayMetrics().heightPixels; + Intent intent = getIntent(); + saveDirPath = intent.getStringExtra(Key.PUZZLE_SAVE_DIR); + saveNamePrefix = intent.getStringExtra(Key.PUZZLE_SAVE_NAME_PREFIX); + + photos = intent.getParcelableArrayListExtra(Key.PUZZLE_FILES); + fileCount = Math.min(photos.size(), 9); + + new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < fileCount; i++) { + Bitmap bitmap = getScaleBitmap(photos.get(i).path, photos.get(i).uri); + bitmaps.add(bitmap); + degrees.add(0); + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + puzzleView.post(new Runnable() { + @Override + public void run() { + loadPhoto(); + } + }); + } + }); + + } + }).start(); + + + } + + private Bitmap getScaleBitmap(String path, Uri uri) { + Bitmap bitmap = null; + + try { + bitmap = Setting.imageEngine.getCacheBitmap(this, uri, deviceWidth / 2, + deviceHeight / 2); + } catch (Exception e) { + bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(path), + deviceWidth / 2, deviceHeight / 2, true); + } + + if (bitmap == null) { + bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(path), + deviceWidth / 2, deviceHeight / 2, true); + } + return bitmap; + + } + + @Override + public void onClick(View view) { + + int id = view.getId(); + + if (R.id.tv_back == id) { + finish(); + } else if (R.id.tv_done == id) { + if (PermissionUtil.checkAndRequestPermissionsInActivity(this, getNeedPermissions())) { + savePhoto(); + } + + } else if (R.id.iv_replace == id) { + controlFlag = -1; + degreeSeekBar.setVisibility(View.GONE); + toggleIvMenu(R.id.iv_replace); + if (null == toClass) { + EasyPhotos.createAlbum(this, true, false, Setting.imageEngine).setCount(1).start(91); + } else { + Intent intent = new Intent(this, toClass.get()); + startActivityForResult(intent, 91); + } + } else if (R.id.iv_rotate == id) { + if (controlFlag == FLAG_CONTROL_ROTATE) { + if (degrees.get(degreeIndex) % 90 != 0) { + puzzleView.rotate(-degrees.get(degreeIndex)); + degrees.remove(degreeIndex); + degrees.add(degreeIndex, 0); + degreeSeekBar.setCurrentDegrees(0); + return; + } + puzzleView.rotate(90); + int degree = degrees.get(degreeIndex) + 90; + if (degree == 360 || degree == -360) { + degree = 0; + } + degrees.remove(degreeIndex); + degrees.add(degreeIndex, degree); + degreeSeekBar.setCurrentDegrees(degrees.get(degreeIndex)); + return; + } + handleSeekBar(FLAG_CONTROL_ROTATE, -360, 360, degrees.get(degreeIndex)); + toggleIvMenu(R.id.iv_rotate); + } else if (R.id.iv_mirror == id) { + degreeSeekBar.setVisibility(View.GONE); + controlFlag = -1; + toggleIvMenu(R.id.iv_mirror); + puzzleView.flipHorizontally(); + } else if (R.id.iv_flip == id) { + controlFlag = -1; + degreeSeekBar.setVisibility(View.GONE); + toggleIvMenu(R.id.iv_flip); + puzzleView.flipVertically(); + } else if (R.id.iv_corner == id) { + handleSeekBar(FLAG_CONTROL_CORNER, 0, 1000, puzzleView.getPieceRadian()); + toggleIvMenu(R.id.iv_corner); + } else if (R.id.iv_padding == id) { + handleSeekBar(FLAG_CONTROL_PADDING, 0, 100, puzzleView.getPiecePadding()); + toggleIvMenu(R.id.iv_padding); + } else if (R.id.tv_template == id) { + tvTemplate.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_accent)); + tvTextSticker.setTextColor(ContextCompat.getColor(this, + R.color.easy_photos_fg_primary)); + + rvPuzzleTemplet.setAdapter(puzzleAdapter); + + } else if (R.id.tv_text_sticker == id) { + tvTextSticker.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_accent)); + tvTemplate.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_primary)); + + rvPuzzleTemplet.setAdapter(textStickerAdapter); + } else if (R.id.fab == id) { + processBottomLayout(); + } + } + + private void processBottomLayout() { + if (View.VISIBLE == mBottomLayout.getVisibility()) { + mBottomLayout.setVisibility(View.GONE); + fab.setImageResource(R.drawable.ic_arrow_up_easy_photos); + } else { + mBottomLayout.setVisibility(View.VISIBLE); + fab.setImageResource(R.drawable.ic_arrow_down_easy_photos); + } + } + + private void handleSeekBar(int controlFlag, int rangeStart, int rangeEnd, float degrees) { + this.controlFlag = controlFlag; + degreeSeekBar.setVisibility(View.VISIBLE); + degreeSeekBar.setDegreeRange(rangeStart, rangeEnd); + degreeSeekBar.setCurrentDegrees((int) degrees); + } + + private void savePhoto() { + mBottomLayout.setVisibility(View.GONE); + fab.setVisibility(View.GONE); + progressBar.setVisibility(View.VISIBLE); + findViewById(R.id.tv_done).setVisibility(View.INVISIBLE); + findViewById(R.id.progress_frame).setVisibility(View.VISIBLE); + + puzzleView.clearHandling(); + puzzleView.invalidate(); + + stickerModel.save(this, mRootView, puzzleView, puzzleView.getWidth(), + puzzleView.getHeight(), saveDirPath, saveNamePrefix, true, + new SaveBitmapCallBack() { + @Override + public void onSuccess(File file) { + Intent intent = new Intent(); + + Photo photo = new Photo(file.getName(), UriUtils.getUri(PuzzleActivity.this, + file), file.getAbsolutePath(), file.lastModified() / 1000, + puzzleView.getWidth(), puzzleView.getHeight(), 0, file.length(), + DurationUtils.getDuration(file.getAbsolutePath()), "image/png"); + intent.putExtra(EasyPhotos.RESULT_PHOTOS, photo); + setResult(RESULT_OK, intent); + PuzzleActivity.this.finish(); + } + + @Override + public void onIOFailed(IOException exception) { + exception.printStackTrace(); + setResult(RESULT_OK); + PuzzleActivity.this.finish(); + } + + @Override + public void onCreateDirFailed() { + setResult(RESULT_OK); + PuzzleActivity.this.finish(); + } + + }); + } + + private void toggleIvMenu(@IdRes int resId) { + int size = ivMenus.size(); + for (int i = 0; i < size; i++) { + ImageView ivMenu = ivMenus.get(i); + if (ivMenu.getId() == resId) { + ivMenu.setColorFilter(ContextCompat.getColor(this, R.color.easy_photos_fg_accent)); + } else { + ivMenu.clearColorFilter(); + } + } + + } + + @Override + public void onItemClick(int themeType, int themeId) { + puzzleView.setPuzzleLayout(PuzzleUtils.getPuzzleLayout(themeType, fileCount, themeId)); + loadPhoto(); + resetDegrees(); + + } + + private void resetDegrees() { + llMenu.setVisibility(View.GONE); + degreeSeekBar.setVisibility(View.GONE); + degreeIndex = -1; + int size = degrees.size(); + for (int i = 0; i < size; i++) { + degrees.remove(i); + degrees.add(i, 0); + } + } + + @Override + protected void onDestroy() { + if (null != toClass) { + toClass.clear(); + toClass = null; + } + super.onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == Code.REQUEST_SETTING_APP_DETAILS) { + if (PermissionUtil.checkAndRequestPermissionsInActivity(this, getNeedPermissions())) { + savePhoto(); + } + return; + } + switch (resultCode) { + case RESULT_OK: + if (degreeIndex != -1) { + degrees.remove(degreeIndex); + degrees.add(degreeIndex, 0); + } + + String tempPath = ""; + Uri tempUri = null; + + ArrayList photos = + data.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS); + Photo photo = photos.get(0); + tempPath = photo.path; + tempUri = photo.uri; + + + final String path = tempPath; + final Uri uri = tempUri; + new Thread(new Runnable() { + @Override + public void run() { + final Bitmap bitmap = getScaleBitmap(path, uri); + runOnUiThread(new Runnable() { + @Override + public void run() { + puzzleView.replace(bitmap); + } + }); + } + }).start(); + + break; + case RESULT_CANCELED: + break; + default: + break; + } + } + + protected String[] getNeedPermissions() { + return new String[]{Manifest.permission.CAMERA, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE}; + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + PermissionUtil.onPermissionResult(this, permissions, grantResults, + new PermissionUtil.PermissionCallBack() { + @Override + public void onSuccess() { + savePhoto(); + } + + @Override + public void onShouldShow() { + Snackbar.make(rvPuzzleTemplet, R.string.permissions_again_easy_photos, + Snackbar.LENGTH_INDEFINITE).setAction("go", + new View.OnClickListener() { + @Override + public void onClick(View view) { + if (PermissionUtil.checkAndRequestPermissionsInActivity(PuzzleActivity.this, getNeedPermissions())) { + savePhoto(); + } + } + }).show(); + } + + @Override + public void onFailed() { + Snackbar.make(rvPuzzleTemplet, R.string.permissions_die_easy_photos, + Snackbar.LENGTH_INDEFINITE).setAction("go", + new View.OnClickListener() { + @Override + public void onClick(View view) { + SettingsUtils.startMyApplicationDetailsForResult(PuzzleActivity.this, + getPackageName()); + } + }).show(); + } + }); + } + + @Override + public void onItemClick(String stickerValue) { + if (stickerValue.equals("-1")) { + + PuzzleLayout puzzleLayout = puzzleView.getPuzzleLayout(); + int size = puzzleLayout.getAreaCount(); + for (int i = 0; i < size; i++) { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + String date = format.format(photos.get(i).time); + stickerModel.addTextSticker(this, getSupportFragmentManager(), date, mRootView); + stickerModel.currTextSticker.isChecked = true; + Area area = puzzleLayout.getArea(i); + stickerModel.currTextSticker.moveTo(area.centerX(), area.centerY()); + } + + return; + } + + stickerModel.addTextSticker(this, getSupportFragmentManager(), stickerValue, mRootView); + } + + @Override + public void onBackPressed() { + if (View.VISIBLE == mBottomLayout.getVisibility()) { + processBottomLayout(); + return; + } + super.onBackPressed(); + } + + private void setClick(@IdRes int... ids) { + for (int id : ids) { + findViewById(id).setOnClickListener(this); + } + } + + private void setClick(View... views) { + for (View v : views) { + v.setOnClickListener(this); + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleSelectorActivity.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleSelectorActivity.java new file mode 100644 index 000000000..1d218ada2 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleSelectorActivity.java @@ -0,0 +1,265 @@ +package com.huantansheng.easyphotos.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.content.Intent; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.widget.RelativeLayout; +import android.widget.Toast; + +import androidx.annotation.IdRes; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Code; +import com.huantansheng.easyphotos.models.album.AlbumModel; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.adapter.AlbumItemsAdapter; +import com.huantansheng.easyphotos.ui.adapter.PuzzleSelectorAdapter; +import com.huantansheng.easyphotos.ui.adapter.PuzzleSelectorPreviewAdapter; +import com.huantansheng.easyphotos.ui.widget.PressedTextView; +import com.huantansheng.easyphotos.utils.color.ColorUtils; +import com.huantansheng.easyphotos.utils.system.SystemUtils; + +import java.util.ArrayList; + +public class PuzzleSelectorActivity extends AppCompatActivity implements View.OnClickListener, AlbumItemsAdapter.OnClickListener, PuzzleSelectorAdapter.OnClickListener, PuzzleSelectorPreviewAdapter.OnClickListener { + + public static void start(Activity activity) { + Intent intent = new Intent(activity, PuzzleSelectorActivity.class); + activity.startActivityForResult(intent, Code.REQUEST_PUZZLE_SELECTOR); + } + + private AlbumModel albumModel; + + private AnimatorSet setShow; + private AnimatorSet setHide; + + private RelativeLayout rootViewAlbumItems, rootSelectorView; + private RecyclerView rvAlbumItems; + private AlbumItemsAdapter albumItemsAdapter; + private PressedTextView tvAlbumItems; + + private ArrayList photoList = new ArrayList<>(); + private PuzzleSelectorAdapter photosAdapter; + private RecyclerView rvPhotos, rvPreview; + private PuzzleSelectorPreviewAdapter previewAdapter; + private ArrayList selectedPhotos = new ArrayList<>(); + + private PressedTextView tvDone; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_puzzle_selector_easy_photos); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + int statusColor = getWindow().getStatusBarColor(); + if (statusColor == Color.TRANSPARENT) { + statusColor = ContextCompat.getColor(this, R.color.easy_photos_status_bar); + } + if (ColorUtils.isWhiteColor(statusColor)) { + SystemUtils.getInstance().setStatusDark(this, true); + } + } + albumModel = AlbumModel.getInstance(); +// albumModel.query(this, null); + if (null == albumModel || albumModel.getAlbumItems().isEmpty()) { + finish(); + return; + } + initView(); + } + + private void initView() { + setClick(R.id.iv_back); + tvAlbumItems = (PressedTextView) findViewById(R.id.tv_album_items); + tvAlbumItems.setText(albumModel.getAlbumItems().get(0).name); + rootSelectorView = (RelativeLayout) findViewById(R.id.m_selector_root); + tvDone = (PressedTextView) findViewById(R.id.tv_done); + tvDone.setOnClickListener(this); + tvAlbumItems.setOnClickListener(this); + initAlbumItems(); + initPhotos(); + initPreview(); + } + + private void initPreview() { + rvPreview = (RecyclerView) findViewById(R.id.rv_preview_selected_photos); + LinearLayoutManager lm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); + previewAdapter = new PuzzleSelectorPreviewAdapter(this, selectedPhotos, this); + rvPreview.setLayoutManager(lm); + rvPreview.setAdapter(previewAdapter); + } + + private void initPhotos() { + rvPhotos = (RecyclerView) findViewById(R.id.rv_photos); + ((SimpleItemAnimator) rvPhotos.getItemAnimator()).setSupportsChangeAnimations(false);//去除item更新的闪光 + + photoList.addAll(albumModel.getCurrAlbumItemPhotos(0)); + photosAdapter = new PuzzleSelectorAdapter(this, photoList, this); + + int columns = getResources().getInteger(R.integer.photos_columns_easy_photos); + + GridLayoutManager gridLayoutManager = new GridLayoutManager(this, columns); + rvPhotos.setLayoutManager(gridLayoutManager); + rvPhotos.setAdapter(photosAdapter); + } + + private void initAlbumItems() { + rootViewAlbumItems = (RelativeLayout) findViewById(R.id.root_view_album_items); + rootViewAlbumItems.setOnClickListener(this); + setClick(R.id.iv_album_items); + rvAlbumItems = (RecyclerView) findViewById(R.id.rv_album_items); + LinearLayoutManager lm = new LinearLayoutManager(this); + ArrayList list = new ArrayList(albumModel.getAlbumItems()); + albumItemsAdapter = new AlbumItemsAdapter(this, list, 0, this); + rvAlbumItems.setLayoutManager(lm); + rvAlbumItems.setAdapter(albumItemsAdapter); + } + + private void setClick(@IdRes int... ids) { + for (int id : ids) { + findViewById(id).setOnClickListener(this); + } + } + + @Override + public void onClick(View view) { + int id = view.getId(); + if (R.id.iv_back == id) { + setResult(RESULT_CANCELED); + finish(); + } else if (R.id.tv_album_items == id || R.id.iv_album_items == id) { + showAlbumItems(View.GONE == rootViewAlbumItems.getVisibility()); + } else if (R.id.root_view_album_items == id) { + showAlbumItems(false); + } else if (R.id.tv_done == id) { + PuzzleActivity.startWithPhotos(this, selectedPhotos, getExternalFilesDir(Environment.DIRECTORY_DCIM).getAbsolutePath(), "IMG", Code.REQUEST_PUZZLE, false, Setting.imageEngine); + + } + } + + private void showAlbumItems(boolean isShow) { + if (null == setShow) { + newAnimators(); + } + if (isShow) { + rootViewAlbumItems.setVisibility(View.VISIBLE); + setShow.start(); + } else { + setHide.start(); + } + } + + private void newAnimators() { + newHideAnim(); + newShowAnim(); + } + + private void newShowAnim() { + ObjectAnimator translationShow = ObjectAnimator.ofFloat(rvAlbumItems, "translationY", rootSelectorView.getTop(), 0); + ObjectAnimator alphaShow = ObjectAnimator.ofFloat(rootViewAlbumItems, "alpha", 0.0f, 1.0f); + translationShow.setDuration(300); + setShow = new AnimatorSet(); + setShow.setInterpolator(new AccelerateDecelerateInterpolator()); + setShow.play(translationShow).with(alphaShow); + } + + private void newHideAnim() { + ObjectAnimator translationHide = ObjectAnimator.ofFloat(rvAlbumItems, "translationY", 0, rootSelectorView.getTop()); + ObjectAnimator alphaHide = ObjectAnimator.ofFloat(rootViewAlbumItems, "alpha", 1.0f, 0.0f); + translationHide.setDuration(200); + setHide = new AnimatorSet(); + setHide.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + rootViewAlbumItems.setVisibility(View.GONE); + } + }); + setHide.setInterpolator(new AccelerateInterpolator()); + setHide.play(translationHide).with(alphaHide); + } + + @Override + public void onAlbumItemClick(int position, int realPosition) { + updatePhotos(realPosition); + showAlbumItems(false); + tvAlbumItems.setText(albumModel.getAlbumItems().get(realPosition).name); + } + + private void updatePhotos(int currAlbumItemIndex) { + photoList.clear(); + photoList.addAll(albumModel.getCurrAlbumItemPhotos(currAlbumItemIndex)); + + photosAdapter.notifyDataSetChanged(); + rvPhotos.scrollToPosition(0); + } + + + @Override + public void onBackPressed() { + + if (null != rootViewAlbumItems && rootViewAlbumItems.getVisibility() == View.VISIBLE) { + showAlbumItems(false); + return; + } + + super.onBackPressed(); + } + + @Override + public void onPhotoClick(int position) { + if (selectedPhotos.size() > 8) { + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_image_hint_easy_photos, 9), Toast.LENGTH_SHORT).show(); + return; + } + + selectedPhotos.add(photoList.get(position)); + previewAdapter.notifyDataSetChanged(); + rvPreview.smoothScrollToPosition(selectedPhotos.size() - 1); + + tvDone.setText(getString(R.string.selector_action_done_easy_photos, selectedPhotos.size(), 9)); + if (selectedPhotos.size() > 1) { + tvDone.setVisibility(View.VISIBLE); + } + } + + @Override + public void onDeleteClick(int position) { + selectedPhotos.remove(position); + previewAdapter.notifyDataSetChanged(); + tvDone.setText(getString(R.string.selector_action_done_easy_photos, selectedPhotos.size(), 9)); + if (selectedPhotos.size() < 2) { + tvDone.setVisibility(View.INVISIBLE); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (resultCode == RESULT_OK) { + if (requestCode == Code.REQUEST_PUZZLE) { + setResult(RESULT_OK, data); + finish(); + } + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/AlbumItemsAdapter.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/AlbumItemsAdapter.java new file mode 100644 index 000000000..ca2477659 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/AlbumItemsAdapter.java @@ -0,0 +1,183 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.RecyclerView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.models.ad.AdViewHolder; +import com.huantansheng.easyphotos.models.album.entity.AlbumItem; +import com.huantansheng.easyphotos.setting.Setting; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * 媒体列表适配器 + * Created by huan on 2017/10/23. + */ + +public class AlbumItemsAdapter extends RecyclerView.Adapter { + private static final int TYPE_AD = 0; + private static final int TYPE_ALBUM_ITEMS = 1; + + private ArrayList dataList; + private LayoutInflater mInflater; + private int selectedPosition; + private OnClickListener listener; + private int adPosition = 0; + private int padding = 0; + + private boolean clearAd = false; + + public interface OnClickListener { + void onAlbumItemClick(int position, int realPosition); + } + + + public AlbumItemsAdapter(Context cxt, ArrayList list, int selectedPosition, OnClickListener listener) { + this.dataList = list; + this.mInflater = LayoutInflater.from(cxt); + this.listener = listener; + this.selectedPosition = selectedPosition; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case TYPE_AD: + return new AdViewHolder(mInflater.inflate(R.layout.item_ad_easy_photos, parent, false)); + default: + return new AlbumItemsViewHolder(mInflater.inflate(R.layout.item_dialog_album_items_easy_photos, parent, false)); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + final int p = position; + if (holder instanceof AlbumItemsViewHolder) { + if (padding == 0) { + padding = ((AlbumItemsViewHolder) holder).mRoot.getPaddingLeft(); + } + if (p == getItemCount() - 1) { + ((AlbumItemsViewHolder) holder).mRoot.setPadding(padding, padding, padding, padding); + } else { + ((AlbumItemsViewHolder) holder).mRoot.setPadding(padding, padding, padding, 0); + } + AlbumItem item = (AlbumItem) dataList.get(p); + Setting.imageEngine.loadPhoto(((AlbumItemsViewHolder) holder).ivAlbumCover.getContext(), item.coverImageUri, ((AlbumItemsViewHolder) holder).ivAlbumCover); + ((AlbumItemsViewHolder) holder).tvAlbumName.setText(item.name); + ((AlbumItemsViewHolder) holder).tvAlbumPhotosCount.setText(String.valueOf(item.photos.size())); + if (selectedPosition == p) { + ((AlbumItemsViewHolder) holder).ivSelected.setVisibility(View.VISIBLE); + } else { + ((AlbumItemsViewHolder) holder).ivSelected.setVisibility(View.INVISIBLE); + } + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + int realPosition = p; + if (Setting.hasAlbumItemsAd()) { + if (p > adPosition) { + realPosition--; + } + } + int tempSelected = selectedPosition; + selectedPosition = p; + notifyItemChanged(tempSelected); + notifyItemChanged(p); + listener.onAlbumItemClick(p, realPosition); + } + }); + return; + } + + if (holder instanceof AdViewHolder) { + if (clearAd) { + ((AdViewHolder) holder).adFrame.removeAllViews(); + ((AdViewHolder) holder).adFrame.setVisibility(View.GONE); + return; + } + adPosition = p; + if (!Setting.albumItemsAdIsOk) { + ((AdViewHolder) holder).adFrame.setVisibility(View.GONE); + return; + } + + WeakReference weakReference = (WeakReference) dataList.get(p); + + if (null != weakReference) { + View adView = (View) weakReference.get(); + if (null != adView) { + if (null != adView.getParent()) { + if (adView.getParent() instanceof FrameLayout) { + ((FrameLayout) adView.getParent()).removeAllViews(); + } + } + ((AdViewHolder) holder).adFrame.setVisibility(View.VISIBLE); + ((AdViewHolder) holder).adFrame.removeAllViews(); + ((AdViewHolder) holder).adFrame.addView(adView); + } + } + } + } + + public void clearAd() { + clearAd = true; + notifyDataSetChanged(); + } + + public void setSelectedPosition(int position) { + int realPosition = position; + if (Setting.hasAlbumItemsAd()) { + if (position > adPosition) { + realPosition--; + } + } + int tempSelected = selectedPosition; + selectedPosition = position; + notifyItemChanged(tempSelected); + notifyItemChanged(position); + listener.onAlbumItemClick(position, realPosition); + } + + @Override + public int getItemCount() { + return dataList.size(); + } + + @Override + public int getItemViewType(int position) { + Object item = dataList.get(position); + if (null == item || item instanceof WeakReference) { + return TYPE_AD; + } else { + return TYPE_ALBUM_ITEMS; + } + } + + public static class AlbumItemsViewHolder extends RecyclerView.ViewHolder { + ImageView ivAlbumCover; + TextView tvAlbumName; + TextView tvAlbumPhotosCount; + ImageView ivSelected; + ConstraintLayout mRoot; + + AlbumItemsViewHolder(View itemView) { + super(itemView); + this.ivAlbumCover = (ImageView) itemView.findViewById(R.id.iv_album_cover); + this.tvAlbumName = (TextView) itemView.findViewById(R.id.tv_album_name); + this.tvAlbumPhotosCount = (TextView) itemView.findViewById(R.id.tv_album_photos_count); + this.ivSelected = (ImageView) itemView.findViewById(R.id.iv_selected); + this.mRoot = (ConstraintLayout) itemView.findViewById(R.id.m_root_view); + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PhotosAdapter.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PhotosAdapter.java new file mode 100644 index 000000000..fece2252e --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PhotosAdapter.java @@ -0,0 +1,306 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.ad.AdViewHolder; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.widget.PressedImageView; +import com.huantansheng.easyphotos.utils.media.DurationUtils; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * 专辑相册适配器 + * Created by huan on 2017/10/23. + */ +public class PhotosAdapter extends RecyclerView.Adapter { + private static final int TYPE_AD = 0; + private static final int TYPE_CAMERA = 1; + private static final int TYPE_ALBUM_ITEMS = 2; + + private ArrayList dataList; + private LayoutInflater mInflater; + private OnClickListener listener; + private boolean unable, isSingle; + private int singlePosition; + + private boolean clearAd = false; + + + public PhotosAdapter(Context cxt, ArrayList dataList, OnClickListener listener) { + this.dataList = dataList; + this.listener = listener; + this.mInflater = LayoutInflater.from(cxt); + this.unable = Result.count() == Setting.count; + this.isSingle = Setting.count == 1; + } + + public void change() { + unable = Result.count() == Setting.count; + notifyDataSetChanged(); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + switch (viewType) { + case TYPE_AD: + return new AdViewHolder(mInflater.inflate(R.layout.item_ad_easy_photos, parent, false)); + case TYPE_CAMERA: + return new CameraViewHolder(mInflater.inflate(R.layout.item_camera_easy_photos, parent, false)); + default: + return new PhotoViewHolder(mInflater.inflate(R.layout.item_rv_photos_easy_photos, parent, false)); + } + } + + @Override + public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) { + final int p = position; + if (holder instanceof PhotoViewHolder) { + final Photo item = (Photo) dataList.get(p); + if (item == null) return; + updateSelector(((PhotoViewHolder) holder).tvSelector, item.selected, item, p); + String path = item.path; + Uri uri = item.uri; + String type = item.type; + long duration = item.duration; + final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF); + if (Setting.showGif && isGif) { + Setting.imageEngine.loadGifAsBitmap(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(R.string.gif_easy_photos); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + ((PhotoViewHolder) holder).ivVideo.setVisibility(View.GONE); + } else if (Setting.showVideo && type.contains(Type.VIDEO)) { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(DurationUtils.format(duration)); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + ((PhotoViewHolder) holder).ivVideo.setVisibility(View.VISIBLE); + + } else { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setVisibility(View.GONE); + ((PhotoViewHolder) holder).ivVideo.setVisibility(View.GONE); + } + + ((PhotoViewHolder) holder).vSelector.setVisibility(View.VISIBLE); + ((PhotoViewHolder) holder).tvSelector.setVisibility(View.VISIBLE); + ((PhotoViewHolder) holder).ivPhoto.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int realPosition = p; + if (Setting.hasPhotosAd()) { + realPosition--; + } + if (Setting.isShowCamera && !Setting.isBottomRightCamera()) { + realPosition--; + } + listener.onPhotoClick(p, realPosition); + } + }); + + + ((PhotoViewHolder) holder).vSelector.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (isSingle) { + singleSelector(item, p); + return; + } + if (unable) { + if (item.selected) { + Result.removePhoto(item); + if (unable) { + unable = false; + } + listener.onSelectorChanged(); + notifyDataSetChanged(); + return; + } + listener.onSelectorOutOfMax(null); + return; + } + item.selected = !item.selected; + if (item.selected) { + int res = Result.addPhoto(item); + if (res != 0) { + listener.onSelectorOutOfMax(res); + item.selected = false; + return; + } + ((PhotoViewHolder) holder).tvSelector.setBackgroundResource(R.drawable.bg_select_true_easy_photos); + ((PhotoViewHolder) holder).tvSelector.setText(String.valueOf(Result.count())); + if (Result.count() == Setting.count) { + unable = true; + notifyDataSetChanged(); + } + } else { + Result.removePhoto(item); + if (unable) { + unable = false; + } + notifyDataSetChanged(); + } + listener.onSelectorChanged(); + } + }); + return; + } + + if (holder instanceof AdViewHolder) { + if (clearAd) { + ((AdViewHolder) holder).adFrame.removeAllViews(); + ((AdViewHolder) holder).adFrame.setVisibility(View.GONE); + return; + } + if (!Setting.photoAdIsOk) { + ((AdViewHolder) holder).adFrame.setVisibility(View.GONE); + return; + } + + WeakReference weakReference = (WeakReference) dataList.get(p); + + if (null != weakReference) { + View adView = (View) weakReference.get(); + if (null != adView) { + if (null != adView.getParent()) { + if (adView.getParent() instanceof FrameLayout) { + ((FrameLayout) adView.getParent()).removeAllViews(); + } + } + ((AdViewHolder) holder).adFrame.setVisibility(View.VISIBLE); + ((AdViewHolder) holder).adFrame.removeAllViews(); + ((AdViewHolder) holder).adFrame.addView(adView); + } + } + } + + if (holder instanceof CameraViewHolder) { + ((CameraViewHolder) holder).flCamera.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onCameraClick(); + } + }); + } + } + + public void clearAd() { + clearAd = true; + notifyDataSetChanged(); + } + + private void singleSelector(Photo photo, int position) { + if (!Result.isEmpty()) { + if (Result.getPhotoPath(0).equals(photo.path)) { + Result.removePhoto(photo); + } else { + Result.removePhoto(0); + Result.addPhoto(photo); + notifyItemChanged(singlePosition); + } + } else { + Result.addPhoto(photo); + } + notifyItemChanged(position); + listener.onSelectorChanged(); + } + + private void updateSelector(TextView tvSelector, boolean selected, Photo photo, int position) { + if (selected) { + String number = Result.getSelectorNumber(photo); + if (number.equals("0")) { + tvSelector.setBackgroundResource(R.drawable.bg_select_false_easy_photos); + tvSelector.setText(null); + return; + } + tvSelector.setText(number); + tvSelector.setBackgroundResource(R.drawable.bg_select_true_easy_photos); + if (isSingle) { + singlePosition = position; + tvSelector.setText("1"); + } + } else { + if (unable) { + tvSelector.setBackgroundResource(R.drawable.bg_select_false_unable_easy_photos); + } else { + tvSelector.setBackgroundResource(R.drawable.bg_select_false_easy_photos); + } + tvSelector.setText(null); + } + } + + @Override + public int getItemCount() { + return dataList.size(); + } + + @Override + public int getItemViewType(int position) { + if (0 == position) { + if (Setting.hasPhotosAd()) { + return TYPE_AD; + } + if (Setting.isShowCamera && !Setting.isBottomRightCamera()) { + return TYPE_CAMERA; + } + } + if (1 == position && !Setting.isBottomRightCamera()) { + if (Setting.hasPhotosAd() && Setting.isShowCamera) { + return TYPE_CAMERA; + } + } + return TYPE_ALBUM_ITEMS; + } + + public interface OnClickListener { + void onCameraClick(); + + void onPhotoClick(int position, int realPosition); + + void onSelectorOutOfMax(@Nullable Integer result); + + void onSelectorChanged(); + } + + private static class CameraViewHolder extends RecyclerView.ViewHolder { + final FrameLayout flCamera; + + CameraViewHolder(View itemView) { + super(itemView); + this.flCamera = itemView.findViewById(R.id.fl_camera); + } + } + + public static class PhotoViewHolder extends RecyclerView.ViewHolder { + final PressedImageView ivPhoto; + final TextView tvSelector; + final View vSelector; + final TextView tvType; + final ImageView ivVideo; + + PhotoViewHolder(View itemView) { + super(itemView); + this.ivPhoto = itemView.findViewById(R.id.iv_photo); + this.tvSelector = itemView.findViewById(R.id.tv_selector); + this.vSelector = itemView.findViewById(R.id.v_selector); + this.tvType = itemView.findViewById(R.id.tv_type); + this.ivVideo = itemView.findViewById(R.id.iv_play); + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosAdapter.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosAdapter.java new file mode 100644 index 000000000..62b5d4317 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosAdapter.java @@ -0,0 +1,154 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import android.content.Intent; +import android.graphics.PointF; +import android.net.Uri; +import android.os.Build; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.davemorrissey.labs.subscaleview.ImageSource; +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; +import com.github.chrisbanes.photoview.OnScaleChangedListener; +import com.github.chrisbanes.photoview.PhotoView; +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.setting.Setting; + +import java.util.ArrayList; + +/** + * 大图预览界面图片集合的适配器 + * Created by huan on 2017/10/26. + */ +public class PreviewPhotosAdapter extends RecyclerView.Adapter { + private ArrayList photos; + private OnClickListener listener; + private LayoutInflater inflater; + + public interface OnClickListener { + void onPhotoClick(); + + void onPhotoScaleChanged(); + } + + public PreviewPhotosAdapter(Context cxt, ArrayList photos, OnClickListener listener) { + this.photos = photos; + this.inflater = LayoutInflater.from(cxt); + this.listener = listener; + } + + @NonNull + @Override + public PreviewPhotosViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new PreviewPhotosViewHolder(inflater.inflate(R.layout.item_preview_photo_easy_photos, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull final PreviewPhotosViewHolder holder, int position) { + Uri uri = photos.get(position).uri; + String path = photos.get(position).path; + String type = photos.get(position).type; + double ratio = 0; + if (photos.get(position).width != 0) { + ratio = (double) photos.get(position).height / (double) photos.get(position).width; + } + + holder.ivPlay.setVisibility(View.GONE); + holder.ivPhotoView.setVisibility(View.GONE); + holder.ivLongPhoto.setVisibility(View.GONE); + + if (type.contains(Type.VIDEO)) { + holder.ivPhotoView.setVisibility(View.VISIBLE); + Setting.imageEngine.loadPhoto(holder.ivPhotoView.getContext(), uri, holder.ivPhotoView); + holder.ivPlay.setVisibility(View.VISIBLE); + holder.ivPlay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + toPlayVideo(v, uri, type); + } + }); + } else if (path.endsWith(Type.GIF) || type.endsWith(Type.GIF)) { + holder.ivPhotoView.setVisibility(View.VISIBLE); + Setting.imageEngine.loadGif(holder.ivPhotoView.getContext(), uri, holder.ivPhotoView); + } else { + if (ratio > 2.3) { + holder.ivLongPhoto.setVisibility(View.VISIBLE); + holder.ivLongPhoto.setImage(ImageSource.uri(path)); + } else { + holder.ivPhotoView.setVisibility(View.VISIBLE); + Setting.imageEngine.loadPhoto(holder.ivPhotoView.getContext(), uri, + holder.ivPhotoView); + } + } + + holder.ivLongPhoto.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onPhotoClick(); + } + }); + holder.ivPhotoView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onPhotoClick(); + } + }); + holder.ivLongPhoto.setOnStateChangedListener(new SubsamplingScaleImageView.OnStateChangedListener() { + @Override + public void onScaleChanged(float newScale, int origin) { + listener.onPhotoScaleChanged(); + } + + @Override + public void onCenterChanged(PointF newCenter, int origin) { + + } + }); + + holder.ivPhotoView.setScale(1f); + + holder.ivPhotoView.setOnScaleChangeListener(new OnScaleChangedListener() { + @Override + public void onScaleChange(float scaleFactor, float focusX, float focusY) { + listener.onPhotoScaleChanged(); + } + }); + } + + private void toPlayVideo(View v, Uri uri, String type) { + Context context = v.getContext(); + Intent intent = new Intent(Intent.ACTION_VIEW); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + intent.setDataAndType(uri, type); + context.startActivity(intent); + } + + @Override + public int getItemCount() { + return photos.size(); + } + + public static class PreviewPhotosViewHolder extends RecyclerView.ViewHolder { + public SubsamplingScaleImageView ivLongPhoto; + ImageView ivPlay; + PhotoView ivPhotoView; + + PreviewPhotosViewHolder(View itemView) { + super(itemView); + ivLongPhoto = itemView.findViewById(R.id.iv_long_photo); + ivPhotoView = itemView.findViewById(R.id.iv_photo_view); + ivPlay = itemView.findViewById(R.id.iv_play); + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosFragmentAdapter.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosFragmentAdapter.java new file mode 100644 index 000000000..d077c8652 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosFragmentAdapter.java @@ -0,0 +1,104 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.widget.PressedImageView; +import com.huantansheng.easyphotos.utils.media.DurationUtils; + +/** + * 预览所有选中图片集合的适配器 + * Created by huan on 2017/12/1. + */ + +public class PreviewPhotosFragmentAdapter extends RecyclerView.Adapter { + private LayoutInflater inflater; + private OnClickListener listener; + private int checkedPosition = -1; + + public PreviewPhotosFragmentAdapter(Context context, OnClickListener listener) { + this.inflater = LayoutInflater.from(context); + this.listener = listener; + } + + + @Override + public PreviewPhotoVH onCreateViewHolder(ViewGroup parent, int viewType) { + return new PreviewPhotoVH(inflater.inflate(R.layout.item_preview_selected_photos_easy_photos, parent, false)); + } + + @Override + public void onBindViewHolder(PreviewPhotoVH holder, int position) { + final int p = position; + String path = Result.getPhotoPath(position); + String type = Result.getPhotoType(position); + Uri uri = Result.getPhotoUri(position); + long duration = Result.getPhotoDuration(position); + + final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF); + if (Setting.showGif && isGif) { + Setting.imageEngine.loadGifAsBitmap(holder.ivPhoto.getContext(), uri, holder.ivPhoto); + holder.tvType.setText(R.string.gif_easy_photos); + holder.tvType.setVisibility(View.VISIBLE); + } else if (Setting.showVideo && type.contains(Type.VIDEO)) { + Setting.imageEngine.loadPhoto(holder.ivPhoto.getContext(), uri, holder.ivPhoto); + holder.tvType.setText(DurationUtils.format(duration)); + holder.tvType.setVisibility(View.VISIBLE); + } else { + Setting.imageEngine.loadPhoto(holder.ivPhoto.getContext(), uri, holder.ivPhoto); + holder.tvType.setVisibility(View.GONE); + } + + if (checkedPosition == p) { + holder.frame.setVisibility(View.VISIBLE); + } else { + holder.frame.setVisibility(View.GONE); + } + holder.ivPhoto.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + listener.onPhotoClick(p); + } + }); + } + + @Override + public int getItemCount() { + return Result.count(); + } + + public void setChecked(int position) { + if (checkedPosition == position) { + return; + } + checkedPosition = position; + notifyDataSetChanged(); + } + + static class PreviewPhotoVH extends RecyclerView.ViewHolder { + PressedImageView ivPhoto; + View frame; + TextView tvType; + + public PreviewPhotoVH(View itemView) { + super(itemView); + ivPhoto = (PressedImageView) itemView.findViewById(R.id.iv_photo); + frame = itemView.findViewById(R.id.v_selector); + tvType = (TextView) itemView.findViewById(R.id.tv_type); + } + } + + public interface OnClickListener { + void onPhotoClick(int position); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleAdapter.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleAdapter.java new file mode 100644 index 000000000..209f9e6c2 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleAdapter.java @@ -0,0 +1,104 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.recyclerview.widget.RecyclerView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; +import com.huantansheng.easyphotos.models.puzzle.SquarePuzzleView; +import com.huantansheng.easyphotos.models.puzzle.template.slant.NumberSlantLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.NumberStraightLayout; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public class PuzzleAdapter extends RecyclerView.Adapter { + + private List layoutData = new ArrayList<>(); + private OnItemClickListener onItemClickListener; + private int selectedNumber = 0; + + @Override + public PuzzleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = + LayoutInflater.from(parent.getContext()).inflate(R.layout.item_puzzle_easy_photos, parent, false); + return new PuzzleViewHolder(itemView); + } + + @Override + public void onBindViewHolder(PuzzleViewHolder holder, int position) { + final PuzzleLayout puzzleLayout = layoutData.get(position); + final int p = position; + if (selectedNumber == position) { + holder.mFrame.setVisibility(View.VISIBLE); + } else { + holder.mFrame.setVisibility(View.GONE); + } + holder.puzzleView.setNeedDrawLine(true); + holder.puzzleView.setNeedDrawOuterLine(true); + holder.puzzleView.setTouchEnable(false); + + holder.puzzleView.setPuzzleLayout(puzzleLayout); + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (selectedNumber == p) { + return; + } + if (onItemClickListener != null) { + int themeType = 0; + int themeId = 0; + if (puzzleLayout instanceof NumberSlantLayout) { + themeType = 0; + themeId = ((NumberSlantLayout) puzzleLayout).getTheme(); + } else if (puzzleLayout instanceof NumberStraightLayout) { + themeType = 1; + themeId = ((NumberStraightLayout) puzzleLayout).getTheme(); + } + selectedNumber = p; + onItemClickListener.onItemClick(themeType, themeId); + notifyDataSetChanged(); + } + } + }); + + } + + @Override + public int getItemCount() { + return layoutData == null ? 0 : layoutData.size(); + } + + public void refreshData(List layoutData) { + this.layoutData = layoutData; + + notifyDataSetChanged(); + } + + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + public static class PuzzleViewHolder extends RecyclerView.ViewHolder { + + SquarePuzzleView puzzleView; + View mFrame; + + public PuzzleViewHolder(View itemView) { + super(itemView); + puzzleView = (SquarePuzzleView) itemView.findViewById(R.id.puzzle); + mFrame = itemView.findViewById(R.id.m_selector); + } + } + + public interface OnItemClickListener { + void onItemClick(int themeType, int themeId); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorAdapter.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorAdapter.java new file mode 100644 index 000000000..9674c28b4 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorAdapter.java @@ -0,0 +1,99 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.utils.media.DurationUtils; + +import java.util.ArrayList; + +/** + * 拼图相册适配器 + * Created by huan on 2017/10/23. + */ + +public class PuzzleSelectorAdapter extends RecyclerView.Adapter { + + + private ArrayList dataList; + private LayoutInflater mInflater; + private OnClickListener listener; + + + public PuzzleSelectorAdapter(Context cxt, ArrayList dataList, OnClickListener listener) { + this.dataList = dataList; + this.listener = listener; + this.mInflater = LayoutInflater.from(cxt); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + + return new PhotoViewHolder(mInflater.inflate(R.layout.item_puzzle_selector_easy_photos, parent, false)); + + } + + @Override + public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { + + final int p = position; + Photo photo = dataList.get(position); + String path = photo.path; + String type = photo.type; + Uri uri = photo.uri; + long duration = photo.duration; + final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF); + if (Setting.showGif && isGif) { + Setting.imageEngine.loadGifAsBitmap(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(R.string.gif_easy_photos); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + } else if (Setting.showVideo && type.contains(Type.VIDEO)) { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(DurationUtils.format(duration)); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + } else { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setVisibility(View.GONE); + } + + ((PhotoViewHolder) holder).ivPhoto.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onPhotoClick(p); + } + }); + } + + + @Override + public int getItemCount() { + return null == dataList ? 0 : dataList.size(); + } + + + public interface OnClickListener { + void onPhotoClick(int position); + } + + public static class PhotoViewHolder extends RecyclerView.ViewHolder { + ImageView ivPhoto; + TextView tvType; + + public PhotoViewHolder(View itemView) { + super(itemView); + this.ivPhoto = (ImageView) itemView.findViewById(R.id.iv_photo); + this.tvType = (TextView) itemView.findViewById(R.id.tv_type); + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorPreviewAdapter.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorPreviewAdapter.java new file mode 100644 index 000000000..67cd39156 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorPreviewAdapter.java @@ -0,0 +1,101 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.utils.media.DurationUtils; + +import java.util.ArrayList; + +/** + * 拼图相册适配器 + * Created by huan on 2017/10/23. + */ + +public class PuzzleSelectorPreviewAdapter extends RecyclerView.Adapter { + + + private ArrayList dataList; + private LayoutInflater mInflater; + private OnClickListener listener; + + + public PuzzleSelectorPreviewAdapter(Context cxt, ArrayList dataList, OnClickListener listener) { + this.dataList = dataList; + this.listener = listener; + this.mInflater = LayoutInflater.from(cxt); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + + return new PhotoViewHolder(mInflater.inflate(R.layout.item_puzzle_selector_preview_easy_photos, parent, false)); + + } + + @Override + public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { + + final int p = position; + Photo photo = dataList.get(position); + String path = photo.path; + String type = photo.type; + Uri uri = photo.uri; + long duration = photo.duration; + final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF); + if (Setting.showGif && isGif) { + Setting.imageEngine.loadGifAsBitmap(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(R.string.gif_easy_photos); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + } else if (Setting.showVideo && type.contains(Type.VIDEO)) { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(DurationUtils.format(duration)); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + } else { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setVisibility(View.GONE); + } + + ((PhotoViewHolder) holder).ivDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onDeleteClick(p); + } + }); + } + + + @Override + public int getItemCount() { + return null == dataList ? 0 : dataList.size(); + } + + + public interface OnClickListener { + void onDeleteClick(int position); + } + + public static class PhotoViewHolder extends RecyclerView.ViewHolder { + ImageView ivPhoto; + ImageView ivDelete; + TextView tvType; + + public PhotoViewHolder(View itemView) { + super(itemView); + this.ivPhoto = (ImageView) itemView.findViewById(R.id.iv_photo); + this.ivDelete = (ImageView) itemView.findViewById(R.id.iv_delete); + this.tvType = (TextView) itemView.findViewById(R.id.tv_type); + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/TextStickerAdapter.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/TextStickerAdapter.java new file mode 100644 index 000000000..3872df506 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/TextStickerAdapter.java @@ -0,0 +1,81 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.models.sticker.StickerModel; +import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author huan + */ +public class TextStickerAdapter extends RecyclerView.Adapter { + + private List datas; + private OnItemClickListener onItemClickListener; + + public TextStickerAdapter(Context cxt, OnItemClickListener listener) { + super(); + this.onItemClickListener = listener; + this.datas = new ArrayList<>(); + TextStickerData data = new TextStickerData(cxt.getString(R.string.text_sticker_hint_name_easy_photos), cxt.getString(R.string.text_sticker_hint_easy_photos)); + this.datas.add(0, data); + TextStickerData d = new TextStickerData(cxt.getString(R.string.text_sticker_date_easy_photos), "-1"); + datas.add(d); + datas.addAll(StickerModel.textDataList); + } + + @Override + public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = + LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text_sticker_easy_photos, parent, false); + return new TextViewHolder(itemView); + } + + @Override + public void onBindViewHolder(TextViewHolder holder, int position) { + final TextStickerData data = datas.get(position); + + holder.tvSticker.setText(data.stickerName); + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(data.stickerValue); + notifyDataSetChanged(); + } + } + }); + + } + + @Override + public int getItemCount() { + return datas == null ? 0 : datas.size(); + } + + + public static class TextViewHolder extends RecyclerView.ViewHolder { + + TextView tvSticker; + + public TextViewHolder(View itemView) { + super(itemView); + tvSticker = (TextView) itemView.findViewById(R.id.puzzle); + } + } + + public interface OnItemClickListener { + void onItemClick(String stickerValue); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/dialog/LoadingDialog.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/dialog/LoadingDialog.java new file mode 100644 index 000000000..3658498ff --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/dialog/LoadingDialog.java @@ -0,0 +1,49 @@ +package com.huantansheng.easyphotos.ui.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import androidx.annotation.NonNull; + +import com.huantansheng.easyphotos.R; + +public class LoadingDialog extends Dialog { + + private LoadingDialog(@NonNull Context context) { + super(context); + Window window = getWindow(); + if (null != window) { + window.requestFeature(Window.FEATURE_NO_TITLE); + } + } + + public static LoadingDialog get(Context context) { + LoadingDialog loading = new LoadingDialog(context); + View view = LayoutInflater.from(context).inflate(R.layout.dialog_loading_easy_photos, null); + loading.setContentView(view); + loading.setCancelable(false); + loading.setCanceledOnTouchOutside(false); + return loading; + } + + @Override + public void show() { + Window window = getWindow(); + if (null != window) { + WindowManager.LayoutParams attributes = window.getAttributes(); + window.setBackgroundDrawableResource(R.color.transparent_easy_photos); + window.setGravity(Gravity.CENTER); + attributes.width = WindowManager.LayoutParams.MATCH_PARENT; + attributes.height = WindowManager.LayoutParams.WRAP_CONTENT; + window.setAttributes(attributes); + } + super.show(); + } + +} + diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedImageView.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedImageView.java new file mode 100644 index 000000000..3c48f8c5c --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedImageView.java @@ -0,0 +1,48 @@ +package com.huantansheng.easyphotos.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +/** + * 自带点击效果的imageview + * Created by huan on 2017/8/15. + */ + +public class PressedImageView extends androidx.appcompat.widget.AppCompatImageView { + private float scaleSize;//按压颜色 + + public PressedImageView(Context context) { + super(context); + this.scaleSize = 0.97f; + } + + public PressedImageView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + this.scaleSize = 0.97f; + + } + + public PressedImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.scaleSize = 0.97f; + + } + + @Override + public void setPressed(boolean pressed) { + super.setPressed(pressed); + if (isPressed()) { + setScaleX(this.scaleSize); + setScaleY(this.scaleSize); + } else { + setScaleX(1.0f); + setScaleY(1.0f); + } + } + + public void setScaleSize(float scaleSize) { + this.scaleSize = scaleSize; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedTextView.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedTextView.java new file mode 100644 index 000000000..ec1b80090 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedTextView.java @@ -0,0 +1,73 @@ +package com.huantansheng.easyphotos.ui.widget; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +/** + * 带点击状态的textview + * Created by huan on 2017/9/15. + */ + +public class PressedTextView extends androidx.appcompat.widget.AppCompatTextView { + private float pressedScale; + private AnimatorSet set; + private int pressedFlag; + + public PressedTextView(Context context) { + super(context); + this.pressedScale = 1.1f; + this.pressedFlag = 1; + } + + public PressedTextView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + this.pressedScale = 1.1f; + this.pressedFlag = 1; + } + + public PressedTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.pressedScale = 1.1f; + this.pressedFlag = 1; + } + + @Override + public void setPressed(boolean pressed) { + super.setPressed(pressed); + if (isPressed()) { + pressedFlag = 1; + if (null == set) { + set = new AnimatorSet(); + set.setDuration(5); + } + if (set.isRunning()) set.cancel(); + ObjectAnimator pScaleX = ObjectAnimator.ofFloat(this, "scaleX", 1.0f, pressedScale); + ObjectAnimator pScaleY = ObjectAnimator.ofFloat(this, "scaleY", 1.0f, pressedScale); + set.play(pScaleX).with(pScaleY); + set.start(); + } else { + if (pressedFlag != 1) { + return; + } + pressedFlag = 2; + if (null == set) { + set = new AnimatorSet(); + set.setDuration(5); + } + if (set.isRunning()) set.cancel(); + ObjectAnimator nScaleX = ObjectAnimator.ofFloat(this, "scaleX", pressedScale, 1.0f); + ObjectAnimator nScaleY = ObjectAnimator.ofFloat(this, "scaleY", pressedScale, 1.0f); + set.play(nScaleX).with(nScaleY); + set.start(); + } + } + + public void setPressedScale(float pressedScale) { + this.pressedScale = pressedScale; + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PreviewRecyclerView.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PreviewRecyclerView.java new file mode 100644 index 000000000..88949febd --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PreviewRecyclerView.java @@ -0,0 +1,60 @@ +package com.huantansheng.easyphotos.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +/** + * 图片预览 RecyclerView + * Create By lishilin On 2019/3/25 + */ +public class PreviewRecyclerView extends RecyclerView { + + private boolean isLock;// 是否锁住 RecyclerView ,避免和 PhotoView 双指放大缩小操作冲突 + + public PreviewRecyclerView(@NonNull Context context) { + super(context); + } + + public PreviewRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public PreviewRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_POINTER_DOWN:// 非第一个触点按下 + isLock = true; + break; + case MotionEvent.ACTION_UP:// 最后一个触点抬起 + isLock = false; + break; + } + if (isLock) { + return false;// 不拦截,交给子View处理 + } + return super.onInterceptTouchEvent(event); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_POINTER_DOWN:// 非第一个触点按下 + isLock = true; + break; + case MotionEvent.ACTION_UP:// 最后一个触点抬起 + isLock = false; + break; + } + return super.dispatchTouchEvent(event); + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/BitmapUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/BitmapUtils.java new file mode 100644 index 000000000..47dc57506 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/BitmapUtils.java @@ -0,0 +1,412 @@ +package com.huantansheng.easyphotos.utils.bitmap; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.MediaStore; +import android.text.TextPaint; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.exifinterface.media.ExifInterface; + +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.utils.uri.UriUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +/** + * bitmap工具类 + * Created by huan on 2017/9/4. + */ + +public class BitmapUtils { + /** + * 回收bitmap + * + * @param bitmap 回收的bitmap + */ + public static void recycle(Bitmap bitmap) { + if (null != bitmap && !bitmap.isRecycled()) { + bitmap.recycle(); + bitmap = null; + } + System.gc(); + } + + public static void recycle(Bitmap... bitmaps) { + for (Bitmap b : bitmaps) { + recycle(b); + } + } + + public static void recycle(List bitmaps) { + for (Bitmap b : bitmaps) { + recycle(b); + } + } + + /** + * 给图片添加水印,水印会根据图片宽高自动缩放处理 + * + * @param watermark 水印 + * @param image 添加水印的图片 + * @param offsetX 添加水印的X轴偏移量 + * @param offsetY 添加水印的Y轴偏移量 + * @param srcWaterMarkImageWidth 水印对应的原图片宽度,即ui制作水印时候参考的图片画布宽度,应该是已知的图片最大宽度 + * @param addInLeft true 在左下角添加水印,false 在右下角添加水印 + * @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap + * 不是用户手机内图片,填0即可。 + * @return 添加水印后的bitmap + */ + public static Bitmap addWatermark(Bitmap watermark, Bitmap image, int srcWaterMarkImageWidth, + int offsetX, int offsetY, boolean addInLeft, int orientation) { + int imageWidth = image.getWidth(); + int imageHeight = image.getHeight(); + if (0 == imageWidth || 0 == imageHeight) { + throw new RuntimeException("AlbumBuilder: 加水印的原图宽或高不能为0!"); + } + switch (orientation) { + case 90: + case 270: + image = adjustBitmapRotation(image, orientation); + int temp = imageHeight; + imageHeight = imageWidth; + imageWidth = temp; + break; + case 180: + image = adjustBitmapRotation(image, orientation); + break; + + } + int watermarkWidth = watermark.getWidth(); + int watermarkHeight = watermark.getHeight(); + float scale = imageWidth / (float) srcWaterMarkImageWidth; + if (scale > 1) scale = 1; + else if (scale < 0.4) scale = 0.4f; + int scaleWatermarkWidth = (int) (watermarkWidth * scale); + int scaleWatermarkHeight = (int) (watermarkHeight * scale); + Bitmap scaleWatermark = Bitmap.createScaledBitmap(watermark, scaleWatermarkWidth, + scaleWatermarkHeight, true); + Canvas canvas = new Canvas(image); + Paint paint = new Paint(); + paint.setAntiAlias(true); + if (addInLeft) { + canvas.drawBitmap(scaleWatermark, offsetX, + imageHeight - scaleWatermarkHeight - offsetY, paint); + } else { + canvas.drawBitmap(scaleWatermark, imageWidth - offsetX - scaleWatermarkWidth, + imageHeight - scaleWatermarkHeight - offsetY, paint); + } + recycle(scaleWatermark); + return image; + } + + /** + * 给图片添加带文字和图片的水印,水印会根据图片宽高自动缩放处理 + * + * @param watermark 水印图片 + * @param image 要加水印的图片 + * @param srcWaterMarkImageWidth 水印对应的原图片宽度,即ui制作水印时候参考的图片画布宽度,应该是已知的图片最大宽度 + * @param text 要添加的文字 + * @param offsetX 添加水印的X轴偏移量 + * @param offsetY 添加水印的Y轴偏移量 + * @param addInLeft true 在左下角添加水印,false 在右下角添加水印 + * @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap + * 不是用户手机内图片,填0即可。 + * @return 添加水印后的bitmap + */ + public static Bitmap addWatermarkWithText(@NonNull Bitmap watermark, Bitmap image, + int srcWaterMarkImageWidth, @NonNull String text, + int offsetX, int offsetY, boolean addInLeft, + int orientation) { + float imageWidth = image.getWidth(); + float imageHeight = image.getHeight(); + if (0 == imageWidth || 0 == imageHeight) { + throw new RuntimeException("AlbumBuilder: 加水印的原图宽或高不能为0!"); + } + switch (orientation) { + case 90: + case 270: + image = adjustBitmapRotation(image, orientation); + float temp = imageHeight; + imageHeight = imageWidth; + imageWidth = temp; + break; + case 180: + image = adjustBitmapRotation(image, orientation); + break; + + } + float watermarkWidth = watermark.getWidth(); + float watermarkHeight = watermark.getHeight(); + float scale = imageWidth / (float) srcWaterMarkImageWidth; + if (scale > 1) scale = 1; + else if (scale < 0.4) scale = 0.4f; + float scaleWatermarkWidth = watermarkWidth * scale; + float scaleWatermarkHeight = watermarkHeight * scale; + Bitmap scaleWatermark = Bitmap.createScaledBitmap(watermark, (int) scaleWatermarkWidth, + (int) scaleWatermarkHeight, true); + Canvas canvas = new Canvas(image); + + Paint textPaint = new TextPaint(); + textPaint.setAntiAlias(true); + textPaint.setColor(Color.WHITE); + float textsize = (float) (scaleWatermark.getHeight() * 2) / (float) 3; + textPaint.setTextSize(textsize); + Rect textRect = new Rect(); + textPaint.getTextBounds(text, 0, text.length(), textRect); + if (addInLeft) { + canvas.drawText(text, scaleWatermarkWidth + offsetX, + imageHeight - textRect.height() - textRect.top - offsetY, textPaint); + } else { + canvas.drawText(text, imageWidth - offsetX - textRect.width() - textRect.left, + imageHeight - textRect.height() - textRect.top - offsetY, textPaint); + } + + Paint sacleWatermarkPaint = new Paint(); + sacleWatermarkPaint.setAntiAlias(true); + if (addInLeft) { + canvas.drawBitmap(scaleWatermark, offsetX, + imageHeight - textRect.height() - offsetY - scaleWatermarkHeight / 6, + sacleWatermarkPaint); + } else { + canvas.drawBitmap(scaleWatermark, + imageWidth - textRect.width() - offsetX - scaleWatermarkWidth / 6, + imageHeight - textRect.height() - offsetY - scaleWatermarkHeight / 6, + sacleWatermarkPaint); + } + recycle(scaleWatermark); + return image; + } + + + /** + * 保存Bitmap到指定文件夹 + * + * @param act 上下文 + * @param dirPath 文件夹全路径 + * @param bitmap bitmap + * @param namePrefix 保存文件的前缀名,文件最终名称格式为:前缀名+自动生成的唯一数字字符+.png + * @param notifyMedia 是否更新到媒体库 + * @param callBack 保存图片后的回调,回调已经处于UI线程 + */ + public static void saveBitmapToDir(final Activity act, final String dirPath, + final String namePrefix, final Bitmap bitmap, + final boolean notifyMedia, + final SaveBitmapCallBack callBack) { + new Thread(new Runnable() { + @Override + public void run() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + //android10+ + saveBitmapToDirQ(act, dirPath, namePrefix, bitmap, callBack); + return; + } + + File dirF = new File(dirPath); + if (!dirF.exists() || !dirF.isDirectory()) { + if (!dirF.mkdirs()) { + act.runOnUiThread(new Runnable() { + @Override + public void run() { + callBack.onCreateDirFailed(); + } + }); + return; + } + } + + try { + final File writeFile = File.createTempFile(namePrefix, ".png", dirF); + + FileOutputStream fos = null; + fos = new FileOutputStream(writeFile); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.flush(); + fos.close(); + if (notifyMedia) { + EasyPhotos.notifyMedia(act, writeFile); + } + act.runOnUiThread(new Runnable() { + @Override + public void run() { + callBack.onSuccess(writeFile); + } + }); + + } catch (final IOException e) { + act.runOnUiThread(new Runnable() { + @Override + public void run() { + callBack.onIOFailed(e); + } + }); + + } + } + }).start(); + + } + + private static void saveBitmapToDirQ(final Activity act, final String dirPath, + final String namePrefix, final Bitmap bitmap, + final SaveBitmapCallBack callBack) { + long dataTake = System.currentTimeMillis(); + String pngName = namePrefix + dataTake + ".png"; + + ContentValues values = new ContentValues(); + values.put(MediaStore.Images.Media.DISPLAY_NAME, pngName); + values.put(MediaStore.Images.Media.MIME_TYPE, "image/png"); + int dirIndex = dirPath.lastIndexOf("/"); + if (dirIndex == dirPath.length()) { + String dirPath2 = dirPath.substring(0, dirIndex - 1); + dirIndex = dirPath2.lastIndexOf("/"); + } + String dirName = dirPath.substring(dirIndex + 1); + values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM + File.separator + dirName); + + Uri external; + ContentResolver resolver = act.getContentResolver(); + String status = Environment.getExternalStorageState(); + // 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储 + if (status.equals(Environment.MEDIA_MOUNTED)) { + external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else { + external = MediaStore.Images.Media.INTERNAL_CONTENT_URI; + } + + final Uri insertUri = resolver.insert(external, values); + if (insertUri == null) { + act.runOnUiThread(new Runnable() { + @Override + public void run() { + callBack.onCreateDirFailed(); + } + }); + return; + } + OutputStream os; + try { + os = resolver.openOutputStream(insertUri); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, os); + if (os != null) { + os.flush(); + os.close(); + } + act.runOnUiThread(new Runnable() { + @Override + public void run() { + String uriPath = UriUtils.getPathByUri(act, insertUri); + if (null == uriPath) { + callBack.onCreateDirFailed(); + } else { + callBack.onSuccess(new File(uriPath)); + } + } + }); + } catch (final IOException e) { + e.printStackTrace(); + act.runOnUiThread(new Runnable() { + @Override + public void run() { + callBack.onIOFailed(e); + } + }); + } + } + + + /** + * 把View画成Bitmap + * + * @param view 要处理的View + * @return Bitmap + */ + public static Bitmap createBitmapFromView(View view) { + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + + return bitmap; + } + + /** + * 计算获取的照片的宽高是否需要交换,如果图片是旋转了90度或270度的,那么就需要交换 + * + * @param photo 需要计算的图片 + * @return 宽高是否需要交换 + */ + public static Boolean needChangeWidthAndHeight(Photo photo) throws IOException { + + ExifInterface exifInterface = new ExifInterface(photo.path); + int exifOrientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL); + + // 如果拿到的照片是旋转了90度或270度的,意味着通过MediaStore获取的宽高需要交换 + return exifOrientation == ExifInterface.ORIENTATION_ROTATE_90 || exifOrientation == ExifInterface.ORIENTATION_ROTATE_270; + + } + + /** + * 通过BitmapFactory.Options重设图片的宽高,在通过MediaStore获取的图片宽高为0时使用 + * + * @param photo 需要计算的图片 + */ + public static void calculateLocalImageSizeThroughBitmapOptions(Photo photo) throws IOException { + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(photo.path, options); + + photo.width = options.outWidth; + photo.height = options.outHeight; + } + + public static Bitmap adjustBitmapRotation(Bitmap bm, final int orientationDegree) { + + Matrix m = new Matrix(); + m.setRotate(orientationDegree, (float) bm.getWidth() / 2, (float) bm.getHeight() / 2); + float targetX, targetY; + if (orientationDegree == 90) { + targetX = bm.getHeight(); + targetY = 0; + } else { + targetX = bm.getHeight(); + targetY = bm.getWidth(); + } + + final float[] values = new float[9]; + m.getValues(values); + + float x1 = values[Matrix.MTRANS_X]; + float y1 = values[Matrix.MTRANS_Y]; + + m.postTranslate(targetX - x1, targetY - y1); + + Bitmap bm1 = Bitmap.createBitmap(bm.getHeight(), bm.getWidth(), Bitmap.Config.ARGB_8888); + + Paint paint = new Paint(); + Canvas canvas = new Canvas(bm1); + canvas.drawBitmap(bm, m, paint); + + + return bm1; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/SaveBitmapCallBack.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/SaveBitmapCallBack.java new file mode 100644 index 000000000..9d01f4033 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/SaveBitmapCallBack.java @@ -0,0 +1,17 @@ +package com.huantansheng.easyphotos.utils.bitmap; + +import java.io.File; +import java.io.IOException; + +/** + * 保存图片到本地的回调 + * Created by huan on 2017/12/6. + */ + +public interface SaveBitmapCallBack { + void onSuccess(File file); + + void onIOFailed(IOException exception); + + void onCreateDirFailed(); +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/color/ColorUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/color/ColorUtils.java new file mode 100644 index 000000000..581c5cfa7 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/color/ColorUtils.java @@ -0,0 +1,37 @@ +package com.huantansheng.easyphotos.utils.color; + +/** + * Created by huan on 2018/1/9. + */ + +public class ColorUtils { + + /** + * 判断颜色是否偏黑色 + * + * @param color 颜色 + * @return + */ + public static boolean isBlackColor(int color) { + int grey = toGrey(color); + return grey < 50; + } + + /** + * 颜色转换成灰度值 + * + * @param rgb 颜色 + * @return 灰度值 + */ + public static int toGrey(int rgb) { + int blue = rgb & 0x000000FF; + int green = (rgb & 0x0000FF00) >> 8; + int red = (rgb & 0x00FF0000) >> 16; + return (red * 38 + green * 75 + blue * 15) >> 7; + } + + public static boolean isWhiteColor(int color) { + int grey = toGrey(color); + return grey > 200; + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/media/DurationUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/media/DurationUtils.java new file mode 100644 index 000000000..7b150ceaf --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/media/DurationUtils.java @@ -0,0 +1,48 @@ +package com.huantansheng.easyphotos.utils.media; + +import android.media.MediaMetadataRetriever; +import android.text.format.DateUtils; + +/** + * DurationUtils + * Create By lishilin On 2019/3/25 + */ +public class DurationUtils { + + /** + * 获取时长 + * + * @param path path + * @return duration + */ + public static long getDuration(String path) { + MediaMetadataRetriever mmr = null; + try { + mmr = new MediaMetadataRetriever(); + mmr.setDataSource(path); + return Long.parseLong(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); + } catch (Exception e) { +// e.printStackTrace(); + } finally { + if (mmr != null) { + mmr.release(); + } + } + return 0; + } + + /** + * 格式化时长(不足一秒则显示为一秒) + * + * @param duration duration + * @return "MM:SS" or "H:MM:SS" + */ + public static String format(long duration) { + long seconds = duration / 1000; + if (seconds == 0) { + seconds++; + } + return DateUtils.formatElapsedTime(seconds); + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/media/MediaScannerConnectionUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/media/MediaScannerConnectionUtils.java new file mode 100644 index 000000000..6dc0cfe55 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/media/MediaScannerConnectionUtils.java @@ -0,0 +1,36 @@ +package com.huantansheng.easyphotos.utils.media; + +import android.content.Context; +import android.media.MediaScannerConnection; + +import java.io.File; +import java.util.List; + +/** + * 更新媒体库 + * Created by huan on 2017/8/1. + */ + +public class MediaScannerConnectionUtils { + + public static void refresh(Context cxt, String... filePaths) { + MediaScannerConnection.scanFile(cxt.getApplicationContext(), + filePaths, null, + null); + } + + + public static void refresh(Context cxt, File... files) { + for (File file : files) { + String filePath = file.getAbsolutePath(); + refresh(cxt, filePath); + } + } + + public static void refresh(Context cxt, List filePathList) { + for (String filePath : filePathList) { + refresh(cxt, filePath); + } + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/permission/PermissionUtil.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/permission/PermissionUtil.java new file mode 100644 index 000000000..0acab380c --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/permission/PermissionUtil.java @@ -0,0 +1,88 @@ +package com.huantansheng.easyphotos.utils.permission; + +import android.app.Activity; +import android.content.pm.PackageManager; + +import androidx.annotation.NonNull; +import androidx.core.app.ActivityCompat; +import androidx.core.content.PermissionChecker; + +import com.huantansheng.easyphotos.constant.Code; + +import java.util.ArrayList; +import java.util.List; + +/** + * 运行时权限工具类 + * Created by huan on 2017/7/27. + */ + +public class PermissionUtil { + + + public interface PermissionCallBack { + void onSuccess(); + + void onShouldShow(); + + void onFailed(); + } + + public static boolean checkAndRequestPermissionsInActivity(Activity cxt, + String... checkPermissions) { + boolean isHas = true; + List permissions = new ArrayList<>(); + for (String checkPermission : checkPermissions) { + if (PermissionChecker.checkSelfPermission(cxt, checkPermission) != PermissionChecker.PERMISSION_GRANTED) { + isHas = false; + permissions.add(checkPermission); + } + } + if (!isHas) { + String[] p = permissions.toArray(new String[permissions.size()]); + requestPermissionsInActivity(cxt, Code.REQUEST_PERMISSION, p); + } + return isHas; + } + + private static void requestPermissionsInActivity(Activity cxt, int requestCode, + String... permissions) { + ActivityCompat.requestPermissions(cxt, permissions, requestCode); + } + + public static void onPermissionResult(Activity cxt, @NonNull String[] permissions, + @NonNull int[] grantResults, + PermissionCallBack listener) { + int length = grantResults.length; + List positions = new ArrayList<>(); + if (length > 0) { + for (int i = 0; i < length; i++) { + if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { + positions.add(i); + } + } + } + if (positions.size() == 0) { + listener.onSuccess(); + return; + } + progressNoPermission(cxt, listener, permissions, positions, 0); + + } + + private static void progressNoPermission(Activity cxt, PermissionCallBack listener, + String[] permissions, List positions, int i) { + int index = positions.get(i); + if (ActivityCompat.shouldShowRequestPermissionRationale(cxt, permissions[index])) { + listener.onShouldShow(); + return; + } + if (i < positions.size() - 1) { + i++; + progressNoPermission(cxt, listener, permissions, positions, i); + return; + } + listener.onFailed(); + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/result/EasyResult.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/result/EasyResult.java new file mode 100644 index 000000000..a17de5d16 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/result/EasyResult.java @@ -0,0 +1,45 @@ +package com.huantansheng.easyphotos.utils.result; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +/** + * EasyResult + * + * @author joker + * @date 2019/4/9. + */ +public class EasyResult { + private static final String TAG = "com.huantansheng.easyphotos"; + + private EasyResult() { + + } + + public static HolderFragment get(FragmentActivity activity) { + return new EasyResult().getHolderFragment(activity.getSupportFragmentManager()); + } + + public static HolderFragment get(Fragment fragment) { + return new EasyResult().getHolderFragment(fragment.getChildFragmentManager()); + } + + private HolderFragment getHolderFragment(FragmentManager fragmentManager) { + HolderFragment holderFragment = findHolderFragment(fragmentManager); + if (holderFragment == null) { + holderFragment = new HolderFragment(); + fragmentManager + .beginTransaction() + .add(holderFragment, TAG) + .commitAllowingStateLoss(); + fragmentManager.executePendingTransactions(); + } + return holderFragment; + } + + private HolderFragment findHolderFragment(FragmentManager fragmentManager) { + return (HolderFragment) fragmentManager.findFragmentByTag(TAG); + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/result/HolderFragment.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/result/HolderFragment.java new file mode 100644 index 000000000..ebad4dfd9 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/result/HolderFragment.java @@ -0,0 +1,86 @@ +package com.huantansheng.easyphotos.utils.result; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.callback.PuzzleCallback; +import com.huantansheng.easyphotos.callback.SelectCallback; +import com.huantansheng.easyphotos.engine.ImageEngine; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.ui.EasyPhotosActivity; +import com.huantansheng.easyphotos.ui.PuzzleActivity; + +import java.util.ArrayList; + +/** + * HolderFragment + * + * @author joker + * @date 2019/4/9. + */ +public class HolderFragment extends Fragment { + + private static final int HOLDER_SELECT_REQUEST_CODE = 0x44; + private static final int HOLDER_PUZZLE_REQUEST_CODE = 0x55; + private SelectCallback mSelectCallback; + private PuzzleCallback mPuzzleCallback; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + public void startEasyPhoto(SelectCallback callback) { + mSelectCallback = callback; + EasyPhotosActivity.start(this, HOLDER_SELECT_REQUEST_CODE); + } + + public void startPuzzleWithPhotos(ArrayList photos, String puzzleSaveDirPath, String puzzleSaveNamePrefix, boolean replaceCustom, @NonNull ImageEngine imageEngine, PuzzleCallback callback) { + mPuzzleCallback = callback; + PuzzleActivity.startWithPhotos(this, photos, puzzleSaveDirPath, puzzleSaveNamePrefix, HOLDER_PUZZLE_REQUEST_CODE, replaceCustom, imageEngine); + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (Activity.RESULT_OK == resultCode) { + switch (requestCode) { + case HOLDER_SELECT_REQUEST_CODE: + if (mSelectCallback != null) { + ArrayList resultPhotos = data.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS); + boolean selectedOriginal = data.getBooleanExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, false); + mSelectCallback.onResult(resultPhotos, selectedOriginal); + } + break; + case HOLDER_PUZZLE_REQUEST_CODE: + if (mPuzzleCallback != null) { + Photo puzzlePhoto = data.getParcelableExtra(EasyPhotos.RESULT_PHOTOS); + mPuzzleCallback.onResult(puzzlePhoto); + } + break; + } + return; + } + if (Activity.RESULT_CANCELED == resultCode) { + switch (requestCode) { + case HOLDER_SELECT_REQUEST_CODE: + if (mSelectCallback != null) { + mSelectCallback.onCancel(); + } + break; + case HOLDER_PUZZLE_REQUEST_CODE: + if (mPuzzleCallback != null) { + mPuzzleCallback.onCancel(); + } + break; + } + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/settings/SettingsUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/settings/SettingsUtils.java new file mode 100644 index 000000000..55084916f --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/settings/SettingsUtils.java @@ -0,0 +1,27 @@ +package com.huantansheng.easyphotos.utils.settings; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.provider.Settings; + +import com.huantansheng.easyphotos.constant.Code; + +/** + * 系统设置界面启动器 + * Created by huan on 2017/8/7. + */ + +public class SettingsUtils { + + /** + * 启动应用详情界面 + * @param cxt 上下文 + * @param packageName 应用包名 + */ + public static void startMyApplicationDetailsForResult(Activity cxt, String packageName) { + Uri packageUri = Uri.parse("package:" + packageName); + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageUri); + cxt.startActivityForResult(intent, Code.REQUEST_SETTING_APP_DETAILS); + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/string/StringUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/string/StringUtils.java new file mode 100644 index 000000000..2313d28d6 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/string/StringUtils.java @@ -0,0 +1,19 @@ +package com.huantansheng.easyphotos.utils.string; + +/** + * 字符串工具类 + * Created by huan on 2017/10/20. + */ +public class StringUtils { + public static String getLastPathSegment(String content) { + if (content == null || content.length() == 0) { + return ""; + } + String[] segments = content.split("/"); + if (segments.length > 0) { + return segments[segments.length - 1]; + } + return ""; + } + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/system/MeiZuStatusUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/system/MeiZuStatusUtils.java new file mode 100644 index 000000000..d6c927135 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/system/MeiZuStatusUtils.java @@ -0,0 +1,229 @@ +package com.huantansheng.easyphotos.utils.system; + +import android.app.Activity; +import android.os.Build; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Created by wangchende on 15-9-7. + */ +public class MeiZuStatusUtils { + private static Method mSetStatusBarColorIcon; + private static Method mSetStatusBarDarkIcon; + private static Field mStatusBarColorFiled; + private static int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0; + + static { + try { + mSetStatusBarColorIcon = Activity.class.getMethod("setStatusBarDarkIcon", int.class); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + try { + mSetStatusBarDarkIcon = Activity.class.getMethod("setStatusBarDarkIcon", boolean.class); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + try { + mStatusBarColorFiled = WindowManager.LayoutParams.class.getField("statusBarColor"); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + try { + Field field = View.class.getField("SYSTEM_UI_FLAG_LIGHT_STATUS_BAR"); + SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = field.getInt(null); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * 判断颜色是否偏黑色 + * + * @param color 颜色 + * @param level 级别 + * @return + */ + public static boolean isBlackColor(int color, int level) { + int grey = toGrey(color); + return grey < level; + } + + /** + * 颜色转换成灰度值 + * + * @param rgb 颜色 + * @return 灰度值 + */ + public static int toGrey(int rgb) { + int blue = rgb & 0x000000FF; + int green = (rgb & 0x0000FF00) >> 8; + int red = (rgb & 0x00FF0000) >> 16; + return (red * 38 + green * 75 + blue * 15) >> 7; + } + + /** + * 设置状态栏字体图标颜色 + * + * @param activity 当前activity + * @param color 颜色 + */ + public static void setStatusBarDarkIcon(Activity activity, int color) { + if (mSetStatusBarColorIcon != null) { + try { + mSetStatusBarColorIcon.invoke(activity, color); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } else { + boolean whiteColor = isBlackColor(color, 50); + if (mStatusBarColorFiled != null) { + setStatusBarDarkIcon(activity, whiteColor, whiteColor); + setStatusBarDarkIcon(activity.getWindow(), color); + } else { + setStatusBarDarkIcon(activity, whiteColor); + } + } + } + + /** + * 设置状态栏字体图标颜色(只限全屏非activity情况) + * + * @param window 当前窗口 + * @param color 颜色 + */ + public static void setStatusBarDarkIcon(Window window, int color) { + try { + setStatusBarColor(window, color); + if (Build.VERSION.SDK_INT > 22) { + setStatusBarDarkIcon(window.getDecorView(), true); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 设置状态栏字体图标颜色 + * + * @param activity 当前activity + * @param dark 是否深色 true为深色 false 为白色 + */ + public static void setStatusBarDarkIcon(Activity activity, boolean dark) { + setStatusBarDarkIcon(activity, dark, true); + } + + private static boolean changeMeizuFlag(WindowManager.LayoutParams winParams, String flagName, boolean on) { + try { + Field f = winParams.getClass().getDeclaredField(flagName); + f.setAccessible(true); + int bits = f.getInt(winParams); + Field f2 = winParams.getClass().getDeclaredField("meizuFlags"); + f2.setAccessible(true); + int meizuFlags = f2.getInt(winParams); + int oldFlags = meizuFlags; + if (on) { + meizuFlags |= bits; + } else { + meizuFlags &= ~bits; + } + if (oldFlags != meizuFlags) { + f2.setInt(winParams, meizuFlags); + return true; + } + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (Throwable e) { + e.printStackTrace(); + } + return false; + } + + /** + * 设置状态栏颜色 + * + * @param view + * @param dark + */ + private static void setStatusBarDarkIcon(View view, boolean dark) { + int oldVis = view.getSystemUiVisibility(); + int newVis = oldVis; + if (dark) { + newVis |= SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else { + newVis &= ~SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + if (newVis != oldVis) { + view.setSystemUiVisibility(newVis); + } + } + + /** + * 设置状态栏颜色 + * + * @param window + * @param color + */ + private static void setStatusBarColor(Window window, int color) { + WindowManager.LayoutParams winParams = window.getAttributes(); + if (mStatusBarColorFiled != null) { + try { + int oldColor = mStatusBarColorFiled.getInt(winParams); + if (oldColor != color) { + mStatusBarColorFiled.set(winParams, color); + window.setAttributes(winParams); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + /** + * 设置状态栏字体图标颜色(只限全屏非activity情况) + * + * @param window 当前窗口 + * @param dark 是否深色 true为深色 false 为白色 + */ + public static void setStatusBarDarkIcon(Window window, boolean dark) { + if (Build.VERSION.SDK_INT < 23) { + changeMeizuFlag(window.getAttributes(), "MEIZU_FLAG_DARK_STATUS_BAR_ICON", dark); + } else { + View decorView = window.getDecorView(); + if (decorView != null) { + setStatusBarDarkIcon(decorView, dark); + setStatusBarColor(window, 0); + } + } + } + + private static void setStatusBarDarkIcon(Activity activity, boolean dark, boolean flag) { + if (mSetStatusBarDarkIcon != null) { + try { + mSetStatusBarDarkIcon.invoke(activity, dark); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } else { + if (flag) { + setStatusBarDarkIcon(activity.getWindow(), dark); + } + } + } +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/system/SystemUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/system/SystemUtils.java new file mode 100644 index 000000000..d548c86ed --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/system/SystemUtils.java @@ -0,0 +1,242 @@ +package com.huantansheng.easyphotos.utils.system; + +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * 系统工具类 + * Created by huan on 2017/11/13. + */ + +public class SystemUtils { + private static SystemUtils instance = null; + private Boolean hasNavigation = null;//是否有导航栏 + + /** + * 私有构造方法 + */ + private SystemUtils() { + } + + /** + * 获取单例 + * + * @return 单例 + */ + public static SystemUtils getInstance() { + if (null == instance) { + synchronized (SystemUtils.class) { + if (null == instance) { + instance = new SystemUtils(); + } + } + } + return instance; + } + + /** + * 是否有导航栏 + * + * @return 有或没有 + */ + public boolean hasNavigationBar(Activity activity) { + if (null == hasNavigation) { + int windowHeight = activity.getResources().getDisplayMetrics().heightPixels; + DisplayMetrics dm = new DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getRealMetrics(dm); + int screenHeight = dm.heightPixels; + hasNavigation = screenHeight - windowHeight > 0; + } + + return hasNavigation; + } + + /** + * 全屏显示的初始化,在setContentView()方法前调用 + * + * @param decorView getWindow().getDecorView(),不同view也可以 + */ + public void systemUiInit(Activity activity, View decorView) { + if (!hasNavigationBar(activity)) { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + return; + } + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + } + + private void hideStatusBar(Activity activity) { + WindowManager.LayoutParams attrs = activity.getWindow().getAttributes(); + attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + activity.getWindow().setAttributes(attrs); + } + + private void showStatusBar(Activity activity) { + WindowManager.LayoutParams attrs = activity.getWindow().getAttributes(); + attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN; + activity.getWindow().setAttributes(attrs); + } + + + /** + * 隐藏导航栏和状态栏 + * + * @param activity 上下文 + * @param decorView getWindow().getDecorView(),不同view也可以 + */ + public void systemUiHide(Activity activity, View decorView) { + + if (!hasNavigationBar(activity)) { + hideStatusBar(activity); + return; + } + + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + + } + + /** + * 显示导航栏和状态栏 + * + * @param activity 上下文 + * @param decorView getWindow().getDecorView(),不同view也可以 + */ + public void systemUiShow(Activity activity, View decorView) { + if (!hasNavigationBar(activity)) { + showStatusBar(activity); + return; + } + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + } + + /** + * 获取状态栏高度 + * + * @param cxt 上下文 + * @return 状态栏高度,单位PX + */ + public int getStatusBarHeight(Context cxt) { + int statusBarHeight = 0; + int resourceId = cxt.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + statusBarHeight = cxt.getResources().getDimensionPixelSize(resourceId); + } else { + statusBarHeight = (int) (23 * (cxt.getResources().getDisplayMetrics().density) + 0.5f); + } + return statusBarHeight; + } + + public void setStatusDark(Activity activity, boolean darkmode) { + if (isFlymeV4OrAbove()) { + MeiZuStatusUtils.setStatusBarDarkIcon(activity, darkmode); + return; + } + if (isMIUIV6OrAbove()) { + setStatusTextBlackMi(activity, darkmode); + return; + } + setStatusTextBlackAndroid(activity, darkmode); + } + + private void setStatusTextBlackMi(Activity activity, boolean darkmode) { + Class clazz = activity.getWindow().getClass(); + try { + int darkModeFlag = 0; + Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); + Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); + darkModeFlag = field.getInt(layoutParams); + Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); + extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag : 0, darkModeFlag); + } catch (Exception e) { + e.printStackTrace(); + } + setStatusTextBlackAndroid(activity, darkmode); + } + + + private void setStatusTextBlackAndroid(Activity activity, boolean darkmode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Window window = activity.getWindow(); + if (darkmode) { + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } else { + int flag = window.getDecorView().getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + window.getDecorView().setSystemUiVisibility(flag); + } + } + } + + + private boolean isFlymeV4OrAbove() { + String displayId = Build.DISPLAY; + if (!TextUtils.isEmpty(displayId) && displayId.contains("Flyme")) { + String[] displayIdArray = displayId.split(" "); + for (String temp : displayIdArray) { + //版本号4以上,形如4.x. + if (temp.matches("^[4-9]\\.(\\d+\\.)+\\S*")) { + return true; + } + } + } + return false; + } + + //MIUI V6对应的versionCode是4 + //MIUI V7对应的versionCode是5 + private boolean isMIUIV6OrAbove() { + String miuiVersionCodeStr = getSystemProperty("ro.miui.ui.version.code"); + if (!TextUtils.isEmpty(miuiVersionCodeStr)) { + try { + int miuiVersionCode = Integer.parseInt(miuiVersionCodeStr); + if (miuiVersionCode >= 4) { + return true; + } + } catch (Exception e) { + } + } + return false; + } + + private String getSystemProperty(String propName) { + String line; + BufferedReader input = null; + try { + Process p = Runtime.getRuntime().exec("getprop " + propName); + input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); + line = input.readLine(); + input.close(); + } catch (IOException ex) { + return null; + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + } + } + } + return line; + } + + +} diff --git a/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/uri/UriUtils.java b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/uri/UriUtils.java new file mode 100644 index 000000000..afda83545 --- /dev/null +++ b/easyphotos/src/main/java/com/huantansheng/easyphotos/utils/uri/UriUtils.java @@ -0,0 +1,174 @@ +package com.huantansheng.easyphotos.utils.uri; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.util.Log; + +import androidx.core.content.FileProvider; + +import com.huantansheng.easyphotos.setting.Setting; + +import java.io.File; +import java.util.Locale; + +public class UriUtils { + public static Uri getUri(Context cxt, File file) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (null == Setting.fileProviderAuthority) { + throw new NullPointerException("Setting.fileProviderAuthority must not be null."); + } + return FileProvider.getUriForFile(cxt, Setting.fileProviderAuthority, file); + } else { + return Uri.fromFile(file); + } + } + + private static final String TAG = "UriUtils"; + + public static String getPathByUri(Context context, Uri uri) { + // DocumentProvider + if (DocumentsContract.isDocumentUri(context, uri)) { + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } else { + return context.getExternalFilesDir(Environment.DIRECTORY_DCIM) + "/" + split[1]; + } + } + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.parseLong(id)); + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{split[1]}; + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + // Return the remote address + if (isGooglePhotosUri(uri)) { + return uri.getLastPathSegment(); + } + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + return null; + } + + private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + String[] projection = {MediaStore.MediaColumns.DATA}; + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + return cursor.getString(column_index); + } + } catch (IllegalArgumentException ex) { + Log.i(TAG, String.format(Locale.getDefault(), "getDataColumn: _data - [%s]", ex.getMessage())); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + private static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + private static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + private static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * 判断是否是Google相册的图片,类似于content://com.google.android.apps.photos.content/... + **/ + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + /** + * 判断是否是Google相册的图片,类似于content://com.google.android.apps.photos.contentprovider/0/1/mediakey:/local%3A821abd2f-9f8c-4931-bbe9-a975d1f5fabc/ORIGINAL/NONE/1075342619 + **/ + public static boolean isGooglePlayPhotosUri(Uri uri) { + return "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority()); + } + + /** + * 图片路径转uri + */ + public static Uri getUriByPath(Context context, String path) { + ContentResolver contentResolver = context.getContentResolver(); + Uri contentUri = MediaStore.Files.getContentUri("external"); + Cursor cursor = contentResolver.query(contentUri, new String[]{MediaStore.Files.FileColumns._ID}, + MediaStore.Files.FileColumns.DATA + "=? ", new String[]{path}, + null); + if (cursor != null && cursor.moveToFirst()) { + int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID)); + cursor.close(); + return Uri.withAppendedPath(contentUri, "" + id); + } else { + if (new File(path).exists()) { + ContentValues values = new ContentValues(); + values.put(MediaStore.Images.Media.DATA, path); + return contentResolver.insert(contentUri, values); + } else { + return null; + } + } + } +} diff --git a/easyphotos/src/main/res/drawable-xhdpi/ic_controller_easy_photos.png b/easyphotos/src/main/res/drawable-xhdpi/ic_controller_easy_photos.png new file mode 100644 index 0000000000000000000000000000000000000000..3a78517a4fe0b52da8bcfb111303a9d80cc618d2 GIT binary patch literal 896 zcmeAS@N?(olHy`uVBq!ia0vp^ZXnFT3?e@WZ39v)>5jgR3=A9lx&I`x0{M)^LGDfr z>(0r%1aeved_r8^3w-_)2cBEmeqFWt^PG7fTG~F(o%ie3tv}D6|NZ>=g`3BR=GIq1 zp+C=^|9Vea5$~+g>=jyopcxx^nfu&tKkV z=lyy9{Nsd)pC(Syb*r=jx`($U$S;^-!UYF~h6f)63>E|&*#C@SJ_7?&qo<2wNW|f_ zGj307QV?m|x?ziu>E$gaH$A%`bMOD(w&q!f*byu2Now z$JngIz4#VC5j6{%s%_Czq_uJ3Dd7{2Q$F?Se(|$xn(r*NSa_Sz)^x*O>$B7MY&ksT ziv2&8^2{EsoRwY2vZtI^T-#v0?b^(1M~fpF&+dBo@VKqy>tzow*zg<+)SB0~)8G2c z;)^?$OSon`$H`p{R4Q5JA6q-|`RsGXM`kdGB}J{R_Sn1r@IhmD$;) zy*4Kg^GuH1!u=|lp}6`;?q@4D^Z8+CS0DfLX!;Y0$x(IR?^+x%u`jHtmi_F$&qJkW zPThumuV$%Vjytm9%j9mxu)FF@TM~~dOkXB-*g~jn>asf;3B0w=M?SbU`qbVjcHzm7 zE)t&mutVaW%hPFghclPY*tgXE+04mbqy3fg4K9_XAMkwg*6&u$L9QQ5jgR3=A9lx&I`x0{M)^LGDfr z>(0r%1ai^>d_r8^3w-_)0lK@pfvP4=n)K?`tD{GczI^#|+qP{VK79D`R5=ZE8!8dUChKptD!R?-Ft}J8Vd2FM3X3AQzc(2$4VNXLj zp1ZzWD|Kb5PEuBZ;62MdNfj$DtXMJ0)#rwqxvT3`m*1{iPMGn#YVHhw$@ZbRMucJN zr^7)~vmY<_DCE1Cws7y+opF0bUbuemGHBK9o4?59TY^qs_b0*Rf;m&fpB=yJKX229 z)w|^dlRKJz#dkIf`GwTEv^T!U5HjUjw9WO3%f@Nc`{{Y(*^ zAMP1*3Gk|gBnw@<;AZM1#JeTrvFX(f{v}@*?BWQFJ-tG&GbB806}QRN(s{268{e#K me)X{Rjbz80K&?NWZ^Y$vFQ#T19BBt8AqG!ZKbLh*2~7Z-dw;zE literal 0 HcmV?d00001 diff --git a/easyphotos/src/main/res/drawable-xhdpi/ic_editor_easy_photos.png b/easyphotos/src/main/res/drawable-xhdpi/ic_editor_easy_photos.png new file mode 100644 index 0000000000000000000000000000000000000000..f8fa4d416b0678f5ea0b91e9b0a493e43afaf4fd GIT binary patch literal 3538 zcmX|E2|QF^8y-88N+MerqwHppiJ3x%MD{VszRV!|GPVgZ)|7n-*(&=|2q8;`vSk~L zq{vXnlBp2!$2Y$3|NGAGch5cN+;gAjecp54`=0y2#OTIJpfC^s0G!m*)y6RHI>#3$ zJEQi4EW`i+O!{tEivSBl0~FrJ3*zYHgL8%uy?hzZ0f4I-L|;d|hjRc3=X}S_TMbNa z>jZ<`oYcU#5r!~BUoB@>H{B3|vss7{79Zk)M>>Hu)PYxtCcQ4W_@QtYeQBvq?cD z4UpUm2aE7;!I!6pU3Rupn(wyn)aTvvX~=s=H*g`09{7!@h0fhuE(je#@F}+{TRwX-K>FSWvqTS}rzFp8-~#)EjRW|nzQYBI zmR9q*i`(q%m8+1}_QDP_#Gzr-)+7z>#}6wB>1k;flY{|f*n0t4nMy5DJawWLC)T%K zp8yYV^ZN9Z7{DR5ecT2dSs-;OKZzK0%xHn6O?aWMmm*qA5{-|RNgYn^@_w6BkW?8A zHMqCuP>aNa7iyl-?xk7xxBPYC4hn7mP#(#U6J?8XO40Ml#@W~zi_`cW2-WX>b7=EW z*O@obPlGkkupDJ^B)7S|`HrtAUI*V1$eDxp&Y&|H@6WI;^Co2z81f1^NP*W>khZSw zMSg3S|Dm^Q?Y2WJU%u+54#whFv7WJbxRX;wb#>&ldT>{$S5lhNu}|ddc!_9J2~K72 z&qlMK0=4DF|J28G;qAmm7A!yZCNo8A+8(giQI;X`ubs?_1hc37`F~2#vL^C64|8)L z)$*Xi(VG@4BxlMs!Z`>zY&LMU>VVApq>WbJ3NFMYT0Pd;$+*}52+X-V_oaXFaZf=F zABpnotWBY1;?a*Jkjy%Hp8V$fd1cKEToFr1YDXMf=aZ@){R-<2oqdrqNy?1D>?#7Q zXctA>8T8m=5qt4ouj1GWL>(HCq6`$#*9A6&Zy;R(pf$y@W6cN3CU40 z_Ggbib|lZZrTwa#;}zj~dT06XfpSj!_6yRdwHiAYz#G=G?&Z3dNhb7ioVV=oTv#5n z-xaYXk6H=(@GZgm^Mv0QXa^QQ@HGr8Hzgf9w0T&ovJVb9s`*GuK5t6*f(NNS)!WzR zEPi}dyIISORAf7fOR~ikBZBD3_*K61*C*iHlm9F|;+cs69K{(DFsJHJ=AX?|(g)XG z3w)f$oGO46s#1*~C z)XuH_Xj)BwHWa!c<@3WnAW1p~y^s$E?hF&i7o;o1OF7bn z{7zao@uC8)1ioV5It-K5{laEK21!icuI?w+z_rSaCh8U?k(C+?GZ%FqKG<{0Zpw_k zM%vqsnCO9t##5<;mBRsdYi%a$c~u3Kf3Q<6+iO#jtjvUQ-fMfzk7{#F`@^1}6!(6zM!v%(^;ZMIwu2HRF>60_~y0uY{$%a1aoco1e*=0hykT=c!Z z{t<0`T*5qZ*Ekj7ABP>WgNgU?^%Tlj(Jr~j{>dj78oPe1N5~c?T?u-h@HoCTmCsBD z{%1d9JUlcF5o#`fkyN3u&$?(Wi?KJp?#GpuD~y&l#vJ6$NewqMNMk}`LyLIGAkHYR z76WRq$W};Yh>PBMVtQmliQtW~w_Z(^cioUl;$CKyMC7!#yZh(i&d&*h{q1YZyz-z? zPHycbiZ?3c`E24A*>uYnT?+W7V3~L?>{(P6uoR#yimY;A<5mV2UexClJ5ahf)JV2O z3Ea_px%6i`L0~@Lns;!17NT6BS}AoB4sNiGF^ONgbO-ZZS*+e=gbe>7F?*oo-40)T z=%((^F2R?c!7j`+P%5S#YII;{d?MdpzdyuNXCY3GHRwlKkyFjymGT45`&<#h=0*c5 z;LRH`qvdm#mUAxi_ieeg?z08a#||#O;Bb7hkWpMKWa+9?QibwL)C>ztx0Gf053{)- zJ`Nh=uUHCybtX0JURrK$BRuFTCUfR%!_GvbJe$?9+ThS5U8(pIWs4PH_H;x4-lXIM zkt)hA-Il5l3O~u)Ln6!(+|?f#oJ)2)eX%r?e5Wwk>?XJ1nXj{ePYmyOLa*idDNR+Vkm>{T6@`*?!V~L)(*;Cd^-MhL={x*q7oh=iWADMlhNdA?T-i7O$ zskN^Tw@SNpRX_L%?b%z=jU|(}Gj!?Ie#%*$ckv!vAG%be5iMQrJ?ehwg*YWvjk&4U z@)sW_MzTznNb1m7i~Mywm3OmB*qgUfga(8K4DHW9Tb(X_AQA+?`qwZS2{KX*tnRjZ zz=WX{bF#IB|CY?KS2V{d3^F3pTWp2IFaZ@%k>4o5*=+G9U3!}4o9g!&w$+W=GB^AY z#c>+*%4BZ95tRn_tUxI{$=m4F6U8mAZVUFvgIAy;!Zc4wBelP zLzbXAIMMJcoXqnH0+se{MpX@r&(`k-5ltS~L;BHApN}$=$TEZ8{3oZ`SqyI^SE(K5 zI*44dvg77S%7(M-&afdMwRbMT*g*a z<&L&{&jcV)`?kYWU%I{$@q3+1IG}fC<0IWHN1%^srNs1%>bKt^Wz3j<)3;eKs1W;S z3G=7f7=c=uf-hsDH}X*rb0WY?hX4oY`TNc^QX}xAP#w1*$yP|z54fA1|J1}lS>N-G zHcYVJt_399R#x53NCq9Qk$apj@TaO`Zi`);uTKPlPvBpDTh2g~U$7+vsn#!DO}=P< zCZbvN)<2@}Qa*(Vr;ccdo;K5I=iCintp?u&ky-(Uq=sueSXj}6ty|J7w@nJtVs zwfSnQ#AGNr_U|W;?|=UBU%gSm_gLd*a?6M>BlUYZX;R`8IA(9xP@Lw25IHuG)>@lq zmk*{GrxlTfZM}e-CP3XTr82SG8T)(HIU&t+6H}(J?G45EGg@iB1WrG1zp*l0(F!3p zUTBlIxP8*6N~I3Mr5~<-w|WkHl*+VBwVKT9eH}&Z3wqJq{4Rt zO)c)v8EsTe!Il=^58NT8jUiug8FF}!4euH`^*-sEf4cuWTe~lKoo{X=ayqf0BxhsL zzME#UE?Z8z@~Y%~baK8fLxTd08U9P@)93hNSJd6L4*^^!rj>jFj8^;^E1e$dDS%{= zKP84*CJ@n>)Y@4?kJ(zV$!{oL>FG7^4BVSYxW!_m)9wonLhtn&x~1@6-d=xz-)kgt ztsvsLR_IL#oz@!I_ku!fFnXv-l(1=j+U%=>?6r)yXM~lF( z@q`@u&n$gt)Q#lvfOUEJh_Q!m-arg8WF7{caFInGaNG~XJVVGb3S!nrKLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(`>RI+y?e7jKeZ#YO-C z3pYtbK~#9!+?;K66y+7ie>=0g$-afW5Li;^A!zgivE@yuEXIn0fJQ_Jm7=Y@N)p6C zu^y^lYCj;ADxy-b#B&r7EqE-Vz|neaR~AKHTkr!!<(z_KAtnKmY+g3I+Yd9D%uKS$ zCYebB&pCTe_Ia{1&-0&q|IfX5?tLuf6Yr!qnm6FL03(5Mz%9V7Kn{=rc;pX2jr^$u z%78P#S>Pz}P0*JYGk9L@_FGK`BOQ1Mm;y`&1|{&b7Lc-l+jL`>y#fE{z$d_-pf9h| zthrl^mKblqF96ekrN9ipX42flz&_v&;6Tuq7d2913Y|j8M0NaDUyykubj5pw)3cM8|w)E3eTh;3@jhza*h;jrlHP1WMh2dxi*ejP11uK(@N2;8pf9h{G+?{||3Kg$ zfKT1u{Oz|~UH=l{Q>W?6X1gty0jpPGz4>PS&>>)E(3kgPqQD4UZpEZ4K$q zpUG?LJ}};Z-wy0m+b5BW7pYuUM7XSs{@%i6WmFax6DcoOci#f+mDkp7iTMjKK>_7* z1yw6bh+MqL_0l3g{6N){PZ7CXq3)g_+h1J=Mz&LwEjdEbO|ebvKmS>~e;Wo) zd64SWPZO)va89wj#>9c~2K+w(O4Ym1mFNFN^LO7HK56TSY#a~fE8iUt=3~8Sgz=pG z-S=Gi}P@=DJx65wWTA9#v6W@F41r>pzfO4Vy3i6%xpl}0_ykeH{}UAAD)GEgyf4x zDk{2U1Ues{Wy+lD_aD#*sEmaIOujmt(Dc<|=(+cjX_Zrn>3n_|~A z9yy}!x*HG$AQx3YyXn|*!*6cO$-yc6L$UgLYBv3~d#afK0JfYQm*1q+2m-t01HiC4mwgK;+0dx8reb+caZM($6VVcjKGu9uP za*q;9h?JKTdT&>cLlntV?lIQ$K6g$}AGrdcgw!MFFX;F6o(YS?fv2cY`ChZ}HDXOo zJvR`G3b8mG2JYJtBL6$D?#U6<@rX#dv5`6V!Uc$#Qh2rT>mxMyzc9ue%FM*QXrZw- zO;8j5944Sj&4^ZCF)jc#d^oPTb6P#xmtHp32`!k7b@*`OTmZh_WJCWso7tduUT9g4da4%ZeO1Ou=rs3ay%xq$2 zChmm`dj3GQYp)T2y4Jy-ZGqP8Y+MDiTM75&4fIBH70kw(ooysAwE}Qi5uX%K&j@!_ ziMfQ^5fQH{>5)aOTYH57D3*;lB&+*`JGFNRw?iWCfkiBOH6hgV0`RReIBaOQ0zww2 zQ)}UF*k~%@c1Xl7S2s`*5r3+A?^Y%Nr<4WSY~9jTjfMNwVZ$ukZV~Z<$GgFe!}Xb= zrv;>pYo#TP9o@x?>IiqkEA(Y^70gzmgbAx*Zmz!Tr~sS>6tmAhc1)K-3TncwduM0w z5pG9Bys~RVtXuO{;IuX9%Ztcq=nnzdM~%W_vn3S8OP+F%CW0?|y`=npb>};P$79M{ zMm-TXeDcrEpRLVC()jV}uFru;OOou~7GNwc7xrIFpz(`?9beGp(hx3yZMjX?pbxybo9#;srKQ>|Dz=Vj{F=M*=YlDnM zYdWhp;C~nRRZHNeqTrDZ-!0ikkCM10941zu_|_bYM2Lk#+P?aFqSew{Pa!R>O%Tdr zw_!<2T=gu;Nnp1_bA+Z7@k6!Z;K5|>dRJ|tyazl2+OnLt6ktRVXTcn5UVA<6MVn5X zH04Q0tE-{9x>uyiSujU$x4o@)B3s%4wN2xkH4`B{jT^NI>1jA;Y0_d&0sGqkBUg^C zZ5794+)EbUu)w&NEY|OgYk^pM64Rn%XB4T)e*jzV$Quq6+sKhP^B+)modN!?kH?^r znkZ1BG2m9ON~DnLx@i`hjg+TX=?%bVfJj_mWN;23)%!^3=H%cmTz2EAP~j8$x0(BZ zJ#9EB&hJ+O*A)L_?i`ZtoOIoTl62=JTyr%wXV-vspL)B%$dzHs)hdj#vqENRYzH15k`d2MtH7`a?7AE{zqxlzXEO{Sz{7``p% zlm0=L0r~Mt0TY!Ng1)>8q$+zQ4MNCBC;jz}Bu%`d{}S!QJ4kHy89e3 zSzZ%80!Br~cd3m(i_1mYy0v(gJrS?Ox6cAqo@Gl(Tensp|2+fTl}O2P!obMjoChW$ z6<*4}7H~Z_pNy?r`h|sT%NSP_p0wZ5^ z7*Yw{oHm2a&C(GD`wy6Ngu(P_B#pm49>2YhbcDhA1lC|YFsh5+hji#gyL))cp%;xu zkI{7eq?G5EXiA$XVwDfQNE$b`vqLY+fEBX8m+0Kh14h1q6?jbcR69Q=<@^OA-(bpYa!EW}|6ACvND;O%~OOp5MC0#eo3VtVIbovpyWetWP^ zoSS9p@RFB-e*vEY9~f~IQ=b5%n}GW8u3M1`!*hUvNZHs5jgR3=A9lx&I`x0{M)^LGDfr z>(0r%1ahJRd_r8^3w-_)0iIjg0#$vUGw;`}TYsKC|Ipm}Dk${t=g%+PJbs=#_clBC z+tzL04<33~UioFks@HLeA16#?kbRvDw1u}M$S;^-!UYF~h6f)63>E|&*#C@SJ_7^O z22U5qkch)aBW`b7t-y07Z+)&H%bu|R|IPbdazveu?u~r?WNpUrlu4Y88bwi`Jmzor zy8U*h-J}lfV^thk{u*P`JN%SWSwG8PR+CPyyMVm#_8;^nUwvA%$39{1G6jWI?~&%d2f9;&}) zC);I>x<}4$SIKY|U2HjZece3nr40Q(v*o3x>e(eOUG=KQBeg9mRCd{Kos-2$YD>+U z<#wLFR$F=Q{9Hy(iT58Za_#yzF*DewPS~|n)?>Eh`RPaZR2V-lXfs~jUAE0y z+j{2dnO}dmZQW)q*^yc4Ha8|D{oRGsl~-7GWNsZkBQWp6(m6NxqH z5|<(n+H`!0vec1xi>r<+l!bVH*>L!3YTb)dpRLt4`|VEs$oO(wXQRNAFOiw19EX&a z{OJsIerR94QA?sTKaSh-{EM{dOd`#SVHb^GO}P81HM+%+=ZwdFy=5`(iE&q_9(&W0 zC^~DO@C?a^a-R1?B{a6q`eV9+FZfe^^G{y$h@S@;9`DU{{F#`Pp}k*izc91fS;=e; zKjzG&UZ)W8SNXXj8Mm8OWVwWFsTJIkyU=&b>VnX(Cw^=)3*57mU&F6Iw59j3^_IrR)!DDio%|j<8M;qR?3vKW zb}Qg<>54@A3m28IIBHzksB=Zp@XAEfD~wiG3hk~ub=t7HFF;)X;JU`9Sg+XqO264V X7%qlpzj@&WOw5jgR3=A9lx&I`x0{M)^LGDfr z>(0r%1aeved_r8^3w-_)2cBEmeqFWt^PG7fTG~F(o%ie3tv}D6|NZ>=g`3BR=GIq1 zp+C=^|9Vea5$~+g>=jyopcxx^nfu&tKkV z=lyy9{Nsd)pC(Syb*r=jx`($U$S;^-!UYF~h6f)63>E|&*#C@SJ_7?&qo<2wNW|f_ zGj307QV?m|x?ziu>E$gaH$A%`bMOD(w&q!f*byu2Now z$JngIz4#VC5j6{%s%_Czq_uJ3Dd7{2Q$F?Se(|$xn(r*NSa_Sz)^x*O>$B7MY&ksT ziv2&8^2{EsoRwY2vZtI^T-#v0?b^(1M~fpF&+dBo@VKqy>tzow*zg<+)SB0~)8G2c z;)^?$OSon`$H`p{R4Q5JA6q-|`RsGXM`kdGB}J{R_Sn1r@IhmD$;) zy*4Kg^GuH1!u=|lp}6`;?q@4D^Z8+CS0DfLX!;Y0$x(IR?^+x%u`jHtmi_F$&qJkW zPThumuV$%Vjytm9%j9mxu)FF@TM~~dOkXB-*g~jn>asf;3B0w=M?SbU`qbVjcHzm7 zE)t&mutVaW%hPFghclPY*tgXE+04mbqy3fg4K9_XAMkwg*6&u$L9QQ5jgR3=A9lx&I`x0{M)^LGDfr z>(0r%1ai^>d_r8^3w-_)0lK@pfvP4=n)K?`tD{GczI^#|+qP{VK79D`R5=ZE8!8dUChKptD!R?-Ft}J8Vd2FM3X3AQzc(2$4VNXLj zp1ZzWD|Kb5PEuBZ;62MdNfj$DtXMJ0)#rwqxvT3`m*1{iPMGn#YVHhw$@ZbRMucJN zr^7)~vmY<_DCE1Cws7y+opF0bUbuemGHBK9o4?59TY^qs_b0*Rf;m&fpB=yJKX229 z)w|^dlRKJz#dkIf`GwTEv^T!U5HjUjw9WO3%f@Nc`{{Y(*^ zAMP1*3Gk|gBnw@<;AZM1#JeTrvFX(f{v}@*?BWQFJ-tG&GbB806}QRN(s{268{e#K me)X{Rjbz80K&?NWZ^Y$vFQ#T19BBt8AqG!ZKbLh*2~7Z-dw;zE literal 0 HcmV?d00001 diff --git a/easyphotos/src/main/res/drawable-xxhdpi/ic_editor_easy_photos.png b/easyphotos/src/main/res/drawable-xxhdpi/ic_editor_easy_photos.png new file mode 100644 index 0000000000000000000000000000000000000000..f8fa4d416b0678f5ea0b91e9b0a493e43afaf4fd GIT binary patch literal 3538 zcmX|E2|QF^8y-88N+MerqwHppiJ3x%MD{VszRV!|GPVgZ)|7n-*(&=|2q8;`vSk~L zq{vXnlBp2!$2Y$3|NGAGch5cN+;gAjecp54`=0y2#OTIJpfC^s0G!m*)y6RHI>#3$ zJEQi4EW`i+O!{tEivSBl0~FrJ3*zYHgL8%uy?hzZ0f4I-L|;d|hjRc3=X}S_TMbNa z>jZ<`oYcU#5r!~BUoB@>H{B3|vss7{79Zk)M>>Hu)PYxtCcQ4W_@QtYeQBvq?cD z4UpUm2aE7;!I!6pU3Rupn(wyn)aTvvX~=s=H*g`09{7!@h0fhuE(je#@F}+{TRwX-K>FSWvqTS}rzFp8-~#)EjRW|nzQYBI zmR9q*i`(q%m8+1}_QDP_#Gzr-)+7z>#}6wB>1k;flY{|f*n0t4nMy5DJawWLC)T%K zp8yYV^ZN9Z7{DR5ecT2dSs-;OKZzK0%xHn6O?aWMmm*qA5{-|RNgYn^@_w6BkW?8A zHMqCuP>aNa7iyl-?xk7xxBPYC4hn7mP#(#U6J?8XO40Ml#@W~zi_`cW2-WX>b7=EW z*O@obPlGkkupDJ^B)7S|`HrtAUI*V1$eDxp&Y&|H@6WI;^Co2z81f1^NP*W>khZSw zMSg3S|Dm^Q?Y2WJU%u+54#whFv7WJbxRX;wb#>&ldT>{$S5lhNu}|ddc!_9J2~K72 z&qlMK0=4DF|J28G;qAmm7A!yZCNo8A+8(giQI;X`ubs?_1hc37`F~2#vL^C64|8)L z)$*Xi(VG@4BxlMs!Z`>zY&LMU>VVApq>WbJ3NFMYT0Pd;$+*}52+X-V_oaXFaZf=F zABpnotWBY1;?a*Jkjy%Hp8V$fd1cKEToFr1YDXMf=aZ@){R-<2oqdrqNy?1D>?#7Q zXctA>8T8m=5qt4ouj1GWL>(HCq6`$#*9A6&Zy;R(pf$y@W6cN3CU40 z_Ggbib|lZZrTwa#;}zj~dT06XfpSj!_6yRdwHiAYz#G=G?&Z3dNhb7ioVV=oTv#5n z-xaYXk6H=(@GZgm^Mv0QXa^QQ@HGr8Hzgf9w0T&ovJVb9s`*GuK5t6*f(NNS)!WzR zEPi}dyIISORAf7fOR~ikBZBD3_*K61*C*iHlm9F|;+cs69K{(DFsJHJ=AX?|(g)XG z3w)f$oGO46s#1*~C z)XuH_Xj)BwHWa!c<@3WnAW1p~y^s$E?hF&i7o;o1OF7bn z{7zao@uC8)1ioV5It-K5{laEK21!icuI?w+z_rSaCh8U?k(C+?GZ%FqKG<{0Zpw_k zM%vqsnCO9t##5<;mBRsdYi%a$c~u3Kf3Q<6+iO#jtjvUQ-fMfzk7{#F`@^1}6!(6zM!v%(^;ZMIwu2HRF>60_~y0uY{$%a1aoco1e*=0hykT=c!Z z{t<0`T*5qZ*Ekj7ABP>WgNgU?^%Tlj(Jr~j{>dj78oPe1N5~c?T?u-h@HoCTmCsBD z{%1d9JUlcF5o#`fkyN3u&$?(Wi?KJp?#GpuD~y&l#vJ6$NewqMNMk}`LyLIGAkHYR z76WRq$W};Yh>PBMVtQmliQtW~w_Z(^cioUl;$CKyMC7!#yZh(i&d&*h{q1YZyz-z? zPHycbiZ?3c`E24A*>uYnT?+W7V3~L?>{(P6uoR#yimY;A<5mV2UexClJ5ahf)JV2O z3Ea_px%6i`L0~@Lns;!17NT6BS}AoB4sNiGF^ONgbO-ZZS*+e=gbe>7F?*oo-40)T z=%((^F2R?c!7j`+P%5S#YII;{d?MdpzdyuNXCY3GHRwlKkyFjymGT45`&<#h=0*c5 z;LRH`qvdm#mUAxi_ieeg?z08a#||#O;Bb7hkWpMKWa+9?QibwL)C>ztx0Gf053{)- zJ`Nh=uUHCybtX0JURrK$BRuFTCUfR%!_GvbJe$?9+ThS5U8(pIWs4PH_H;x4-lXIM zkt)hA-Il5l3O~u)Ln6!(+|?f#oJ)2)eX%r?e5Wwk>?XJ1nXj{ePYmyOLa*idDNR+Vkm>{T6@`*?!V~L)(*;Cd^-MhL={x*q7oh=iWADMlhNdA?T-i7O$ zskN^Tw@SNpRX_L%?b%z=jU|(}Gj!?Ie#%*$ckv!vAG%be5iMQrJ?ehwg*YWvjk&4U z@)sW_MzTznNb1m7i~Mywm3OmB*qgUfga(8K4DHW9Tb(X_AQA+?`qwZS2{KX*tnRjZ zz=WX{bF#IB|CY?KS2V{d3^F3pTWp2IFaZ@%k>4o5*=+G9U3!}4o9g!&w$+W=GB^AY z#c>+*%4BZ95tRn_tUxI{$=m4F6U8mAZVUFvgIAy;!Zc4wBelP zLzbXAIMMJcoXqnH0+se{MpX@r&(`k-5ltS~L;BHApN}$=$TEZ8{3oZ`SqyI^SE(K5 zI*44dvg77S%7(M-&afdMwRbMT*g*a z<&L&{&jcV)`?kYWU%I{$@q3+1IG}fC<0IWHN1%^srNs1%>bKt^Wz3j<)3;eKs1W;S z3G=7f7=c=uf-hsDH}X*rb0WY?hX4oY`TNc^QX}xAP#w1*$yP|z54fA1|J1}lS>N-G zHcYVJt_399R#x53NCq9Qk$apj@TaO`Zi`);uTKPlPvBpDTh2g~U$7+vsn#!DO}=P< zCZbvN)<2@}Qa*(Vr;ccdo;K5I=iCintp?u&ky-(Uq=sueSXj}6ty|J7w@nJtVs zwfSnQ#AGNr_U|W;?|=UBU%gSm_gLd*a?6M>BlUYZX;R`8IA(9xP@Lw25IHuG)>@lq zmk*{GrxlTfZM}e-CP3XTr82SG8T)(HIU&t+6H}(J?G45EGg@iB1WrG1zp*l0(F!3p zUTBlIxP8*6N~I3Mr5~<-w|WkHl*+VBwVKT9eH}&Z3wqJq{4Rt zO)c)v8EsTe!Il=^58NT8jUiug8FF}!4euH`^*-sEf4cuWTe~lKoo{X=ayqf0BxhsL zzME#UE?Z8z@~Y%~baK8fLxTd08U9P@)93hNSJd6L4*^^!rj>jFj8^;^E1e$dDS%{= zKP84*CJ@n>)Y@4?kJ(zV$!{oL>FG7^4BVSYxW!_m)9wonLhtn&x~1@6-d=xz-)kgt ztsvsLR_IL#oz@!I_ku!fFnXv-l(1=j+U%=>?6r)yXM~lF( z@q`@u&n$gt)Q#lvfOUEJh_Q!m-arg8WF7{caFInGaNG~XJVVGb3S!nrKLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(`>RI+y?e7jKeZ#YO-C z3pYtbK~#9!+?;K66y+7ie>=0g$-afW5Li;^A!zgivE@yuEXIn0fJQ_Jm7=Y@N)p6C zu^y^lYCj;ADxy-b#B&r7EqE-Vz|neaR~AKHTkr!!<(z_KAtnKmY+g3I+Yd9D%uKS$ zCYebB&pCTe_Ia{1&-0&q|IfX5?tLuf6Yr!qnm6FL03(5Mz%9V7Kn{=rc;pX2jr^$u z%78P#S>Pz}P0*JYGk9L@_FGK`BOQ1Mm;y`&1|{&b7Lc-l+jL`>y#fE{z$d_-pf9h| zthrl^mKblqF96ekrN9ipX42flz&_v&;6Tuq7d2913Y|j8M0NaDUyykubj5pw)3cM8|w)E3eTh;3@jhza*h;jrlHP1WMh2dxi*ejP11uK(@N2;8pf9h{G+?{||3Kg$ zfKT1u{Oz|~UH=l{Q>W?6X1gty0jpPGz4>PS&>>)E(3kgPqQD4UZpEZ4K$q zpUG?LJ}};Z-wy0m+b5BW7pYuUM7XSs{@%i6WmFax6DcoOci#f+mDkp7iTMjKK>_7* z1yw6bh+MqL_0l3g{6N){PZ7CXq3)g_+h1J=Mz&LwEjdEbO|ebvKmS>~e;Wo) zd64SWPZO)va89wj#>9c~2K+w(O4Ym1mFNFN^LO7HK56TSY#a~fE8iUt=3~8Sgz=pG z-S=Gi}P@=DJx65wWTA9#v6W@F41r>pzfO4Vy3i6%xpl}0_ykeH{}UAAD)GEgyf4x zDk{2U1Ues{Wy+lD_aD#*sEmaIOujmt(Dc<|=(+cjX_Zrn>3n_|~A z9yy}!x*HG$AQx3YyXn|*!*6cO$-yc6L$UgLYBv3~d#afK0JfYQm*1q+2m-t01HiC4mwgK;+0dx8reb+caZM($6VVcjKGu9uP za*q;9h?JKTdT&>cLlntV?lIQ$K6g$}AGrdcgw!MFFX;F6o(YS?fv2cY`ChZ}HDXOo zJvR`G3b8mG2JYJtBL6$D?#U6<@rX#dv5`6V!Uc$#Qh2rT>mxMyzc9ue%FM*QXrZw- zO;8j5944Sj&4^ZCF)jc#d^oPTb6P#xmtHp32`!k7b@*`OTmZh_WJCWso7tduUT9g4da4%ZeO1Ou=rs3ay%xq$2 zChmm`dj3GQYp)T2y4Jy-ZGqP8Y+MDiTM75&4fIBH70kw(ooysAwE}Qi5uX%K&j@!_ ziMfQ^5fQH{>5)aOTYH57D3*;lB&+*`JGFNRw?iWCfkiBOH6hgV0`RReIBaOQ0zww2 zQ)}UF*k~%@c1Xl7S2s`*5r3+A?^Y%Nr<4WSY~9jTjfMNwVZ$ukZV~Z<$GgFe!}Xb= zrv;>pYo#TP9o@x?>IiqkEA(Y^70gzmgbAx*Zmz!Tr~sS>6tmAhc1)K-3TncwduM0w z5pG9Bys~RVtXuO{;IuX9%Ztcq=nnzdM~%W_vn3S8OP+F%CW0?|y`=npb>};P$79M{ zMm-TXeDcrEpRLVC()jV}uFru;OOou~7GNwc7xrIFpz(`?9beGp(hx3yZMjX?pbxybo9#;srKQ>|Dz=Vj{F=M*=YlDnM zYdWhp;C~nRRZHNeqTrDZ-!0ikkCM10941zu_|_bYM2Lk#+P?aFqSew{Pa!R>O%Tdr zw_!<2T=gu;Nnp1_bA+Z7@k6!Z;K5|>dRJ|tyazl2+OnLt6ktRVXTcn5UVA<6MVn5X zH04Q0tE-{9x>uyiSujU$x4o@)B3s%4wN2xkH4`B{jT^NI>1jA;Y0_d&0sGqkBUg^C zZ5794+)EbUu)w&NEY|OgYk^pM64Rn%XB4T)e*jzV$Quq6+sKhP^B+)modN!?kH?^r znkZ1BG2m9ON~DnLx@i`hjg+TX=?%bVfJj_mWN;23)%!^3=H%cmTz2EAP~j8$x0(BZ zJ#9EB&hJ+O*A)L_?i`ZtoOIoTl62=JTyr%wXV-vspL)B%$dzHs)hdj#vqENRYzH15k`d2MtH7`a?7AE{zqxlzXEO{Sz{7``p% zlm0=L0r~Mt0TY!Ng1)>8q$+zQ4MNCBC;jz}Bu%`d{}S!QJ4kHy89e3 zSzZ%80!Br~cd3m(i_1mYy0v(gJrS?Ox6cAqo@Gl(Tensp|2+fTl}O2P!obMjoChW$ z6<*4}7H~Z_pNy?r`h|sT%NSP_p0wZ5^ z7*Yw{oHm2a&C(GD`wy6Ngu(P_B#pm49>2YhbcDhA1lC|YFsh5+hji#gyL))cp%;xu zkI{7eq?G5EXiA$XVwDfQNE$b`vqLY+fEBX8m+0Kh14h1q6?jbcR69Q=<@^OA-(bpYa!EW}|6ACvND;O%~OOp5MC0#eo3VtVIbovpyWetWP^ zoSS9p@RFB-e*vEY9~f~IQ=b5%n}GW8u3M1`!*hUvNZHs5jgR3=A9lx&I`x0{M)^LGDfr z>(0r%1ahJRd_r8^3w-_)0iIjg0#$vUGw;`}TYsKC|Ipm}Dk${t=g%+PJbs=#_clBC z+tzL04<33~UioFks@HLeA16#?kbRvDw1u}M$S;^-!UYF~h6f)63>E|&*#C@SJ_7^O z22U5qkch)aBW`b7t-y07Z+)&H%bu|R|IPbdazveu?u~r?WNpUrlu4Y88bwi`Jmzor zy8U*h-J}lfV^thk{u*P`JN%SWSwG8PR+CPyyMVm#_8;^nUwvA%$39{1G6jWI?~&%d2f9;&}) zC);I>x<}4$SIKY|U2HjZece3nr40Q(v*o3x>e(eOUG=KQBeg9mRCd{Kos-2$YD>+U z<#wLFR$F=Q{9Hy(iT58Za_#yzF*DewPS~|n)?>Eh`RPaZR2V-lXfs~jUAE0y z+j{2dnO}dmZQW)q*^yc4Ha8|D{oRGsl~-7GWNsZkBQWp6(m6NxqH z5|<(n+H`!0vec1xi>r<+l!bVH*>L!3YTb)dpRLt4`|VEs$oO(wXQRNAFOiw19EX&a z{OJsIerR94QA?sTKaSh-{EM{dOd`#SVHb^GO}P81HM+%+=ZwdFy=5`(iE&q_9(&W0 zC^~DO@C?a^a-R1?B{a6q`eV9+FZfe^^G{y$h@S@;9`DU{{F#`Pp}k*izc91fS;=e; zKjzG&UZ)W8SNXXj8Mm8OWVwWFsTJIkyU=&b>VnX(Cw^=)3*57mU&F6Iw59j3^_IrR)!DDio%|j<8M;qR?3vKW zb}Qg<>54@A3m28IIBHzksB=Zp@XAEfD~wiG3hk~ub=t7HFF;)X;JU`9Sg+XqO264V X7%qlpzj@&WOw + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/bg_dialog_loading_easy_photos.xml b/easyphotos/src/main/res/drawable/bg_dialog_loading_easy_photos.xml new file mode 100644 index 000000000..f898eb637 --- /dev/null +++ b/easyphotos/src/main/res/drawable/bg_dialog_loading_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/bg_menu_done_easy_photos.xml b/easyphotos/src/main/res/drawable/bg_menu_done_easy_photos.xml new file mode 100644 index 000000000..4957cdc73 --- /dev/null +++ b/easyphotos/src/main/res/drawable/bg_menu_done_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/bg_second_level_menu_easy_photos.xml b/easyphotos/src/main/res/drawable/bg_second_level_menu_easy_photos.xml new file mode 100644 index 000000000..29b52114e --- /dev/null +++ b/easyphotos/src/main/res/drawable/bg_second_level_menu_easy_photos.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/bg_seek_bar_alpha_easy_photos.xml b/easyphotos/src/main/res/drawable/bg_seek_bar_alpha_easy_photos.xml new file mode 100644 index 000000000..3ca35f386 --- /dev/null +++ b/easyphotos/src/main/res/drawable/bg_seek_bar_alpha_easy_photos.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/bg_select_false_easy_photos.xml b/easyphotos/src/main/res/drawable/bg_select_false_easy_photos.xml new file mode 100644 index 000000000..6793f8c61 --- /dev/null +++ b/easyphotos/src/main/res/drawable/bg_select_false_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/bg_select_false_unable_easy_photos.xml b/easyphotos/src/main/res/drawable/bg_select_false_unable_easy_photos.xml new file mode 100644 index 000000000..a7f3d10f1 --- /dev/null +++ b/easyphotos/src/main/res/drawable/bg_select_false_unable_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/bg_select_true_easy_photos.xml b/easyphotos/src/main/res/drawable/bg_select_true_easy_photos.xml new file mode 100644 index 000000000..3c115bcc3 --- /dev/null +++ b/easyphotos/src/main/res/drawable/bg_select_true_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/bg_selected_frame_easy_photos.xml b/easyphotos/src/main/res/drawable/bg_selected_frame_easy_photos.xml new file mode 100644 index 000000000..0e1a5a169 --- /dev/null +++ b/easyphotos/src/main/res/drawable/bg_selected_frame_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/bg_selected_frame_puzzle_easy_photos.xml b/easyphotos/src/main/res/drawable/bg_selected_frame_puzzle_easy_photos.xml new file mode 100644 index 000000000..0e1a5a169 --- /dev/null +++ b/easyphotos/src/main/res/drawable/bg_selected_frame_puzzle_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/bg_text_sticker_editor_easy_photos.xml b/easyphotos/src/main/res/drawable/bg_text_sticker_editor_easy_photos.xml new file mode 100644 index 000000000..6e0c8d79f --- /dev/null +++ b/easyphotos/src/main/res/drawable/bg_text_sticker_editor_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/ic_album_item_choose_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_album_item_choose_easy_photos.xml new file mode 100644 index 000000000..2fe707f10 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_album_item_choose_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_album_items_name_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_album_items_name_easy_photos.xml new file mode 100644 index 000000000..61697899f --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_album_items_name_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_arrow_back_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_arrow_back_easy_photos.xml new file mode 100644 index 000000000..416d75472 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_arrow_back_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_arrow_back_preview_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_arrow_back_preview_easy_photos.xml new file mode 100644 index 000000000..4bf947d98 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_arrow_back_preview_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_arrow_down_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_arrow_down_easy_photos.xml new file mode 100644 index 000000000..217e54440 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_arrow_down_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_arrow_up_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_arrow_up_easy_photos.xml new file mode 100644 index 000000000..dd0f1a3cc --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_arrow_up_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_black_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_black_easy_photos.xml new file mode 100644 index 000000000..496e40a27 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_black_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/ic_blue_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_blue_easy_photos.xml new file mode 100644 index 000000000..6a0315188 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_blue_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/ic_camera_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_camera_easy_photos.xml new file mode 100644 index 000000000..dcf2c4747 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_camera_easy_photos.xml @@ -0,0 +1,12 @@ + + + + diff --git a/easyphotos/src/main/res/drawable/ic_clear_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_clear_easy_photos.xml new file mode 100644 index 000000000..5ffd8162f --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_clear_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_cyan_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_cyan_easy_photos.xml new file mode 100644 index 000000000..2c9f2d88b --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_cyan_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/ic_delete_easyy_photos.xml b/easyphotos/src/main/res/drawable/ic_delete_easyy_photos.xml new file mode 100644 index 000000000..adfa9cbe1 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_delete_easyy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_edit_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_edit_easy_photos.xml new file mode 100644 index 000000000..be1eed3e0 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_edit_easy_photos.xml @@ -0,0 +1,12 @@ + + + + diff --git a/easyphotos/src/main/res/drawable/ic_gray_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_gray_easy_photos.xml new file mode 100644 index 000000000..f9c38a2c0 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_gray_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/ic_green_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_green_easy_photos.xml new file mode 100644 index 000000000..a1394e2b9 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_green_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/ic_notifications_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_notifications_easy_photos.xml new file mode 100644 index 000000000..d05014afa --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_notifications_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_orange_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_orange_easy_photos.xml new file mode 100644 index 000000000..a91b3f084 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_orange_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/ic_play_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_play_easy_photos.xml new file mode 100644 index 000000000..ffc0a53f5 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_play_easy_photos.xml @@ -0,0 +1,12 @@ + + + + diff --git a/easyphotos/src/main/res/drawable/ic_purple_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_purple_easy_photos.xml new file mode 100644 index 000000000..29c24adf3 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_purple_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/ic_puzzle_corner_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_puzzle_corner_easy_photos.xml new file mode 100644 index 000000000..db47dccd4 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_puzzle_corner_easy_photos.xml @@ -0,0 +1,14 @@ + + + + diff --git a/easyphotos/src/main/res/drawable/ic_puzzle_flip_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_puzzle_flip_easy_photos.xml new file mode 100644 index 000000000..b50a433e4 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_puzzle_flip_easy_photos.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/easyphotos/src/main/res/drawable/ic_puzzle_mirror_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_puzzle_mirror_easy_photos.xml new file mode 100644 index 000000000..94faa3b1b --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_puzzle_mirror_easy_photos.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/easyphotos/src/main/res/drawable/ic_puzzle_padding_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_puzzle_padding_easy_photos.xml new file mode 100644 index 000000000..0e552bab0 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_puzzle_padding_easy_photos.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/easyphotos/src/main/res/drawable/ic_puzzle_replace_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_puzzle_replace_easy_photos.xml new file mode 100644 index 000000000..ac21d46e5 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_puzzle_replace_easy_photos.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/easyphotos/src/main/res/drawable/ic_puzzle_rotate_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_puzzle_rotate_easy_photos.xml new file mode 100644 index 000000000..3c42147ac --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_puzzle_rotate_easy_photos.xml @@ -0,0 +1,12 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_red_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_red_easy_photos.xml new file mode 100644 index 000000000..98e95cd40 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_red_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/ic_selector_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_selector_easy_photos.xml new file mode 100644 index 000000000..70706ca0b --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_selector_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_selector_true_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_selector_true_easy_photos.xml new file mode 100644 index 000000000..ddaa87d4d --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_selector_true_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_settings_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_settings_easy_photos.xml new file mode 100644 index 000000000..68ebe9485 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_settings_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyphotos/src/main/res/drawable/ic_white_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_white_easy_photos.xml new file mode 100644 index 000000000..56412d616 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_white_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/ic_yelow_easy_photos.xml b/easyphotos/src/main/res/drawable/ic_yelow_easy_photos.xml new file mode 100644 index 000000000..1a5f8e808 --- /dev/null +++ b/easyphotos/src/main/res/drawable/ic_yelow_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/progress_bar_easy_photos.xml b/easyphotos/src/main/res/drawable/progress_bar_easy_photos.xml new file mode 100644 index 000000000..abb1c5400 --- /dev/null +++ b/easyphotos/src/main/res/drawable/progress_bar_easy_photos.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/drawable/thumb_seek_bar_alpha_easy_photos.xml b/easyphotos/src/main/res/drawable/thumb_seek_bar_alpha_easy_photos.xml new file mode 100644 index 000000000..c4624bac2 --- /dev/null +++ b/easyphotos/src/main/res/drawable/thumb_seek_bar_alpha_easy_photos.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/activity_easy_photos.xml b/easyphotos/src/main/res/layout/activity_easy_photos.xml new file mode 100644 index 000000000..6cbc239ff --- /dev/null +++ b/easyphotos/src/main/res/layout/activity_easy_photos.xml @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/activity_preview_easy_photos.xml b/easyphotos/src/main/res/layout/activity_preview_easy_photos.xml new file mode 100644 index 000000000..ad3abee3f --- /dev/null +++ b/easyphotos/src/main/res/layout/activity_preview_easy_photos.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/easyphotos/src/main/res/layout/activity_puzzle_easy_photos.xml b/easyphotos/src/main/res/layout/activity_puzzle_easy_photos.xml new file mode 100644 index 000000000..d3eaf197d --- /dev/null +++ b/easyphotos/src/main/res/layout/activity_puzzle_easy_photos.xml @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/easyphotos/src/main/res/layout/activity_puzzle_selector_easy_photos.xml b/easyphotos/src/main/res/layout/activity_puzzle_selector_easy_photos.xml new file mode 100644 index 000000000..32942e16d --- /dev/null +++ b/easyphotos/src/main/res/layout/activity_puzzle_selector_easy_photos.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/easyphotos/src/main/res/layout/dialog_loading_easy_photos.xml b/easyphotos/src/main/res/layout/dialog_loading_easy_photos.xml new file mode 100644 index 000000000..b09502620 --- /dev/null +++ b/easyphotos/src/main/res/layout/dialog_loading_easy_photos.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/fragment_preview_easy_photos.xml b/easyphotos/src/main/res/layout/fragment_preview_easy_photos.xml new file mode 100644 index 000000000..3288f22d5 --- /dev/null +++ b/easyphotos/src/main/res/layout/fragment_preview_easy_photos.xml @@ -0,0 +1,17 @@ + + + + diff --git a/easyphotos/src/main/res/layout/fragment_text_sticker_easy_photos.xml b/easyphotos/src/main/res/layout/fragment_text_sticker_easy_photos.xml new file mode 100644 index 000000000..809e957ac --- /dev/null +++ b/easyphotos/src/main/res/layout/fragment_text_sticker_easy_photos.xml @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/item_ad_easy_photos.xml b/easyphotos/src/main/res/layout/item_ad_easy_photos.xml new file mode 100644 index 000000000..2cedb126e --- /dev/null +++ b/easyphotos/src/main/res/layout/item_ad_easy_photos.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/item_camera_easy_photos.xml b/easyphotos/src/main/res/layout/item_camera_easy_photos.xml new file mode 100644 index 000000000..5678a13f6 --- /dev/null +++ b/easyphotos/src/main/res/layout/item_camera_easy_photos.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/item_dialog_album_items_easy_photos.xml b/easyphotos/src/main/res/layout/item_dialog_album_items_easy_photos.xml new file mode 100644 index 000000000..ef8090393 --- /dev/null +++ b/easyphotos/src/main/res/layout/item_dialog_album_items_easy_photos.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/item_preview_photo_easy_photos.xml b/easyphotos/src/main/res/layout/item_preview_photo_easy_photos.xml new file mode 100644 index 000000000..619c65fa6 --- /dev/null +++ b/easyphotos/src/main/res/layout/item_preview_photo_easy_photos.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/item_preview_selected_photos_easy_photos.xml b/easyphotos/src/main/res/layout/item_preview_selected_photos_easy_photos.xml new file mode 100644 index 000000000..676541dc4 --- /dev/null +++ b/easyphotos/src/main/res/layout/item_preview_selected_photos_easy_photos.xml @@ -0,0 +1,43 @@ + + + + + + + + + + diff --git a/easyphotos/src/main/res/layout/item_puzzle_easy_photos.xml b/easyphotos/src/main/res/layout/item_puzzle_easy_photos.xml new file mode 100644 index 000000000..efdfa50e1 --- /dev/null +++ b/easyphotos/src/main/res/layout/item_puzzle_easy_photos.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/item_puzzle_selector_easy_photos.xml b/easyphotos/src/main/res/layout/item_puzzle_selector_easy_photos.xml new file mode 100644 index 000000000..3123350cb --- /dev/null +++ b/easyphotos/src/main/res/layout/item_puzzle_selector_easy_photos.xml @@ -0,0 +1,44 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/item_puzzle_selector_preview_easy_photos.xml b/easyphotos/src/main/res/layout/item_puzzle_selector_preview_easy_photos.xml new file mode 100644 index 000000000..ad46c9423 --- /dev/null +++ b/easyphotos/src/main/res/layout/item_puzzle_selector_preview_easy_photos.xml @@ -0,0 +1,52 @@ + + + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/item_rv_photos_easy_photos.xml b/easyphotos/src/main/res/layout/item_rv_photos_easy_photos.xml new file mode 100644 index 000000000..c208ab327 --- /dev/null +++ b/easyphotos/src/main/res/layout/item_rv_photos_easy_photos.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/layout/item_text_sticker_easy_photos.xml b/easyphotos/src/main/res/layout/item_text_sticker_easy_photos.xml new file mode 100644 index 000000000..5333954b1 --- /dev/null +++ b/easyphotos/src/main/res/layout/item_text_sticker_easy_photos.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/values-v19/styles.xml b/easyphotos/src/main/res/values-v19/styles.xml new file mode 100644 index 000000000..f5f917670 --- /dev/null +++ b/easyphotos/src/main/res/values-v19/styles.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/values-v21/styles.xml b/easyphotos/src/main/res/values-v21/styles.xml new file mode 100644 index 000000000..ad4554225 --- /dev/null +++ b/easyphotos/src/main/res/values-v21/styles.xml @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/easyphotos/src/main/res/values-w480dp/integers.xml b/easyphotos/src/main/res/values-w480dp/integers.xml new file mode 100644 index 000000000..b37c5e46a --- /dev/null +++ b/easyphotos/src/main/res/values-w480dp/integers.xml @@ -0,0 +1,4 @@ + + + 4 + \ No newline at end of file diff --git a/easyphotos/src/main/res/values-w640dp/integers.xml b/easyphotos/src/main/res/values-w640dp/integers.xml new file mode 100644 index 000000000..b4a3fa570 --- /dev/null +++ b/easyphotos/src/main/res/values-w640dp/integers.xml @@ -0,0 +1,4 @@ + + + 5 + \ No newline at end of file diff --git a/easyphotos/src/main/res/values/attrs.xml b/easyphotos/src/main/res/values/attrs.xml new file mode 100644 index 000000000..244100f60 --- /dev/null +++ b/easyphotos/src/main/res/values/attrs.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/easyphotos/src/main/res/values/colors.xml b/easyphotos/src/main/res/values/colors.xml new file mode 100644 index 000000000..20f467928 --- /dev/null +++ b/easyphotos/src/main/res/values/colors.xml @@ -0,0 +1,52 @@ + + + #303135 + #00000000 + #FFFFFF + #FFFFFF//按钮上的文字颜色,选择器上的文字颜色,选择器的边框颜色 + + + #FFFFFF//专辑项目列表的背景颜色 + #3e4145//专辑项目列表中专辑名称的字体颜色 + #4f5555//专辑项目列表中包含图片张数的字体颜色 + + + #33FFFFFF加载框背景颜色 + + + #d0021b//编辑字体颜色为红色 + #f5a623//编辑字体颜色为橙色 + #f8e71c//编辑字体颜色为黄色 + #7ed321//编辑字体颜色为绿色 + #50e3c2//编辑字体颜色为青色 + #4a90e2//编辑字体颜色为蓝色 + #9013fe//编辑字体颜色为紫色 + #000000//编辑字体颜色为黑色 + #4a4a4a//编辑字体颜色为灰色 + #ffffff//编辑字体颜色为白色 + #2c2e30//编辑界面背景色 + #393a3f//编辑界面操作栏颜色 + + + #ffffff//3e4145 前景主色,如字体颜色、icon颜色 + #ffffff//3e4145 前景主色,如字体颜色、icon颜色,预览页用的 + #9b9b9b//前景偏暗色,如不可用状态的字体颜色 + #9b9b9b//前景偏暗色,如不可用状态的字体颜色,预览页用的 + #00AA00//578fff 前景突出色,如发送按钮的背景色,选中状态的颜色 + + + #000000//f9f9f9 背景色,如每个activity的背景色,相册的图片间隔线颜色 + #000000//f9f9f9 背景色,如每个activity的背景色,相册的图片间隔线颜色,预览页用的 + + + #393a3f//ffffff 操作栏主色,如相册页顶部和底部操作栏 + #303135//f9f9f9 操作栏偏暗色,操作栏间隔线颜色,如相册页返回按钮右边的竖线 + #eb212123//b4ffffff 操作栏带透明的色值,如预览页的底部操作栏颜色,相册页的相机按钮背景颜色。 + #393a3f//ffffff 操作栏主色,如相册页顶部和底部操作栏,预览页的 + #303135//f9f9f9 操作栏偏暗色,操作栏间隔线颜色,如相册页返回按钮右边的竖线,预览页的 + #eb212123//b4ffffff 操作栏带透明的色值,如预览页的底部操作栏颜色,相册页的相机按钮背景颜色,预览页的。 + + + #393a3f + #393a3f//预览页用的 + \ No newline at end of file diff --git a/easyphotos/src/main/res/values/dimens.xml b/easyphotos/src/main/res/values/dimens.xml new file mode 100644 index 000000000..66687c9b7 --- /dev/null +++ b/easyphotos/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + 16sp + 42dp + diff --git a/easyphotos/src/main/res/values/integers.xml b/easyphotos/src/main/res/values/integers.xml new file mode 100644 index 000000000..d09129524 --- /dev/null +++ b/easyphotos/src/main/res/values/integers.xml @@ -0,0 +1,4 @@ + + + 3 + \ No newline at end of file diff --git a/easyphotos/src/main/res/values/strings.xml b/easyphotos/src/main/res/values/strings.xml new file mode 100644 index 000000000..c6f279c84 --- /dev/null +++ b/easyphotos/src/main/res/values/strings.xml @@ -0,0 +1,79 @@ +]> + + &app_name; + + 所有圖片 + + 圖片和視頻 + + 所有視頻 + + 選擇 + + + 完成(%1$d/%2$d) + + 預覽 + + 超出最大選擇數 + + 最多只能選擇%d張圖片 + + 最多只能選擇%d個視頻 + + 需要選擇同種類型的文件 + + 無法啟動相機! + + 圖片錯誤 + + 權限錯誤,無法正常工作! + + 圖片的寬度必須大於%1$d,高度必須大於%2$d + + 不支持此圖片格式 + + 編輯 + + 清空 + + 沒有符合要求的圖片,拍一張吧 + + 沒有符合要求的視頻 + + 請點擊並允許相關權限 + + 請點擊並在設置中允許相關權限 + + 原圖 + + %1$d/%2$d + + 完成 + + 取消 + + 模板 + + 文字 + + 日期 + + 雙擊編輯 + + 自定義 + + 圖片選擇 + + 視頻選擇 + + 拼一張 + + 請選擇2 \- 9張照片 + + 動圖 + + 視頻 + + + diff --git a/easyphotos/src/main/res/values/styles.xml b/easyphotos/src/main/res/values/styles.xml new file mode 100644 index 000000000..e77cad3ef --- /dev/null +++ b/easyphotos/src/main/res/values/styles.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/gradle.properties b/gradle.properties index 78c946d79..443d72c34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,5 +24,9 @@ only_arm64=false channel_file=channel.txt +COMPILE_SDK_VERSION=32 +MIN_SDK_VERSION=21 +TARGET_SDK_VERSION=32 + version_name=1.2.1 version_code=121 \ No newline at end of file diff --git a/library/build.gradle b/library/build.gradle index a30296df2..50ce294ca 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -2,10 +2,10 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 32 + compileSdkVersion COMPILE_SDK_VERSION.toInteger() defaultConfig { - minSdkVersion 21 - targetSdkVersion 32 + minSdkVersion MIN_SDK_VERSION.toInteger() + targetSdkVersion TARGET_SDK_VERSION.toInteger() testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/library/src/main/java/com/yizhuan/xchat_android_library/utils/TimeUtils.java b/library/src/main/java/com/yizhuan/xchat_android_library/utils/TimeUtils.java index 12fb95aac..b1a49bcc2 100644 --- a/library/src/main/java/com/yizhuan/xchat_android_library/utils/TimeUtils.java +++ b/library/src/main/java/com/yizhuan/xchat_android_library/utils/TimeUtils.java @@ -739,6 +739,17 @@ public class TimeUtils { return sdf.format(cal.getTime()); } + public static String date2Str(Date d, String format) {// yyyy-MM-dd HH:mm:ss + if (d == null) { + return null; + } + if (format == null || format.length() == 0) { + format = TIME_FORMAT; + } + SimpleDateFormat sdf = new SimpleDateFormat(format); + return sdf.format(d); + } + public static class YEARS { public static long toMillis(long years) { return checkOverflow(years, DAYS_OF_YEAR * HOURS_OF_DAY * MINUTES_OF_HOUR * SECONDS_OF_MINUTE * MILLIS_OF_SECOND); diff --git a/nim_uikit/build.gradle b/nim_uikit/build.gradle index b3a448679..ec7cda71e 100644 --- a/nim_uikit/build.gradle +++ b/nim_uikit/build.gradle @@ -3,11 +3,11 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 32 + compileSdkVersion COMPILE_SDK_VERSION.toInteger() defaultConfig { - minSdkVersion 21 - targetSdkVersion 32 + minSdkVersion MIN_SDK_VERSION.toInteger() + targetSdkVersion TARGET_SDK_VERSION.toInteger() renderscriptTargetApi 26 renderscriptSupportModeEnabled true diff --git a/settings.gradle b/settings.gradle index e3e1ab2ef..956654a99 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,3 +4,4 @@ include ':trtc_release' include ':core' include ':library' include ':nim_uikit' +include ':easyphotos'