() {
+
+ @Override
+ public String doInBackground() {
+ BufferedSource buffer = null;
+ try {
+ buffer = Okio.buffer(Okio.source(Objects.requireNonNull(getContentResolver().openInputStream(inputUri))));
+ OutputStream outputStream = getContentResolver().openOutputStream(uri);
+ boolean bufferCopy = PictureFileUtils.bufferCopy(buffer, outputStream);
+ if (bufferCopy) {
+ return PictureFileUtils.getPath(getContext(), uri);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (buffer != null && buffer.isOpen()) {
+ PictureFileUtils.close(buffer);
+ }
+ }
+ return "";
+ }
+
+ @Override
+ public void onSuccess(String result) {
+ PictureThreadUtils.cancel(PictureThreadUtils.getIoPool());
+ onSuccessful(result);
+ }
+ });
+ }
+
+
+ /**
+ * 针对Q版本创建uri
+ *
+ * @return
+ */
+ private Uri createOutImageUri() {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, DateUtils.getCreateFileName("IMG_"));
+ contentValues.put(MediaStore.Images.Media.DATE_TAKEN, ValueOf.toString(System.currentTimeMillis()));
+ contentValues.put(MediaStore.Images.Media.MIME_TYPE, mMimeType);
+ contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, PictureMimeType.DCIM);
+
+ return getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
+ }
+
+ // 下载图片保存至手机
+ public String showLoadingImage(String urlPath) {
+ Uri outImageUri = null;
+ OutputStream outputStream = null;
+ InputStream inputStream = null;
+ BufferedSource inBuffer = null;
+ try {
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ outImageUri = createOutImageUri();
+ } else {
+ String suffix = PictureMimeType.getLastImgSuffix(mMimeType);
+ String state = Environment.getExternalStorageState();
+ File rootDir =
+ state.equals(Environment.MEDIA_MOUNTED)
+ ? Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
+ : getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+ if (rootDir != null) {
+ if (!rootDir.exists()) {
+ rootDir.mkdirs();
+ }
+ File folderDir = new File(!state.equals(Environment.MEDIA_MOUNTED)
+ ? rootDir.getAbsolutePath() : rootDir.getAbsolutePath() + File.separator + PictureMimeType.CAMERA + File.separator);
+ if (!folderDir.exists() && folderDir.mkdirs()) {
+ }
+ String fileName = DateUtils.getCreateFileName("IMG_") + suffix;
+ File file = new File(folderDir, fileName);
+ outImageUri = Uri.fromFile(file);
+ }
+ }
+ if (outImageUri != null) {
+ outputStream = Objects.requireNonNull(getContentResolver().openOutputStream(outImageUri));
+ URL u = new URL(urlPath);
+ inputStream = u.openStream();
+ inBuffer = Okio.buffer(Okio.source(inputStream));
+ boolean bufferCopy = PictureFileUtils.bufferCopy(inBuffer, outputStream);
+ if (bufferCopy) {
+ return PictureFileUtils.getPath(this, outImageUri);
+ }
+ }
+ } catch (Exception e) {
+ if (outImageUri != null && SdkVersionUtils.checkedAndroid_Q()) {
+ getContentResolver().delete(outImageUri, null, null);
+ }
+ } finally {
+ PictureFileUtils.close(inputStream);
+ PictureFileUtils.close(outputStream);
+ PictureFileUtils.close(inBuffer);
+ }
+ return null;
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ finish();
+ exitAnimation();
+ }
+
+ private void exitAnimation() {
+ overridePendingTransition(R.anim.picture_anim_fade_in, config.windowAnimationStyle != null
+ && config.windowAnimationStyle.activityPreviewExitAnimation != 0
+ ? config.windowAnimationStyle.activityPreviewExitAnimation : R.anim.picture_anim_exit);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (adapter != null) {
+ adapter.clear();
+ }
+ if (PictureSelectionConfig.customVideoPlayCallback != null) {
+ PictureSelectionConfig.customVideoPlayCallback = null;
+ }
+ if (PictureSelectionConfig.onCustomCameraInterfaceListener != null) {
+ PictureSelectionConfig.onCustomCameraInterfaceListener = null;
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ switch (requestCode) {
+ case PictureConfig.APPLY_STORAGE_PERMISSIONS_CODE:
+ // 存储权限
+ for (int i = 0; i < grantResults.length; i++) {
+ if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
+ showDownLoadDialog();
+ } else {
+ ToastUtils.s(getContext(), getString(R.string.picture_jurisdiction));
+ }
+ }
+ break;
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PictureFileProvider.java b/picture_library/src/main/java/com/luck/picture/lib/PictureFileProvider.java
new file mode 100644
index 0000000..54816d4
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PictureFileProvider.java
@@ -0,0 +1,12 @@
+package com.luck.picture.lib;
+
+import androidx.core.content.FileProvider;
+
+/**
+ * @author:luck
+ * @data:2016/3/28 下午2:00
+ * @描述: 自定义FileProvider 解决FileProvider冲突问题
+ */
+
+public class PictureFileProvider extends FileProvider {
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PictureMediaScannerConnection.java b/picture_library/src/main/java/com/luck/picture/lib/PictureMediaScannerConnection.java
new file mode 100644
index 0000000..75a5f5b
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PictureMediaScannerConnection.java
@@ -0,0 +1,49 @@
+package com.luck.picture.lib;
+
+import android.content.Context;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.text.TextUtils;
+
+/**
+ * @author:luck
+ * @date:2019-12-03 10:41
+ * @describe:刷新相册
+ */
+public class PictureMediaScannerConnection implements MediaScannerConnection.MediaScannerConnectionClient {
+ public interface ScanListener {
+ void onScanFinish();
+ }
+
+ private MediaScannerConnection mMs;
+ private String mPath;
+ private ScanListener mListener;
+
+ public PictureMediaScannerConnection(Context context, String path, ScanListener l) {
+ this.mListener = l;
+ this.mPath = path;
+ this.mMs = new MediaScannerConnection(context.getApplicationContext(), this);
+ this.mMs.connect();
+ }
+
+ public PictureMediaScannerConnection(Context context, String path) {
+ this.mPath = path;
+ this.mMs = new MediaScannerConnection(context.getApplicationContext(), this);
+ this.mMs.connect();
+ }
+
+ @Override
+ public void onMediaScannerConnected() {
+ if (!TextUtils.isEmpty(mPath)) {
+ mMs.scanFile(mPath, null);
+ }
+ }
+
+ @Override
+ public void onScanCompleted(String path, Uri uri) {
+ mMs.disconnect();
+ if (mListener != null) {
+ mListener.onScanFinish();
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PicturePlayAudioActivity.java b/picture_library/src/main/java/com/luck/picture/lib/PicturePlayAudioActivity.java
new file mode 100644
index 0000000..0940b0e
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PicturePlayAudioActivity.java
@@ -0,0 +1,211 @@
+package com.luck.picture.lib;
+
+import android.media.MediaPlayer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.tools.DateUtils;
+
+/**
+ * # No longer maintain audio related functions,
+ * but can continue to use but there will be phone compatibility issues.
+ *
+ * 不再维护音频相关功能,但可以继续使用但会有机型兼容性问题
+ */
+@Deprecated
+public class PicturePlayAudioActivity extends PictureBaseActivity implements View.OnClickListener {
+ private String audio_path;
+ private MediaPlayer mediaPlayer;
+ private SeekBar musicSeekBar;
+ private boolean isPlayAudio = false;
+ private TextView tv_PlayPause, tv_Stop, tv_Quit,
+ tv_musicStatus, tv_musicTotal, tv_musicTime;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN
+ , WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public int getResourceId() {
+ return R.layout.picture_play_audio;
+ }
+
+ @Override
+ protected void initWidgets() {
+ super.initWidgets();
+ audio_path = getIntent().getStringExtra(PictureConfig.EXTRA_AUDIO_PATH);
+ tv_musicStatus = findViewById(R.id.tv_musicStatus);
+ tv_musicTime = findViewById(R.id.tv_musicTime);
+ musicSeekBar = findViewById(R.id.musicSeekBar);
+ tv_musicTotal = findViewById(R.id.tv_musicTotal);
+ tv_PlayPause = findViewById(R.id.tv_PlayPause);
+ tv_Stop = findViewById(R.id.tv_Stop);
+ tv_Quit = findViewById(R.id.tv_Quit);
+ handler.postDelayed(() -> initPlayer(audio_path), 30);
+ tv_PlayPause.setOnClickListener(this);
+ tv_Stop.setOnClickListener(this);
+ tv_Quit.setOnClickListener(this);
+ musicSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ mediaPlayer.seekTo(progress);
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+ });
+ }
+
+ // 通过 Handler 更新 UI 上的组件状态
+ public Handler handler = new Handler();
+ public Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (mediaPlayer != null) {
+ tv_musicTime.setText(DateUtils.formatDurationTime(mediaPlayer.getCurrentPosition()));
+ musicSeekBar.setProgress(mediaPlayer.getCurrentPosition());
+ musicSeekBar.setMax(mediaPlayer.getDuration());
+ tv_musicTotal.setText(DateUtils.formatDurationTime(mediaPlayer.getDuration()));
+ handler.postDelayed(runnable, 200);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ /**
+ * 初始化音频播放组件
+ *
+ * @param path
+ */
+ private void initPlayer(String path) {
+ mediaPlayer = new MediaPlayer();
+ try {
+ mediaPlayer.setDataSource(path);
+ mediaPlayer.prepare();
+ mediaPlayer.setLooping(true);
+ playAudio();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ int i = v.getId();
+ if (i == R.id.tv_PlayPause) {
+ playAudio();
+
+ }
+ if (i == R.id.tv_Stop) {
+ tv_musicStatus.setText(getString(R.string.picture_stop_audio));
+ tv_PlayPause.setText(getString(R.string.picture_play_audio));
+ stop(audio_path);
+
+ }
+ if (i == R.id.tv_Quit) {
+ handler.removeCallbacks(runnable);
+ new Handler().postDelayed(() -> stop(audio_path), 30);
+ try {
+ closeActivity();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+ /**
+ * 播放音频
+ */
+ private void playAudio() {
+ if (mediaPlayer != null) {
+ musicSeekBar.setProgress(mediaPlayer.getCurrentPosition());
+ musicSeekBar.setMax(mediaPlayer.getDuration());
+ }
+ String ppStr = tv_PlayPause.getText().toString();
+ if (ppStr.equals(getString(R.string.picture_play_audio))) {
+ tv_PlayPause.setText(getString(R.string.picture_pause_audio));
+ tv_musicStatus.setText(getString(R.string.picture_play_audio));
+ playOrPause();
+ } else {
+ tv_PlayPause.setText(getString(R.string.picture_play_audio));
+ tv_musicStatus.setText(getString(R.string.picture_pause_audio));
+ playOrPause();
+ }
+ if (!isPlayAudio) {
+ handler.post(runnable);
+ isPlayAudio = true;
+ }
+ }
+
+ /**
+ * 停止播放
+ *
+ * @param path
+ */
+ public void stop(String path) {
+ if (mediaPlayer != null) {
+ try {
+ mediaPlayer.stop();
+ mediaPlayer.reset();
+ mediaPlayer.setDataSource(path);
+ mediaPlayer.prepare();
+ mediaPlayer.seekTo(0);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * 暂停播放
+ */
+ public void playOrPause() {
+ try {
+ if (mediaPlayer != null) {
+ if (mediaPlayer.isPlaying()) {
+ mediaPlayer.pause();
+ } else {
+ mediaPlayer.start();
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ closeActivity();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mediaPlayer != null && handler != null) {
+ handler.removeCallbacks(runnable);
+ mediaPlayer.release();
+ mediaPlayer = null;
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PicturePreviewActivity.java b/picture_library/src/main/java/com/luck/picture/lib/PicturePreviewActivity.java
new file mode 100644
index 0000000..47bef7f
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PicturePreviewActivity.java
@@ -0,0 +1,1032 @@
+package com.luck.picture.lib;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+import androidx.viewpager.widget.ViewPager;
+
+import com.luck.picture.lib.adapter.PictureSimpleFragmentAdapter;
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.listener.OnQueryDataResultListener;
+import com.luck.picture.lib.model.LocalMediaPageLoader;
+import com.luck.picture.lib.observable.ImagesObservable;
+import com.luck.picture.lib.tools.MediaUtils;
+import com.luck.picture.lib.tools.ScreenUtils;
+import com.luck.picture.lib.tools.StringUtils;
+import com.luck.picture.lib.tools.ToastUtils;
+import com.luck.picture.lib.tools.ValueOf;
+import com.luck.picture.lib.tools.VoiceUtils;
+import com.luck.picture.lib.widget.PreviewViewPager;
+import com.yalantis.ucrop.UCrop;
+import com.yalantis.ucrop.model.CutInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @data:2016/1/29 下午21:50
+ * @描述:图片预览
+ */
+public class PicturePreviewActivity extends PictureBaseActivity implements
+ View.OnClickListener, PictureSimpleFragmentAdapter.OnCallBackActivity {
+ private static final String TAG = PicturePreviewActivity.class.getSimpleName();
+ protected ImageView pictureLeftBack;
+ protected TextView tvMediaNum, tvTitle, mTvPictureOk;
+ protected PreviewViewPager viewPager;
+ protected int position;
+ protected boolean isBottomPreview;
+ private int totalNumber;
+ protected List selectData = new ArrayList<>();
+ protected PictureSimpleFragmentAdapter adapter;
+ protected Animation animation;
+ protected TextView check;
+ protected View btnCheck;
+ protected boolean refresh;
+ protected int index;
+ protected int screenWidth;
+ protected Handler mHandler;
+ protected RelativeLayout selectBarLayout;
+ protected CheckBox mCbOriginal;
+ protected View titleViewBg;
+ protected boolean isShowCamera;
+ protected String currentDirectory;
+ /**
+ * 是否已完成选择
+ */
+ protected boolean isCompleteOrSelected;
+ /**
+ * 是否改变已选的数据
+ */
+ protected boolean isChangeSelectedData;
+
+ /**
+ * 分页码
+ */
+ private int mPage = 0;
+
+
+ @Override
+ public int getResourceId() {
+ return R.layout.picture_preview;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ // 防止内存不足时activity被回收,导致图片未选中
+ selectData = PictureSelector.obtainSelectorList(savedInstanceState);
+ isCompleteOrSelected = savedInstanceState.getBoolean(PictureConfig.EXTRA_COMPLETE_SELECTED, false);
+ isChangeSelectedData = savedInstanceState.getBoolean(PictureConfig.EXTRA_CHANGE_SELECTED_DATA, false);
+ onImageChecked(position);
+ onSelectNumChange(false);
+ }
+ }
+
+ @Override
+ protected void initWidgets() {
+ super.initWidgets();
+ mHandler = new Handler();
+ titleViewBg = findViewById(R.id.titleViewBg);
+ screenWidth = ScreenUtils.getScreenWidth(this);
+ animation = AnimationUtils.loadAnimation(this, R.anim.picture_anim_modal_in);
+ pictureLeftBack = findViewById(R.id.pictureLeftBack);
+ viewPager = findViewById(R.id.preview_pager);
+ btnCheck = findViewById(R.id.btnCheck);
+ check = findViewById(R.id.check);
+ pictureLeftBack.setOnClickListener(this);
+ mTvPictureOk = findViewById(R.id.tv_ok);
+ mCbOriginal = findViewById(R.id.cb_original);
+ tvMediaNum = findViewById(R.id.tvMediaNum);
+ selectBarLayout = findViewById(R.id.select_bar_layout);
+ mTvPictureOk.setOnClickListener(this);
+ tvMediaNum.setOnClickListener(this);
+ tvTitle = findViewById(R.id.picture_title);
+ position = getIntent().getIntExtra(PictureConfig.EXTRA_POSITION, 0);
+ if (numComplete) {
+ initCompleteText(0);
+ }
+ tvMediaNum.setSelected(config.checkNumMode);
+ btnCheck.setOnClickListener(this);
+ selectData = getIntent().
+ getParcelableArrayListExtra(PictureConfig.EXTRA_SELECT_LIST);
+ isBottomPreview = getIntent().
+ getBooleanExtra(PictureConfig.EXTRA_BOTTOM_PREVIEW, false);
+ isShowCamera = getIntent().getBooleanExtra(PictureConfig.EXTRA_SHOW_CAMERA, config.isCamera);
+ // 当前目录
+ currentDirectory = getIntent().getStringExtra(PictureConfig.EXTRA_IS_CURRENT_DIRECTORY);
+ List data;
+ if (isBottomPreview) {
+ // 底部预览模式
+ data = getIntent().
+ getParcelableArrayListExtra(PictureConfig.EXTRA_PREVIEW_SELECT_LIST);
+ initViewPageAdapterData(data);
+ } else {
+ data = ImagesObservable.getInstance().readPreviewMediaData();
+ boolean isEmpty = data.size() == 0;
+ totalNumber = getIntent().getIntExtra(PictureConfig.EXTRA_DATA_COUNT, 0);
+ if (config.isPageStrategy) {
+ // 分页模式
+ if (isEmpty) {
+ // 这种情况有可能是单例被回收了导致readPreviewMediaData();返回的数据为0,那就从第一页开始加载吧
+ setNewTitle();
+ } else {
+ mPage = getIntent().getIntExtra(PictureConfig.EXTRA_PAGE, 0);
+ }
+ initViewPageAdapterData(data);
+ loadData();
+ setTitle();
+ } else {
+ // 普通模式
+ initViewPageAdapterData(data);
+ if (isEmpty) {
+ // 这种情况有可能是单例被回收了导致readPreviewMediaData();返回的数据为0,暂时自动切换成分页模式去获取数据
+ config.isPageStrategy = true;
+ setNewTitle();
+ loadData();
+ }
+ }
+ }
+
+ viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ isPreviewEggs(config.previewEggs, position, positionOffsetPixels);
+ }
+
+ @Override
+ public void onPageSelected(int i) {
+ position = i;
+ setTitle();
+ LocalMedia media = adapter.getItem(position);
+ if (media == null) {
+ return;
+ }
+ index = media.getPosition();
+ if (!config.previewEggs) {
+ if (config.checkNumMode) {
+ check.setText(ValueOf.toString(media.getNum()));
+ notifyCheckChanged(media);
+ }
+ onImageChecked(position);
+ }
+
+ if (config.isOriginalControl) {
+ boolean isHasVideo = PictureMimeType.isHasVideo(media.getMimeType());
+ mCbOriginal.setVisibility(isHasVideo ? View.GONE : View.VISIBLE);
+ mCbOriginal.setChecked(config.isCheckOriginalImage);
+ }
+ onPageSelectedChange(media);
+
+ if (config.isPageStrategy && !isBottomPreview) {
+ if (isHasMore) {
+ // 滑到adapter.getSize() - PictureConfig.MIN_PAGE_SIZE时或最后一条时预加载
+ if (position == (adapter.getSize() - 1) - PictureConfig.MIN_PAGE_SIZE || position == adapter.getSize() - 1) {
+ loadMoreData();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ }
+ });
+ // 原图
+ if (config.isOriginalControl) {
+ boolean isCheckOriginal = getIntent()
+ .getBooleanExtra(PictureConfig.EXTRA_CHANGE_ORIGINAL, config.isCheckOriginalImage);
+ mCbOriginal.setVisibility(View.VISIBLE);
+ config.isCheckOriginalImage = isCheckOriginal;
+ mCbOriginal.setChecked(config.isCheckOriginalImage);
+ mCbOriginal.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ config.isCheckOriginalImage = isChecked;
+ });
+ }
+ }
+
+ /**
+ * 从本地获取数据
+ */
+ private void loadData() {
+ long bucketId = getIntent().getLongExtra(PictureConfig.EXTRA_BUCKET_ID, -1);
+ mPage++;
+ LocalMediaPageLoader.getInstance(getContext(), config).loadPageMediaData(bucketId, mPage, config.pageSize,
+ (OnQueryDataResultListener) (result, currentPage, isHasMore) -> {
+ if (!isFinishing()) {
+ this.isHasMore = isHasMore;
+ if (isHasMore) {
+ int size = result.size();
+ if (size > 0 && adapter != null) {
+ adapter.getData().addAll(result);
+ adapter.notifyDataSetChanged();
+ } else {
+ // 这种情况就是开启过滤损坏文件刚好导致某一页全是损坏的虽然result为0,但还要请求下一页数据
+ loadMoreData();
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * 加载更多
+ */
+ private void loadMoreData() {
+ long bucketId = getIntent().getLongExtra(PictureConfig.EXTRA_BUCKET_ID, -1);
+ mPage++;
+ LocalMediaPageLoader.getInstance(getContext(), config).loadPageMediaData(bucketId, mPage, config.pageSize,
+ (OnQueryDataResultListener) (result, currentPage, isHasMore) -> {
+ if (!isFinishing()) {
+ this.isHasMore = isHasMore;
+ if (isHasMore) {
+ int size = result.size();
+ if (size > 0 && adapter != null) {
+ adapter.getData().addAll(result);
+ adapter.notifyDataSetChanged();
+ } else {
+ // 这种情况就是开启过滤损坏文件刚好导致某一页全是损坏的虽然result为0,但还要请求下一页数据
+ loadMoreData();
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void initCompleteText(int startCount) {
+ boolean isNotEmptyStyle = config.style != null;
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ if (startCount <= 0) {
+ // 未选择任何图片
+ mTvPictureOk.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureUnCompleteText)
+ ? config.style.pictureUnCompleteText : getString(R.string.picture_please_select));
+ } else {
+ // 已选择
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mTvPictureOk.setText(String.format(config.style.pictureCompleteText, startCount, 1));
+ } else {
+ mTvPictureOk.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureCompleteText)
+ ? config.style.pictureCompleteText : getString(R.string.picture_done));
+ }
+ }
+ } else {
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (startCount <= 0) {
+ // 未选择任何图片
+ mTvPictureOk.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureUnCompleteText)
+ ? config.style.pictureUnCompleteText : getString(R.string.picture_done_front_num,
+ startCount, config.maxSelectNum));
+ } else {
+ // 已选择
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mTvPictureOk.setText(String.format(config.style.pictureCompleteText, startCount, config.maxSelectNum));
+ } else {
+ mTvPictureOk.setText(getString(R.string.picture_done_front_num,
+ startCount, config.maxSelectNum));
+ }
+ }
+ }
+ }
+
+ /**
+ * ViewPage滑动数据变化回调
+ *
+ * @param media
+ */
+ protected void onPageSelectedChange(LocalMedia media) {
+
+ }
+
+ /**
+ * 动态设置相册主题
+ */
+ @Override
+ public void initPictureSelectorStyle() {
+ if (config.style != null) {
+ if (config.style.pictureTitleTextColor != 0) {
+ tvTitle.setTextColor(config.style.pictureTitleTextColor);
+ }
+ if (config.style.pictureTitleTextSize != 0) {
+ tvTitle.setTextSize(config.style.pictureTitleTextSize);
+ }
+ if (config.style.pictureLeftBackIcon != 0) {
+ pictureLeftBack.setImageResource(config.style.pictureLeftBackIcon);
+ }
+ if (config.style.picturePreviewBottomBgColor != 0) {
+ selectBarLayout.setBackgroundColor(config.style.picturePreviewBottomBgColor);
+ }
+ if (config.style.pictureCheckNumBgStyle != 0) {
+ tvMediaNum.setBackgroundResource(config.style.pictureCheckNumBgStyle);
+ }
+ if (config.style.pictureCheckedStyle != 0) {
+ check.setBackgroundResource(config.style.pictureCheckedStyle);
+ }
+ if (config.style.pictureUnCompleteTextColor != 0) {
+ mTvPictureOk.setTextColor(config.style.pictureUnCompleteTextColor);
+ }
+ if (!TextUtils.isEmpty(config.style.pictureUnCompleteText)) {
+ mTvPictureOk.setText(config.style.pictureUnCompleteText);
+ }
+ }
+ titleViewBg.setBackgroundColor(colorPrimary);
+
+ if (config.isOriginalControl) {
+ if (config.style != null) {
+ if (config.style.pictureOriginalControlStyle != 0) {
+ mCbOriginal.setButtonDrawable(config.style.pictureOriginalControlStyle);
+ } else {
+ mCbOriginal.setButtonDrawable(ContextCompat.getDrawable(this, R.drawable.picture_original_checkbox));
+ }
+ if (config.style.pictureOriginalFontColor != 0) {
+ mCbOriginal.setTextColor(config.style.pictureOriginalFontColor);
+ } else {
+ mCbOriginal.setTextColor(ContextCompat.getColor(this, R.color.picture_color_53575e));
+ }
+ if (config.style.pictureOriginalTextSize != 0) {
+ mCbOriginal.setTextSize(config.style.pictureOriginalTextSize);
+ }
+ } else {
+ mCbOriginal.setButtonDrawable(ContextCompat.getDrawable(this, R.drawable.picture_original_checkbox));
+
+ mCbOriginal.setTextColor(ContextCompat.getColor(this, R.color.picture_color_53575e));
+ }
+ }
+
+ onSelectNumChange(false);
+ }
+
+ /**
+ * 这里没实际意义,好处是预览图片时 滑动到屏幕一半以上可看到下一张图片是否选中了
+ *
+ * @param previewEggs 是否显示预览友好体验
+ * @param positionOffsetPixels 滑动偏移量
+ */
+ private void isPreviewEggs(boolean previewEggs, int position, int positionOffsetPixels) {
+ if (previewEggs) {
+ if (adapter.getSize() > 0) {
+ LocalMedia media;
+ int num;
+ if (positionOffsetPixels < screenWidth / 2) {
+ media = adapter.getItem(position);
+ if (media != null) {
+ check.setSelected(isSelected(media));
+ if (config.isWeChatStyle) {
+ onUpdateSelectedChange(media);
+ } else {
+ if (config.checkNumMode) {
+ num = media.getNum();
+ check.setText(ValueOf.toString(num));
+ notifyCheckChanged(media);
+ onImageChecked(position);
+ }
+ }
+ }
+ } else {
+ media = adapter.getItem(position + 1);
+ if (media != null) {
+ check.setSelected(isSelected(media));
+ if (config.isWeChatStyle) {
+ onUpdateSelectedChange(media);
+ } else {
+ if (config.checkNumMode) {
+ num = media.getNum();
+ check.setText(ValueOf.toString(num));
+ notifyCheckChanged(media);
+ onImageChecked(position + 1);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 初始化ViewPage数据
+ *
+ * @param list
+ */
+ private void initViewPageAdapterData(List list) {
+ adapter = new PictureSimpleFragmentAdapter(config, this);
+ adapter.bindData(list);
+ viewPager.setAdapter(adapter);
+ viewPager.setCurrentItem(position);
+ setTitle();
+ onImageChecked(position);
+ LocalMedia media = adapter.getItem(position);
+ if (media != null) {
+ index = media.getPosition();
+ if (config.checkNumMode) {
+ tvMediaNum.setSelected(true);
+ check.setText(ValueOf.toString(media.getNum()));
+ notifyCheckChanged(media);
+ }
+ }
+ }
+
+ /**
+ * 重置标题栏和分页码
+ */
+ private void setNewTitle() {
+ mPage = 0;
+ position = 0;
+ setTitle();
+ }
+
+ /**
+ * 设置标题
+ */
+ private void setTitle() {
+ if (config.isPageStrategy && !isBottomPreview) {
+ tvTitle.setText(getString(R.string.picture_preview_image_num,
+ position + 1, totalNumber));
+ } else {
+ tvTitle.setText(getString(R.string.picture_preview_image_num,
+ position + 1, adapter.getSize()));
+ }
+ }
+
+ /**
+ * 选择按钮更新
+ */
+ private void notifyCheckChanged(LocalMedia imageBean) {
+ if (config.checkNumMode) {
+ check.setText("");
+ int size = selectData.size();
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = selectData.get(i);
+ if (media.getPath().equals(imageBean.getPath())
+ || media.getId() == imageBean.getId()) {
+ imageBean.setNum(media.getNum());
+ check.setText(String.valueOf(imageBean.getNum()));
+ }
+ }
+ }
+ }
+
+ /**
+ * 更新选择的顺序
+ */
+ private void subSelectPosition() {
+ for (int index = 0, len = selectData.size(); index < len; index++) {
+ LocalMedia media = selectData.get(index);
+ media.setNum(index + 1);
+ }
+ }
+
+ /**
+ * 判断当前图片是否选中
+ *
+ * @param position
+ */
+ public void onImageChecked(int position) {
+ if (adapter.getSize() > 0) {
+ LocalMedia media = adapter.getItem(position);
+ if (media != null) {
+ check.setSelected(isSelected(media));
+ }
+ } else {
+ check.setSelected(false);
+ }
+ }
+
+ /**
+ * 当前图片是否选中
+ *
+ * @param image
+ * @return
+ */
+ protected boolean isSelected(LocalMedia image) {
+ int size = selectData.size();
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = selectData.get(i);
+ if (media.getPath().equals(image.getPath()) || media.getId() == image.getId()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 更新图片选择数量
+ */
+
+ protected void onSelectNumChange(boolean isRefresh) {
+ this.refresh = isRefresh;
+ boolean enable = selectData.size() != 0;
+ if (enable) {
+ mTvPictureOk.setEnabled(true);
+ mTvPictureOk.setSelected(true);
+ if (config.style != null) {
+ if (config.style.pictureCompleteTextColor != 0) {
+ mTvPictureOk.setTextColor(config.style.pictureCompleteTextColor);
+ } else {
+ mTvPictureOk.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_fa632d));
+ }
+ }
+ if (numComplete) {
+ initCompleteText(selectData.size());
+ } else {
+ if (refresh) {
+ tvMediaNum.startAnimation(animation);
+ }
+ tvMediaNum.setVisibility(View.VISIBLE);
+ tvMediaNum.setText(String.valueOf(selectData.size()));
+ if (config.style != null && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mTvPictureOk.setText(config.style.pictureCompleteText);
+ } else {
+ mTvPictureOk.setText(getString(R.string.picture_completed));
+ }
+ }
+ } else {
+ mTvPictureOk.setEnabled(false);
+ mTvPictureOk.setSelected(false);
+ if (config.style != null) {
+ if (config.style.pictureUnCompleteTextColor != 0) {
+ mTvPictureOk.setTextColor(config.style.pictureUnCompleteTextColor);
+ } else {
+ mTvPictureOk.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_9b));
+ }
+ }
+ if (numComplete) {
+ initCompleteText(0);
+ } else {
+ tvMediaNum.setVisibility(View.INVISIBLE);
+ if (config.style != null && !TextUtils.isEmpty(config.style.pictureUnCompleteText)) {
+ mTvPictureOk.setText(config.style.pictureUnCompleteText);
+ } else {
+ mTvPictureOk.setText(getString(R.string.picture_please_select));
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onClick(View view) {
+ int id = view.getId();
+ if (id == R.id.pictureLeftBack) {
+ onBackPressed();
+ } else if (id == R.id.tv_ok || id == R.id.tvMediaNum) {
+ onComplete();
+ } else if (id == R.id.btnCheck) {
+ onCheckedComplete();
+ }
+ }
+
+ protected void onCheckedComplete() {
+ if (adapter.getSize() > 0) {
+ LocalMedia image = adapter.getItem(viewPager.getCurrentItem());
+ String mimeType = selectData.size() > 0 ?
+ selectData.get(0).getMimeType() : "";
+ int currentSize = selectData.size();
+ if (config.isWithVideoImage) {
+ // 混选模式
+ int videoSize = 0;
+ for (int i = 0; i < currentSize; i++) {
+ LocalMedia media = selectData.get(i);
+ if (PictureMimeType.isHasVideo(media.getMimeType())) {
+ videoSize++;
+ }
+ }
+ if (image != null && PictureMimeType.isHasVideo(image.getMimeType())) {
+ if (config.maxVideoSelectNum <= 0) {
+ // 如果视频可选数量是0
+ showPromptDialog(getString(R.string.picture_rule));
+ return;
+ }
+
+ if (selectData.size() >= config.maxSelectNum && !check.isSelected()) {
+ showPromptDialog(getString(R.string.picture_message_max_num, config.maxSelectNum));
+ return;
+ }
+
+ if (videoSize >= config.maxVideoSelectNum && !check.isSelected()) {
+ // 如果选择的是视频
+ showPromptDialog(StringUtils.getMsg(getContext(), image.getMimeType(), config.maxVideoSelectNum));
+ return;
+ }
+
+ if (!check.isSelected() && config.videoMinSecond > 0 && image.getDuration() < config.videoMinSecond) {
+ // 视频小于最低指定的长度
+ showPromptDialog(getContext().getString(R.string.picture_choose_min_seconds, config.videoMinSecond / 1000));
+ return;
+ }
+
+ if (!check.isSelected() && config.videoMaxSecond > 0 && image.getDuration() > config.videoMaxSecond) {
+ // 视频时长超过了指定的长度
+ showPromptDialog(getContext().getString(R.string.picture_choose_max_seconds, config.videoMaxSecond / 1000));
+ return;
+ }
+ }
+ if (image != null && PictureMimeType.isHasImage(image.getMimeType())) {
+ if (selectData.size() >= config.maxSelectNum && !check.isSelected()) {
+ showPromptDialog(getString(R.string.picture_message_max_num, config.maxSelectNum));
+ return;
+ }
+ }
+ } else {
+ // 非混选模式
+ if (!TextUtils.isEmpty(mimeType)) {
+ boolean mimeTypeSame = PictureMimeType.isMimeTypeSame(mimeType, image.getMimeType());
+ if (!mimeTypeSame) {
+ showPromptDialog(getString(R.string.picture_rule));
+ return;
+ }
+ }
+ if (PictureMimeType.isHasVideo(mimeType) && config.maxVideoSelectNum > 0) {
+ if (currentSize >= config.maxVideoSelectNum && !check.isSelected()) {
+ // 如果先选择的是视频
+ showPromptDialog(StringUtils.getMsg(getContext(), mimeType, config.maxVideoSelectNum));
+ return;
+ }
+
+ if (!check.isSelected() && config.videoMinSecond > 0 && image.getDuration() < config.videoMinSecond) {
+ // 视频小于最低指定的长度
+ showPromptDialog(getContext().getString(R.string.picture_choose_min_seconds, config.videoMinSecond / 1000));
+ return;
+ }
+
+ if (!check.isSelected() && config.videoMaxSecond > 0 && image.getDuration() > config.videoMaxSecond) {
+ // 视频时长超过了指定的长度
+ showPromptDialog(getContext().getString(R.string.picture_choose_max_seconds, config.videoMaxSecond / 1000));
+ return;
+ }
+ } else {
+ if (currentSize >= config.maxSelectNum && !check.isSelected()) {
+ showPromptDialog(StringUtils.getMsg(getContext(), mimeType, config.maxSelectNum));
+ return;
+ }
+ if (PictureMimeType.isHasVideo(image.getMimeType())) {
+ if (!check.isSelected() && config.videoMinSecond > 0 && image.getDuration() < config.videoMinSecond) {
+ // 视频小于最低指定的长度
+ showPromptDialog(getContext().getString(R.string.picture_choose_min_seconds, config.videoMinSecond / 1000));
+ return;
+ }
+
+ if (!check.isSelected() && config.videoMaxSecond > 0 && image.getDuration() > config.videoMaxSecond) {
+ // 视频时长超过了指定的长度
+ showPromptDialog(getContext().getString(R.string.picture_choose_max_seconds, config.videoMaxSecond / 1000));
+ return;
+ }
+ }
+ }
+ }
+ // 刷新图片列表中图片状态
+ boolean isChecked;
+ if (!check.isSelected()) {
+ isChecked = true;
+ check.setSelected(true);
+ check.startAnimation(animation);
+ } else {
+ isChecked = false;
+ check.setSelected(false);
+ }
+ isChangeSelectedData = true;
+ if (isChecked) {
+ VoiceUtils.getInstance().play();
+ // 如果是单选,则清空已选中的并刷新列表(作单一选择)
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ selectData.clear();
+ }
+
+ // 如果宽高为0,重新获取宽高
+ if (image.getWidth() == 0 || image.getHeight() == 0) {
+ int width = 0, height = 0;
+ image.setOrientation(-1);
+ if (PictureMimeType.isContent(image.getPath())) {
+ if (PictureMimeType.isHasVideo(image.getMimeType())) {
+ int[] size = MediaUtils.getVideoSizeForUri(getContext(), Uri.parse(image.getPath()));
+ width = size[0];
+ height = size[1];
+ } else if (PictureMimeType.isHasImage(image.getMimeType())) {
+ int[] size = MediaUtils.getImageSizeForUri(getContext(), Uri.parse(image.getPath()));
+ width = size[0];
+ height = size[1];
+ }
+ } else {
+ if (PictureMimeType.isHasVideo(image.getMimeType())) {
+ int[] size = MediaUtils.getVideoSizeForUrl(image.getPath());
+ width = size[0];
+ height = size[1];
+ } else if (PictureMimeType.isHasImage(image.getMimeType())) {
+ int[] size = MediaUtils.getImageSizeForUrl(image.getPath());
+ width = size[0];
+ height = size[1];
+ }
+ }
+ image.setWidth(width);
+ image.setHeight(height);
+ }
+
+ // 如果有旋转信息图片宽高则是相反
+ MediaUtils.setOrientationAsynchronous(getContext(), image, config.isAndroidQChangeWH, config.isAndroidQChangeVideoWH, null);
+ selectData.add(image);
+ onSelectedChange(true, image);
+ image.setNum(selectData.size());
+ if (config.checkNumMode) {
+ check.setText(String.valueOf(image.getNum()));
+ }
+ } else {
+ int size = selectData.size();
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = selectData.get(i);
+ if (media.getPath().equals(image.getPath())
+ || media.getId() == image.getId()) {
+ selectData.remove(media);
+ onSelectedChange(false, image);
+ subSelectPosition();
+ notifyCheckChanged(media);
+ break;
+ }
+ }
+ }
+ onSelectNumChange(true);
+ }
+ }
+
+ /**
+ * 选中或是移除
+ *
+ * @param isAddRemove
+ * @param media
+ */
+ protected void onSelectedChange(boolean isAddRemove, LocalMedia media) {
+
+ }
+
+ /**
+ * 更新选中或是移除状态
+ *
+ * @param media
+ */
+ protected void onUpdateSelectedChange(LocalMedia media) {
+
+ }
+
+ protected void onComplete() {
+ // 如果设置了图片最小选择数量,则判断是否满足条件
+ int size = selectData.size();
+ LocalMedia image = selectData.size() > 0 ? selectData.get(0) : null;
+ String mimeType = image != null ? image.getMimeType() : "";
+ if (config.isWithVideoImage) {
+ // 混选模式
+ int videoSize = 0;
+ int imageSize = 0;
+ int currentSize = selectData.size();
+ for (int i = 0; i < currentSize; i++) {
+ LocalMedia media = selectData.get(i);
+ if (PictureMimeType.isHasVideo(media.getMimeType())) {
+ videoSize++;
+ } else {
+ imageSize++;
+ }
+ }
+ if (config.selectionMode == PictureConfig.MULTIPLE) {
+ if (config.minSelectNum > 0) {
+ if (imageSize < config.minSelectNum) {
+ showPromptDialog(getString(R.string.picture_min_img_num, config.minSelectNum));
+ return;
+ }
+ }
+ if (config.minVideoSelectNum > 0) {
+ if (videoSize < config.minVideoSelectNum) {
+ showPromptDialog(getString(R.string.picture_min_video_num, config.minVideoSelectNum));
+ return;
+ }
+ }
+ }
+ } else {
+ // 单选模式(同类型)
+ if (config.selectionMode == PictureConfig.MULTIPLE) {
+ if (PictureMimeType.isHasImage(mimeType) && config.minSelectNum > 0 && size < config.minSelectNum) {
+ String str = getString(R.string.picture_min_img_num, config.minSelectNum);
+ showPromptDialog(str);
+ return;
+ }
+ if (PictureMimeType.isHasVideo(mimeType) && config.minVideoSelectNum > 0 && size < config.minVideoSelectNum) {
+ String str = getString(R.string.picture_min_video_num, config.minVideoSelectNum);
+ showPromptDialog(str);
+ return;
+ }
+ }
+ }
+ isCompleteOrSelected = true;
+ isChangeSelectedData = true;
+ if (config.isCheckOriginalImage) {
+ onBackPressed();
+ return;
+ }
+ if (config.chooseMode == PictureMimeType.ofAll() && config.isWithVideoImage) {
+ bothMimeTypeWith(mimeType, image);
+ } else {
+ separateMimeTypeWith(mimeType, image);
+ }
+ }
+
+ /**
+ * 两者不同类型的处理方式
+ *
+ * @param mimeType
+ * @param image
+ */
+ private void bothMimeTypeWith(String mimeType, LocalMedia image) {
+ if (config.enableCrop) {
+ isCompleteOrSelected = false;
+ boolean isHasImage = PictureMimeType.isHasImage(mimeType);
+ if (config.selectionMode == PictureConfig.SINGLE && isHasImage) {
+ config.originalPath = image.getPath();
+ startCrop(config.originalPath, image.getMimeType());
+ } else {
+ // 是图片和选择压缩并且是多张,调用批量压缩
+ ArrayList cuts = new ArrayList<>();
+ int count = selectData.size();
+ int imageNum = 0;
+ for (int i = 0; i < count; i++) {
+ LocalMedia media = selectData.get(i);
+ if (media == null
+ || TextUtils.isEmpty(media.getPath())) {
+ continue;
+ }
+ if (PictureMimeType.isHasImage(media.getMimeType())) {
+ imageNum++;
+ }
+ CutInfo cutInfo = new CutInfo();
+ cutInfo.setId(media.getId());
+ cutInfo.setPath(media.getPath());
+ cutInfo.setImageWidth(media.getWidth());
+ cutInfo.setImageHeight(media.getHeight());
+ cutInfo.setMimeType(media.getMimeType());
+ cutInfo.setAndroidQToPath(media.getAndroidQToPath());
+ cutInfo.setId(media.getId());
+ cutInfo.setDuration(media.getDuration());
+ cutInfo.setRealPath(media.getRealPath());
+ cuts.add(cutInfo);
+ }
+ if (imageNum <= 0) {
+ // 全是视频
+ isCompleteOrSelected = true;
+ onBackPressed();
+ } else {
+ // 图片和视频共存
+ startCrop(cuts);
+ }
+ }
+ } else {
+ onBackPressed();
+ }
+ }
+
+ /**
+ * 同一类型的图片或视频处理逻辑
+ *
+ * @param mimeType
+ * @param image
+ */
+ private void separateMimeTypeWith(String mimeType, LocalMedia image) {
+ if (config.enableCrop && PictureMimeType.isHasImage(mimeType)) {
+ isCompleteOrSelected = false;
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ config.originalPath = image.getPath();
+ startCrop(config.originalPath, image.getMimeType());
+ } else {
+ // 是图片和选择压缩并且是多张,调用批量压缩
+ ArrayList cuts = new ArrayList<>();
+ int count = selectData.size();
+ for (int i = 0; i < count; i++) {
+ LocalMedia media = selectData.get(i);
+ if (media == null
+ || TextUtils.isEmpty(media.getPath())) {
+ continue;
+ }
+ CutInfo cutInfo = new CutInfo();
+ cutInfo.setId(media.getId());
+ cutInfo.setPath(media.getPath());
+ cutInfo.setImageWidth(media.getWidth());
+ cutInfo.setImageHeight(media.getHeight());
+ cutInfo.setMimeType(media.getMimeType());
+ cutInfo.setAndroidQToPath(media.getAndroidQToPath());
+ cutInfo.setId(media.getId());
+ cutInfo.setDuration(media.getDuration());
+ cutInfo.setRealPath(media.getRealPath());
+ cuts.add(cutInfo);
+ }
+ startCrop(cuts);
+ }
+ } else {
+ onBackPressed();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
+ switch (requestCode) {
+ case UCrop.REQUEST_MULTI_CROP:
+ // 裁剪数据
+ List list = UCrop.getMultipleOutput(data);
+ data.putParcelableArrayListExtra(UCrop.Options.EXTRA_OUTPUT_URI_LIST,
+ (ArrayList extends Parcelable>) list);
+ // 已选数量
+ data.putParcelableArrayListExtra(PictureConfig.EXTRA_SELECT_LIST,
+ (ArrayList extends Parcelable>) selectData);
+ setResult(RESULT_OK, data);
+ finish();
+ break;
+ case UCrop.REQUEST_CROP:
+ if (data != null) {
+ data.putParcelableArrayListExtra(PictureConfig.EXTRA_SELECT_LIST,
+ (ArrayList extends Parcelable>) selectData);
+ setResult(RESULT_OK, data);
+ }
+ finish();
+ break;
+ }
+ } else if (resultCode == UCrop.RESULT_ERROR) {
+ Throwable throwable = (Throwable) data.getSerializableExtra(UCrop.EXTRA_ERROR);
+ ToastUtils.s(getContext(), throwable.getMessage());
+ }
+ }
+
+
+ @Override
+ public void onBackPressed() {
+ updateResult();
+ if (config.windowAnimationStyle != null
+ && config.windowAnimationStyle.activityPreviewExitAnimation != 0) {
+ finish();
+ overridePendingTransition(0, config.windowAnimationStyle != null
+ && config.windowAnimationStyle.activityPreviewExitAnimation != 0 ?
+ config.windowAnimationStyle.activityPreviewExitAnimation : R.anim.picture_anim_exit);
+ } else {
+ closeActivity();
+ }
+ }
+
+ /**
+ * 更新选中数据
+ */
+ private void updateResult() {
+ Intent intent = new Intent();
+ if (isChangeSelectedData) {
+ intent.putExtra(PictureConfig.EXTRA_COMPLETE_SELECTED, isCompleteOrSelected);
+ intent.putParcelableArrayListExtra(PictureConfig.EXTRA_SELECT_LIST,
+ (ArrayList extends Parcelable>) selectData);
+ }
+ // 把是否原图标识返回,主要用于开启了开发者选项不保留活动或内存不足时 原图选中状态没有全局同步问题
+ if (config.isOriginalControl) {
+ intent.putExtra(PictureConfig.EXTRA_CHANGE_ORIGINAL, config.isCheckOriginalImage);
+ }
+ setResult(RESULT_CANCELED, intent);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(PictureConfig.EXTRA_COMPLETE_SELECTED, isCompleteOrSelected);
+ outState.putBoolean(PictureConfig.EXTRA_CHANGE_SELECTED_DATA, isChangeSelectedData);
+ PictureSelector.saveSelectorList(outState, selectData);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (!isOnSaveInstanceState) {
+ ImagesObservable.getInstance().clearPreviewMediaData();
+ }
+ if (mHandler != null) {
+ mHandler.removeCallbacksAndMessages(null);
+ mHandler = null;
+ }
+ if (animation != null) {
+ animation.cancel();
+ animation = null;
+ }
+ if (adapter != null) {
+ adapter.clear();
+ }
+ }
+
+ @Override
+ public void onActivityBackPressed() {
+ onBackPressed();
+ }
+
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PictureSelectionModel.java b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectionModel.java
new file mode 100644
index 0000000..ff5ae91
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectionModel.java
@@ -0,0 +1,1389 @@
+package com.luck.picture.lib;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Build;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.FloatRange;
+import androidx.annotation.IntRange;
+import androidx.annotation.StyleRes;
+import androidx.fragment.app.Fragment;
+
+import com.luck.picture.lib.animators.AnimationType;
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.config.PictureSelectionConfig;
+import com.luck.picture.lib.config.UCropOptions;
+import com.luck.picture.lib.engine.CacheResourcesEngine;
+import com.luck.picture.lib.engine.ImageEngine;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.listener.OnCustomCameraInterfaceListener;
+import com.luck.picture.lib.listener.OnResultCallbackListener;
+import com.luck.picture.lib.listener.OnVideoSelectedPlayCallback;
+import com.luck.picture.lib.style.PictureCropParameterStyle;
+import com.luck.picture.lib.style.PictureParameterStyle;
+import com.luck.picture.lib.style.PictureWindowAnimationStyle;
+import com.luck.picture.lib.tools.DoubleUtils;
+import com.luck.picture.lib.tools.SdkVersionUtils;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+import static android.os.Build.VERSION_CODES.KITKAT;
+
+/**
+ * @author:luck
+ * @date:2017-5-24 21:30
+ * @describe:PictureSelectionModel
+ */
+
+public class PictureSelectionModel {
+ private PictureSelectionConfig selectionConfig;
+ private PictureSelector selector;
+
+ public PictureSelectionModel(PictureSelector selector, int chooseMode) {
+ this.selector = selector;
+ selectionConfig = PictureSelectionConfig.getCleanInstance();
+ selectionConfig.chooseMode = chooseMode;
+ }
+
+ public PictureSelectionModel(PictureSelector selector, int chooseMode, boolean camera) {
+ this.selector = selector;
+ selectionConfig = PictureSelectionConfig.getCleanInstance();
+ selectionConfig.camera = camera;
+ selectionConfig.chooseMode = chooseMode;
+ }
+
+ /**
+ * @param themeStyleId PictureSelector Theme style
+ * @return PictureSelectionModel
+ * Use {@link R.style#picture_default_style#picture_Sina_style#picture_white_style#picture_QQ_style#picture_WeChat_style}
+ */
+ public PictureSelectionModel theme(@StyleRes int themeStyleId) {
+ selectionConfig.themeStyleId = themeStyleId;
+ return this;
+ }
+
+ /**
+ * @param locale Language
+ * @return PictureSelectionModel
+ */
+ public PictureSelectionModel setLanguage(int language) {
+ selectionConfig.language = language;
+ return this;
+ }
+
+ /**
+ * Change the desired orientation of this activity. If the activity
+ * is currently in the foreground or otherwise impacting the screen
+ * orientation, the screen will immediately be changed (possibly causing
+ * the activity to be restarted). Otherwise, this will be used the next
+ * time the activity is visible.
+ *
+ * @param requestedOrientation An orientation constant as used in
+ * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
+ */
+ public PictureSelectionModel setRequestedOrientation(int requestedOrientation) {
+ selectionConfig.requestedOrientation = requestedOrientation;
+ return this;
+ }
+
+ /**
+ * @param engine Image Load the engine
+ * @return Use {@link .imageEngine()}.
+ */
+ @Deprecated
+ public PictureSelectionModel loadImageEngine(ImageEngine engine) {
+ if (PictureSelectionConfig.imageEngine != engine) {
+ PictureSelectionConfig.imageEngine = engine;
+ }
+ return this;
+ }
+
+ /**
+ * @param engine Image Load the engine
+ * @return
+ */
+ public PictureSelectionModel imageEngine(ImageEngine engine) {
+ if (PictureSelectionConfig.imageEngine != engine) {
+ PictureSelectionConfig.imageEngine = engine;
+ }
+ return this;
+ }
+
+ /**
+ * Only for Android version Q
+ *
+ * @param cacheResourcesEngine Image Cache
+ * @return
+ */
+ @Deprecated
+ public PictureSelectionModel loadCacheResourcesCallback(CacheResourcesEngine cacheResourcesEngine) {
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ if (PictureSelectionConfig.cacheResourcesEngine != cacheResourcesEngine) {
+ PictureSelectionConfig.cacheResourcesEngine = new WeakReference<>(cacheResourcesEngine).get();
+ }
+ }
+ return this;
+ }
+
+ /**
+ * @param selectionMode PictureSelector Selection model and PictureConfig.MULTIPLE or PictureConfig.SINGLE
+ * @return
+ */
+ public PictureSelectionModel selectionMode(int selectionMode) {
+ selectionConfig.selectionMode = selectionMode;
+ return this;
+ }
+
+ /**
+ * @param isWeChatStyle Select style with or without WeChat enabled
+ * @return
+ */
+ public PictureSelectionModel isWeChatStyle(boolean isWeChatStyle) {
+ selectionConfig.isWeChatStyle = isWeChatStyle;
+ return this;
+ }
+
+ /**
+ * @param isUseCustomCamera Whether to use a custom camera
+ * @return
+ */
+ public PictureSelectionModel isUseCustomCamera(boolean isUseCustomCamera) {
+ selectionConfig.isUseCustomCamera = Build.VERSION.SDK_INT > KITKAT && isUseCustomCamera;
+ return this;
+ }
+
+ /**
+ * @param callback Provide video playback control,Users are free to customize the video display interface
+ * @return
+ */
+ public PictureSelectionModel bindCustomPlayVideoCallback(OnVideoSelectedPlayCallback callback) {
+ PictureSelectionConfig.customVideoPlayCallback = new WeakReference<>(callback).get();
+ return this;
+ }
+
+ /**
+ * # The developer provides an additional callback interface to the user where the user can perform some custom actions
+ * {link 如果是自定义相机则必须使用.startActivityForResult(this,PictureConfig.REQUEST_CAMERA);方式启动否则PictureSelector处理不了相机后的回调}
+ *
+ * @param listener
+ * @return Use ${bindCustomCameraInterfaceListener}
+ */
+ @Deprecated
+ public PictureSelectionModel bindPictureSelectorInterfaceListener(OnCustomCameraInterfaceListener listener) {
+ PictureSelectionConfig.onCustomCameraInterfaceListener = new WeakReference<>(listener).get();
+ return this;
+ }
+
+ /**
+ * # The developer provides an additional callback interface to the user where the user can perform some custom actions
+ * {link 如果是自定义相机则必须使用.startActivityForResult(this,PictureConfig.REQUEST_CAMERA);方式启动否则PictureSelector处理不了相机后的回调}
+ *
+ * @param listener
+ * @return
+ */
+ public PictureSelectionModel bindCustomCameraInterfaceListener(OnCustomCameraInterfaceListener listener) {
+ PictureSelectionConfig.onCustomCameraInterfaceListener = new WeakReference<>(listener).get();
+ return this;
+ }
+
+ /**
+ * @param buttonFeatures Set the record button function
+ * # 具体参考 CustomCameraView.BUTTON_STATE_BOTH、BUTTON_STATE_ONLY_CAPTURE、BUTTON_STATE_ONLY_RECORDER
+ * @return
+ */
+ public PictureSelectionModel setButtonFeatures(int buttonFeatures) {
+ selectionConfig.buttonFeatures = buttonFeatures;
+ return this;
+ }
+
+ /**
+ * @param enableCrop Do you want to start cutting ?
+ * @return Use {link .isEnableCrop()}
+ */
+ @Deprecated
+ public PictureSelectionModel enableCrop(boolean enableCrop) {
+ selectionConfig.enableCrop = enableCrop;
+ return this;
+ }
+
+ /**
+ * @param enableCrop Do you want to start cutting ?
+ * @return
+ */
+ public PictureSelectionModel isEnableCrop(boolean enableCrop) {
+ selectionConfig.enableCrop = enableCrop;
+ return this;
+ }
+
+ /**
+ * @param uCropOptions UCrop parameter configuration is provided
+ * @return
+ */
+ public PictureSelectionModel basicUCropConfig(UCropOptions uCropOptions) {
+ selectionConfig.uCropOptions = uCropOptions;
+ return this;
+ }
+
+ /**
+ * @param isMultipleSkipCrop Whether multiple images can be skipped when cropping
+ * @return
+ */
+ public PictureSelectionModel isMultipleSkipCrop(boolean isMultipleSkipCrop) {
+ selectionConfig.isMultipleSkipCrop = isMultipleSkipCrop;
+ return this;
+ }
+
+
+ /**
+ * @param enablePreviewAudio Do you want to ic_play audio ?
+ * @return
+ */
+ @Deprecated
+ public PictureSelectionModel enablePreviewAudio(boolean enablePreviewAudio) {
+ selectionConfig.enablePreviewAudio = enablePreviewAudio;
+ return this;
+ }
+
+ /**
+ * @param enablePreviewAudio Do you want to ic_play audio ?
+ * @return
+ */
+ @Deprecated
+ public PictureSelectionModel isEnablePreviewAudio(boolean enablePreviewAudio) {
+ selectionConfig.enablePreviewAudio = enablePreviewAudio;
+ return this;
+ }
+
+ /**
+ * @param freeStyleCropEnabled Crop frame is move ?
+ * @return
+ */
+ public PictureSelectionModel freeStyleCropEnabled(boolean freeStyleCropEnabled) {
+ selectionConfig.freeStyleCropEnabled = freeStyleCropEnabled;
+ return this;
+ }
+
+ /**
+ * @param scaleEnabled Crop frame is zoom ?
+ * @return
+ */
+ public PictureSelectionModel scaleEnabled(boolean scaleEnabled) {
+ selectionConfig.scaleEnabled = scaleEnabled;
+ return this;
+ }
+
+ /**
+ * @param rotateEnabled Crop frame is rotate ?
+ * @return
+ */
+ public PictureSelectionModel rotateEnabled(boolean rotateEnabled) {
+ selectionConfig.rotateEnabled = rotateEnabled;
+ return this;
+ }
+
+ /**
+ * @param circleDimmedLayer Circular head cutting
+ * @return
+ */
+ public PictureSelectionModel circleDimmedLayer(boolean circleDimmedLayer) {
+ selectionConfig.circleDimmedLayer = circleDimmedLayer;
+ return this;
+ }
+
+ /**
+ * @param circleDimmedColor setCircleDimmedColor
+ * @return
+ */
+ @Deprecated
+ public PictureSelectionModel setCircleDimmedColor(int circleDimmedColor) {
+ selectionConfig.circleDimmedColor = circleDimmedColor;
+ return this;
+ }
+
+ /**
+ * @param dimmedColor
+ * @return
+ */
+ public PictureSelectionModel setCropDimmedColor(int dimmedColor) {
+ selectionConfig.circleDimmedColor = dimmedColor;
+ return this;
+ }
+
+ /**
+ * @param circleDimmedBorderColor setCircleDimmedBorderColor
+ * @return
+ */
+ public PictureSelectionModel setCircleDimmedBorderColor(int circleDimmedBorderColor) {
+ selectionConfig.circleDimmedBorderColor = circleDimmedBorderColor;
+ return this;
+ }
+
+ /**
+ * @param circleStrokeWidth setCircleStrokeWidth
+ * @return
+ */
+ public PictureSelectionModel setCircleStrokeWidth(int circleStrokeWidth) {
+ selectionConfig.circleStrokeWidth = circleStrokeWidth;
+ return this;
+ }
+
+ /**
+ * @param showCropFrame Whether to show crop frame
+ * @return
+ */
+ public PictureSelectionModel showCropFrame(boolean showCropFrame) {
+ selectionConfig.showCropFrame = showCropFrame;
+ return this;
+ }
+
+ /**
+ * @param showCropGrid Whether to show CropGrid
+ * @return
+ */
+ public PictureSelectionModel showCropGrid(boolean showCropGrid) {
+ selectionConfig.showCropGrid = showCropGrid;
+ return this;
+ }
+
+ /**
+ * @param hideBottomControls Whether is Clipping function bar
+ * 单选有效
+ * @return
+ */
+ public PictureSelectionModel hideBottomControls(boolean hideBottomControls) {
+ selectionConfig.hideBottomControls = hideBottomControls;
+ return this;
+ }
+
+ /**
+ * @param aspect_ratio_x Crop Proportion x
+ * @param aspect_ratio_y Crop Proportion y
+ * @return
+ */
+ public PictureSelectionModel withAspectRatio(int aspect_ratio_x, int aspect_ratio_y) {
+ selectionConfig.aspect_ratio_x = aspect_ratio_x;
+ selectionConfig.aspect_ratio_y = aspect_ratio_y;
+ return this;
+ }
+
+ /**
+ * @param isWithVideoImage Whether the pictures and videos can be selected together
+ * @return
+ */
+ public PictureSelectionModel isWithVideoImage(boolean isWithVideoImage) {
+ selectionConfig.isWithVideoImage =
+ selectionConfig.selectionMode != PictureConfig.SINGLE
+ && selectionConfig.chooseMode == PictureMimeType.ofAll() && isWithVideoImage;
+ return this;
+ }
+
+ /**
+ * When the maximum number of choices is reached, does the list enable the mask effect
+ *
+ * @param isMaxSelectEnabledMask
+ * @return
+ */
+ public PictureSelectionModel isMaxSelectEnabledMask(boolean isMaxSelectEnabledMask) {
+ selectionConfig.isMaxSelectEnabledMask = isMaxSelectEnabledMask;
+ return this;
+ }
+
+ /**
+ * @param maxSelectNum PictureSelector max selection
+ * @return
+ */
+ public PictureSelectionModel maxSelectNum(int maxSelectNum) {
+ selectionConfig.maxSelectNum = maxSelectNum;
+ return this;
+ }
+
+ /**
+ * @param minSelectNum PictureSelector min selection
+ * @return
+ */
+ public PictureSelectionModel minSelectNum(int minSelectNum) {
+ selectionConfig.minSelectNum = minSelectNum;
+ return this;
+ }
+
+ /**
+ * @param maxVideoSelectNum PictureSelector video max selection
+ * @return
+ */
+ public PictureSelectionModel maxVideoSelectNum(int maxVideoSelectNum) {
+ selectionConfig.maxVideoSelectNum = maxVideoSelectNum;
+ return this;
+ }
+
+ /**
+ * @param minVideoSelectNum PictureSelector video min selection
+ * @return
+ */
+ public PictureSelectionModel minVideoSelectNum(int minVideoSelectNum) {
+ selectionConfig.minVideoSelectNum = minVideoSelectNum;
+ return this;
+ }
+
+ /**
+ * Turn off Android Q to solve the problem that the width and height are reversed
+ *
+ * @param isChangeWH
+ * @return
+ */
+ public PictureSelectionModel closeAndroidQChangeWH(boolean isChangeWH) {
+ selectionConfig.isAndroidQChangeWH = isChangeWH;
+ return this;
+ }
+
+ /**
+ * Turn off Android Q to solve the problem that the width and height are reversed
+ *
+ * @param isChangeVideoWH
+ * @return
+ */
+ public PictureSelectionModel closeAndroidQChangeVideoWH(boolean isChangeVideoWH) {
+ selectionConfig.isAndroidQChangeVideoWH = isChangeVideoWH;
+ return this;
+ }
+
+ /**
+ * By clicking the title bar consecutively, RecyclerView automatically rolls back to the top
+ *
+ * @param isAutomaticTitleRecyclerTop
+ * @return
+ */
+ public PictureSelectionModel isAutomaticTitleRecyclerTop(boolean isAutomaticTitleRecyclerTop) {
+ selectionConfig.isAutomaticTitleRecyclerTop = isAutomaticTitleRecyclerTop;
+ return this;
+ }
+
+
+ /**
+ * @param Select whether to return directly
+ * @return
+ */
+ public PictureSelectionModel isSingleDirectReturn(boolean isSingleDirectReturn) {
+ selectionConfig.isSingleDirectReturn = selectionConfig.selectionMode
+ == PictureConfig.SINGLE && isSingleDirectReturn;
+ selectionConfig.isOriginalControl = (selectionConfig.selectionMode != PictureConfig.SINGLE || !isSingleDirectReturn) && selectionConfig.isOriginalControl;
+ return this;
+ }
+
+ /**
+ * Whether to turn on paging mode
+ *
+ * @param isPageStrategy
+ * @param pageSize Maximum number of pages {@link PageSize is preferably no less than 20}
+ * @return
+ */
+ public PictureSelectionModel isPageStrategy(boolean isPageStrategy, int pageSize) {
+ selectionConfig.isPageStrategy = isPageStrategy;
+ selectionConfig.pageSize = pageSize < PictureConfig.MIN_PAGE_SIZE ? PictureConfig.MAX_PAGE_SIZE : pageSize;
+ return this;
+ }
+
+ /**
+ * Whether to turn on paging mode
+ *
+ * @param isPageStrategy
+ * @param pageSize Maximum number of pages {@link PageSize is preferably no less than 20}
+ * @param isFilterInvalidFile Whether to filter invalid files {@link Some of the query performance is consumed,Especially on the Q version}
+ * @return
+ */
+ public PictureSelectionModel isPageStrategy(boolean isPageStrategy, int pageSize, boolean isFilterInvalidFile) {
+ selectionConfig.isPageStrategy = isPageStrategy;
+ selectionConfig.pageSize = pageSize < PictureConfig.MIN_PAGE_SIZE ? PictureConfig.MAX_PAGE_SIZE : pageSize;
+ selectionConfig.isFilterInvalidFile = isFilterInvalidFile;
+ return this;
+ }
+
+ /**
+ * Whether to turn on paging mode
+ *
+ * @param isPageStrategy
+ * @return
+ */
+ public PictureSelectionModel isPageStrategy(boolean isPageStrategy) {
+ selectionConfig.isPageStrategy = isPageStrategy;
+ return this;
+ }
+
+ /**
+ * Whether to turn on paging mode
+ *
+ * @param isPageStrategy
+ * @param isFilterInvalidFile Whether to filter invalid files {@link Some of the query performance is consumed,Especially on the Q version}
+ * @return
+ */
+ public PictureSelectionModel isPageStrategy(boolean isPageStrategy, boolean isFilterInvalidFile) {
+ selectionConfig.isPageStrategy = isPageStrategy;
+ selectionConfig.isFilterInvalidFile = isFilterInvalidFile;
+ return this;
+ }
+
+ /**
+ * @param videoQuality video quality and 0 or 1
+ * @return
+ */
+ public PictureSelectionModel videoQuality(int videoQuality) {
+ selectionConfig.videoQuality = videoQuality;
+ return this;
+ }
+
+ /**
+ * # alternative api cameraFileName(xxx.PNG);
+ *
+ * @param suffixType PictureSelector media format
+ * @return
+ */
+ public PictureSelectionModel imageFormat(String suffixType) {
+ selectionConfig.suffixType = suffixType;
+ return this;
+ }
+
+
+ /**
+ * @param cropWidth crop width
+ * @param cropHeight crop height
+ * @return this
+ * @deprecated Crop image output width and height
+ * {@link cropImageWideHigh()}
+ */
+ @Deprecated
+ public PictureSelectionModel cropWH(int cropWidth, int cropHeight) {
+ selectionConfig.cropWidth = cropWidth;
+ selectionConfig.cropHeight = cropHeight;
+ return this;
+ }
+
+ /**
+ * @param cropWidth crop width
+ * @param cropHeight crop height
+ * @return this
+ */
+ public PictureSelectionModel cropImageWideHigh(int cropWidth, int cropHeight) {
+ selectionConfig.cropWidth = cropWidth;
+ selectionConfig.cropHeight = cropHeight;
+ return this;
+ }
+
+ /**
+ * @param videoMaxSecond selection video max second
+ * @return
+ */
+ public PictureSelectionModel videoMaxSecond(int videoMaxSecond) {
+ selectionConfig.videoMaxSecond = (videoMaxSecond * 1000);
+ return this;
+ }
+
+ /**
+ * @param videoMinSecond selection video min second
+ * @return
+ */
+ public PictureSelectionModel videoMinSecond(int videoMinSecond) {
+ selectionConfig.videoMinSecond = videoMinSecond * 1000;
+ return this;
+ }
+
+
+ /**
+ * @param recordVideoSecond video record second
+ * @return
+ */
+ public PictureSelectionModel recordVideoSecond(int recordVideoSecond) {
+ selectionConfig.recordVideoSecond = recordVideoSecond;
+ return this;
+ }
+
+ /**
+ * @param width glide width
+ * @param height glide height
+ * @return 2.2.9开始 Glide改为外部用户自己定义此方法没有意义了
+ */
+ @Deprecated
+ public PictureSelectionModel glideOverride(@IntRange(from = 100) int width,
+ @IntRange(from = 100) int height) {
+ selectionConfig.overrideWidth = width;
+ selectionConfig.overrideHeight = height;
+ return this;
+ }
+
+ /**
+ * @param sizeMultiplier The multiplier to apply to the
+ * {@link com.bumptech.glide.request.target.Target}'s dimensions when
+ * loading the resource.
+ * @return 2.2.9开始Glide改为外部用户自己定义此方法没有意义了
+ */
+ @Deprecated
+ public PictureSelectionModel sizeMultiplier(@FloatRange(from = 0.1f) float sizeMultiplier) {
+ selectionConfig.sizeMultiplier = sizeMultiplier;
+ return this;
+ }
+
+ /**
+ * @param imageSpanCount PictureSelector image span count
+ * @return
+ */
+ public PictureSelectionModel imageSpanCount(int imageSpanCount) {
+ selectionConfig.imageSpanCount = imageSpanCount;
+ return this;
+ }
+
+ /**
+ * @param Less than how many KB images are not compressed
+ * @return
+ */
+ public PictureSelectionModel minimumCompressSize(int size) {
+ selectionConfig.minimumCompressSize = size;
+ return this;
+ }
+
+ /**
+ * @param compressQuality crop compress quality default 90
+ * @return 请使用 cutOutQuality();方法
+ */
+ @Deprecated
+ public PictureSelectionModel cropCompressQuality(int compressQuality) {
+ selectionConfig.cropCompressQuality = compressQuality;
+ return this;
+ }
+
+ /**
+ * @param cutQuality crop compress quality default 90
+ * @return
+ */
+ public PictureSelectionModel cutOutQuality(int cutQuality) {
+ selectionConfig.cropCompressQuality = cutQuality;
+ return this;
+ }
+
+ /**
+ * @param isCompress Whether to open compress
+ * @return Use {link .isCompress()}
+ */
+ @Deprecated
+ public PictureSelectionModel compress(boolean isCompress) {
+ selectionConfig.isCompress = isCompress;
+ return this;
+ }
+
+ /**
+ * @param isCompress Whether to open compress
+ * @return
+ */
+ public PictureSelectionModel isCompress(boolean isCompress) {
+ selectionConfig.isCompress = isCompress;
+ return this;
+ }
+
+ /**
+ * @param compressQuality Image compressed output quality
+ * @return
+ */
+ public PictureSelectionModel compressQuality(int compressQuality) {
+ selectionConfig.compressQuality = compressQuality;
+ return this;
+ }
+
+ /**
+ * @param returnEmpty No data can be returned
+ * @return
+ */
+ public PictureSelectionModel isReturnEmpty(boolean returnEmpty) {
+ selectionConfig.returnEmpty = returnEmpty;
+ return this;
+ }
+
+ /**
+ * @param synOrAsy Synchronous or asynchronous compression
+ * @return
+ */
+ public PictureSelectionModel synOrAsy(boolean synOrAsy) {
+ selectionConfig.synOrAsy = synOrAsy;
+ return this;
+ }
+
+ /**
+ * @param focusAlpha After compression, the transparent channel is retained
+ * @return
+ */
+ public PictureSelectionModel compressFocusAlpha(boolean focusAlpha) {
+ selectionConfig.focusAlpha = focusAlpha;
+ return this;
+ }
+
+ /**
+ * After recording with the system camera, does it support playing the video immediately using the system player
+ *
+ * @param isQuickCapture
+ * @return
+ */
+ public PictureSelectionModel isQuickCapture(boolean isQuickCapture) {
+ selectionConfig.isQuickCapture = isQuickCapture;
+ return this;
+ }
+
+ /**
+ * @param isOriginalControl Whether the original image is displayed
+ * @return
+ */
+ public PictureSelectionModel isOriginalImageControl(boolean isOriginalControl) {
+ selectionConfig.isOriginalControl = !selectionConfig.camera
+ && selectionConfig.chooseMode != PictureMimeType.ofVideo()
+ && selectionConfig.chooseMode != PictureMimeType.ofAudio() && isOriginalControl;
+ return this;
+ }
+
+ /**
+ * @param path save path
+ * @return
+ */
+ public PictureSelectionModel compressSavePath(String path) {
+ selectionConfig.compressSavePath = path;
+ return this;
+ }
+
+ /**
+ * Camera custom local file name
+ * # Such as xxx.png
+ *
+ * @param fileName
+ * @return
+ */
+ public PictureSelectionModel cameraFileName(String fileName) {
+ selectionConfig.cameraFileName = fileName;
+ return this;
+ }
+
+ /**
+ * crop custom local file name
+ * # Such as xxx.png
+ *
+ * @param renameCropFileName
+ * @return
+ */
+ public PictureSelectionModel renameCropFileName(String renameCropFileName) {
+ selectionConfig.renameCropFileName = renameCropFileName;
+ return this;
+ }
+
+ /**
+ * custom compress local file name
+ * # Such as xxx.png
+ *
+ * @param renameFile
+ * @return
+ */
+ public PictureSelectionModel renameCompressFile(String renameFile) {
+ selectionConfig.renameCompressFileName = renameFile;
+ return this;
+ }
+
+ /**
+ * @param zoomAnim Picture list zoom anim
+ * @return
+ */
+ public PictureSelectionModel isZoomAnim(boolean zoomAnim) {
+ selectionConfig.zoomAnim = zoomAnim;
+ return this;
+ }
+
+ /**
+ * @param previewEggs preview eggs It doesn't make much sense
+ * @return Use {link .isPreviewEggs()}
+ */
+ @Deprecated
+ public PictureSelectionModel previewEggs(boolean previewEggs) {
+ selectionConfig.previewEggs = previewEggs;
+ return this;
+ }
+
+ /**
+ * @param previewEggs preview eggs It doesn't make much sense
+ * @return
+ */
+ public PictureSelectionModel isPreviewEggs(boolean previewEggs) {
+ selectionConfig.previewEggs = previewEggs;
+ return this;
+ }
+
+ /**
+ * @param isCamera Whether to open camera button
+ * @return
+ */
+ public PictureSelectionModel isCamera(boolean isCamera) {
+ selectionConfig.isCamera = isCamera;
+ return this;
+ }
+
+ /**
+ * Extra used with {@link #Environment.getExternalStorageDirectory() + File.separator + "CustomCamera" + File.separator} to indicate that
+ *
+ * @param outPutCameraPath Camera save path 只支持Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
+ * @return
+ */
+ public PictureSelectionModel setOutputCameraPath(String outPutCameraPath) {
+ selectionConfig.outPutCameraPath = outPutCameraPath;
+ return this;
+ }
+
+
+ /**
+ * # file size The unit is M
+ *
+ * @param fileSize Filter file size
+ * @return
+ */
+ public PictureSelectionModel queryMaxFileSize(float fileSize) {
+ selectionConfig.filterFileSize = fileSize;
+ return this;
+ }
+
+ /**
+ * @param isGif Whether to open gif
+ * @return
+ */
+ public PictureSelectionModel isGif(boolean isGif) {
+ selectionConfig.isGif = isGif;
+ return this;
+ }
+
+ /**
+ * @param enablePreview Do you want to preview the picture?
+ * @return Use {link .isPreviewImage()}
+ */
+ @Deprecated
+ public PictureSelectionModel previewImage(boolean enablePreview) {
+ selectionConfig.enablePreview = enablePreview;
+ return this;
+ }
+
+ /**
+ * @param enablePreview Do you want to preview the picture?
+ * @return
+ */
+ public PictureSelectionModel isPreviewImage(boolean enablePreview) {
+ selectionConfig.enablePreview = enablePreview;
+ return this;
+ }
+
+ /**
+ * @param enPreviewVideo Do you want to preview the video?
+ * @return Use {link .isPreviewVideo()}
+ */
+ @Deprecated
+ public PictureSelectionModel previewVideo(boolean enPreviewVideo) {
+ selectionConfig.enPreviewVideo = enPreviewVideo;
+ return this;
+ }
+
+ /**
+ * @param enPreviewVideo Do you want to preview the video?
+ * @return
+ */
+ public PictureSelectionModel isPreviewVideo(boolean enPreviewVideo) {
+ selectionConfig.enPreviewVideo = enPreviewVideo;
+ return this;
+ }
+
+ /**
+ * @param isNotPreviewDownload Previews do not show downloads
+ * @return
+ */
+ public PictureSelectionModel isNotPreviewDownload(boolean isNotPreviewDownload) {
+ selectionConfig.isNotPreviewDownload = isNotPreviewDownload;
+ return this;
+ }
+
+ /**
+ * @param Specify get image format
+ * @return
+ */
+ public PictureSelectionModel querySpecifiedFormatSuffix(String specifiedFormat) {
+ selectionConfig.specifiedFormat = specifiedFormat;
+ return this;
+ }
+
+ /**
+ * @param openClickSound Whether to open click voice
+ * @return Use {link .isOpenClickSound()}
+ */
+ @Deprecated
+ public PictureSelectionModel openClickSound(boolean openClickSound) {
+ selectionConfig.openClickSound = !selectionConfig.camera && openClickSound;
+ return this;
+ }
+
+ /**
+ * @param isOpenClickSound Whether to open click voice
+ * @return
+ */
+ public PictureSelectionModel isOpenClickSound(boolean openClickSound) {
+ selectionConfig.openClickSound = !selectionConfig.camera && openClickSound;
+ return this;
+ }
+
+ /**
+ * 是否可拖动裁剪框(setFreeStyleCropEnabled 为true 有效)
+ */
+ public PictureSelectionModel isDragFrame(boolean isDragFrame) {
+ selectionConfig.isDragFrame = isDragFrame;
+ return this;
+ }
+
+ /**
+ * Whether the multi-graph clipping list is animated or not
+ *
+ * @param isAnimation
+ * @return
+ */
+ public PictureSelectionModel isMultipleRecyclerAnimation(boolean isAnimation) {
+ selectionConfig.isMultipleRecyclerAnimation = isAnimation;
+ return this;
+ }
+
+
+ /**
+ * 设置摄像头方向(前后 默认后置)
+ */
+ public PictureSelectionModel isCameraAroundState(boolean isCameraAroundState) {
+ selectionConfig.isCameraAroundState = isCameraAroundState;
+ return this;
+ }
+
+ /**
+ * @param selectionMedia Select the selected picture set
+ * @return Use {link .selectionData()}
+ */
+ @Deprecated
+ public PictureSelectionModel selectionMedia(List selectionMedia) {
+ if (selectionConfig.selectionMode == PictureConfig.SINGLE && selectionConfig.isSingleDirectReturn) {
+ selectionConfig.selectionMedias = null;
+ } else {
+ selectionConfig.selectionMedias = selectionMedia;
+ }
+ return this;
+ }
+
+ /**
+ * @param selectionData Select the selected picture set
+ * @return
+ */
+ public PictureSelectionModel selectionData(List selectionData) {
+ if (selectionConfig.selectionMode == PictureConfig.SINGLE && selectionConfig.isSingleDirectReturn) {
+ selectionConfig.selectionMedias = null;
+ } else {
+ selectionConfig.selectionMedias = selectionData;
+ }
+ return this;
+ }
+
+ /**
+ * 是否改变状态栏字段颜色(黑白字体转换)
+ * #适合所有style使用
+ *
+ * @param isChangeStatusBarFontColor
+ * @return
+ */
+ @Deprecated
+ public PictureSelectionModel isChangeStatusBarFontColor(boolean isChangeStatusBarFontColor) {
+ selectionConfig.isChangeStatusBarFontColor = isChangeStatusBarFontColor;
+ return this;
+ }
+
+ /**
+ * 选择图片样式0/9
+ * #适合所有style使用
+ *
+ * @param isOpenStyleNumComplete
+ * @return 使用setPictureStyle方法
+ */
+ @Deprecated
+ public PictureSelectionModel isOpenStyleNumComplete(boolean isOpenStyleNumComplete) {
+ selectionConfig.isOpenStyleNumComplete = isOpenStyleNumComplete;
+ return this;
+ }
+
+ /**
+ * 是否开启数字选择模式
+ * #适合qq style 样式使用
+ *
+ * @param isOpenStyleCheckNumMode
+ * @return 使用setPictureStyle方法
+ */
+ @Deprecated
+ public PictureSelectionModel isOpenStyleCheckNumMode(boolean isOpenStyleCheckNumMode) {
+ selectionConfig.isOpenStyleCheckNumMode = isOpenStyleCheckNumMode;
+ return this;
+ }
+
+ /**
+ * 设置标题栏背景色
+ *
+ * @param color
+ * @return 使用setPictureStyle方法
+ */
+ @Deprecated
+ public PictureSelectionModel setTitleBarBackgroundColor(@ColorInt int color) {
+ selectionConfig.titleBarBackgroundColor = color;
+ return this;
+ }
+
+
+ /**
+ * 状态栏背景色
+ *
+ * @param color
+ * @return 使用setPictureStyle方法
+ */
+ @Deprecated
+ public PictureSelectionModel setStatusBarColorPrimaryDark(@ColorInt int color) {
+ selectionConfig.pictureStatusBarColor = color;
+ return this;
+ }
+
+
+ /**
+ * 裁剪页面标题背景色
+ *
+ * @param color
+ * @return 使用setPictureCropStyle方法
+ */
+ @Deprecated
+ public PictureSelectionModel setCropTitleBarBackgroundColor(@ColorInt int color) {
+ selectionConfig.cropTitleBarBackgroundColor = color;
+ return this;
+ }
+
+ /**
+ * 裁剪页面状态栏背景色
+ *
+ * @param color
+ * @return 使用setPictureCropStyle方法
+ */
+ @Deprecated
+ public PictureSelectionModel setCropStatusBarColorPrimaryDark(@ColorInt int color) {
+ selectionConfig.cropStatusBarColorPrimaryDark = color;
+ return this;
+ }
+
+ /**
+ * 裁剪页面标题文字颜色
+ *
+ * @param color
+ * @return 使用setPictureCropStyle方法
+ */
+ @Deprecated
+ public PictureSelectionModel setCropTitleColor(@ColorInt int color) {
+ selectionConfig.cropTitleColor = color;
+ return this;
+ }
+
+ /**
+ * 设置相册标题右侧向上箭头图标
+ *
+ * @param resId
+ * @return 使用setPictureStyle方法
+ */
+ @Deprecated
+ public PictureSelectionModel setUpArrowDrawable(int resId) {
+ selectionConfig.upResId = resId;
+ return this;
+ }
+
+ /**
+ * 设置相册标题右侧向下箭头图标
+ *
+ * @param resId
+ * @return 使用setPictureStyle方法
+ */
+ @Deprecated
+ public PictureSelectionModel setDownArrowDrawable(int resId) {
+ selectionConfig.downResId = resId;
+ return this;
+ }
+
+ /**
+ * 动态设置裁剪主题样式
+ *
+ * @param style 裁剪页主题
+ * @return
+ */
+ public PictureSelectionModel setPictureCropStyle(PictureCropParameterStyle style) {
+ selectionConfig.cropStyle = style;
+ return this;
+ }
+
+ /**
+ * 动态设置相册主题样式
+ *
+ * @param style 主题
+ * @return
+ */
+ public PictureSelectionModel setPictureStyle(PictureParameterStyle style) {
+ selectionConfig.style = style;
+ return this;
+ }
+
+ /**
+ * Dynamically set the album to start and exit the animation
+ *
+ * @param style Activity Launch exit animation theme
+ * @return
+ */
+ public PictureSelectionModel setPictureWindowAnimationStyle(PictureWindowAnimationStyle windowAnimationStyle) {
+ selectionConfig.windowAnimationStyle = windowAnimationStyle;
+ return this;
+ }
+
+ /**
+ * Photo album list animation {}
+ * Use {@link AnimationType#ALPHA_IN_ANIMATION or SLIDE_IN_BOTTOM_ANIMATION} directly.
+ *
+ * @param animationMode
+ * @return
+ */
+ public PictureSelectionModel setRecyclerAnimationMode(int animationMode) {
+ selectionConfig.animationMode = animationMode;
+ return this;
+ }
+
+ /**
+ * # If you want to handle the Android Q path, if not, just return the uri,
+ * The getAndroidQToPath(); field will be empty
+ *
+ * @param isAndroidQTransform
+ * @return
+ */
+ public PictureSelectionModel isAndroidQTransform(boolean isAndroidQTransform) {
+ selectionConfig.isAndroidQTransform = isAndroidQTransform;
+ return this;
+ }
+
+ /**
+ * # 内部方法-要使用此方法时最好先咨询作者!!!
+ *
+ * @param isFallbackVersion 仅供特殊情况内部使用 如果某功能出错此开关可以回退至之前版本
+ * @return
+ */
+ public PictureSelectionModel isFallbackVersion(boolean isFallbackVersion) {
+ selectionConfig.isFallbackVersion = isFallbackVersion;
+ return this;
+ }
+
+ /**
+ * # 内部方法-要使用此方法时最好先咨询作者!!!
+ *
+ * @param isFallbackVersion 仅供特殊情况内部使用 如果某功能出错此开关可以回退至之前版本
+ * @return
+ */
+ public PictureSelectionModel isFallbackVersion2(boolean isFallbackVersion) {
+ selectionConfig.isFallbackVersion2 = isFallbackVersion;
+ return this;
+ }
+
+ /**
+ * # 内部方法-要使用此方法时最好先咨询作者!!!
+ *
+ * @param isFallbackVersion 仅供特殊情况内部使用 如果某功能出错此开关可以回退至之前版本
+ * @return
+ */
+ public PictureSelectionModel isFallbackVersion3(boolean isFallbackVersion) {
+ selectionConfig.isFallbackVersion3 = isFallbackVersion;
+ return this;
+ }
+
+ /**
+ * Start to select media and wait for result.
+ *
+ * @param requestCode Identity of the request Activity or Fragment.
+ */
+ public void forResult(int requestCode) {
+ if (!DoubleUtils.isFastDoubleClick()) {
+ Activity activity = selector.getActivity();
+ if (activity == null || selectionConfig == null) {
+ return;
+ }
+ Intent intent;
+ if (selectionConfig.camera && selectionConfig.isUseCustomCamera) {
+ intent = new Intent(activity, PictureCustomCameraActivity.class);
+ } else {
+ intent = new Intent(activity, selectionConfig.camera
+ ? PictureSelectorCameraEmptyActivity.class :
+ selectionConfig.isWeChatStyle ? PictureSelectorWeChatStyleActivity.class
+ : PictureSelectorActivity.class);
+ }
+ selectionConfig.isCallbackMode = false;
+ Fragment fragment = selector.getFragment();
+ if (fragment != null) {
+ fragment.startActivityForResult(intent, requestCode);
+ } else {
+ activity.startActivityForResult(intent, requestCode);
+ }
+ PictureWindowAnimationStyle windowAnimationStyle = selectionConfig.windowAnimationStyle;
+ activity.overridePendingTransition(windowAnimationStyle != null &&
+ windowAnimationStyle.activityEnterAnimation != 0 ?
+ windowAnimationStyle.activityEnterAnimation :
+ R.anim.picture_anim_enter, R.anim.picture_anim_fade_in);
+ }
+ }
+
+ /**
+ * # replace for setPictureWindowAnimationStyle();
+ * Start to select media and wait for result.
+ *
+ * # Use PictureWindowAnimationStyle to achieve animation effects
+ *
+ * @param requestCode Identity of the request Activity or Fragment.
+ */
+ @Deprecated
+ public void forResult(int requestCode, int enterAnim, int exitAnim) {
+ if (!DoubleUtils.isFastDoubleClick()) {
+ Activity activity = selector.getActivity();
+ if (activity == null) {
+ return;
+ }
+ Intent intent = new Intent(activity, selectionConfig != null && selectionConfig.camera
+ ? PictureSelectorCameraEmptyActivity.class :
+ selectionConfig.isWeChatStyle ? PictureSelectorWeChatStyleActivity.class :
+ PictureSelectorActivity.class);
+ selectionConfig.isCallbackMode = false;
+ Fragment fragment = selector.getFragment();
+ if (fragment != null) {
+ fragment.startActivityForResult(intent, requestCode);
+ } else {
+ activity.startActivityForResult(intent, requestCode);
+ }
+ activity.overridePendingTransition(enterAnim, exitAnim);
+ }
+ }
+
+
+ /**
+ * Start to select media and wait for result.
+ *
+ * @param listener The resulting callback listens
+ */
+ public void forResult(OnResultCallbackListener listener) {
+ if (!DoubleUtils.isFastDoubleClick()) {
+ Activity activity = selector.getActivity();
+ if (activity == null || selectionConfig == null) {
+ return;
+ }
+ // 绑定回调监听
+ PictureSelectionConfig.listener = new WeakReference<>(listener).get();
+ selectionConfig.isCallbackMode = true;
+ Intent intent;
+ if (selectionConfig.camera && selectionConfig.isUseCustomCamera) {
+ intent = new Intent(activity, PictureCustomCameraActivity.class);
+ } else {
+ intent = new Intent(activity, selectionConfig.camera
+ ? PictureSelectorCameraEmptyActivity.class :
+ selectionConfig.isWeChatStyle ? PictureSelectorWeChatStyleActivity.class
+ : PictureSelectorActivity.class);
+ }
+ Fragment fragment = selector.getFragment();
+ if (fragment != null) {
+ fragment.startActivity(intent);
+ } else {
+ activity.startActivity(intent);
+ }
+ PictureWindowAnimationStyle windowAnimationStyle = selectionConfig.windowAnimationStyle;
+ activity.overridePendingTransition(windowAnimationStyle != null &&
+ windowAnimationStyle.activityEnterAnimation != 0 ?
+ windowAnimationStyle.activityEnterAnimation :
+ R.anim.picture_anim_enter, R.anim.picture_anim_fade_in);
+ }
+ }
+
+ /**
+ * Start to select media and wait for result.
+ *
+ * @param requestCode Identity of the request Activity or Fragment.
+ * @param listener The resulting callback listens
+ */
+ public void forResult(int requestCode, OnResultCallbackListener listener) {
+ if (!DoubleUtils.isFastDoubleClick()) {
+ Activity activity = selector.getActivity();
+ if (activity == null || selectionConfig == null) {
+ return;
+ }
+ // 绑定回调监听
+ PictureSelectionConfig.listener = new WeakReference<>(listener).get();
+ selectionConfig.isCallbackMode = true;
+ Intent intent;
+ if (selectionConfig.camera && selectionConfig.isUseCustomCamera) {
+ intent = new Intent(activity, PictureCustomCameraActivity.class);
+ } else {
+ intent = new Intent(activity, selectionConfig.camera
+ ? PictureSelectorCameraEmptyActivity.class :
+ selectionConfig.isWeChatStyle ? PictureSelectorWeChatStyleActivity.class
+ : PictureSelectorActivity.class);
+ }
+ Fragment fragment = selector.getFragment();
+ if (fragment != null) {
+ fragment.startActivityForResult(intent, requestCode);
+ } else {
+ activity.startActivityForResult(intent, requestCode);
+ }
+ PictureWindowAnimationStyle windowAnimationStyle = selectionConfig.windowAnimationStyle;
+ activity.overridePendingTransition(windowAnimationStyle != null &&
+ windowAnimationStyle.activityEnterAnimation != 0 ?
+ windowAnimationStyle.activityEnterAnimation :
+ R.anim.picture_anim_enter, R.anim.picture_anim_fade_in);
+ }
+ }
+
+ /**
+ * 提供外部预览图片方法
+ *
+ * @param position
+ * @param medias
+ */
+ public void openExternalPreview(int position, List medias) {
+ if (selector != null) {
+ selector.externalPicturePreview(position, medias,
+ selectionConfig.windowAnimationStyle != null &&
+ selectionConfig.windowAnimationStyle.activityPreviewEnterAnimation != 0
+ ? selectionConfig.windowAnimationStyle.activityPreviewEnterAnimation : 0);
+ } else {
+ throw new NullPointerException("This PictureSelector is Null");
+ }
+ }
+
+
+ /**
+ * 提供外部预览图片方法-带自定义下载保存路径
+ * # 废弃 由于Android Q沙盒机制 此方法不在需要了
+ *
+ * @param position
+ * @param medias
+ */
+ @Deprecated
+ public void openExternalPreview(int position, String directory_path, List medias) {
+ if (selector != null) {
+ selector.externalPicturePreview(position, directory_path, medias,
+ selectionConfig.windowAnimationStyle != null &&
+ selectionConfig.windowAnimationStyle.activityPreviewEnterAnimation != 0
+ ? selectionConfig.windowAnimationStyle.activityPreviewEnterAnimation : 0);
+ } else {
+ throw new NullPointerException("This PictureSelector is Null");
+ }
+ }
+
+ /**
+ * set preview video
+ *
+ * @param path
+ */
+ public void externalPictureVideo(String path) {
+ if (selector != null) {
+ selector.externalPictureVideo(path);
+ } else {
+ throw new NullPointerException("This PictureSelector is Null");
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PictureSelector.java b/picture_library/src/main/java/com/luck/picture/lib/PictureSelector.java
new file mode 100644
index 0000000..81b8a6b
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PictureSelector.java
@@ -0,0 +1,244 @@
+package com.luck.picture.lib;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.style.PictureParameterStyle;
+import com.luck.picture.lib.tools.DoubleUtils;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2017-5-24 22:30
+ * @describe:PictureSelector
+ */
+
+public final class PictureSelector {
+
+ private final WeakReference mActivity;
+ private final WeakReference mFragment;
+
+ private PictureSelector(Activity activity) {
+ this(activity, null);
+ }
+
+ private PictureSelector(Fragment fragment) {
+ this(fragment.getActivity(), fragment);
+ }
+
+ private PictureSelector(Activity activity, Fragment fragment) {
+ mActivity = new WeakReference<>(activity);
+ mFragment = new WeakReference<>(fragment);
+ }
+
+ /**
+ * Start PictureSelector for Activity.
+ *
+ * @param activity
+ * @return PictureSelector instance.
+ */
+ public static PictureSelector create(Activity activity) {
+ return new PictureSelector(activity);
+ }
+
+ /**
+ * Start PictureSelector for Fragment.
+ *
+ * @param fragment
+ * @return PictureSelector instance.
+ */
+ public static PictureSelector create(Fragment fragment) {
+ return new PictureSelector(fragment);
+ }
+
+ /**
+ * @param chooseMode Select the type of picture you want,all or Picture or Video .
+ * @return LocalMedia PictureSelectionModel
+ * Use {@link PictureMimeType.ofAll(),ofImage(),ofVideo(),ofAudio()}.
+ */
+ public PictureSelectionModel openGallery(int chooseMode) {
+ return new PictureSelectionModel(this, chooseMode);
+ }
+
+ /**
+ * @param chooseMode Select the type of picture you want,Picture or Video.
+ * @return LocalMedia PictureSelectionModel
+ * Use {@link PictureMimeType.ofImage(),ofVideo()}.
+ */
+ public PictureSelectionModel openCamera(int chooseMode) {
+ return new PictureSelectionModel(this, chooseMode, true);
+ }
+
+ /**
+ * 外部预览时设置样式
+ *
+ * @param themeStyle
+ * @return
+ */
+ public PictureSelectionModel themeStyle(int themeStyle) {
+ return new PictureSelectionModel(this, PictureMimeType.ofImage())
+ .theme(themeStyle);
+ }
+
+ /**
+ * 外部预览时动态代码设置样式
+ *
+ * @param style
+ * @return
+ */
+ public PictureSelectionModel setPictureStyle(PictureParameterStyle style) {
+ return new PictureSelectionModel(this, PictureMimeType.ofImage())
+ .setPictureStyle(style);
+ }
+
+ /**
+ * @param data
+ * @return Selector Multiple LocalMedia
+ */
+ public static List obtainMultipleResult(Intent data) {
+ if (data != null) {
+ List result = data.getParcelableArrayListExtra(PictureConfig.EXTRA_RESULT_SELECTION);
+ return result == null ? new ArrayList<>() : result;
+ }
+ return new ArrayList<>();
+ }
+
+ /**
+ * @param data
+ * @return Put image Intent Data
+ */
+ public static Intent putIntentResult(List data) {
+ return new Intent().putParcelableArrayListExtra(PictureConfig.EXTRA_RESULT_SELECTION,
+ (ArrayList extends Parcelable>) data);
+ }
+
+ /**
+ * @param bundle
+ * @return get Selector LocalMedia
+ */
+ public static List obtainSelectorList(Bundle bundle) {
+ if (bundle != null) {
+ List selectionMedias = bundle.getParcelableArrayList(PictureConfig.EXTRA_SELECT_LIST);
+ return selectionMedias == null ? new ArrayList<>() : selectionMedias;
+ }
+ return new ArrayList<>();
+ }
+
+ /**
+ * @param selectedImages
+ * @return put Selector LocalMedia
+ */
+ public static void saveSelectorList(Bundle outState, List selectedImages) {
+ outState.putParcelableArrayList(PictureConfig.EXTRA_SELECT_LIST,
+ (ArrayList extends Parcelable>) selectedImages);
+ }
+
+ /**
+ * set preview image
+ *
+ * @param position
+ * @param medias
+ */
+ public void externalPicturePreview(int position, List medias, int enterAnimation) {
+ if (!DoubleUtils.isFastDoubleClick()) {
+ if (getActivity() != null) {
+ Intent intent = new Intent(getActivity(), PictureExternalPreviewActivity.class);
+ intent.putParcelableArrayListExtra(PictureConfig.EXTRA_PREVIEW_SELECT_LIST,
+ (ArrayList extends Parcelable>) medias);
+ intent.putExtra(PictureConfig.EXTRA_POSITION, position);
+ getActivity().startActivity(intent);
+ getActivity().overridePendingTransition(enterAnimation != 0
+ ? enterAnimation : R.anim.picture_anim_enter, R.anim.picture_anim_fade_in);
+ } else {
+ throw new NullPointerException("Starting the PictureSelector Activity cannot be empty ");
+ }
+ }
+ }
+
+ /**
+ * set preview image
+ *
+ * @param position
+ * @param medias
+ * @param directory_path
+ */
+ public void externalPicturePreview(int position, String directory_path, List medias, int enterAnimation) {
+ if (!DoubleUtils.isFastDoubleClick()) {
+ if (getActivity() != null) {
+ Intent intent = new Intent(getActivity(), PictureExternalPreviewActivity.class);
+ intent.putParcelableArrayListExtra(PictureConfig.EXTRA_PREVIEW_SELECT_LIST, (ArrayList extends Parcelable>) medias);
+ intent.putExtra(PictureConfig.EXTRA_POSITION, position);
+ intent.putExtra(PictureConfig.EXTRA_DIRECTORY_PATH, directory_path);
+ getActivity().startActivity(intent);
+ getActivity().overridePendingTransition(enterAnimation != 0
+ ? enterAnimation : R.anim.picture_anim_enter, R.anim.picture_anim_fade_in);
+ } else {
+ throw new NullPointerException("Starting the PictureSelector Activity cannot be empty ");
+ }
+ }
+ }
+
+ /**
+ * set preview video
+ *
+ * @param path
+ */
+ public void externalPictureVideo(String path) {
+ if (!DoubleUtils.isFastDoubleClick()) {
+ if (getActivity() != null) {
+ Intent intent = new Intent(getActivity(), PictureVideoPlayActivity.class);
+ intent.putExtra(PictureConfig.EXTRA_VIDEO_PATH, path);
+ intent.putExtra(PictureConfig.EXTRA_PREVIEW_VIDEO, true);
+ getActivity().startActivity(intent);
+ } else {
+ throw new NullPointerException("Starting the PictureSelector Activity cannot be empty ");
+ }
+ }
+ }
+
+ /**
+ * set preview audio
+ *
+ * @param path
+ */
+ public void externalPictureAudio(String path) {
+ if (!DoubleUtils.isFastDoubleClick()) {
+ if (getActivity() != null) {
+ Intent intent = new Intent(getActivity(), PicturePlayAudioActivity.class);
+ intent.putExtra(PictureConfig.EXTRA_AUDIO_PATH, path);
+ getActivity().startActivity(intent);
+ getActivity().overridePendingTransition(R.anim.picture_anim_enter, 0);
+ } else {
+ throw new NullPointerException("Starting the PictureSelector Activity cannot be empty ");
+ }
+ }
+ }
+
+ /**
+ * @return Activity.
+ */
+ @Nullable
+ Activity getActivity() {
+ return mActivity.get();
+ }
+
+ /**
+ * @return Fragment.
+ */
+ @Nullable
+ Fragment getFragment() {
+ return mFragment != null ? mFragment.get() : null;
+ }
+
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorActivity.java b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorActivity.java
new file mode 100644
index 0000000..5d64b94
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorActivity.java
@@ -0,0 +1,2253 @@
+package com.luck.picture.lib;
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.SimpleItemAnimator;
+
+import com.luck.picture.lib.adapter.PictureImageGridAdapter;
+import com.luck.picture.lib.animators.AlphaInAnimationAdapter;
+import com.luck.picture.lib.animators.AnimationType;
+import com.luck.picture.lib.animators.SlideInBottomAnimationAdapter;
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.config.PictureSelectionConfig;
+import com.luck.picture.lib.decoration.GridSpacingItemDecoration;
+import com.luck.picture.lib.dialog.PhotoItemSelectedDialog;
+import com.luck.picture.lib.dialog.PictureCustomDialog;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.entity.LocalMediaFolder;
+import com.luck.picture.lib.listener.OnAlbumItemClickListener;
+import com.luck.picture.lib.listener.OnItemClickListener;
+import com.luck.picture.lib.listener.OnPhotoSelectChangedListener;
+import com.luck.picture.lib.listener.OnQueryDataResultListener;
+import com.luck.picture.lib.listener.OnRecyclerViewPreloadMoreListener;
+import com.luck.picture.lib.model.LocalMediaLoader;
+import com.luck.picture.lib.model.LocalMediaPageLoader;
+import com.luck.picture.lib.observable.ImagesObservable;
+import com.luck.picture.lib.permissions.PermissionChecker;
+import com.luck.picture.lib.style.PictureWindowAnimationStyle;
+import com.luck.picture.lib.thread.PictureThreadUtils;
+import com.luck.picture.lib.tools.AttrsUtils;
+import com.luck.picture.lib.tools.BitmapUtils;
+import com.luck.picture.lib.tools.DateUtils;
+import com.luck.picture.lib.tools.DoubleUtils;
+import com.luck.picture.lib.tools.JumpUtils;
+import com.luck.picture.lib.tools.MediaUtils;
+import com.luck.picture.lib.tools.PictureFileUtils;
+import com.luck.picture.lib.tools.ScreenUtils;
+import com.luck.picture.lib.tools.SdkVersionUtils;
+import com.luck.picture.lib.tools.StringUtils;
+import com.luck.picture.lib.tools.ToastUtils;
+import com.luck.picture.lib.tools.ValueOf;
+import com.luck.picture.lib.widget.FolderPopWindow;
+import com.luck.picture.lib.widget.RecyclerPreloadView;
+import com.yalantis.ucrop.UCrop;
+import com.yalantis.ucrop.model.CutInfo;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @data:2018/1/27 19:12
+ * @describe: PictureSelectorActivity
+ */
+public class PictureSelectorActivity extends PictureBaseActivity implements View.OnClickListener,
+ OnAlbumItemClickListener, OnPhotoSelectChangedListener, OnItemClickListener,
+ OnRecyclerViewPreloadMoreListener {
+ private static final String TAG = PictureSelectorActivity.class.getSimpleName();
+ protected ImageView mIvPictureLeftBack;
+ protected ImageView mIvArrow;
+ protected View titleViewBg;
+ protected TextView mTvPictureTitle, mTvPictureRight, mTvPictureOk, mTvEmpty,
+ mTvPictureImgNum, mTvPicturePreview, mTvPlayPause, mTvStop, mTvQuit,
+ mTvMusicStatus, mTvMusicTotal, mTvMusicTime;
+ protected RecyclerPreloadView mRecyclerView;
+ protected RelativeLayout mBottomLayout;
+ protected PictureImageGridAdapter mAdapter;
+ protected FolderPopWindow folderWindow;
+ protected Animation animation = null;
+ protected boolean isStartAnimation = false;
+ protected MediaPlayer mediaPlayer;
+ protected SeekBar musicSeekBar;
+ protected boolean isPlayAudio = false;
+ protected PictureCustomDialog audioDialog;
+ protected CheckBox mCbOriginal;
+ protected int oldCurrentListSize;
+ protected boolean isEnterSetting;
+ private long intervalClickTime = 0;
+ private int allFolderSize;
+ private int mOpenCameraCount;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ allFolderSize = savedInstanceState.getInt(PictureConfig.EXTRA_ALL_FOLDER_SIZE);
+ oldCurrentListSize = savedInstanceState.getInt(PictureConfig.EXTRA_OLD_CURRENT_LIST_SIZE, 0);
+ selectionMedias = PictureSelector.obtainSelectorList(savedInstanceState);
+ if (mAdapter != null) {
+ isStartAnimation = true;
+ mAdapter.bindSelectData(selectionMedias);
+ }
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (isEnterSetting) {
+ if (PermissionChecker
+ .checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) &&
+ PermissionChecker
+ .checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ if (mAdapter.isDataEmpty()) {
+ readLocalMedia();
+ }
+ } else {
+ showPermissionsDialog(false, getString(R.string.picture_jurisdiction));
+ }
+ isEnterSetting = false;
+ }
+
+ if (config.isOriginalControl) {
+ if (mCbOriginal != null) {
+ mCbOriginal.setChecked(config.isCheckOriginalImage);
+ }
+ }
+ }
+
+ @Override
+ public int getResourceId() {
+ return R.layout.picture_selector;
+ }
+
+ @Override
+ protected void initWidgets() {
+ super.initWidgets();
+ container = findViewById(R.id.container);
+ titleViewBg = findViewById(R.id.titleViewBg);
+ mIvPictureLeftBack = findViewById(R.id.pictureLeftBack);
+ mTvPictureTitle = findViewById(R.id.picture_title);
+ mTvPictureRight = findViewById(R.id.picture_right);
+ mTvPictureOk = findViewById(R.id.picture_tv_ok);
+ mCbOriginal = findViewById(R.id.cb_original);
+ mIvArrow = findViewById(R.id.ivArrow);
+ mTvPicturePreview = findViewById(R.id.picture_id_preview);
+ mTvPictureImgNum = findViewById(R.id.picture_tvMediaNum);
+ mRecyclerView = findViewById(R.id.picture_recycler);
+ mBottomLayout = findViewById(R.id.rl_bottom);
+ mTvEmpty = findViewById(R.id.tv_empty);
+ isNumComplete(numComplete);
+ if (!numComplete) {
+ animation = AnimationUtils.loadAnimation(this, R.anim.picture_anim_modal_in);
+ }
+ mTvPicturePreview.setOnClickListener(this);
+ if (config.isAutomaticTitleRecyclerTop) {
+ titleViewBg.setOnClickListener(this);
+ }
+ mTvPicturePreview.setVisibility(config.chooseMode != PictureMimeType.ofAudio() && config.enablePreview ? View.VISIBLE : View.GONE);
+ mBottomLayout.setVisibility(config.selectionMode == PictureConfig.SINGLE
+ && config.isSingleDirectReturn ? View.GONE : View.VISIBLE);
+ mIvPictureLeftBack.setOnClickListener(this);
+ mTvPictureRight.setOnClickListener(this);
+ mTvPictureOk.setOnClickListener(this);
+ mTvPictureImgNum.setOnClickListener(this);
+ mTvPictureTitle.setOnClickListener(this);
+ mIvArrow.setOnClickListener(this);
+ String title = config.chooseMode == PictureMimeType.ofAudio() ?
+ getString(R.string.picture_all_audio) : getString(R.string.picture_camera_roll);
+ mTvPictureTitle.setText(title);
+ mTvPictureTitle.setTag(R.id.view_tag, -1);
+ folderWindow = new FolderPopWindow(this, config);
+ folderWindow.setArrowImageView(mIvArrow);
+ folderWindow.setOnAlbumItemClickListener(this);
+ mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(config.imageSpanCount,
+ ScreenUtils.dip2px(this, 2), false));
+ mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), config.imageSpanCount));
+ if (!config.isPageStrategy) {
+ mRecyclerView.setHasFixedSize(true);
+ } else {
+ mRecyclerView.setReachBottomRow(RecyclerPreloadView.BOTTOM_PRELOAD);
+ mRecyclerView.setOnRecyclerViewPreloadListener(PictureSelectorActivity.this);
+ }
+ RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
+ if (itemAnimator != null) {
+ ((SimpleItemAnimator) itemAnimator).setSupportsChangeAnimations(false);
+ mRecyclerView.setItemAnimator(null);
+ }
+ loadAllMediaData();
+ mTvEmpty.setText(config.chooseMode == PictureMimeType.ofAudio() ?
+ getString(R.string.picture_audio_empty)
+ : getString(R.string.picture_empty));
+ StringUtils.tempTextFont(mTvEmpty, config.chooseMode);
+ mAdapter = new PictureImageGridAdapter(getContext(), config);
+ mAdapter.setOnPhotoSelectChangedListener(this);
+
+ switch (config.animationMode) {
+ case AnimationType
+ .ALPHA_IN_ANIMATION:
+ mRecyclerView.setAdapter(new AlphaInAnimationAdapter(mAdapter));
+ break;
+ case AnimationType
+ .SLIDE_IN_BOTTOM_ANIMATION:
+ mRecyclerView.setAdapter(new SlideInBottomAnimationAdapter(mAdapter));
+ break;
+ default:
+ mRecyclerView.setAdapter(mAdapter);
+ break;
+ }
+ if (config.isOriginalControl) {
+ mCbOriginal.setVisibility(View.VISIBLE);
+ mCbOriginal.setChecked(config.isCheckOriginalImage);
+ mCbOriginal.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ config.isCheckOriginalImage = isChecked;
+ });
+ }
+ }
+
+ @Override
+ public void onRecyclerViewPreloadMore() {
+ loadMoreData();
+ }
+
+ /**
+ * getPageLimit
+ * # If the user clicks to take a photo and returns, the Limit should be adjusted dynamically
+ *
+ * @return
+ */
+ private int getPageLimit() {
+ int bucketId = ValueOf.toInt(mTvPictureTitle.getTag(R.id.view_tag));
+ if (bucketId == -1) {
+ int limit = mOpenCameraCount > 0 ? config.pageSize - mOpenCameraCount : config.pageSize;
+ mOpenCameraCount = 0;
+ return limit;
+ }
+ return config.pageSize;
+ }
+
+ /**
+ * load more data
+ */
+ private void loadMoreData() {
+ if (mAdapter != null) {
+ if (isHasMore) {
+ mPage++;
+ long bucketId = ValueOf.toLong(mTvPictureTitle.getTag(R.id.view_tag));
+ LocalMediaPageLoader.getInstance(getContext(), config).loadPageMediaData(bucketId, mPage, getPageLimit(),
+ (OnQueryDataResultListener) (result, currentPage, isHasMore) -> {
+ if (!isFinishing()) {
+ this.isHasMore = isHasMore;
+ if (isHasMore) {
+ hideDataNull();
+ int size = result.size();
+ if (size > 0) {
+ int positionStart = mAdapter.getSize();
+ mAdapter.getData().addAll(result);
+ int itemCount = mAdapter.getItemCount();
+ mAdapter.notifyItemRangeChanged(positionStart, itemCount);
+ } else {
+ onRecyclerViewPreloadMore();
+ }
+ if (size < PictureConfig.MIN_PAGE_SIZE) {
+ mRecyclerView.onScrolled(mRecyclerView.getScrollX(), mRecyclerView.getScrollY());
+ }
+ } else {
+ boolean isEmpty = mAdapter.isDataEmpty();
+ if (isEmpty) {
+ showDataNull(bucketId == -1 ? getString(R.string.picture_empty) : getString(R.string.picture_data_null), R.drawable.picture_icon_no_data);
+ }
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * load All Data
+ */
+ private void loadAllMediaData() {
+ if (PermissionChecker
+ .checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) &&
+ PermissionChecker
+ .checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ readLocalMedia();
+ } else {
+ PermissionChecker.requestPermissions(this, new String[]{
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE}, PictureConfig.APPLY_STORAGE_PERMISSIONS_CODE);
+ }
+ }
+
+ @Override
+ public void initPictureSelectorStyle() {
+ if (config.style != null) {
+ if (config.style.pictureTitleDownResId != 0) {
+ Drawable drawable = ContextCompat.getDrawable(this, config.style.pictureTitleDownResId);
+ mIvArrow.setImageDrawable(drawable);
+ }
+ if (config.style.pictureTitleTextColor != 0) {
+ mTvPictureTitle.setTextColor(config.style.pictureTitleTextColor);
+ }
+ if (config.style.pictureTitleTextSize != 0) {
+ mTvPictureTitle.setTextSize(config.style.pictureTitleTextSize);
+ }
+
+ if (config.style.pictureRightDefaultTextColor != 0) {
+ mTvPictureRight.setTextColor(config.style.pictureRightDefaultTextColor);
+ } else {
+ if (config.style.pictureCancelTextColor != 0) {
+ mTvPictureRight.setTextColor(config.style.pictureCancelTextColor);
+ }
+ }
+
+ if (config.style.pictureRightTextSize != 0) {
+ mTvPictureRight.setTextSize(config.style.pictureRightTextSize);
+ }
+
+ if (config.style.pictureLeftBackIcon != 0) {
+ mIvPictureLeftBack.setImageResource(config.style.pictureLeftBackIcon);
+ }
+ if (config.style.pictureUnPreviewTextColor != 0) {
+ mTvPicturePreview.setTextColor(config.style.pictureUnPreviewTextColor);
+ }
+ if (config.style.picturePreviewTextSize != 0) {
+ mTvPicturePreview.setTextSize(config.style.picturePreviewTextSize);
+ }
+ if (config.style.pictureCheckNumBgStyle != 0) {
+ mTvPictureImgNum.setBackgroundResource(config.style.pictureCheckNumBgStyle);
+ }
+ if (config.style.pictureUnCompleteTextColor != 0) {
+ mTvPictureOk.setTextColor(config.style.pictureUnCompleteTextColor);
+ }
+ if (config.style.pictureCompleteTextSize != 0) {
+ mTvPictureOk.setTextSize(config.style.pictureCompleteTextSize);
+ }
+ if (config.style.pictureBottomBgColor != 0) {
+ mBottomLayout.setBackgroundColor(config.style.pictureBottomBgColor);
+ }
+ if (config.style.pictureContainerBackgroundColor != 0) {
+ container.setBackgroundColor(config.style.pictureContainerBackgroundColor);
+ }
+ if (!TextUtils.isEmpty(config.style.pictureRightDefaultText)) {
+ mTvPictureRight.setText(config.style.pictureRightDefaultText);
+ }
+ if (!TextUtils.isEmpty(config.style.pictureUnCompleteText)) {
+ mTvPictureOk.setText(config.style.pictureUnCompleteText);
+ }
+ if (!TextUtils.isEmpty(config.style.pictureUnPreviewText)) {
+ mTvPicturePreview.setText(config.style.pictureUnPreviewText);
+ }
+ } else {
+ if (config.downResId != 0) {
+ Drawable drawable = ContextCompat.getDrawable(this, config.downResId);
+ mIvArrow.setImageDrawable(drawable);
+ }
+ int pictureBottomBgColor = AttrsUtils.
+ getTypeValueColor(getContext(), R.attr.picture_bottom_bg);
+ if (pictureBottomBgColor != 0) {
+ mBottomLayout.setBackgroundColor(pictureBottomBgColor);
+ }
+ }
+ titleViewBg.setBackgroundColor(colorPrimary);
+
+ if (config.isOriginalControl) {
+ if (config.style != null) {
+ if (config.style.pictureOriginalControlStyle != 0) {
+ mCbOriginal.setButtonDrawable(config.style.pictureOriginalControlStyle);
+ } else {
+ mCbOriginal.setButtonDrawable(ContextCompat.getDrawable(this, R.drawable.picture_original_checkbox));
+ }
+ if (config.style.pictureOriginalFontColor != 0) {
+ mCbOriginal.setTextColor(config.style.pictureOriginalFontColor);
+ } else {
+ mCbOriginal.setTextColor(ContextCompat.getColor(this, R.color.picture_color_53575e));
+ }
+ if (config.style.pictureOriginalTextSize != 0) {
+ mCbOriginal.setTextSize(config.style.pictureOriginalTextSize);
+ }
+ } else {
+ mCbOriginal.setButtonDrawable(ContextCompat.getDrawable(this, R.drawable.picture_original_checkbox));
+ mCbOriginal.setTextColor(ContextCompat.getColor(this, R.color.picture_color_53575e));
+ }
+ }
+
+ mAdapter.bindSelectData(selectionMedias);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mAdapter != null) {
+ // Save the number of pictures or videos in the current list
+ outState.putInt(PictureConfig.EXTRA_OLD_CURRENT_LIST_SIZE, mAdapter.getSize());
+ // Save the number of Camera film and Camera folder files
+ int size = folderWindow.getFolderData().size();
+ if (size > 0) {
+ outState.putInt(PictureConfig.EXTRA_ALL_FOLDER_SIZE, folderWindow.getFolder(0).getImageNum());
+ }
+ if (mAdapter.getSelectedData() != null) {
+ List selectedImages = mAdapter.getSelectedData();
+ PictureSelector.saveSelectorList(outState, selectedImages);
+ }
+ }
+ }
+
+ /**
+ * none number style
+ */
+ private void isNumComplete(boolean numComplete) {
+ if (numComplete) {
+ initCompleteText(0);
+ }
+ }
+
+ /**
+ * init Text
+ */
+ @Override
+ protected void initCompleteText(int startCount) {
+ boolean isNotEmptyStyle = config.style != null;
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ if (startCount <= 0) {
+ mTvPictureOk.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureUnCompleteText)
+ ? config.style.pictureUnCompleteText : getString(R.string.picture_please_select));
+ } else {
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mTvPictureOk.setText(String.format(config.style.pictureCompleteText, startCount, 1));
+ } else {
+ mTvPictureOk.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureCompleteText)
+ ? config.style.pictureCompleteText : getString(R.string.picture_done));
+ }
+ }
+
+ } else {
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (startCount <= 0) {
+ mTvPictureOk.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureUnCompleteText)
+ ? config.style.pictureUnCompleteText : getString(R.string.picture_done_front_num,
+ startCount, config.maxSelectNum));
+ } else {
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mTvPictureOk.setText(String.format(config.style.pictureCompleteText, startCount, config.maxSelectNum));
+ } else {
+ mTvPictureOk.setText(getString(R.string.picture_done_front_num,
+ startCount, config.maxSelectNum));
+ }
+ }
+ }
+ }
+
+
+ /**
+ * get LocalMedia s
+ */
+ protected void readLocalMedia() {
+ showPleaseDialog();
+ if (config.isPageStrategy) {
+ LocalMediaPageLoader.getInstance(getContext(), config).loadAllMedia(
+ (OnQueryDataResultListener) (data, currentPage, isHasMore) -> {
+ if (!isFinishing()) {
+ this.isHasMore = true;
+ initPageModel(data);
+ synchronousCover();
+ }
+ });
+ } else {
+ PictureThreadUtils.executeByIo(new PictureThreadUtils.SimpleTask>() {
+
+ @Override
+ public List doInBackground() {
+ return new LocalMediaLoader(getContext(), config).loadAllMedia();
+ }
+
+ @Override
+ public void onSuccess(List folders) {
+ initStandardModel(folders);
+ }
+ });
+ }
+ }
+
+ /**
+ * Page Model
+ *
+ * @param folders
+ */
+ private void initPageModel(List folders) {
+ if (folders != null) {
+ folderWindow.bindFolder(folders);
+ mPage = 1;
+ LocalMediaFolder folder = folderWindow.getFolder(0);
+ mTvPictureTitle.setTag(R.id.view_count_tag, folder != null ? folder.getImageNum() : 0);
+ mTvPictureTitle.setTag(R.id.view_index_tag, 0);
+ long bucketId = folder != null ? folder.getBucketId() : -1;
+ mRecyclerView.setEnabledLoadMore(true);
+ LocalMediaPageLoader.getInstance(getContext(), config).loadPageMediaData(bucketId, mPage,
+ (OnQueryDataResultListener) (data, currentPage, isHasMore) -> {
+ if (!isFinishing()) {
+ dismissDialog();
+ if (mAdapter != null) {
+ this.isHasMore = true;
+ // IsHasMore being true means that there's still data, but data being 0 might be a filter that's turned on and that doesn't happen to fit on the whole page
+ if (isHasMore && data.size() == 0) {
+ onRecyclerViewPreloadMore();
+ return;
+ }
+ int currentSize = mAdapter.getSize();
+ int resultSize = data.size();
+ oldCurrentListSize = oldCurrentListSize + currentSize;
+ if (resultSize >= currentSize) {
+ // This situation is mainly caused by the use of camera memory, the Activity is recycled
+ if (currentSize > 0 && currentSize < resultSize && oldCurrentListSize != resultSize) {
+ if (isLocalMediaSame(data.get(0))) {
+ mAdapter.bindData(data);
+ } else {
+ mAdapter.getData().addAll(data);
+ }
+ } else {
+ mAdapter.bindData(data);
+ }
+ }
+ boolean isEmpty = mAdapter.isDataEmpty();
+ if (isEmpty) {
+ showDataNull(getString(R.string.picture_empty), R.drawable.picture_icon_no_data);
+ } else {
+ hideDataNull();
+ }
+
+ }
+ }
+ });
+ } else {
+ showDataNull(getString(R.string.picture_data_exception), R.drawable.picture_icon_data_error);
+ dismissDialog();
+ }
+ }
+
+ /**
+ * ofAll Page Model Synchronous cover
+ */
+ private void synchronousCover() {
+ if (config.chooseMode == PictureMimeType.ofAll()) {
+ PictureThreadUtils.executeByIo(new PictureThreadUtils.SimpleTask() {
+
+ @Override
+ public Boolean doInBackground() {
+ int size = folderWindow.getFolderData().size();
+ for (int i = 0; i < size; i++) {
+ LocalMediaFolder mediaFolder = folderWindow.getFolder(i);
+ if (mediaFolder == null) {
+ continue;
+ }
+ String firstCover = LocalMediaPageLoader
+ .getInstance(getContext(), config).getFirstCover(mediaFolder.getBucketId());
+ mediaFolder.setFirstImagePath(firstCover);
+ }
+ return true;
+ }
+
+ @Override
+ public void onSuccess(Boolean result) {
+ // TODO Synchronous Success
+ }
+ });
+ }
+ }
+
+ /**
+ * Standard Model
+ *
+ * @param folders
+ */
+ private void initStandardModel(List folders) {
+ if (folders != null) {
+ if (folders.size() > 0) {
+ folderWindow.bindFolder(folders);
+ LocalMediaFolder folder = folders.get(0);
+ folder.setChecked(true);
+ mTvPictureTitle.setTag(R.id.view_count_tag, folder.getImageNum());
+ List result = folder.getData();
+ if (mAdapter != null) {
+ int currentSize = mAdapter.getSize();
+ int resultSize = result.size();
+ oldCurrentListSize = oldCurrentListSize + currentSize;
+ if (resultSize >= currentSize) {
+ // This situation is mainly caused by the use of camera memory, the Activity is recycled
+ if (currentSize > 0 && currentSize < resultSize && oldCurrentListSize != resultSize) {
+ mAdapter.getData().addAll(result);
+ LocalMedia media = mAdapter.getData().get(0);
+ folder.setFirstImagePath(media.getPath());
+ folder.getData().add(0, media);
+ folder.setCheckedNum(1);
+ folder.setImageNum(folder.getImageNum() + 1);
+ updateMediaFolder(folderWindow.getFolderData(), media);
+ } else {
+ mAdapter.bindData(result);
+ }
+ }
+ boolean isEmpty = mAdapter.isDataEmpty();
+ if (isEmpty) {
+ showDataNull(getString(R.string.picture_empty), R.drawable.picture_icon_no_data);
+ } else {
+ hideDataNull();
+ }
+ }
+ } else {
+ showDataNull(getString(R.string.picture_empty), R.drawable.picture_icon_no_data);
+ }
+ } else {
+ showDataNull(getString(R.string.picture_data_exception), R.drawable.picture_icon_data_error);
+ }
+ dismissDialog();
+ }
+
+ /**
+ * isSame
+ *
+ * @param newMedia
+ * @return
+ */
+ private boolean isLocalMediaSame(LocalMedia newMedia) {
+ LocalMedia oldMedia = mAdapter.getItem(0);
+ if (oldMedia == null || newMedia == null) {
+ return false;
+ }
+ if (oldMedia.getPath().equals(newMedia.getPath())) {
+ return true;
+ }
+ // if Content:// type,determines whether the suffix id is consistent, mainly to solve the following two types of problems
+ // content://media/external/images/media/5844
+ // content://media/external/file/5844
+ if (PictureMimeType.isContent(newMedia.getPath())
+ && PictureMimeType.isContent(oldMedia.getPath())) {
+ if (!TextUtils.isEmpty(newMedia.getPath()) && !TextUtils.isEmpty(oldMedia.getPath())) {
+ String newId = newMedia.getPath().substring(newMedia.getPath().lastIndexOf("/") + 1);
+ String oldId = oldMedia.getPath().substring(oldMedia.getPath().lastIndexOf("/") + 1);
+ if (newId.equals(oldId)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Open Camera
+ */
+ public void startCamera() {
+ if (!DoubleUtils.isFastDoubleClick()) {
+ if (PictureSelectionConfig.onCustomCameraInterfaceListener != null) {
+ if (config.chooseMode == PictureConfig.TYPE_ALL) {
+ PhotoItemSelectedDialog selectedDialog = PhotoItemSelectedDialog.newInstance();
+ selectedDialog.setOnItemClickListener(this);
+ selectedDialog.show(getSupportFragmentManager(), "PhotoItemSelectedDialog");
+ } else {
+ PictureSelectionConfig.onCustomCameraInterfaceListener.onCameraClick(getContext(), config, config.chooseMode);
+ config.cameraMimeType = config.chooseMode;
+ }
+ return;
+ }
+ if (config.isUseCustomCamera) {
+ startCustomCamera();
+ return;
+ }
+ switch (config.chooseMode) {
+ case PictureConfig.TYPE_ALL:
+ PhotoItemSelectedDialog selectedDialog = PhotoItemSelectedDialog.newInstance();
+ selectedDialog.setOnItemClickListener(this);
+ selectedDialog.show(getSupportFragmentManager(), "PhotoItemSelectedDialog");
+ break;
+ case PictureConfig.TYPE_IMAGE:
+ startOpenCamera();
+ break;
+ case PictureConfig.TYPE_VIDEO:
+ startOpenCameraVideo();
+ break;
+ case PictureConfig.TYPE_AUDIO:
+ startOpenCameraAudio();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Open Custom Camera
+ */
+ private void startCustomCamera() {
+ if (PermissionChecker.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)) {
+ Intent intent = new Intent(this, PictureCustomCameraActivity.class);
+ startActivityForResult(intent, PictureConfig.REQUEST_CAMERA);
+ PictureWindowAnimationStyle windowAnimationStyle = config.windowAnimationStyle;
+ overridePendingTransition(windowAnimationStyle != null &&
+ windowAnimationStyle.activityEnterAnimation != 0 ?
+ windowAnimationStyle.activityEnterAnimation :
+ R.anim.picture_anim_enter, R.anim.picture_anim_fade_in);
+ } else {
+ PermissionChecker
+ .requestPermissions(this,
+ new String[]{Manifest.permission.RECORD_AUDIO}, PictureConfig.APPLY_RECORD_AUDIO_PERMISSIONS_CODE);
+ }
+ }
+
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ if (id == R.id.pictureLeftBack || id == R.id.picture_right) {
+ if (folderWindow != null && folderWindow.isShowing()) {
+ folderWindow.dismiss();
+ } else {
+ onBackPressed();
+ }
+ return;
+ }
+ if (id == R.id.picture_title || id == R.id.ivArrow) {
+ if (folderWindow.isShowing()) {
+ folderWindow.dismiss();
+ } else {
+ if (!folderWindow.isEmpty()) {
+ folderWindow.showAsDropDown(titleViewBg);
+ if (!config.isSingleDirectReturn) {
+ List selectedImages = mAdapter.getSelectedData();
+ folderWindow.updateFolderCheckStatus(selectedImages);
+ }
+ }
+ }
+ return;
+ }
+
+ if (id == R.id.picture_id_preview) {
+ onPreview();
+ return;
+ }
+
+ if (id == R.id.picture_tv_ok || id == R.id.picture_tvMediaNum) {
+ onComplete();
+ return;
+ }
+
+ if (id == R.id.titleViewBg) {
+ if (config.isAutomaticTitleRecyclerTop) {
+ int intervalTime = 500;
+ if (SystemClock.uptimeMillis() - intervalClickTime < intervalTime) {
+ if (mAdapter.getItemCount() > 0) {
+ mRecyclerView.scrollToPosition(0);
+ }
+ } else {
+ intervalClickTime = SystemClock.uptimeMillis();
+ }
+ }
+ }
+ }
+
+ /**
+ * Preview
+ */
+ private void onPreview() {
+ List selectedImages = mAdapter.getSelectedData();
+ List medias = new ArrayList<>();
+ int size = selectedImages.size();
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = selectedImages.get(i);
+ medias.add(media);
+ }
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(PictureConfig.EXTRA_PREVIEW_SELECT_LIST, (ArrayList extends Parcelable>) medias);
+ bundle.putParcelableArrayList(PictureConfig.EXTRA_SELECT_LIST, (ArrayList extends Parcelable>) selectedImages);
+ bundle.putBoolean(PictureConfig.EXTRA_BOTTOM_PREVIEW, true);
+ bundle.putBoolean(PictureConfig.EXTRA_CHANGE_ORIGINAL, config.isCheckOriginalImage);
+ bundle.putBoolean(PictureConfig.EXTRA_SHOW_CAMERA, mAdapter.isShowCamera());
+ bundle.putString(PictureConfig.EXTRA_IS_CURRENT_DIRECTORY, mTvPictureTitle.getText().toString());
+ JumpUtils.startPicturePreviewActivity(getContext(), config.isWeChatStyle, bundle,
+ config.selectionMode == PictureConfig.SINGLE ? UCrop.REQUEST_CROP : UCrop.REQUEST_MULTI_CROP);
+
+ overridePendingTransition(config.windowAnimationStyle != null
+ && config.windowAnimationStyle.activityPreviewEnterAnimation != 0
+ ? config.windowAnimationStyle.activityPreviewEnterAnimation : R.anim.picture_anim_enter,
+ R.anim.picture_anim_fade_in);
+ }
+
+ /**
+ * Complete
+ */
+ private void onComplete() {
+ List result = mAdapter.getSelectedData();
+ int size = result.size();
+ LocalMedia image = result.size() > 0 ? result.get(0) : null;
+ String mimeType = image != null ? image.getMimeType() : "";
+ boolean isHasImage = PictureMimeType.isHasImage(mimeType);
+ if (config.isWithVideoImage) {
+ int videoSize = 0;
+ int imageSize = 0;
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = result.get(i);
+ if (PictureMimeType.isHasVideo(media.getMimeType())) {
+ videoSize++;
+ } else {
+ imageSize++;
+ }
+ }
+ if (config.selectionMode == PictureConfig.MULTIPLE) {
+ if (config.minSelectNum > 0) {
+ if (imageSize < config.minSelectNum) {
+ showPromptDialog(getString(R.string.picture_min_img_num, config.minSelectNum));
+ return;
+ }
+ }
+ if (config.minVideoSelectNum > 0) {
+ if (videoSize < config.minVideoSelectNum) {
+ showPromptDialog(getString(R.string.picture_min_video_num, config.minVideoSelectNum));
+ return;
+ }
+ }
+ }
+ } else {
+ if (config.selectionMode == PictureConfig.MULTIPLE) {
+ if (PictureMimeType.isHasImage(mimeType) && config.minSelectNum > 0 && size < config.minSelectNum) {
+ String str = getString(R.string.picture_min_img_num, config.minSelectNum);
+ showPromptDialog(str);
+ return;
+ }
+ if (PictureMimeType.isHasVideo(mimeType) && config.minVideoSelectNum > 0 && size < config.minVideoSelectNum) {
+ String str = getString(R.string.picture_min_video_num, config.minVideoSelectNum);
+ showPromptDialog(str);
+ return;
+ }
+ }
+ }
+
+ if (config.returnEmpty && size == 0) {
+ if (config.selectionMode == PictureConfig.MULTIPLE) {
+ if (config.minSelectNum > 0 && size < config.minSelectNum) {
+ String str = getString(R.string.picture_min_img_num, config.minSelectNum);
+ showPromptDialog(str);
+ return;
+ }
+ if (config.minVideoSelectNum > 0 && size < config.minVideoSelectNum) {
+ String str = getString(R.string.picture_min_video_num, config.minVideoSelectNum);
+ showPromptDialog(str);
+ return;
+ }
+ }
+ if (PictureSelectionConfig.listener != null) {
+ PictureSelectionConfig.listener.onResult(result);
+ } else {
+ Intent intent = PictureSelector.putIntentResult(result);
+ setResult(RESULT_OK, intent);
+ }
+ closeActivity();
+ return;
+ }
+ if (config.isCheckOriginalImage) {
+ onResult(result);
+ return;
+ }
+ if (config.chooseMode == PictureMimeType.ofAll() && config.isWithVideoImage) {
+ bothMimeTypeWith(isHasImage, result);
+ } else {
+ separateMimeTypeWith(isHasImage, result);
+ }
+ }
+
+
+ /**
+ * They are different types of processing
+ *
+ * @param isHasImage
+ * @param images
+ */
+ private void bothMimeTypeWith(boolean isHasImage, List images) {
+ LocalMedia image = images.size() > 0 ? images.get(0) : null;
+ if (image == null) {
+ return;
+ }
+ if (config.enableCrop) {
+ if (config.selectionMode == PictureConfig.SINGLE && isHasImage) {
+ config.originalPath = image.getPath();
+ startCrop(config.originalPath, image.getMimeType());
+ } else {
+ ArrayList cuts = new ArrayList<>();
+ int count = images.size();
+ int imageNum = 0;
+ for (int i = 0; i < count; i++) {
+ LocalMedia media = images.get(i);
+ if (media == null
+ || TextUtils.isEmpty(media.getPath())) {
+ continue;
+ }
+ if (PictureMimeType.isHasImage(media.getMimeType())) {
+ imageNum++;
+ }
+ CutInfo cutInfo = new CutInfo();
+ cutInfo.setId(media.getId());
+ cutInfo.setPath(media.getPath());
+ cutInfo.setImageWidth(media.getWidth());
+ cutInfo.setImageHeight(media.getHeight());
+ cutInfo.setMimeType(media.getMimeType());
+ cutInfo.setDuration(media.getDuration());
+ cutInfo.setRealPath(media.getRealPath());
+ cuts.add(cutInfo);
+ }
+ if (imageNum <= 0) {
+ onResult(images);
+ } else {
+ startCrop(cuts);
+ }
+ }
+ } else if (config.isCompress) {
+ int size = images.size();
+ int imageNum = 0;
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = images.get(i);
+ if (PictureMimeType.isHasImage(media.getMimeType())) {
+ imageNum++;
+ break;
+ }
+ }
+ if (imageNum <= 0) {
+ onResult(images);
+ } else {
+ compressImage(images);
+ }
+ } else {
+ onResult(images);
+ }
+ }
+
+ /**
+ * Same type of image or video processing logic
+ *
+ * @param isHasImage
+ * @param images
+ */
+ private void separateMimeTypeWith(boolean isHasImage, List images) {
+ LocalMedia image = images.size() > 0 ? images.get(0) : null;
+ if (image == null) {
+ return;
+ }
+ if (config.enableCrop && isHasImage) {
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ config.originalPath = image.getPath();
+ startCrop(config.originalPath, image.getMimeType());
+ } else {
+ ArrayList cuts = new ArrayList<>();
+ int count = images.size();
+ for (int i = 0; i < count; i++) {
+ LocalMedia media = images.get(i);
+ if (media == null
+ || TextUtils.isEmpty(media.getPath())) {
+ continue;
+ }
+ CutInfo cutInfo = new CutInfo();
+ cutInfo.setId(media.getId());
+ cutInfo.setPath(media.getPath());
+ cutInfo.setImageWidth(media.getWidth());
+ cutInfo.setImageHeight(media.getHeight());
+ cutInfo.setMimeType(media.getMimeType());
+ cutInfo.setDuration(media.getDuration());
+ cutInfo.setRealPath(media.getRealPath());
+ cuts.add(cutInfo);
+ }
+ startCrop(cuts);
+ }
+ } else if (config.isCompress
+ && isHasImage) {
+ compressImage(images);
+ } else {
+ onResult(images);
+ }
+ }
+
+ /**
+ * Play Audio
+ *
+ * @param path
+ */
+ private void AudioDialog(final String path) {
+ if (!isFinishing()) {
+ audioDialog = new PictureCustomDialog(getContext(), R.layout.picture_audio_dialog);
+ if (audioDialog.getWindow() != null) {
+ audioDialog.getWindow().setWindowAnimations(R.style.Picture_Theme_Dialog_AudioStyle);
+ }
+ mTvMusicStatus = audioDialog.findViewById(R.id.tv_musicStatus);
+ mTvMusicTime = audioDialog.findViewById(R.id.tv_musicTime);
+ musicSeekBar = audioDialog.findViewById(R.id.musicSeekBar);
+ mTvMusicTotal = audioDialog.findViewById(R.id.tv_musicTotal);
+ mTvPlayPause = audioDialog.findViewById(R.id.tv_PlayPause);
+ mTvStop = audioDialog.findViewById(R.id.tv_Stop);
+ mTvQuit = audioDialog.findViewById(R.id.tv_Quit);
+ if (mHandler != null) {
+ mHandler.postDelayed(() -> initPlayer(path), 30);
+ }
+ mTvPlayPause.setOnClickListener(new AudioOnClick(path));
+ mTvStop.setOnClickListener(new AudioOnClick(path));
+ mTvQuit.setOnClickListener(new AudioOnClick(path));
+ musicSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ mediaPlayer.seekTo(progress);
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+ });
+ audioDialog.setOnDismissListener(dialog -> {
+ if (mHandler != null) {
+ mHandler.removeCallbacks(mRunnable);
+ }
+ new Handler().postDelayed(() -> stop(path), 30);
+ try {
+ if (audioDialog != null
+ && audioDialog.isShowing()) {
+ audioDialog.dismiss();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ if (mHandler != null) {
+ mHandler.post(mRunnable);
+ }
+ audioDialog.show();
+ }
+ }
+
+ public Runnable mRunnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (mediaPlayer != null) {
+ mTvMusicTime.setText(DateUtils.formatDurationTime(mediaPlayer.getCurrentPosition()));
+ musicSeekBar.setProgress(mediaPlayer.getCurrentPosition());
+ musicSeekBar.setMax(mediaPlayer.getDuration());
+ mTvMusicTotal.setText(DateUtils.formatDurationTime(mediaPlayer.getDuration()));
+ if (mHandler != null) {
+ mHandler.postDelayed(mRunnable, 200);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ /**
+ * init Player
+ *
+ * @param path
+ */
+ private void initPlayer(String path) {
+ mediaPlayer = new MediaPlayer();
+ try {
+ mediaPlayer.setDataSource(path);
+ mediaPlayer.prepare();
+ mediaPlayer.setLooping(true);
+ playAudio();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Audio Click
+ */
+ public class AudioOnClick implements View.OnClickListener {
+ private String path;
+
+ public AudioOnClick(String path) {
+ super();
+ this.path = path;
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ if (id == R.id.tv_PlayPause) {
+ playAudio();
+ }
+ if (id == R.id.tv_Stop) {
+ mTvMusicStatus.setText(getString(R.string.picture_stop_audio));
+ mTvPlayPause.setText(getString(R.string.picture_play_audio));
+ stop(path);
+ }
+ if (id == R.id.tv_Quit) {
+ if (mHandler != null) {
+ mHandler.postDelayed(() -> stop(path), 30);
+ try {
+ if (audioDialog != null
+ && audioDialog.isShowing()) {
+ audioDialog.dismiss();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ mHandler.removeCallbacks(mRunnable);
+ }
+ }
+ }
+ }
+
+ /**
+ * Play Audio
+ */
+ private void playAudio() {
+ if (mediaPlayer != null) {
+ musicSeekBar.setProgress(mediaPlayer.getCurrentPosition());
+ musicSeekBar.setMax(mediaPlayer.getDuration());
+ }
+ String ppStr = mTvPlayPause.getText().toString();
+ if (ppStr.equals(getString(R.string.picture_play_audio))) {
+ mTvPlayPause.setText(getString(R.string.picture_pause_audio));
+ mTvMusicStatus.setText(getString(R.string.picture_play_audio));
+ playOrPause();
+ } else {
+ mTvPlayPause.setText(getString(R.string.picture_play_audio));
+ mTvMusicStatus.setText(getString(R.string.picture_pause_audio));
+ playOrPause();
+ }
+ if (!isPlayAudio) {
+ if (mHandler != null) {
+ mHandler.post(mRunnable);
+ }
+ isPlayAudio = true;
+ }
+ }
+
+ /**
+ * Audio Stop
+ *
+ * @param path
+ */
+ public void stop(String path) {
+ if (mediaPlayer != null) {
+ try {
+ mediaPlayer.stop();
+ mediaPlayer.reset();
+ mediaPlayer.setDataSource(path);
+ mediaPlayer.prepare();
+ mediaPlayer.seekTo(0);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Audio Pause
+ */
+ public void playOrPause() {
+ try {
+ if (mediaPlayer != null) {
+ if (mediaPlayer.isPlaying()) {
+ mediaPlayer.pause();
+ } else {
+ mediaPlayer.start();
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ @Override
+ public void onItemClick(int position, boolean isCameraFolder, long bucketId, String
+ folderName, List data) {
+ boolean camera = config.isCamera && isCameraFolder;
+ mAdapter.setShowCamera(camera);
+ mTvPictureTitle.setText(folderName);
+ long currentBucketId = ValueOf.toLong(mTvPictureTitle.getTag(R.id.view_tag));
+ mTvPictureTitle.setTag(R.id.view_count_tag, folderWindow.getFolder(position) != null
+ ? folderWindow.getFolder(position).getImageNum() : 0);
+ if (config.isPageStrategy) {
+ if (currentBucketId != bucketId) {
+ setLastCacheFolderData();
+ boolean isCurrentCacheFolderData = isCurrentCacheFolderData(position);
+ if (!isCurrentCacheFolderData) {
+ mPage = 1;
+ showPleaseDialog();
+ LocalMediaPageLoader.getInstance(getContext(), config).loadPageMediaData(bucketId, mPage,
+ (OnQueryDataResultListener) (result, currentPage, isHasMore) -> {
+ this.isHasMore = isHasMore;
+ if (!isFinishing()) {
+ if (result.size() == 0) {
+ mAdapter.clear();
+ }
+ mAdapter.bindData(result);
+ mRecyclerView.onScrolled(0, 0);
+ mRecyclerView.smoothScrollToPosition(0);
+ dismissDialog();
+ }
+ });
+ }
+ }
+ } else {
+ mAdapter.bindData(data);
+ mRecyclerView.smoothScrollToPosition(0);
+ }
+ mTvPictureTitle.setTag(R.id.view_tag, bucketId);
+ folderWindow.dismiss();
+ }
+
+ /**
+ * Before switching directories, set the current directory cache
+ */
+ private void setLastCacheFolderData() {
+ int oldPosition = ValueOf.toInt(mTvPictureTitle.getTag(R.id.view_index_tag));
+ LocalMediaFolder lastFolder = folderWindow.getFolder(oldPosition);
+ lastFolder.setData(mAdapter.getData());
+ lastFolder.setCurrentDataPage(mPage);
+ lastFolder.setHasMore(isHasMore);
+ }
+
+ /**
+ * Does the current album have a cache
+ *
+ * @param position
+ */
+ private boolean isCurrentCacheFolderData(int position) {
+ mTvPictureTitle.setTag(R.id.view_index_tag, position);
+ LocalMediaFolder currentFolder = folderWindow.getFolder(position);
+ if (currentFolder != null
+ && currentFolder.getData() != null
+ && currentFolder.getData().size() > 0) {
+ mAdapter.bindData(currentFolder.getData());
+ mPage = currentFolder.getCurrentDataPage();
+ isHasMore = currentFolder.isHasMore();
+ mRecyclerView.smoothScrollToPosition(0);
+
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onTakePhoto() {
+ // Check the permissions
+ if (PermissionChecker.checkSelfPermission(this, Manifest.permission.CAMERA)) {
+ if (PermissionChecker
+ .checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) &&
+ PermissionChecker
+ .checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ startCamera();
+ } else {
+ PermissionChecker.requestPermissions(this, new String[]{
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE}, PictureConfig.APPLY_CAMERA_STORAGE_PERMISSIONS_CODE);
+ }
+ } else {
+ PermissionChecker
+ .requestPermissions(this,
+ new String[]{Manifest.permission.CAMERA}, PictureConfig.APPLY_CAMERA_PERMISSIONS_CODE);
+ }
+ }
+
+ @Override
+ public void onChange(List selectData) {
+ changeImageNumber(selectData);
+ }
+
+ @Override
+ public void onPictureClick(LocalMedia media, int position) {
+ if (config.selectionMode == PictureConfig.SINGLE && config.isSingleDirectReturn) {
+ List list = new ArrayList<>();
+ list.add(media);
+ if (config.enableCrop && PictureMimeType.isHasImage(media.getMimeType()) && !config.isCheckOriginalImage) {
+ mAdapter.bindSelectData(list);
+ startCrop(media.getPath(), media.getMimeType());
+ } else {
+ handlerResult(list);
+ }
+ } else {
+ List data = mAdapter.getData();
+ startPreview(data, position);
+ }
+ }
+
+ /**
+ * preview image and video
+ *
+ * @param previewImages
+ * @param position
+ */
+ public void startPreview(List previewImages, int position) {
+ LocalMedia media = previewImages.get(position);
+ String mimeType = media.getMimeType();
+ Bundle bundle = new Bundle();
+ List result = new ArrayList<>();
+ if (PictureMimeType.isHasVideo(mimeType)) {
+ // video
+ if (config.selectionMode == PictureConfig.SINGLE && !config.enPreviewVideo) {
+ result.add(media);
+ onResult(result);
+ } else {
+ if (PictureSelectionConfig.customVideoPlayCallback != null) {
+ PictureSelectionConfig.customVideoPlayCallback.startPlayVideo(media);
+ } else {
+ bundle.putParcelable(PictureConfig.EXTRA_MEDIA_KEY, media);
+ JumpUtils.startPictureVideoPlayActivity(getContext(), bundle, PictureConfig.PREVIEW_VIDEO_CODE);
+ }
+ }
+ } else if (PictureMimeType.isHasAudio(mimeType)) {
+ // audio
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ result.add(media);
+ onResult(result);
+ } else {
+ AudioDialog(media.getPath());
+ }
+ } else {
+ // image
+ List selectedData = mAdapter.getSelectedData();
+ ImagesObservable.getInstance().savePreviewMediaData(new ArrayList<>(previewImages));
+ bundle.putParcelableArrayList(PictureConfig.EXTRA_SELECT_LIST, (ArrayList extends Parcelable>) selectedData);
+ bundle.putInt(PictureConfig.EXTRA_POSITION, position);
+ bundle.putBoolean(PictureConfig.EXTRA_CHANGE_ORIGINAL, config.isCheckOriginalImage);
+ bundle.putBoolean(PictureConfig.EXTRA_SHOW_CAMERA, mAdapter.isShowCamera());
+ bundle.putLong(PictureConfig.EXTRA_BUCKET_ID, ValueOf.toLong(mTvPictureTitle.getTag(R.id.view_tag)));
+ bundle.putInt(PictureConfig.EXTRA_PAGE, mPage);
+ bundle.putParcelable(PictureConfig.EXTRA_CONFIG, config);
+ bundle.putInt(PictureConfig.EXTRA_DATA_COUNT, ValueOf.toInt(mTvPictureTitle.getTag(R.id.view_count_tag)));
+ bundle.putString(PictureConfig.EXTRA_IS_CURRENT_DIRECTORY, mTvPictureTitle.getText().toString());
+ JumpUtils.startPicturePreviewActivity(getContext(), config.isWeChatStyle, bundle,
+ config.selectionMode == PictureConfig.SINGLE ? UCrop.REQUEST_CROP : UCrop.REQUEST_MULTI_CROP);
+ overridePendingTransition(config.windowAnimationStyle != null
+ && config.windowAnimationStyle.activityPreviewEnterAnimation != 0
+ ? config.windowAnimationStyle.activityPreviewEnterAnimation : R.anim.picture_anim_enter, R.anim.picture_anim_fade_in);
+ }
+ }
+
+
+ /**
+ * change image selector state
+ *
+ * @param selectData
+ */
+ protected void changeImageNumber(List selectData) {
+ boolean enable = selectData.size() != 0;
+ if (enable) {
+ mTvPictureOk.setEnabled(true);
+ mTvPictureOk.setSelected(true);
+ mTvPicturePreview.setEnabled(true);
+ mTvPicturePreview.setSelected(true);
+ if (config.style != null) {
+ if (config.style.pictureCompleteTextColor != 0) {
+ mTvPictureOk.setTextColor(config.style.pictureCompleteTextColor);
+ }
+ if (config.style.picturePreviewTextColor != 0) {
+ mTvPicturePreview.setTextColor(config.style.picturePreviewTextColor);
+ }
+ }
+ if (config.style != null && !TextUtils.isEmpty(config.style.picturePreviewText)) {
+ mTvPicturePreview.setText(config.style.picturePreviewText);
+ } else {
+ mTvPicturePreview.setText(getString(R.string.picture_preview_num, selectData.size()));
+ }
+ if (numComplete) {
+ initCompleteText(selectData.size());
+ } else {
+ if (!isStartAnimation) {
+ mTvPictureImgNum.startAnimation(animation);
+ }
+ mTvPictureImgNum.setVisibility(View.VISIBLE);
+ mTvPictureImgNum.setText(String.valueOf(selectData.size()));
+ if (config.style != null && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mTvPictureOk.setText(config.style.pictureCompleteText);
+ } else {
+ mTvPictureOk.setText(getString(R.string.picture_completed));
+ }
+ isStartAnimation = false;
+ }
+ } else {
+ mTvPictureOk.setEnabled(config.returnEmpty);
+ mTvPictureOk.setSelected(false);
+ mTvPicturePreview.setEnabled(false);
+ mTvPicturePreview.setSelected(false);
+ if (config.style != null) {
+ if (config.style.pictureUnCompleteTextColor != 0) {
+ mTvPictureOk.setTextColor(config.style.pictureUnCompleteTextColor);
+ }
+ if (config.style.pictureUnPreviewTextColor != 0) {
+ mTvPicturePreview.setTextColor(config.style.pictureUnPreviewTextColor);
+ }
+ }
+ if (config.style != null && !TextUtils.isEmpty(config.style.pictureUnPreviewText)) {
+ mTvPicturePreview.setText(config.style.pictureUnPreviewText);
+ } else {
+ mTvPicturePreview.setText(getString(R.string.picture_preview));
+ }
+ if (numComplete) {
+ initCompleteText(selectData.size());
+ } else {
+ mTvPictureImgNum.setVisibility(View.INVISIBLE);
+ if (config.style != null && !TextUtils.isEmpty(config.style.pictureUnCompleteText)) {
+ mTvPictureOk.setText(config.style.pictureUnCompleteText);
+ } else {
+ mTvPictureOk.setText(getString(R.string.picture_please_select));
+ }
+ }
+ }
+ }
+
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
+ switch (requestCode) {
+ case PictureConfig.PREVIEW_VIDEO_CODE:
+ if (data != null) {
+ List list = data.getParcelableArrayListExtra(PictureConfig.EXTRA_SELECT_LIST);
+ if (list != null && list.size() > 0) {
+ onResult(list);
+ }
+ }
+ break;
+ case UCrop.REQUEST_CROP:
+ singleCropHandleResult(data);
+ break;
+ case UCrop.REQUEST_MULTI_CROP:
+ multiCropHandleResult(data);
+ break;
+ case PictureConfig.REQUEST_CAMERA:
+ dispatchHandleCamera(data);
+ break;
+ default:
+ break;
+ }
+ } else if (resultCode == RESULT_CANCELED) {
+ previewCallback(data);
+ } else if (resultCode == UCrop.RESULT_ERROR) {
+ if (data != null) {
+ Throwable throwable = (Throwable) data.getSerializableExtra(UCrop.EXTRA_ERROR);
+ if (throwable != null) {
+ ToastUtils.s(getContext(), throwable.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * Preview interface callback processing
+ *
+ * @param data
+ */
+ private void previewCallback(Intent data) {
+ if (data == null) {
+ return;
+ }
+ if (config.isOriginalControl) {
+ config.isCheckOriginalImage = data.getBooleanExtra(PictureConfig.EXTRA_CHANGE_ORIGINAL, config.isCheckOriginalImage);
+ mCbOriginal.setChecked(config.isCheckOriginalImage);
+ }
+ List list = data.getParcelableArrayListExtra(PictureConfig.EXTRA_SELECT_LIST);
+ if (mAdapter != null && list != null) {
+ boolean isCompleteOrSelected = data.getBooleanExtra(PictureConfig.EXTRA_COMPLETE_SELECTED, false);
+ if (isCompleteOrSelected) {
+ onChangeData(list);
+ if (config.isWithVideoImage) {
+ int size = list.size();
+ int imageSize = 0;
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = list.get(i);
+ if (PictureMimeType.isHasImage(media.getMimeType())) {
+ imageSize++;
+ break;
+ }
+ }
+ if (imageSize <= 0 || !config.isCompress || config.isCheckOriginalImage) {
+ onResult(list);
+ } else {
+ compressImage(list);
+ }
+ } else {
+ // Determine if the resource is of the same type
+ String mimeType = list.size() > 0 ? list.get(0).getMimeType() : "";
+ if (config.isCompress && PictureMimeType.isHasImage(mimeType)
+ && !config.isCheckOriginalImage) {
+ compressImage(list);
+ } else {
+ onResult(list);
+ }
+ }
+ } else {
+ // Resources are selected on the preview page
+ isStartAnimation = true;
+ }
+ mAdapter.bindSelectData(list);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * Preview the callback
+ *
+ * @param list
+ */
+ protected void onChangeData(List list) {
+
+ }
+
+ /**
+ * singleDirectReturn
+ *
+ * @param mimeType
+ */
+ private void singleDirectReturnCameraHandleResult(String mimeType) {
+ boolean isHasImage = PictureMimeType.isHasImage(mimeType);
+ if (config.enableCrop && isHasImage) {
+ config.originalPath = config.cameraPath;
+ startCrop(config.cameraPath, mimeType);
+ } else if (config.isCompress && isHasImage) {
+ List selectedImages = mAdapter.getSelectedData();
+ compressImage(selectedImages);
+ } else {
+ onResult(mAdapter.getSelectedData());
+ }
+ }
+
+ /**
+ * Camera Handle
+ *
+ * @param intent
+ */
+ private void dispatchHandleCamera(Intent intent) {
+ // If PictureSelectionConfig is not empty, synchronize it
+ PictureSelectionConfig selectionConfig = intent != null ? intent.getParcelableExtra(PictureConfig.EXTRA_CONFIG) : null;
+ if (selectionConfig != null) {
+ config = selectionConfig;
+ }
+ boolean isAudio = config.chooseMode == PictureMimeType.ofAudio();
+ config.cameraPath = isAudio ? getAudioPath(intent) : config.cameraPath;
+ if (TextUtils.isEmpty(config.cameraPath)) {
+ return;
+ }
+ showPleaseDialog();
+ PictureThreadUtils.executeByIo(new PictureThreadUtils.SimpleTask() {
+
+ @Override
+ public LocalMedia doInBackground() {
+ LocalMedia media = new LocalMedia();
+ String mimeType = isAudio ? PictureMimeType.MIME_TYPE_AUDIO : "";
+ int[] newSize = new int[2];
+ long duration = 0;
+ if (!isAudio) {
+ if (PictureMimeType.isContent(config.cameraPath)) {
+ // content: Processing rules
+ String path = PictureFileUtils.getPath(getContext(), Uri.parse(config.cameraPath));
+ if (!TextUtils.isEmpty(path)) {
+ File cameraFile = new File(path);
+ mimeType = PictureMimeType.getMimeType(config.cameraMimeType);
+ media.setSize(cameraFile.length());
+ }
+ if (PictureMimeType.isHasImage(mimeType)) {
+ newSize = MediaUtils.getImageSizeForUrlToAndroidQ(getContext(), config.cameraPath);
+ } else if (PictureMimeType.isHasVideo(mimeType)) {
+ newSize = MediaUtils.getVideoSizeForUri(getContext(), Uri.parse(config.cameraPath));
+ duration = MediaUtils.extractDuration(getContext(), SdkVersionUtils.checkedAndroid_Q(), config.cameraPath);
+ }
+ int lastIndexOf = config.cameraPath.lastIndexOf("/") + 1;
+ media.setId(lastIndexOf > 0 ? ValueOf.toLong(config.cameraPath.substring(lastIndexOf)) : -1);
+ media.setRealPath(path);
+ // Custom photo has been in the application sandbox into the file
+ String mediaPath = intent != null ? intent.getStringExtra(PictureConfig.EXTRA_MEDIA_PATH) : null;
+ media.setAndroidQToPath(mediaPath);
+ } else {
+ File cameraFile = new File(config.cameraPath);
+ mimeType = PictureMimeType.getMimeType(config.cameraMimeType);
+ media.setSize(cameraFile.length());
+ if (PictureMimeType.isHasImage(mimeType)) {
+ int degree = PictureFileUtils.readPictureDegree(getContext(), config.cameraPath);
+ BitmapUtils.rotateImage(degree, config.cameraPath);
+ newSize = MediaUtils.getImageSizeForUrl(config.cameraPath);
+ } else if (PictureMimeType.isHasVideo(mimeType)) {
+ newSize = MediaUtils.getVideoSizeForUrl(config.cameraPath);
+ duration = MediaUtils.extractDuration(getContext(), SdkVersionUtils.checkedAndroid_Q(), config.cameraPath);
+ }
+ // Taking a photo generates a temporary id
+ media.setId(System.currentTimeMillis());
+ }
+ media.setPath(config.cameraPath);
+ media.setDuration(duration);
+ media.setMimeType(mimeType);
+ media.setWidth(newSize[0]);
+ media.setHeight(newSize[1]);
+ if (SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isHasVideo(media.getMimeType())) {
+ media.setParentFolderName(Environment.DIRECTORY_MOVIES);
+ } else {
+ media.setParentFolderName(PictureMimeType.CAMERA);
+ }
+ media.setChooseModel(config.chooseMode);
+ long bucketId = MediaUtils.getCameraFirstBucketId(getContext());
+ media.setBucketId(bucketId);
+ // The width and height of the image are reversed if there is rotation information
+ MediaUtils.setOrientationSynchronous(getContext(), media, config.isAndroidQChangeWH,config.isAndroidQChangeVideoWH);
+ }
+ return media;
+ }
+
+ @Override
+ public void onSuccess(LocalMedia result) {
+ dismissDialog();
+ // Refresh the system library
+ if (!SdkVersionUtils.checkedAndroid_Q()) {
+ if (config.isFallbackVersion3) {
+ new PictureMediaScannerConnection(getContext(), config.cameraPath);
+ } else {
+ sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(new File(config.cameraPath))));
+ }
+ }
+ // add data Adapter
+ notifyAdapterData(result);
+ // Solve some phone using Camera, DCIM will produce repetitive problems
+ if (!SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isHasImage(result.getMimeType())) {
+ int lastImageId = MediaUtils.getDCIMLastImageId(getContext());
+ if (lastImageId != -1) {
+ MediaUtils.removeMedia(getContext(), lastImageId);
+ }
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Update Adapter Data
+ *
+ * @param media
+ */
+ private void notifyAdapterData(LocalMedia media) {
+ if (mAdapter != null) {
+ boolean isAddSameImp = isAddSameImp(folderWindow.getFolder(0) != null ? folderWindow.getFolder(0).getImageNum() : 0);
+ if (!isAddSameImp) {
+ mAdapter.getData().add(0, media);
+ mOpenCameraCount++;
+ }
+ if (checkVideoLegitimacy(media)) {
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ dispatchHandleSingle(media);
+ } else {
+ dispatchHandleMultiple(media);
+ }
+ }
+ mAdapter.notifyItemInserted(config.isCamera ? 1 : 0);
+ mAdapter.notifyItemRangeChanged(config.isCamera ? 1 : 0, mAdapter.getSize());
+ // Solve the problem that some mobile phones do not refresh the system library timely after using Camera
+ if (config.isPageStrategy) {
+ manualSaveFolderForPageModel(media);
+ } else {
+ manualSaveFolder(media);
+ }
+ mTvEmpty.setVisibility(mAdapter.getSize() > 0 || config.isSingleDirectReturn ? View.GONE : View.VISIBLE);
+ // update all count
+ if (folderWindow.getFolder(0) != null) {
+ mTvPictureTitle.setTag(R.id.view_count_tag, folderWindow.getFolder(0).getImageNum());
+ }
+ allFolderSize = 0;
+ }
+ }
+
+ /**
+ * After using Camera, MultiSelect mode handles the logic
+ *
+ * @param media
+ */
+ private void dispatchHandleMultiple(LocalMedia media) {
+ List selectedData = mAdapter.getSelectedData();
+ int count = selectedData.size();
+ String oldMimeType = count > 0 ? selectedData.get(0).getMimeType() : "";
+ boolean mimeTypeSame = PictureMimeType.isMimeTypeSame(oldMimeType, media.getMimeType());
+ if (config.isWithVideoImage) {
+ int videoSize = 0;
+ for (int i = 0; i < count; i++) {
+ LocalMedia item = selectedData.get(i);
+ if (PictureMimeType.isHasVideo(item.getMimeType())) {
+ videoSize++;
+ }
+ }
+ if (PictureMimeType.isHasVideo(media.getMimeType())) {
+ if (config.maxVideoSelectNum <= 0) {
+ showPromptDialog(getString(R.string.picture_rule));
+ } else {
+ if (selectedData.size() >= config.maxSelectNum) {
+ showPromptDialog(getString(R.string.picture_message_max_num, config.maxSelectNum));
+ } else {
+ if (videoSize < config.maxVideoSelectNum) {
+ selectedData.add(0, media);
+ mAdapter.bindSelectData(selectedData);
+ } else {
+ showPromptDialog(StringUtils.getMsg(getContext(), media.getMimeType(),
+ config.maxVideoSelectNum));
+ }
+ }
+ }
+ } else {
+ if (selectedData.size() < config.maxSelectNum) {
+ selectedData.add(0, media);
+ mAdapter.bindSelectData(selectedData);
+ } else {
+ showPromptDialog(StringUtils.getMsg(getContext(), media.getMimeType(),
+ config.maxSelectNum));
+ }
+ }
+
+ } else {
+ if (PictureMimeType.isHasVideo(oldMimeType) && config.maxVideoSelectNum > 0) {
+ if (count < config.maxVideoSelectNum) {
+ if (mimeTypeSame || count == 0) {
+ if (selectedData.size() < config.maxVideoSelectNum) {
+ selectedData.add(0, media);
+ mAdapter.bindSelectData(selectedData);
+ }
+ }
+ } else {
+ showPromptDialog(StringUtils.getMsg(getContext(), oldMimeType,
+ config.maxVideoSelectNum));
+ }
+ } else {
+ if (count < config.maxSelectNum) {
+ if (mimeTypeSame || count == 0) {
+ selectedData.add(0, media);
+ mAdapter.bindSelectData(selectedData);
+ }
+ } else {
+ showPromptDialog(StringUtils.getMsg(getContext(), oldMimeType,
+ config.maxSelectNum));
+ }
+ }
+ }
+ }
+
+ /**
+ * After using the camera, the radio mode handles the logic
+ *
+ * @param media
+ */
+ private void dispatchHandleSingle(LocalMedia media) {
+ if (config.isSingleDirectReturn) {
+ List selectedData = mAdapter.getSelectedData();
+ selectedData.add(media);
+ mAdapter.bindSelectData(selectedData);
+ singleDirectReturnCameraHandleResult(media.getMimeType());
+ } else {
+ List selectedData = mAdapter.getSelectedData();
+ String mimeType = selectedData.size() > 0 ? selectedData.get(0).getMimeType() : "";
+ boolean mimeTypeSame = PictureMimeType.isMimeTypeSame(mimeType, media.getMimeType());
+ if (mimeTypeSame || selectedData.size() == 0) {
+ singleRadioMediaImage();
+ selectedData.add(media);
+ mAdapter.bindSelectData(selectedData);
+ }
+ }
+ }
+
+ /**
+ * Verify the validity of the video
+ *
+ * @param media
+ * @return
+ */
+ private boolean checkVideoLegitimacy(LocalMedia media) {
+ boolean isEnterNext = true;
+ if (PictureMimeType.isHasVideo(media.getMimeType())) {
+ if (config.videoMinSecond > 0 && config.videoMaxSecond > 0) {
+ // The user sets the minimum and maximum video length to determine whether the video is within the interval
+ if (media.getDuration() < config.videoMinSecond || media.getDuration() > config.videoMaxSecond) {
+ isEnterNext = false;
+ showPromptDialog(getString(R.string.picture_choose_limit_seconds, config.videoMinSecond / 1000, config.videoMaxSecond / 1000));
+ }
+ } else if (config.videoMinSecond > 0) {
+ // The user has only set a minimum video length limit
+ if (media.getDuration() < config.videoMinSecond) {
+ isEnterNext = false;
+ showPromptDialog(getString(R.string.picture_choose_min_seconds, config.videoMinSecond / 1000));
+ }
+ } else if (config.videoMaxSecond > 0) {
+ // Only the maximum length of video is set
+ if (media.getDuration() > config.videoMaxSecond) {
+ isEnterNext = false;
+ showPromptDialog(getString(R.string.picture_choose_max_seconds, config.videoMaxSecond / 1000));
+ }
+ }
+ }
+ return isEnterNext;
+ }
+
+ /**
+ * Single picture clipping callback
+ *
+ * @param data
+ */
+ private void singleCropHandleResult(Intent data) {
+ if (data == null) {
+ return;
+ }
+ Uri resultUri = UCrop.getOutput(data);
+ if (resultUri == null) {
+ return;
+ }
+ List result = new ArrayList<>();
+ String cutPath = resultUri.getPath();
+ if (mAdapter != null) {
+ List list = data.getParcelableArrayListExtra(PictureConfig.EXTRA_SELECT_LIST);
+ if (list != null) {
+ mAdapter.bindSelectData(list);
+ mAdapter.notifyDataSetChanged();
+ }
+ List mediaList = mAdapter.getSelectedData();
+ LocalMedia media = mediaList != null && mediaList.size() > 0 ? mediaList.get(0) : null;
+ if (media != null) {
+ config.originalPath = media.getPath();
+ media.setCutPath(cutPath);
+ media.setChooseModel(config.chooseMode);
+ boolean isCutPathEmpty = !TextUtils.isEmpty(cutPath);
+ if (SdkVersionUtils.checkedAndroid_Q()
+ && PictureMimeType.isContent(media.getPath())) {
+ if (isCutPathEmpty) {
+ media.setSize(new File(cutPath).length());
+ } else {
+ media.setSize(!TextUtils.isEmpty(media.getRealPath()) ? new File(media.getRealPath()).length() : 0);
+ }
+ media.setAndroidQToPath(cutPath);
+ } else {
+ media.setSize(isCutPathEmpty ? new File(cutPath).length() : 0);
+ }
+ media.setCut(isCutPathEmpty);
+ result.add(media);
+ handlerResult(result);
+ } else {
+ // Preview screen selects the image and crop the callback
+ media = list != null && list.size() > 0 ? list.get(0) : null;
+ if (media != null) {
+ config.originalPath = media.getPath();
+ media.setCutPath(cutPath);
+ media.setChooseModel(config.chooseMode);
+ boolean isCutPathEmpty = !TextUtils.isEmpty(cutPath);
+ if (SdkVersionUtils.checkedAndroid_Q()
+ && PictureMimeType.isContent(media.getPath())) {
+ if (isCutPathEmpty) {
+ media.setSize(new File(cutPath).length());
+ } else {
+ media.setSize(!TextUtils.isEmpty(media.getRealPath()) ? new File(media.getRealPath()).length() : 0);
+ }
+ media.setAndroidQToPath(cutPath);
+ } else {
+ media.setSize(isCutPathEmpty ? new File(cutPath).length() : 0);
+ }
+ media.setCut(isCutPathEmpty);
+ result.add(media);
+ handlerResult(result);
+ }
+ }
+ }
+ }
+
+ /**
+ * Multiple picture crop
+ *
+ * @param data
+ */
+ protected void multiCropHandleResult(Intent data) {
+ if (data == null) {
+ return;
+ }
+ List mCuts = UCrop.getMultipleOutput(data);
+ if (mCuts == null || mCuts.size() == 0) {
+ return;
+ }
+ int size = mCuts.size();
+ boolean isAndroidQ = SdkVersionUtils.checkedAndroid_Q();
+ List list = data.getParcelableArrayListExtra(PictureConfig.EXTRA_SELECT_LIST);
+ if (list != null) {
+ mAdapter.bindSelectData(list);
+ mAdapter.notifyDataSetChanged();
+ }
+ int oldSize = mAdapter != null ? mAdapter.getSelectedData().size() : 0;
+ if (oldSize == size) {
+ List result = mAdapter.getSelectedData();
+ for (int i = 0; i < size; i++) {
+ CutInfo c = mCuts.get(i);
+ LocalMedia media = result.get(i);
+ media.setCut(!TextUtils.isEmpty(c.getCutPath()));
+ media.setPath(c.getPath());
+ media.setMimeType(c.getMimeType());
+ media.setCutPath(c.getCutPath());
+ media.setWidth(c.getImageWidth());
+ media.setHeight(c.getImageHeight());
+ media.setAndroidQToPath(isAndroidQ ? c.getCutPath() : media.getAndroidQToPath());
+ media.setSize(!TextUtils.isEmpty(c.getCutPath()) ? new File(c.getCutPath()).length() : media.getSize());
+ }
+ handlerResult(result);
+ } else {
+ // Fault-tolerant processing
+ List result = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ CutInfo c = mCuts.get(i);
+ LocalMedia media = new LocalMedia();
+ media.setId(c.getId());
+ media.setCut(!TextUtils.isEmpty(c.getCutPath()));
+ media.setPath(c.getPath());
+ media.setCutPath(c.getCutPath());
+ media.setMimeType(c.getMimeType());
+ media.setWidth(c.getImageWidth());
+ media.setHeight(c.getImageHeight());
+ media.setDuration(c.getDuration());
+ media.setChooseModel(config.chooseMode);
+ media.setAndroidQToPath(isAndroidQ ? c.getCutPath() : c.getAndroidQToPath());
+ if (!TextUtils.isEmpty(c.getCutPath())) {
+ media.setSize(new File(c.getCutPath()).length());
+ } else {
+ if (SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isContent(c.getPath())) {
+ media.setSize(!TextUtils.isEmpty(c.getRealPath()) ? new File(c.getRealPath()).length() : 0);
+ } else {
+ media.setSize(new File(c.getPath()).length());
+ }
+ }
+ result.add(media);
+ }
+ handlerResult(result);
+ }
+ }
+
+ /**
+ * Just make sure you pick one
+ */
+ private void singleRadioMediaImage() {
+ List selectData = mAdapter.getSelectedData();
+ if (selectData != null
+ && selectData.size() > 0) {
+ LocalMedia media = selectData.get(0);
+ int position = media.getPosition();
+ selectData.clear();
+ mAdapter.notifyItemChanged(position);
+ }
+ }
+
+ /**
+ * Manually add the photo to the list of photos and set it to select-paging mode
+ *
+ * @param media
+ */
+ private void manualSaveFolderForPageModel(LocalMedia media) {
+ if (media == null) {
+ return;
+ }
+ int count = folderWindow.getFolderData().size();
+ LocalMediaFolder allFolder = count > 0 ? folderWindow.getFolderData().get(0) : new LocalMediaFolder();
+ if (allFolder != null) {
+ int totalNum = allFolder.getImageNum();
+ allFolder.setFirstImagePath(media.getPath());
+ allFolder.setImageNum(isAddSameImp(totalNum) ? allFolder.getImageNum() : allFolder.getImageNum() + 1);
+ // Create All folder
+ if (count == 0) {
+ allFolder.setName(config.chooseMode == PictureMimeType.ofAudio() ?
+ getString(R.string.picture_all_audio) : getString(R.string.picture_camera_roll));
+ allFolder.setOfAllType(config.chooseMode);
+ allFolder.setCameraFolder(true);
+ allFolder.setChecked(true);
+ allFolder.setBucketId(-1);
+ folderWindow.getFolderData().add(0, allFolder);
+ // Create Camera
+ LocalMediaFolder cameraFolder = new LocalMediaFolder();
+ cameraFolder.setName(media.getParentFolderName());
+ cameraFolder.setImageNum(isAddSameImp(totalNum) ? cameraFolder.getImageNum() : cameraFolder.getImageNum() + 1);
+ cameraFolder.setFirstImagePath(media.getPath());
+ cameraFolder.setBucketId(media.getBucketId());
+ folderWindow.getFolderData().add(folderWindow.getFolderData().size(), cameraFolder);
+ } else {
+ boolean isCamera = false;
+ String newFolder = SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isHasVideo(media.getMimeType())
+ ? Environment.DIRECTORY_MOVIES : PictureMimeType.CAMERA;
+ for (int i = 0; i < count; i++) {
+ LocalMediaFolder cameraFolder = folderWindow.getFolderData().get(i);
+ if (cameraFolder.getName().startsWith(newFolder)) {
+ media.setBucketId(cameraFolder.getBucketId());
+ cameraFolder.setFirstImagePath(config.cameraPath);
+ cameraFolder.setImageNum(isAddSameImp(totalNum) ? cameraFolder.getImageNum() : cameraFolder.getImageNum() + 1);
+ if (cameraFolder.getData() != null && cameraFolder.getData().size() > 0) {
+ cameraFolder.getData().add(0, media);
+ }
+ isCamera = true;
+ break;
+ }
+ }
+ if (!isCamera) {
+ // There is no Camera folder locally. Create one
+ LocalMediaFolder cameraFolder = new LocalMediaFolder();
+ cameraFolder.setName(media.getParentFolderName());
+ cameraFolder.setImageNum(isAddSameImp(totalNum) ? cameraFolder.getImageNum() : cameraFolder.getImageNum() + 1);
+ cameraFolder.setFirstImagePath(media.getPath());
+ cameraFolder.setBucketId(media.getBucketId());
+ folderWindow.getFolderData().add(cameraFolder);
+ sortFolder(folderWindow.getFolderData());
+ }
+ }
+ folderWindow.bindFolder(folderWindow.getFolderData());
+ }
+ }
+
+ /**
+ * Manually add the photo to the list of photos and set it to select
+ *
+ * @param media
+ */
+ private void manualSaveFolder(LocalMedia media) {
+ try {
+ boolean isEmpty = folderWindow.isEmpty();
+ int totalNum = folderWindow.getFolder(0) != null ? folderWindow.getFolder(0).getImageNum() : 0;
+ LocalMediaFolder allFolder;
+ if (isEmpty) {
+ // All Folder
+ createNewFolder(folderWindow.getFolderData());
+ allFolder = folderWindow.getFolderData().size() > 0 ? folderWindow.getFolderData().get(0) : null;
+ if (allFolder == null) {
+ allFolder = new LocalMediaFolder();
+ folderWindow.getFolderData().add(0, allFolder);
+ }
+ } else {
+ // All Folder
+ allFolder = folderWindow.getFolderData().get(0);
+ }
+ allFolder.setFirstImagePath(media.getPath());
+ allFolder.setData(mAdapter.getData());
+ allFolder.setBucketId(-1);
+ allFolder.setImageNum(isAddSameImp(totalNum) ? allFolder.getImageNum() : allFolder.getImageNum() + 1);
+
+ // Camera
+ LocalMediaFolder cameraFolder = getImageFolder(media.getPath(), media.getRealPath(), folderWindow.getFolderData());
+ if (cameraFolder != null) {
+ cameraFolder.setImageNum(isAddSameImp(totalNum) ? cameraFolder.getImageNum() : cameraFolder.getImageNum() + 1);
+ if (!isAddSameImp(totalNum)) {
+ cameraFolder.getData().add(0, media);
+ }
+ cameraFolder.setBucketId(media.getBucketId());
+ cameraFolder.setFirstImagePath(config.cameraPath);
+ }
+ folderWindow.bindFolder(folderWindow.getFolderData());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Is the quantity consistent
+ *
+ * @return
+ */
+ private boolean isAddSameImp(int totalNum) {
+ if (totalNum == 0) {
+ return false;
+ }
+ return allFolderSize > 0 && allFolderSize < totalNum;
+ }
+
+ /**
+ * Update Folder
+ *
+ * @param imageFolders
+ */
+ private void updateMediaFolder(List imageFolders, LocalMedia media) {
+ File imageFile = new File(media.getRealPath());
+ File folderFile = imageFile.getParentFile();
+ if (folderFile == null) {
+ return;
+ }
+ int size = imageFolders.size();
+ for (int i = 0; i < size; i++) {
+ LocalMediaFolder folder = imageFolders.get(i);
+ String name = folder.getName();
+ if (TextUtils.isEmpty(name)) {
+ continue;
+ }
+ if (name.equals(folderFile.getName())) {
+ folder.setFirstImagePath(config.cameraPath);
+ folder.setImageNum(folder.getImageNum() + 1);
+ folder.setCheckedNum(1);
+ folder.getData().add(0, media);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ if (config != null && PictureSelectionConfig.listener != null) {
+ PictureSelectionConfig.listener.onCancel();
+ }
+ closeActivity();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (animation != null) {
+ animation.cancel();
+ animation = null;
+ }
+ if (mediaPlayer != null && mHandler != null) {
+ mHandler.removeCallbacks(mRunnable);
+ mediaPlayer.release();
+ mediaPlayer = null;
+ }
+ }
+
+ @Override
+ public void onItemClick(View view, int position) {
+ switch (position) {
+ case PhotoItemSelectedDialog.IMAGE_CAMERA:
+ if (PictureSelectionConfig.onCustomCameraInterfaceListener != null) {
+ PictureSelectionConfig.onCustomCameraInterfaceListener.onCameraClick(getContext(), config, PictureConfig.TYPE_IMAGE);
+ config.cameraMimeType = PictureMimeType.ofImage();
+ } else {
+ startOpenCamera();
+ }
+ break;
+ case PhotoItemSelectedDialog.VIDEO_CAMERA:
+ if (PictureSelectionConfig.onCustomCameraInterfaceListener != null) {
+ PictureSelectionConfig.onCustomCameraInterfaceListener.onCameraClick(getContext(), config, PictureConfig.TYPE_IMAGE);
+ config.cameraMimeType = PictureMimeType.ofVideo();
+ } else {
+ startOpenCameraVideo();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ switch (requestCode) {
+ case PictureConfig.APPLY_STORAGE_PERMISSIONS_CODE:
+ // Store Permissions
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ readLocalMedia();
+ } else {
+ showPermissionsDialog(false, getString(R.string.picture_jurisdiction));
+ }
+ break;
+ case PictureConfig.APPLY_CAMERA_PERMISSIONS_CODE:
+ // Camera Permissions
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ onTakePhoto();
+ } else {
+ showPermissionsDialog(true, getString(R.string.picture_camera));
+ }
+ break;
+ case PictureConfig.APPLY_CAMERA_STORAGE_PERMISSIONS_CODE:
+ // Using the camera, retrieve the storage permission
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ startCamera();
+ } else {
+ showPermissionsDialog(false, getString(R.string.picture_jurisdiction));
+ }
+ break;
+ case PictureConfig.APPLY_RECORD_AUDIO_PERMISSIONS_CODE:
+ // Recording Permissions
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ startCustomCamera();
+ } else {
+ showPermissionsDialog(false, getString(R.string.picture_audio));
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void showPermissionsDialog(boolean isCamera, String errorMsg) {
+ if (isFinishing()) {
+ return;
+ }
+ final PictureCustomDialog dialog =
+ new PictureCustomDialog(getContext(), R.layout.picture_wind_base_dialog);
+ dialog.setCancelable(false);
+ dialog.setCanceledOnTouchOutside(false);
+ Button btn_cancel = dialog.findViewById(R.id.btn_cancel);
+ Button btn_commit = dialog.findViewById(R.id.btn_commit);
+ btn_commit.setText(getString(R.string.picture_go_setting));
+ TextView tvTitle = dialog.findViewById(R.id.tvTitle);
+ TextView tv_content = dialog.findViewById(R.id.tv_content);
+ tvTitle.setText(getString(R.string.picture_prompt));
+ tv_content.setText(errorMsg);
+ btn_cancel.setOnClickListener(v -> {
+ if (!isFinishing()) {
+ dialog.dismiss();
+ }
+ if (!isCamera) {
+ closeActivity();
+ }
+ });
+ btn_commit.setOnClickListener(v -> {
+ if (!isFinishing()) {
+ dialog.dismiss();
+ }
+ PermissionChecker.launchAppDetailsSettings(getContext());
+ isEnterSetting = true;
+ });
+ dialog.show();
+ }
+
+
+ /**
+ * set Data Null
+ *
+ * @param msg
+ */
+ private void showDataNull(String msg, int topErrorResId) {
+ if (mTvEmpty.getVisibility() == View.GONE || mTvEmpty.getVisibility() == View.INVISIBLE) {
+ mTvEmpty.setCompoundDrawablesRelativeWithIntrinsicBounds(0, topErrorResId, 0, 0);
+ mTvEmpty.setText(msg);
+ mTvEmpty.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * hidden
+ */
+ private void hideDataNull() {
+ if (mTvEmpty.getVisibility() == View.VISIBLE) {
+ mTvEmpty.setVisibility(View.GONE);
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorCameraEmptyActivity.java b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorCameraEmptyActivity.java
new file mode 100644
index 0000000..1d20dad
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorCameraEmptyActivity.java
@@ -0,0 +1,395 @@
+package com.luck.picture.lib;
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.config.PictureSelectionConfig;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.immersive.ImmersiveManage;
+import com.luck.picture.lib.permissions.PermissionChecker;
+import com.luck.picture.lib.thread.PictureThreadUtils;
+import com.luck.picture.lib.tools.BitmapUtils;
+import com.luck.picture.lib.tools.MediaUtils;
+import com.luck.picture.lib.tools.PictureFileUtils;
+import com.luck.picture.lib.tools.SdkVersionUtils;
+import com.luck.picture.lib.tools.ToastUtils;
+import com.luck.picture.lib.tools.ValueOf;
+import com.yalantis.ucrop.UCrop;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2019-11-15 21:41
+ * @describe:PictureSelectorCameraEmptyActivity
+ */
+public class PictureSelectorCameraEmptyActivity extends PictureBaseActivity {
+
+ @Override
+ public void immersive() {
+ ImmersiveManage.immersiveAboveAPI23(this
+ , ContextCompat.getColor(this, R.color.picture_color_transparent)
+ , ContextCompat.getColor(this, R.color.picture_color_transparent)
+ , openWhiteStatusBar);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (config == null) {
+ closeActivity();
+ return;
+ }
+ if (!config.isUseCustomCamera) {
+ if (savedInstanceState == null) {
+ if (PermissionChecker
+ .checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) &&
+ PermissionChecker
+ .checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+
+ if (PictureSelectionConfig.onCustomCameraInterfaceListener != null) {
+ if (config.chooseMode == PictureConfig.TYPE_VIDEO) {
+ PictureSelectionConfig.onCustomCameraInterfaceListener.onCameraClick(getContext(), config, PictureConfig.TYPE_VIDEO);
+ } else {
+ PictureSelectionConfig.onCustomCameraInterfaceListener.onCameraClick(getContext(), config, PictureConfig.TYPE_IMAGE);
+ }
+ } else {
+ onTakePhoto();
+ }
+ } else {
+ PermissionChecker.requestPermissions(this, new String[]{
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE}, PictureConfig.APPLY_STORAGE_PERMISSIONS_CODE);
+ }
+ }
+ setTheme(R.style.Picture_Theme_Translucent);
+ }
+ }
+
+
+ @Override
+ public int getResourceId() {
+ return R.layout.picture_empty;
+ }
+
+
+ /**
+ * open camera
+ */
+ private void onTakePhoto() {
+ if (PermissionChecker
+ .checkSelfPermission(this, Manifest.permission.CAMERA)) {
+ boolean isPermissionChecker = true;
+ if (config != null && config.isUseCustomCamera) {
+ isPermissionChecker = PermissionChecker.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO);
+ }
+ if (isPermissionChecker) {
+ startCamera();
+ } else {
+ PermissionChecker
+ .requestPermissions(this,
+ new String[]{Manifest.permission.RECORD_AUDIO}, PictureConfig.APPLY_RECORD_AUDIO_PERMISSIONS_CODE);
+ }
+ } else {
+ PermissionChecker.requestPermissions(this,
+ new String[]{Manifest.permission.CAMERA}, PictureConfig.APPLY_CAMERA_PERMISSIONS_CODE);
+ }
+ }
+
+ /**
+ * Open the Camera by type
+ */
+ private void startCamera() {
+ switch (config.chooseMode) {
+ case PictureConfig.TYPE_ALL:
+ case PictureConfig.TYPE_IMAGE:
+ startOpenCamera();
+ break;
+ case PictureConfig.TYPE_VIDEO:
+ startOpenCameraVideo();
+ break;
+ case PictureConfig.TYPE_AUDIO:
+ startOpenCameraAudio();
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
+ switch (requestCode) {
+ case UCrop.REQUEST_CROP:
+ singleCropHandleResult(data);
+ break;
+ case PictureConfig.REQUEST_CAMERA:
+ dispatchHandleCamera(data);
+ break;
+ default:
+ break;
+ }
+ } else if (resultCode == RESULT_CANCELED) {
+ if (config != null && PictureSelectionConfig.listener != null) {
+ PictureSelectionConfig.listener.onCancel();
+ }
+ closeActivity();
+ } else if (resultCode == UCrop.RESULT_ERROR) {
+ if (data == null) {
+ return;
+ }
+ Throwable throwable = (Throwable) data.getSerializableExtra(UCrop.EXTRA_ERROR);
+ ToastUtils.s(getContext(), throwable.getMessage());
+ }
+ }
+
+ /**
+ * Single picture clipping callback
+ *
+ * @param data
+ */
+ protected void singleCropHandleResult(Intent data) {
+ if (data == null) {
+ return;
+ }
+ List medias = new ArrayList<>();
+ Uri resultUri = UCrop.getOutput(data);
+ if (resultUri == null) {
+ return;
+ }
+ String cutPath = resultUri.getPath();
+ boolean isCutEmpty = TextUtils.isEmpty(cutPath);
+ LocalMedia media = new LocalMedia(config.cameraPath, 0, false,
+ config.isCamera ? 1 : 0, 0, config.chooseMode);
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ int lastIndexOf = config.cameraPath.lastIndexOf("/") + 1;
+ media.setId(lastIndexOf > 0 ? ValueOf.toLong(config.cameraPath.substring(lastIndexOf)) : -1);
+ media.setAndroidQToPath(cutPath);
+ if (isCutEmpty) {
+ if (PictureMimeType.isContent(config.cameraPath)) {
+ String path = PictureFileUtils.getPath(this, Uri.parse(config.cameraPath));
+ media.setSize(!TextUtils.isEmpty(path) ? new File(path).length() : 0);
+ } else {
+ media.setSize(new File(config.cameraPath).length());
+ }
+ } else {
+ media.setSize(new File(cutPath).length());
+ }
+ } else {
+ // Taking a photo generates a temporary id
+ media.setId(System.currentTimeMillis());
+ media.setSize(new File(isCutEmpty ? media.getPath() : cutPath).length());
+ }
+ media.setCut(!isCutEmpty);
+ media.setCutPath(cutPath);
+ String mimeType = PictureMimeType.getImageMimeType(cutPath);
+ media.setMimeType(mimeType);
+ int width = 0, height = 0;
+ media.setOrientation(-1);
+ if (PictureMimeType.isContent(media.getPath())) {
+ if (PictureMimeType.isHasVideo(media.getMimeType())) {
+ int[] size = MediaUtils.getVideoSizeForUri(getContext(), Uri.parse(media.getPath()));
+ width = size[0];
+ height = size[1];
+ } else if (PictureMimeType.isHasImage(media.getMimeType())) {
+ int[] size = MediaUtils.getImageSizeForUri(getContext(), Uri.parse(media.getPath()));
+ width = size[0];
+ height = size[1];
+ }
+ } else {
+ if (PictureMimeType.isHasVideo(media.getMimeType())) {
+ int[] size = MediaUtils.getVideoSizeForUrl(media.getPath());
+ width = size[0];
+ height = size[1];
+ } else if (PictureMimeType.isHasImage(media.getMimeType())) {
+ int[] size = MediaUtils.getImageSizeForUrl(media.getPath());
+ width = size[0];
+ height = size[1];
+ }
+ }
+ media.setWidth(width);
+ media.setHeight(height);
+ // The width and height of the image are reversed if there is rotation information
+ MediaUtils.setOrientationAsynchronous(getContext(), media, config.isAndroidQChangeWH, config.isAndroidQChangeVideoWH,
+ item -> {
+ medias.add(item);
+ handlerResult(medias);
+ });
+ }
+
+ /**
+ * dispatchHandleCamera
+ *
+ * @param intent
+ */
+ protected void dispatchHandleCamera(Intent intent) {
+ boolean isAudio = config.chooseMode == PictureMimeType.ofAudio();
+ config.cameraPath = isAudio ? getAudioPath(intent) : config.cameraPath;
+ if (TextUtils.isEmpty(config.cameraPath)) {
+ return;
+ }
+ showPleaseDialog();
+ PictureThreadUtils.executeByIo(new PictureThreadUtils.SimpleTask() {
+
+ @Override
+ public LocalMedia doInBackground() {
+ LocalMedia media = new LocalMedia();
+ String mimeType = isAudio ? PictureMimeType.MIME_TYPE_AUDIO : "";
+ int[] newSize = new int[2];
+ long duration = 0;
+ if (!isAudio) {
+ if (PictureMimeType.isContent(config.cameraPath)) {
+ // content: Processing rules
+ String path = PictureFileUtils.getPath(getContext(), Uri.parse(config.cameraPath));
+ if (!TextUtils.isEmpty(path)) {
+ File cameraFile = new File(path);
+ mimeType = PictureMimeType.getMimeType(config.cameraMimeType);
+ media.setSize(cameraFile.length());
+ }
+ if (PictureMimeType.isHasImage(mimeType)) {
+ newSize = MediaUtils.getImageSizeForUrlToAndroidQ(getContext(), config.cameraPath);
+ } else if (PictureMimeType.isHasVideo(mimeType)) {
+ newSize = MediaUtils.getVideoSizeForUri(getContext(), Uri.parse(config.cameraPath));
+ duration = MediaUtils.extractDuration(getContext(), SdkVersionUtils.checkedAndroid_Q(), config.cameraPath);
+ }
+ int lastIndexOf = config.cameraPath.lastIndexOf("/") + 1;
+ media.setId(lastIndexOf > 0 ? ValueOf.toLong(config.cameraPath.substring(lastIndexOf)) : -1);
+ media.setRealPath(path);
+ // Custom photo has been in the application sandbox into the file
+ String mediaPath = intent != null ? intent.getStringExtra(PictureConfig.EXTRA_MEDIA_PATH) : null;
+ media.setAndroidQToPath(mediaPath);
+ } else {
+ File cameraFile = new File(config.cameraPath);
+ mimeType = PictureMimeType.getMimeType(config.cameraMimeType);
+ media.setSize(cameraFile.length());
+ if (PictureMimeType.isHasImage(mimeType)) {
+ int degree = PictureFileUtils.readPictureDegree(getContext(), config.cameraPath);
+ BitmapUtils.rotateImage(degree, config.cameraPath);
+ newSize = MediaUtils.getImageSizeForUrl(config.cameraPath);
+ } else if (PictureMimeType.isHasVideo(mimeType)) {
+ newSize = MediaUtils.getVideoSizeForUrl(config.cameraPath);
+ duration = MediaUtils.extractDuration(getContext(), SdkVersionUtils.checkedAndroid_Q(), config.cameraPath);
+ }
+ // Taking a photo generates a temporary id
+ media.setId(System.currentTimeMillis());
+ }
+ media.setPath(config.cameraPath);
+ media.setDuration(duration);
+ media.setMimeType(mimeType);
+ media.setWidth(newSize[0]);
+ media.setHeight(newSize[1]);
+ if (SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isHasVideo(media.getMimeType())) {
+ media.setParentFolderName(Environment.DIRECTORY_MOVIES);
+ } else {
+ media.setParentFolderName(PictureMimeType.CAMERA);
+ }
+ media.setChooseModel(config.chooseMode);
+ long bucketId = MediaUtils.getCameraFirstBucketId(getContext());
+ media.setBucketId(bucketId);
+ // The width and height of the image are reversed if there is rotation information
+ MediaUtils.setOrientationSynchronous(getContext(), media, config.isAndroidQChangeWH, config.isAndroidQChangeVideoWH);
+ }
+ return media;
+ }
+
+ @Override
+ public void onSuccess(LocalMedia result) {
+ // Refresh the system library
+ dismissDialog();
+ if (!SdkVersionUtils.checkedAndroid_Q()) {
+ if (config.isFallbackVersion3) {
+ new PictureMediaScannerConnection(getContext(), config.cameraPath);
+ } else {
+ sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(new File(config.cameraPath))));
+ }
+ }
+ dispatchCameraHandleResult(result);
+ // Solve some phone using Camera, DCIM will produce repetitive problems
+ if (!SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isHasImage(result.getMimeType())) {
+ int lastImageId = MediaUtils.getDCIMLastImageId(getContext());
+ if (lastImageId != -1) {
+ MediaUtils.removeMedia(getContext(), lastImageId);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * dispatchCameraHandleResult
+ *
+ * @param media
+ */
+ private void dispatchCameraHandleResult(LocalMedia media) {
+ boolean isHasImage = PictureMimeType.isHasImage(media.getMimeType());
+ if (config.enableCrop && isHasImage) {
+ config.originalPath = config.cameraPath;
+ startCrop(config.cameraPath, media.getMimeType());
+ } else if (config.isCompress && isHasImage && !config.isCheckOriginalImage) {
+ List result = new ArrayList<>();
+ result.add(media);
+ compressImage(result);
+ } else {
+ List result = new ArrayList<>();
+ result.add(media);
+ onResult(result);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ switch (requestCode) {
+ case PictureConfig.APPLY_STORAGE_PERMISSIONS_CODE:
+ // Store Permissions
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ PermissionChecker.requestPermissions(this,
+ new String[]{Manifest.permission.CAMERA}, PictureConfig.APPLY_CAMERA_PERMISSIONS_CODE);
+ } else {
+ ToastUtils.s(getContext(), getString(R.string.picture_jurisdiction));
+ closeActivity();
+ }
+ break;
+ case PictureConfig.APPLY_CAMERA_PERMISSIONS_CODE:
+ // Camera Permissions
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ onTakePhoto();
+ } else {
+ closeActivity();
+ ToastUtils.s(getContext(), getString(R.string.picture_camera));
+ }
+ break;
+ case PictureConfig.APPLY_RECORD_AUDIO_PERMISSIONS_CODE:
+ // Recording Permissions
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ onTakePhoto();
+ } else {
+ closeActivity();
+ ToastUtils.s(getContext(), getString(R.string.picture_audio));
+ }
+ break;
+ }
+ }
+
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ closeActivity();
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorExternalUtils.java b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorExternalUtils.java
new file mode 100644
index 0000000..1d3e71b
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorExternalUtils.java
@@ -0,0 +1,46 @@
+package com.luck.picture.lib;
+
+import android.content.Context;
+import android.media.ExifInterface;
+import android.net.Uri;
+
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.tools.PictureFileUtils;
+import com.luck.picture.lib.tools.SdkVersionUtils;
+
+import java.io.InputStream;
+
+/**
+ * @author:luck
+ * @date:2020-04-12 13:13
+ * @describe:PictureSelector对外提供的一些方法
+ */
+public class PictureSelectorExternalUtils {
+ /**
+ * 获取ExifInterface
+ *
+ * @param context
+ * @param url
+ * @return
+ */
+ public static ExifInterface getExifInterface(Context context, String url) {
+ ExifInterface exifInterface = null;
+ InputStream inputStream = null;
+ try {
+ if (SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isContent(url)) {
+ inputStream = context.getContentResolver().openInputStream(Uri.parse(url));
+ if (inputStream != null) {
+ exifInterface = new ExifInterface(inputStream);
+ }
+ } else {
+ exifInterface = new ExifInterface(url);
+ }
+ return exifInterface;
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ PictureFileUtils.close(inputStream);
+ }
+ return null;
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorPreviewWeChatStyleActivity.java b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorPreviewWeChatStyleActivity.java
new file mode 100644
index 0000000..0a76be6
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorPreviewWeChatStyleActivity.java
@@ -0,0 +1,387 @@
+package com.luck.picture.lib;
+
+import android.text.TextUtils;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.luck.picture.lib.adapter.PictureWeChatPreviewGalleryAdapter;
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.decoration.GridSpacingItemDecoration;
+import com.luck.picture.lib.decoration.WrapContentLinearLayoutManager;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.tools.ScreenUtils;
+
+
+/**
+ * @author:luck
+ * @date:2019-11-30 17:16
+ * @describe:PictureSelector WeChatStyle
+ */
+public class PictureSelectorPreviewWeChatStyleActivity extends PicturePreviewActivity {
+ /**
+ * alpha duration
+ */
+ private final static int ALPHA_DURATION = 300;
+ private TextView mPictureSendView;
+ private RecyclerView mRvGallery;
+ private TextView tvSelected;
+ private View bottomLine;
+ private PictureWeChatPreviewGalleryAdapter mGalleryAdapter;
+
+ @Override
+ public int getResourceId() {
+ return R.layout.picture_wechat_style_preview;
+ }
+
+ private void goneParent() {
+ if (tvMediaNum.getVisibility() == View.VISIBLE) {
+ tvMediaNum.setVisibility(View.GONE);
+ }
+ if (mTvPictureOk.getVisibility() == View.VISIBLE) {
+ mTvPictureOk.setVisibility(View.GONE);
+ }
+ if (!TextUtils.isEmpty(check.getText())) {
+ check.setText("");
+ }
+ }
+
+ @Override
+ protected void initWidgets() {
+ super.initWidgets();
+ goneParent();
+ mRvGallery = findViewById(R.id.rv_gallery);
+ bottomLine = findViewById(R.id.bottomLine);
+ tvSelected = findViewById(R.id.tv_selected);
+ mPictureSendView = findViewById(R.id.picture_send);
+ mPictureSendView.setOnClickListener(this);
+ mPictureSendView.setText(getString(R.string.picture_send));
+ mCbOriginal.setTextSize(16);
+ mGalleryAdapter = new PictureWeChatPreviewGalleryAdapter(config);
+ WrapContentLinearLayoutManager layoutManager = new WrapContentLinearLayoutManager(getContext());
+ layoutManager.setOrientation(WrapContentLinearLayoutManager.HORIZONTAL);
+ mRvGallery.setLayoutManager(layoutManager);
+ mRvGallery.addItemDecoration(new GridSpacingItemDecoration(Integer.MAX_VALUE,
+ ScreenUtils.dip2px(this, 8), false));
+ mRvGallery.setAdapter(mGalleryAdapter);
+ mGalleryAdapter.setItemClickListener((position, media, v) -> {
+ if (viewPager != null && media != null) {
+ if (isEqualsDirectory(media.getParentFolderName(), currentDirectory)) {
+ int newPosition = isBottomPreview ? position : isShowCamera ? media.position - 1 : media.position;
+ viewPager.setCurrentItem(newPosition);
+ } else {
+ // TODO The picture is not in the album directory, click invalid
+ }
+ }
+ });
+ if (isBottomPreview) {
+ if (selectData != null && selectData.size() > position) {
+ int size = selectData.size();
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = selectData.get(i);
+ media.setChecked(false);
+ }
+ LocalMedia media = selectData.get(position);
+ media.setChecked(true);
+ }
+ } else {
+ int size = selectData != null ? selectData.size() : 0;
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = selectData.get(i);
+ if (isEqualsDirectory(media.getParentFolderName(), currentDirectory)) {
+ media.setChecked(isShowCamera ? media.position - 1 == position : media.position == position);
+ }
+ }
+ }
+ }
+
+ /**
+ * Is it the same directory
+ *
+ * @param parentFolderName
+ * @param currentDirectory
+ * @return
+ */
+ private boolean isEqualsDirectory(String parentFolderName, String currentDirectory) {
+ return isBottomPreview
+ || TextUtils.isEmpty(parentFolderName)
+ || TextUtils.isEmpty(currentDirectory)
+ || currentDirectory.equals(getString(R.string.picture_camera_roll))
+ || parentFolderName.equals(currentDirectory);
+ }
+
+ @Override
+ public void initPictureSelectorStyle() {
+ super.initPictureSelectorStyle();
+ if (config.style != null) {
+ if (config.style.pictureCompleteBackgroundStyle != 0) {
+ mPictureSendView.setBackgroundResource(config.style.pictureCompleteBackgroundStyle);
+ } else {
+ mPictureSendView.setBackgroundResource(R.drawable.picture_send_button_bg);
+ }
+ if (config.style.pictureRightTextSize != 0) {
+ mPictureSendView.setTextSize(config.style.pictureRightTextSize);
+ }
+ if (!TextUtils.isEmpty(config.style.pictureWeChatPreviewSelectedText)) {
+ tvSelected.setText(config.style.pictureWeChatPreviewSelectedText);
+ }
+ if (config.style.pictureWeChatPreviewSelectedTextSize != 0) {
+ tvSelected.setTextSize(config.style.pictureWeChatPreviewSelectedTextSize);
+ }
+ if (config.style.picturePreviewBottomBgColor != 0) {
+ selectBarLayout.setBackgroundColor(config.style.picturePreviewBottomBgColor);
+ } else {
+ selectBarLayout.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.picture_color_half_grey));
+ }
+ if (config.style.pictureCompleteTextColor != 0) {
+ mPictureSendView.setTextColor(config.style.pictureCompleteTextColor);
+ } else {
+ if (config.style.pictureCancelTextColor != 0) {
+ mPictureSendView.setTextColor(config.style.pictureCancelTextColor);
+ } else {
+ mPictureSendView.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_white));
+ }
+ }
+ if (config.style.pictureOriginalFontColor == 0) {
+ mCbOriginal.setTextColor(ContextCompat
+ .getColor(this, R.color.picture_color_white));
+ }
+ if (config.style.pictureWeChatChooseStyle != 0) {
+ check.setBackgroundResource(config.style.pictureWeChatChooseStyle);
+ } else {
+ check.setBackgroundResource(R.drawable.picture_wechat_select_cb);
+ }
+ if (config.isOriginalControl) {
+ if (config.style.pictureOriginalControlStyle == 0) {
+ mCbOriginal.setButtonDrawable(ContextCompat
+ .getDrawable(this, R.drawable.picture_original_wechat_checkbox));
+ }
+ }
+ if (config.style.pictureWeChatLeftBackStyle != 0) {
+ pictureLeftBack.setImageResource(config.style.pictureWeChatLeftBackStyle);
+ } else {
+ pictureLeftBack.setImageResource(R.drawable.picture_icon_back);
+ }
+ if (!TextUtils.isEmpty(config.style.pictureUnCompleteText)) {
+ mPictureSendView.setText(config.style.pictureUnCompleteText);
+ }
+ } else {
+ mPictureSendView.setBackgroundResource(R.drawable.picture_send_button_bg);
+ mPictureSendView.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_white));
+ selectBarLayout.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.picture_color_half_grey));
+ check.setBackgroundResource(R.drawable.picture_wechat_select_cb);
+ pictureLeftBack.setImageResource(R.drawable.picture_icon_back);
+ mCbOriginal.setTextColor(ContextCompat
+ .getColor(this, R.color.picture_color_white));
+ if (config.isOriginalControl) {
+ mCbOriginal.setButtonDrawable(ContextCompat
+ .getDrawable(this, R.drawable.picture_original_wechat_checkbox));
+ }
+ }
+
+ onSelectNumChange(false);
+ }
+
+ @Override
+ public void onClick(View v) {
+ super.onClick(v);
+ int id = v.getId();
+ if (id == R.id.picture_send) {
+ boolean enable = selectData.size() != 0;
+ if (enable) {
+ mTvPictureOk.performClick();
+ } else {
+ btnCheck.performClick();
+ boolean isNewEnableStatus = selectData.size() != 0;
+ if (isNewEnableStatus) {
+ mTvPictureOk.performClick();
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onUpdateSelectedChange(LocalMedia media) {
+ onChangeMediaStatus(media);
+ }
+
+ @Override
+ protected void onSelectedChange(boolean isAddRemove, LocalMedia media) {
+ if (isAddRemove) {
+ media.setChecked(true);
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ mGalleryAdapter.addSingleMediaToData(media);
+ }
+ } else {
+ media.setChecked(false);
+ mGalleryAdapter.removeMediaToData(media);
+ if (isBottomPreview) {
+ if (selectData != null && selectData.size() > position) {
+ selectData.get(position).setChecked(true);
+ }
+ if (mGalleryAdapter.isDataEmpty()) {
+ onActivityBackPressed();
+ } else {
+ int currentItem = viewPager.getCurrentItem();
+ adapter.remove(currentItem);
+ adapter.removeCacheView(currentItem);
+ position = currentItem;
+ tvTitle.setText(getString(R.string.picture_preview_image_num,
+ position + 1, adapter.getSize()));
+ check.setSelected(true);
+ adapter.notifyDataSetChanged();
+ }
+ }
+ }
+ int itemCount = mGalleryAdapter.getItemCount();
+ if (itemCount > 5) {
+ mRvGallery.smoothScrollToPosition(itemCount - 1);
+ }
+ }
+
+ @Override
+ protected void onPageSelectedChange(LocalMedia media) {
+ super.onPageSelectedChange(media);
+ goneParent();
+ if (!config.previewEggs) {
+ onChangeMediaStatus(media);
+ }
+ }
+
+ /**
+ * onChangeMediaStatus
+ *
+ * @param media
+ */
+ private void onChangeMediaStatus(LocalMedia media) {
+ if (mGalleryAdapter != null) {
+ int itemCount = mGalleryAdapter.getItemCount();
+ if (itemCount > 0) {
+ boolean isChangeData = false;
+ for (int i = 0; i < itemCount; i++) {
+ LocalMedia item = mGalleryAdapter.getItem(i);
+ if (item == null || TextUtils.isEmpty(item.getPath())) {
+ continue;
+ }
+ boolean isOldChecked = item.isChecked();
+ boolean isNewChecked = item.getPath().equals(media.getPath()) || item.getId() == media.getId();
+ if (!isChangeData) {
+ isChangeData = (isOldChecked && !isNewChecked) || (!isOldChecked && isNewChecked);
+ }
+ item.setChecked(isNewChecked);
+ }
+ if (isChangeData) {
+ mGalleryAdapter.notifyDataSetChanged();
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onSelectNumChange(boolean isRefresh) {
+ if (mPictureSendView == null) {
+ return;
+ }
+ goneParent();
+ boolean enable = selectData.size() != 0;
+ if (enable) {
+ initCompleteText(selectData.size());
+ if (mRvGallery.getVisibility() == View.GONE) {
+ mRvGallery.animate().alpha(1).setDuration(ALPHA_DURATION).setInterpolator(new AccelerateInterpolator());
+ mRvGallery.setVisibility(View.VISIBLE);
+ bottomLine.animate().alpha(1).setDuration(ALPHA_DURATION).setInterpolator(new AccelerateInterpolator());
+ bottomLine.setVisibility(View.VISIBLE);
+ // 重置一片内存区域 不然在其他地方添加也影响这里的数量
+ mGalleryAdapter.setNewData(selectData);
+ }
+ if (config.style != null) {
+ if (config.style.pictureCompleteTextColor != 0) {
+ mPictureSendView.setTextColor(config.style.pictureCompleteTextColor);
+ }
+ if (config.style.pictureCompleteBackgroundStyle != 0) {
+ mPictureSendView.setBackgroundResource(config.style.pictureCompleteBackgroundStyle);
+ }
+ } else {
+ mPictureSendView.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_white));
+ mPictureSendView.setBackgroundResource(R.drawable.picture_send_button_bg);
+ }
+ } else {
+ if (config.style != null && !TextUtils.isEmpty(config.style.pictureUnCompleteText)) {
+ mPictureSendView.setText(config.style.pictureUnCompleteText);
+ } else {
+ mPictureSendView.setText(getString(R.string.picture_send));
+ }
+ mRvGallery.animate().alpha(0).setDuration(ALPHA_DURATION).setInterpolator(new AccelerateInterpolator());
+ mRvGallery.setVisibility(View.GONE);
+ bottomLine.animate().alpha(0).setDuration(ALPHA_DURATION).setInterpolator(new AccelerateInterpolator());
+ bottomLine.setVisibility(View.GONE);
+ }
+ }
+
+
+ /**
+ * initCompleteText
+ */
+ @Override
+ protected void initCompleteText(int startCount) {
+ boolean isNotEmptyStyle = config.style != null;
+ if (config.isWithVideoImage) {
+ // 混选模式
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ if (startCount <= 0) {
+ mPictureSendView.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureUnCompleteText)
+ ? config.style.pictureUnCompleteText : getString(R.string.picture_send));
+ } else {
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mPictureSendView.setText(String.format(config.style.pictureCompleteText, selectData.size(), 1));
+ } else {
+ mPictureSendView.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureCompleteText)
+ ? config.style.pictureCompleteText : getString(R.string.picture_send));
+ }
+ }
+ } else {
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mPictureSendView.setText(String.format(config.style.pictureCompleteText,
+ selectData.size(), config.maxSelectNum));
+ } else {
+ mPictureSendView.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureUnCompleteText)
+ ? config.style.pictureUnCompleteText : getString(R.string.picture_send_num, selectData.size(),
+ config.maxSelectNum));
+ }
+ }
+ } else {
+ String mimeType = selectData.get(0).getMimeType();
+ int maxSize = PictureMimeType.isHasVideo(mimeType) && config.maxVideoSelectNum > 0 ? config.maxVideoSelectNum : config.maxSelectNum;
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ if (startCount <= 0) {
+ mPictureSendView.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureUnCompleteText)
+ ? config.style.pictureUnCompleteText : getString(R.string.picture_send));
+ } else {
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mPictureSendView.setText(String.format(config.style.pictureCompleteText, selectData.size(),
+ 1));
+ } else {
+ mPictureSendView.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureCompleteText)
+ ? config.style.pictureCompleteText : getString(R.string.picture_send));
+ }
+ }
+ } else {
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mPictureSendView.setText(String.format(config.style.pictureCompleteText, selectData.size(), maxSize));
+ } else {
+ mPictureSendView.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureUnCompleteText)
+ ? config.style.pictureUnCompleteText
+ : getString(R.string.picture_send_num, selectData.size(), maxSize));
+ }
+ }
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorWeChatStyleActivity.java b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorWeChatStyleActivity.java
new file mode 100644
index 0000000..2ab1e2a
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PictureSelectorWeChatStyleActivity.java
@@ -0,0 +1,286 @@
+package com.luck.picture.lib;
+
+
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.tools.AttrsUtils;
+
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2019-11-30 13:27
+ * @describe:PictureSelector WeChatStyle
+ */
+public class PictureSelectorWeChatStyleActivity extends PictureSelectorActivity {
+ private TextView mPictureSendView;
+ private RelativeLayout rlAlbum;
+
+
+ @Override
+ public int getResourceId() {
+ return R.layout.picture_wechat_style_selector;
+ }
+
+ @Override
+ protected void initWidgets() {
+ super.initWidgets();
+ rlAlbum = findViewById(R.id.rlAlbum);
+ mPictureSendView = findViewById(R.id.picture_send);
+ mPictureSendView.setOnClickListener(this);
+ mPictureSendView.setText(getString(R.string.picture_send));
+ mTvPicturePreview.setTextSize(16);
+ mCbOriginal.setTextSize(16);
+ boolean isChooseMode = config.selectionMode ==
+ PictureConfig.SINGLE && config.isSingleDirectReturn;
+ mPictureSendView.setVisibility(isChooseMode ? View.GONE : View.VISIBLE);
+ if (rlAlbum.getLayoutParams() != null
+ && rlAlbum.getLayoutParams() instanceof RelativeLayout.LayoutParams) {
+ RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) rlAlbum.getLayoutParams();
+ if (isChooseMode) {
+ lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ } else {
+ lp.addRule(RelativeLayout.RIGHT_OF, R.id.pictureLeftBack);
+ }
+ }
+ }
+
+ @Override
+ public void initPictureSelectorStyle() {
+ if (config.style != null) {
+ if (config.style.pictureUnCompleteBackgroundStyle != 0) {
+ mPictureSendView.setBackgroundResource(config.style.pictureUnCompleteBackgroundStyle);
+ } else {
+ mPictureSendView.setBackgroundResource(R.drawable.picture_send_button_default_bg);
+ }
+ if (config.style.pictureBottomBgColor != 0) {
+ mBottomLayout.setBackgroundColor(config.style.pictureBottomBgColor);
+ } else {
+ mBottomLayout.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.picture_color_grey));
+ }
+ if (config.style.pictureUnCompleteTextColor != 0) {
+ mPictureSendView.setTextColor(config.style.pictureUnCompleteTextColor);
+ } else {
+ if (config.style.pictureCancelTextColor != 0) {
+ mPictureSendView.setTextColor(config.style.pictureCancelTextColor);
+ } else {
+ mPictureSendView.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_53575e));
+ }
+ }
+ if (config.style.pictureRightTextSize != 0) {
+ mPictureSendView.setTextSize(config.style.pictureRightTextSize);
+ }
+ if (config.style.pictureOriginalFontColor == 0) {
+ mCbOriginal.setTextColor(ContextCompat
+ .getColor(this, R.color.picture_color_white));
+ }
+ if (config.isOriginalControl) {
+ if (config.style.pictureOriginalControlStyle == 0) {
+ mCbOriginal.setButtonDrawable(ContextCompat
+ .getDrawable(this, R.drawable.picture_original_wechat_checkbox));
+ }
+ }
+ if (config.style.pictureContainerBackgroundColor != 0) {
+ container.setBackgroundColor(config.style.pictureContainerBackgroundColor);
+ }
+
+ if (config.style.pictureWeChatTitleBackgroundStyle != 0) {
+ rlAlbum.setBackgroundResource(config.style.pictureWeChatTitleBackgroundStyle);
+ } else {
+ rlAlbum.setBackgroundResource(R.drawable.picture_album_bg);
+ }
+
+ if (!TextUtils.isEmpty(config.style.pictureUnCompleteText)) {
+ mPictureSendView.setText(config.style.pictureUnCompleteText);
+ }
+
+ } else {
+ mPictureSendView.setBackgroundResource(R.drawable.picture_send_button_default_bg);
+ rlAlbum.setBackgroundResource(R.drawable.picture_album_bg);
+ mPictureSendView.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_53575e));
+ int pictureBottomBgColor = AttrsUtils.getTypeValueColor(getContext(), R.attr.picture_bottom_bg);
+ mBottomLayout.setBackgroundColor(pictureBottomBgColor != 0
+ ? pictureBottomBgColor : ContextCompat.getColor(getContext(), R.color.picture_color_grey));
+
+ mCbOriginal.setTextColor(ContextCompat
+ .getColor(this, R.color.picture_color_white));
+ Drawable drawable = ContextCompat.getDrawable(this, R.drawable.picture_icon_wechat_down);
+ mIvArrow.setImageDrawable(drawable);
+ if (config.isOriginalControl) {
+ mCbOriginal.setButtonDrawable(ContextCompat
+ .getDrawable(this, R.drawable.picture_original_wechat_checkbox));
+ }
+ }
+ super.initPictureSelectorStyle();
+ goneParentView();
+ }
+
+ /**
+ * Hide views that are not needed by the parent container
+ */
+ private void goneParentView() {
+ mTvPictureRight.setVisibility(View.GONE);
+ mTvPictureImgNum.setVisibility(View.GONE);
+ mTvPictureOk.setVisibility(View.GONE);
+ }
+
+ @Override
+ protected void changeImageNumber(List selectData) {
+ if (mPictureSendView == null) {
+ return;
+ }
+ int size = selectData.size();
+ boolean enable = size != 0;
+ if (enable) {
+ mPictureSendView.setEnabled(true);
+ mPictureSendView.setSelected(true);
+ mTvPicturePreview.setEnabled(true);
+ mTvPicturePreview.setSelected(true);
+ initCompleteText(selectData);
+ if (config.style != null) {
+ if (config.style.pictureCompleteBackgroundStyle != 0) {
+ mPictureSendView.setBackgroundResource(config.style.pictureCompleteBackgroundStyle);
+ } else {
+ mPictureSendView.setBackgroundResource(R.drawable.picture_send_button_bg);
+ }
+ if (config.style.pictureCompleteTextColor != 0) {
+ mPictureSendView.setTextColor(config.style.pictureCompleteTextColor);
+ } else {
+ mPictureSendView.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_white));
+ }
+ if (config.style.picturePreviewTextColor != 0) {
+ mTvPicturePreview.setTextColor(config.style.picturePreviewTextColor);
+ } else {
+ mTvPicturePreview.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_white));
+ }
+ if (!TextUtils.isEmpty(config.style.picturePreviewText)) {
+ mTvPicturePreview.setText(config.style.picturePreviewText);
+ } else {
+ mTvPicturePreview.setText(getString(R.string.picture_preview_num, size));
+ }
+ } else {
+ mPictureSendView.setBackgroundResource(R.drawable.picture_send_button_bg);
+ mPictureSendView.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_white));
+ mTvPicturePreview.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_white));
+ mTvPicturePreview.setText(getString(R.string.picture_preview_num, size));
+ }
+ } else {
+ mPictureSendView.setEnabled(false);
+ mPictureSendView.setSelected(false);
+ mTvPicturePreview.setEnabled(false);
+ mTvPicturePreview.setSelected(false);
+ if (config.style != null) {
+ if (config.style.pictureUnCompleteBackgroundStyle != 0) {
+ mPictureSendView.setBackgroundResource(config.style.pictureUnCompleteBackgroundStyle);
+ } else {
+ mPictureSendView.setBackgroundResource(R.drawable.picture_send_button_default_bg);
+ }
+ if (config.style.pictureUnCompleteTextColor != 0) {
+ mPictureSendView.setTextColor(config.style.pictureUnCompleteTextColor);
+ } else {
+ mPictureSendView.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_53575e));
+ }
+ if (config.style.pictureUnPreviewTextColor != 0) {
+ mTvPicturePreview.setTextColor(config.style.pictureUnPreviewTextColor);
+ } else {
+ mTvPicturePreview.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_9b));
+ }
+ if (!TextUtils.isEmpty(config.style.pictureUnCompleteText)) {
+ mPictureSendView.setText(config.style.pictureUnCompleteText);
+ } else {
+ mPictureSendView.setText(getString(R.string.picture_send));
+ }
+ if (!TextUtils.isEmpty(config.style.pictureUnPreviewText)) {
+ mTvPicturePreview.setText(config.style.pictureUnPreviewText);
+ } else {
+ mTvPicturePreview.setText(getString(R.string.picture_preview));
+ }
+ } else {
+ mPictureSendView.setBackgroundResource(R.drawable.picture_send_button_default_bg);
+ mPictureSendView.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_53575e));
+ mTvPicturePreview.setTextColor(ContextCompat.getColor(getContext(), R.color.picture_color_9b));
+ mTvPicturePreview.setText(getString(R.string.picture_preview));
+ mPictureSendView.setText(getString(R.string.picture_send));
+ }
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ super.onClick(v);
+ int id = v.getId();
+ if (id == R.id.picture_send) {
+ if (folderWindow != null
+ && folderWindow.isShowing()) {
+ folderWindow.dismiss();
+ } else {
+ mTvPictureOk.performClick();
+ }
+ }
+ }
+
+ @Override
+ protected void onChangeData(List list) {
+ super.onChangeData(list);
+ initCompleteText(list);
+ }
+
+ @Override
+ protected void initCompleteText(List list) {
+ int size = list.size();
+ boolean isNotEmptyStyle = config.style != null;
+ if (config.isWithVideoImage) {
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ if (size <= 0) {
+ mPictureSendView.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureUnCompleteText)
+ ? config.style.pictureUnCompleteText : getString(R.string.picture_send));
+ } else {
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mPictureSendView.setText(String.format(config.style.pictureCompleteText, size, 1));
+ } else {
+ mPictureSendView.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureCompleteText)
+ ? config.style.pictureCompleteText : getString(R.string.picture_send));
+ }
+ }
+ } else {
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mPictureSendView.setText(String.format(config.style.pictureCompleteText, size, config.maxSelectNum));
+ } else {
+ mPictureSendView.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureUnCompleteText)
+ ? config.style.pictureUnCompleteText : getString(R.string.picture_send_num, size, config.maxSelectNum));
+ }
+ }
+ } else {
+ String mimeType = list.get(0).getMimeType();
+ int maxSize = PictureMimeType.isHasVideo(mimeType) && config.maxVideoSelectNum > 0 ? config.maxVideoSelectNum : config.maxSelectNum;
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mPictureSendView.setText(String.format(config.style.pictureCompleteText, size, 1));
+ } else {
+ mPictureSendView.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureCompleteText)
+ ? config.style.pictureCompleteText : getString(R.string.picture_send));
+ }
+ } else {
+ boolean isCompleteReplaceNum = isNotEmptyStyle && config.style.isCompleteReplaceNum;
+ if (isCompleteReplaceNum && !TextUtils.isEmpty(config.style.pictureCompleteText)) {
+ mPictureSendView.setText(String.format(config.style.pictureCompleteText, size, maxSize));
+ } else {
+ mPictureSendView.setText(isNotEmptyStyle && !TextUtils.isEmpty(config.style.pictureUnCompleteText)
+ ? config.style.pictureUnCompleteText : getString(R.string.picture_send_num, size, maxSize));
+ }
+ }
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/PictureVideoPlayActivity.java b/picture_library/src/main/java/com/luck/picture/lib/PictureVideoPlayActivity.java
new file mode 100644
index 0000000..01a752d
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/PictureVideoPlayActivity.java
@@ -0,0 +1,218 @@
+package com.luck.picture.lib;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.graphics.Color;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.MediaController;
+import android.widget.TextView;
+import android.widget.VideoView;
+
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.tools.SdkVersionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @data:2017/8/28 下午11:00
+ * @描述: 视频播放类
+ */
+public class PictureVideoPlayActivity extends PictureBaseActivity implements
+ MediaPlayer.OnErrorListener, MediaPlayer.OnPreparedListener,
+ MediaPlayer.OnCompletionListener, View.OnClickListener {
+ private String videoPath;
+ private ImageButton ibLeftBack;
+ private MediaController mMediaController;
+ private VideoView mVideoView;
+ private TextView tvConfirm;
+ private ImageView iv_play;
+ private int mPositionWhenPaused = -1;
+
+ @Override
+ public boolean isImmersive() {
+ return false;
+ }
+
+ @Override
+ public boolean isRequestedOrientation() {
+ return false;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public int getResourceId() {
+ return R.layout.picture_activity_video_play;
+ }
+
+ @Override
+ protected void initPictureSelectorStyle() {
+ if (config.style != null) {
+ if (config.style.pictureLeftBackIcon != 0) {
+ ibLeftBack.setImageResource(config.style.pictureLeftBackIcon);
+ }
+ }
+ }
+
+ @Override
+ protected void initWidgets() {
+ super.initWidgets();
+ videoPath = getIntent().getStringExtra(PictureConfig.EXTRA_VIDEO_PATH);
+ boolean isExternalPreview = getIntent().getBooleanExtra
+ (PictureConfig.EXTRA_PREVIEW_VIDEO, false);
+ if (TextUtils.isEmpty(videoPath)) {
+ LocalMedia media = getIntent().getParcelableExtra(PictureConfig.EXTRA_MEDIA_KEY);
+ if (media == null || TextUtils.isEmpty(media.getPath())) {
+ finish();
+ return;
+ }
+ videoPath = media.getPath();
+ }
+ if (TextUtils.isEmpty(videoPath)) {
+ closeActivity();
+ return;
+ }
+ ibLeftBack = findViewById(R.id.pictureLeftBack);
+ mVideoView = findViewById(R.id.video_view);
+ tvConfirm = findViewById(R.id.tv_confirm);
+ mVideoView.setBackgroundColor(Color.BLACK);
+ iv_play = findViewById(R.id.iv_play);
+ mMediaController = new MediaController(this);
+ mVideoView.setOnCompletionListener(this);
+ mVideoView.setOnPreparedListener(this);
+ mVideoView.setMediaController(mMediaController);
+ ibLeftBack.setOnClickListener(this);
+ iv_play.setOnClickListener(this);
+ tvConfirm.setOnClickListener(this);
+
+ tvConfirm.setVisibility(config.selectionMode
+ == PictureConfig.SINGLE
+ && config.enPreviewVideo && !isExternalPreview ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void onStart() {
+ // Play Video
+ if (SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isContent(videoPath)) {
+ mVideoView.setVideoURI(Uri.parse(videoPath));
+ } else {
+ mVideoView.setVideoPath(videoPath);
+ }
+ mVideoView.start();
+ super.onStart();
+ }
+
+ @Override
+ public void onPause() {
+ // Stop video when the activity is pause.
+ mPositionWhenPaused = mVideoView.getCurrentPosition();
+ mVideoView.stopPlayback();
+
+ super.onPause();
+ }
+
+ @Override
+ protected void onDestroy() {
+ mMediaController = null;
+ mVideoView = null;
+ iv_play = null;
+ super.onDestroy();
+ }
+
+ @Override
+ public void onResume() {
+ // Resume video player
+ if (mPositionWhenPaused >= 0) {
+ mVideoView.seekTo(mPositionWhenPaused);
+ mPositionWhenPaused = -1;
+ }
+
+ super.onResume();
+ }
+
+ @Override
+ public boolean onError(MediaPlayer player, int arg1, int arg2) {
+ return false;
+ }
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ if (null != iv_play) {
+ iv_play.setVisibility(View.VISIBLE);
+ }
+
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ if (id == R.id.pictureLeftBack) {
+ onBackPressed();
+ } else if (id == R.id.iv_play) {
+ mVideoView.start();
+ iv_play.setVisibility(View.INVISIBLE);
+ } else if (id == R.id.tv_confirm) {
+ List result = new ArrayList<>();
+ result.add(getIntent().getParcelableExtra(PictureConfig.EXTRA_MEDIA_KEY));
+ setResult(RESULT_OK, new Intent()
+ .putParcelableArrayListExtra(PictureConfig.EXTRA_SELECT_LIST,
+ (ArrayList extends Parcelable>) result));
+ onBackPressed();
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (config.windowAnimationStyle != null
+ && config.windowAnimationStyle.activityPreviewExitAnimation != 0) {
+ finish();
+ overridePendingTransition(0, config.windowAnimationStyle != null
+ && config.windowAnimationStyle.activityPreviewExitAnimation != 0 ?
+ config.windowAnimationStyle.activityPreviewExitAnimation : R.anim.picture_anim_exit);
+ } else {
+ closeActivity();
+ }
+ }
+
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(new ContextWrapper(newBase) {
+ @Override
+ public Object getSystemService(String name) {
+ if (Context.AUDIO_SERVICE.equals(name)) {
+ return getApplicationContext().getSystemService(name);
+ }
+ return super.getSystemService(name);
+ }
+ });
+ }
+
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ mp.setOnInfoListener((mp1, what, extra) -> {
+ if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
+ // video started
+ mVideoView.setBackgroundColor(Color.TRANSPARENT);
+ return true;
+ }
+ return false;
+ });
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureAlbumDirectoryAdapter.java b/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureAlbumDirectoryAdapter.java
new file mode 100644
index 0000000..c9b16aa
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureAlbumDirectoryAdapter.java
@@ -0,0 +1,122 @@
+package com.luck.picture.lib.adapter;
+
+import android.content.Context;
+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.luck.picture.lib.R;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.config.PictureSelectionConfig;
+import com.luck.picture.lib.entity.LocalMediaFolder;
+import com.luck.picture.lib.listener.OnAlbumItemClickListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2016-12-11 17:02
+ * @describe:PictureAlbumDirectoryAdapter
+ */
+public class PictureAlbumDirectoryAdapter extends RecyclerView.Adapter {
+ private List folders = new ArrayList<>();
+ private int chooseMode;
+ private PictureSelectionConfig config;
+
+ public PictureAlbumDirectoryAdapter(PictureSelectionConfig config) {
+ super();
+ this.config = config;
+ this.chooseMode = config.chooseMode;
+ }
+
+ public void bindFolderData(List folders) {
+ this.folders = folders == null ? new ArrayList<>() : folders;
+ notifyDataSetChanged();
+ }
+
+ public void setChooseMode(int chooseMode) {
+ this.chooseMode = chooseMode;
+ }
+
+ public List getFolderData() {
+ return folders == null ? new ArrayList<>() : folders;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View itemView = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.picture_album_folder_item, parent, false);
+ return new ViewHolder(itemView);
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ final LocalMediaFolder folder = folders.get(position);
+ String name = folder.getName();
+ int imageNum = folder.getImageNum();
+ String imagePath = folder.getFirstImagePath();
+ boolean isChecked = folder.isChecked();
+ int checkedNum = folder.getCheckedNum();
+ holder.tvSign.setVisibility(checkedNum > 0 ? View.VISIBLE : View.INVISIBLE);
+ holder.itemView.setSelected(isChecked);
+ if (config.style != null && config.style.pictureAlbumStyle != 0) {
+ holder.itemView.setBackgroundResource(config.style.pictureAlbumStyle);
+ }
+ if (chooseMode == PictureMimeType.ofAudio()) {
+ holder.ivFirstImage.setImageResource(R.drawable.picture_audio_placeholder);
+ } else {
+ if (PictureSelectionConfig.imageEngine != null) {
+ PictureSelectionConfig.imageEngine.loadFolderImage(holder.itemView.getContext(),
+ imagePath, holder.ivFirstImage);
+ }
+ }
+ Context context = holder.itemView.getContext();
+ String firstTitle = folder.getOfAllType() != -1 ? folder.getOfAllType() == PictureMimeType.ofAudio() ?
+ context.getString(R.string.picture_all_audio)
+ : context.getString(R.string.picture_camera_roll) : name;
+ holder.tvFolderName.setText(context.getString(R.string.picture_camera_roll_num, firstTitle, imageNum));
+ holder.itemView.setOnClickListener(view -> {
+ if (onAlbumItemClickListener != null) {
+ int size = folders.size();
+ for (int i = 0; i < size; i++) {
+ LocalMediaFolder mediaFolder = folders.get(i);
+ mediaFolder.setChecked(false);
+ }
+ folder.setChecked(true);
+ notifyDataSetChanged();
+ onAlbumItemClickListener.onItemClick(position, folder.isCameraFolder(), folder.getBucketId(), folder.getName(), folder.getData());
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return folders.size();
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ ImageView ivFirstImage;
+ TextView tvFolderName, tvSign;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ ivFirstImage = itemView.findViewById(R.id.first_image);
+ tvFolderName = itemView.findViewById(R.id.tv_folder_name);
+ tvSign = itemView.findViewById(R.id.tv_sign);
+ if (config.style != null && config.style.pictureFolderCheckedDotStyle != 0) {
+ tvSign.setBackgroundResource(config.style.pictureFolderCheckedDotStyle);
+ }
+ }
+ }
+
+ private OnAlbumItemClickListener onAlbumItemClickListener;
+
+ public void setOnAlbumItemClickListener(OnAlbumItemClickListener listener) {
+ this.onAlbumItemClickListener = listener;
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureImageGridAdapter.java b/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureImageGridAdapter.java
new file mode 100644
index 0000000..0801be1
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureImageGridAdapter.java
@@ -0,0 +1,682 @@
+package com.luck.picture.lib.adapter;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.luck.picture.lib.R;
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.config.PictureSelectionConfig;
+import com.luck.picture.lib.dialog.PictureCustomDialog;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.listener.OnPhotoSelectChangedListener;
+import com.luck.picture.lib.tools.AnimUtils;
+import com.luck.picture.lib.tools.DateUtils;
+import com.luck.picture.lib.tools.MediaUtils;
+import com.luck.picture.lib.tools.PictureFileUtils;
+import com.luck.picture.lib.tools.SdkVersionUtils;
+import com.luck.picture.lib.tools.StringUtils;
+import com.luck.picture.lib.tools.ToastUtils;
+import com.luck.picture.lib.tools.VoiceUtils;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * @author:luck
+ * @date:2016-12-30 12:02
+ * @describe:PictureImageGridAdapter
+ */
+public class PictureImageGridAdapter extends RecyclerView.Adapter {
+
+ private Context context;
+ private boolean showCamera;
+ private OnPhotoSelectChangedListener imageSelectChangedListener;
+ private List data = new ArrayList<>();
+ private List selectData = new ArrayList<>();
+ private PictureSelectionConfig config;
+
+ public PictureImageGridAdapter(Context context, PictureSelectionConfig config) {
+ this.context = context;
+ this.config = config;
+ this.showCamera = config.isCamera;
+ }
+
+ public void setShowCamera(boolean showCamera) {
+ this.showCamera = showCamera;
+ }
+
+ public boolean isShowCamera() {
+ return showCamera;
+ }
+
+ /**
+ * 全量刷新
+ *
+ * @param data
+ */
+ public void bindData(List data) {
+ this.data = data == null ? new ArrayList<>() : data;
+ this.notifyDataSetChanged();
+ }
+
+
+ public void bindSelectData(List images) {
+ // 这里重新构构造一个新集合,不然会产生已选集合一变,结果集合也会添加的问题
+ List selection = new ArrayList<>();
+ int size = images.size();
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = images.get(i);
+ selection.add(media);
+ }
+ this.selectData = selection;
+ if (!config.isSingleDirectReturn) {
+ subSelectPosition();
+ if (imageSelectChangedListener != null) {
+ imageSelectChangedListener.onChange(selectData);
+ }
+ }
+ }
+
+ public List getSelectedData() {
+ return selectData == null ? new ArrayList<>() : selectData;
+ }
+
+ public int getSelectedSize() {
+ return selectData == null ? 0 : selectData.size();
+ }
+
+ public List getData() {
+ return data == null ? new ArrayList<>() : data;
+ }
+
+ public boolean isDataEmpty() {
+ return data == null || data.size() == 0;
+ }
+
+ public void clear() {
+ if (getSize() > 0) {
+ data.clear();
+ }
+ }
+
+ public int getSize() {
+ return data == null ? 0 : data.size();
+ }
+
+ public LocalMedia getItem(int position) {
+ return getSize() > 0 ? data.get(position) : null;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (showCamera && position == 0) {
+ return PictureConfig.TYPE_CAMERA;
+ } else {
+ return PictureConfig.TYPE_PICTURE;
+ }
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ if (viewType == PictureConfig.TYPE_CAMERA) {
+ View view = LayoutInflater.from(context).inflate(R.layout.picture_item_camera, parent, false);
+ return new CameraViewHolder(view);
+ } else {
+ View view = LayoutInflater.from(context).inflate(R.layout.picture_image_grid_item, parent, false);
+ return new ViewHolder(view);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NotNull final RecyclerView.ViewHolder holder, final int position) {
+ if (getItemViewType(position) == PictureConfig.TYPE_CAMERA) {
+ CameraViewHolder headerHolder = (CameraViewHolder) holder;
+ headerHolder.headerView.setOnClickListener(v -> {
+ if (imageSelectChangedListener != null) {
+ imageSelectChangedListener.onTakePhoto();
+ }
+ });
+ } else {
+ final ViewHolder contentHolder = (ViewHolder) holder;
+ final LocalMedia image = data.get(showCamera ? position - 1 : position);
+ image.position = contentHolder.getAdapterPosition();
+ final String path = image.getPath();
+ final String mimeType = image.getMimeType();
+ if (config.checkNumMode) {
+ notifyCheckChanged(contentHolder, image);
+ }
+ if (config.isSingleDirectReturn) {
+ contentHolder.tvCheck.setVisibility(View.GONE);
+ contentHolder.btnCheck.setVisibility(View.GONE);
+ } else {
+ selectImage(contentHolder, isSelected(image));
+ contentHolder.tvCheck.setVisibility(View.VISIBLE);
+ contentHolder.btnCheck.setVisibility(View.VISIBLE);
+ // 启用了蒙层效果
+ if (config.isMaxSelectEnabledMask) {
+ dispatchHandleMask(contentHolder, image);
+ }
+ }
+ contentHolder.tvIsGif.setVisibility(PictureMimeType.isGif(mimeType) ? View.VISIBLE : View.GONE);
+ if (PictureMimeType.isHasImage(image.getMimeType())) {
+ if (image.loadLongImageStatus == PictureConfig.NORMAL) {
+ image.isLongImage = MediaUtils.isLongImg(image);
+ image.loadLongImageStatus = PictureConfig.LOADED;
+ }
+ contentHolder.tvLongChart.setVisibility(image.isLongImage ? View.VISIBLE : View.GONE);
+ } else {
+ image.loadLongImageStatus = PictureConfig.NORMAL;
+ contentHolder.tvLongChart.setVisibility(View.GONE);
+ }
+ boolean isHasVideo = PictureMimeType.isHasVideo(mimeType);
+ if (isHasVideo || PictureMimeType.isHasAudio(mimeType)) {
+ contentHolder.tvDuration.setVisibility(View.VISIBLE);
+ contentHolder.tvDuration.setText(DateUtils.formatDurationTime(image.getDuration()));
+ contentHolder.tvDuration.setCompoundDrawablesRelativeWithIntrinsicBounds
+ (isHasVideo ? R.drawable.picture_icon_video : R.drawable.picture_icon_audio,
+ 0, 0, 0);
+ } else {
+ contentHolder.tvDuration.setVisibility(View.GONE);
+ }
+ if (config.chooseMode == PictureMimeType.ofAudio()) {
+ contentHolder.ivPicture.setImageResource(R.drawable.picture_audio_placeholder);
+ } else {
+ if (PictureSelectionConfig.imageEngine != null) {
+ PictureSelectionConfig.imageEngine.loadGridImage(context, path, contentHolder.ivPicture);
+ }
+ }
+
+ if (config.enablePreview || config.enPreviewVideo || config.enablePreviewAudio) {
+ contentHolder.btnCheck.setOnClickListener(v -> {
+ if (config.isMaxSelectEnabledMask) {
+ if (!contentHolder.tvCheck.isSelected() && getSelectedSize() >= config.maxSelectNum) {
+ String msg = StringUtils.getMsg(context, config.chooseMode == PictureMimeType.ofAll() ? null : image.getMimeType(), config.maxSelectNum);
+ showPromptDialog(msg);
+ return;
+ }
+ }
+ // If the original path does not exist or the path does exist but the file does not exist
+ String newPath = image.getRealPath();
+ if (!TextUtils.isEmpty(newPath) && !new File(newPath).exists()) {
+ ToastUtils.s(context, PictureMimeType.s(context, mimeType));
+ return;
+ }
+ // The width and height of the image are reversed if there is rotation information
+ MediaUtils.setOrientationAsynchronous(context, image, config.isAndroidQChangeWH, config.isAndroidQChangeVideoWH, null);
+ changeCheckboxState(contentHolder, image);
+ });
+ }
+ contentHolder.contentView.setOnClickListener(v -> {
+ if (config.isMaxSelectEnabledMask) {
+ if (image.isMaxSelectEnabledMask()) {
+ return;
+ }
+ }
+ // If the original path does not exist or the path does exist but the file does not exist
+ String newPath = image.getRealPath();
+ if (!TextUtils.isEmpty(newPath) && !new File(newPath).exists()) {
+ ToastUtils.s(context, PictureMimeType.s(context, mimeType));
+ return;
+ }
+ int index = showCamera ? position - 1 : position;
+ if (index == -1) {
+ return;
+ }
+ // The width and height of the image are reversed if there is rotation information
+ MediaUtils.setOrientationAsynchronous(context, image, config.isAndroidQChangeWH, config.isAndroidQChangeVideoWH, null);
+ boolean eqResult =
+ PictureMimeType.isHasImage(mimeType) && config.enablePreview
+ || PictureMimeType.isHasVideo(mimeType) && (config.enPreviewVideo
+ || config.selectionMode == PictureConfig.SINGLE)
+ || PictureMimeType.isHasAudio(mimeType) && (config.enablePreviewAudio
+ || config.selectionMode == PictureConfig.SINGLE);
+ if (eqResult) {
+ if (PictureMimeType.isHasVideo(image.getMimeType())) {
+ if (config.videoMinSecond > 0 && image.getDuration() < config.videoMinSecond) {
+ // The video is less than the minimum specified length
+ showPromptDialog(context.getString(R.string.picture_choose_min_seconds, config.videoMinSecond / 1000));
+ return;
+ }
+ if (config.videoMaxSecond > 0 && image.getDuration() > config.videoMaxSecond) {
+ // The length of the video exceeds the specified length
+ showPromptDialog(context.getString(R.string.picture_choose_max_seconds, config.videoMaxSecond / 1000));
+ return;
+ }
+ }
+ imageSelectChangedListener.onPictureClick(image, index);
+ } else {
+ changeCheckboxState(contentHolder, image);
+ }
+ });
+ }
+ }
+
+ /**
+ * Handle mask effects
+ *
+ * @param contentHolder
+ * @param item
+ */
+ private void dispatchHandleMask(ViewHolder contentHolder, LocalMedia item) {
+ if (config.isWithVideoImage && config.maxVideoSelectNum > 0) {
+ if (getSelectedSize() >= config.maxSelectNum) {
+ boolean isSelected = contentHolder.tvCheck.isSelected();
+ contentHolder.ivPicture.setColorFilter(ContextCompat.getColor
+ (context, isSelected ? R.color.picture_color_80 : R.color.picture_color_half_white), PorterDuff.Mode.SRC_ATOP);
+ item.setMaxSelectEnabledMask(!isSelected);
+ } else {
+ item.setMaxSelectEnabledMask(false);
+ }
+ } else {
+ LocalMedia media = selectData.size() > 0 ? selectData.get(0) : null;
+ if (media != null) {
+ boolean isSelected = contentHolder.tvCheck.isSelected();
+ if (config.chooseMode == PictureMimeType.ofAll()) {
+ if (PictureMimeType.isHasImage(media.getMimeType())) {
+ // All videos are not optional
+ if (!isSelected && !PictureMimeType.isHasImage(item.getMimeType())) {
+ contentHolder.ivPicture.setColorFilter(ContextCompat.getColor
+ (context, PictureMimeType.isHasVideo(item.getMimeType()) ? R.color.picture_color_half_white : R.color.picture_color_20), PorterDuff.Mode.SRC_ATOP);
+ }
+ item.setMaxSelectEnabledMask(PictureMimeType.isHasVideo(item.getMimeType()));
+ } else if (PictureMimeType.isHasVideo(media.getMimeType())) {
+ // All images are not optional
+ if (!isSelected && !PictureMimeType.isHasVideo(item.getMimeType())) {
+ contentHolder.ivPicture.setColorFilter(ContextCompat.getColor
+ (context, PictureMimeType.isHasImage(item.getMimeType()) ? R.color.picture_color_half_white : R.color.picture_color_20), PorterDuff.Mode.SRC_ATOP);
+ }
+ item.setMaxSelectEnabledMask(PictureMimeType.isHasImage(item.getMimeType()));
+ }
+ } else {
+ if (config.chooseMode == PictureMimeType.ofVideo() && config.maxVideoSelectNum > 0) {
+ if (!isSelected && getSelectedSize() == config.maxVideoSelectNum) {
+ contentHolder.ivPicture.setColorFilter(ContextCompat.getColor
+ (context, R.color.picture_color_half_white), PorterDuff.Mode.SRC_ATOP);
+ }
+ item.setMaxSelectEnabledMask(!isSelected && getSelectedSize() == config.maxVideoSelectNum);
+ } else {
+ if (!isSelected && getSelectedSize() == config.maxSelectNum) {
+ contentHolder.ivPicture.setColorFilter(ContextCompat.getColor
+ (context, R.color.picture_color_half_white), PorterDuff.Mode.SRC_ATOP);
+ }
+ item.setMaxSelectEnabledMask(!isSelected && getSelectedSize() == config.maxSelectNum);
+ }
+ }
+ }
+ }
+ }
+
+
+ @Override
+ public int getItemCount() {
+ return showCamera ? data.size() + 1 : data.size();
+ }
+
+ public class CameraViewHolder extends RecyclerView.ViewHolder {
+ View headerView;
+ TextView tvCamera;
+
+ public CameraViewHolder(View itemView) {
+ super(itemView);
+ headerView = itemView;
+ tvCamera = itemView.findViewById(R.id.tvCamera);
+ String title = config.chooseMode == PictureMimeType.ofAudio() ?
+ context.getString(R.string.picture_tape)
+ : context.getString(R.string.picture_take_picture);
+ tvCamera.setText(title);
+ }
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder {
+ ImageView ivPicture;
+ TextView tvCheck;
+ TextView tvDuration, tvIsGif, tvLongChart;
+ View contentView;
+ View btnCheck;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ contentView = itemView;
+ ivPicture = itemView.findViewById(R.id.ivPicture);
+ tvCheck = itemView.findViewById(R.id.tvCheck);
+ btnCheck = itemView.findViewById(R.id.btnCheck);
+ tvDuration = itemView.findViewById(R.id.tv_duration);
+ tvIsGif = itemView.findViewById(R.id.tv_isGif);
+ tvLongChart = itemView.findViewById(R.id.tv_long_chart);
+ if (config.style != null) {
+ if (config.style.pictureCheckedStyle != 0) {
+ tvCheck.setBackgroundResource(config.style.pictureCheckedStyle);
+ }
+ }
+ }
+ }
+
+ public boolean isSelected(LocalMedia image) {
+ int size = selectData.size();
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = selectData.get(i);
+ if (media == null || TextUtils.isEmpty(media.getPath())) {
+ continue;
+ }
+ if (media.getPath()
+ .equals(image.getPath())
+ || media.getId() == image.getId()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Update button status
+ */
+ private void notifyCheckChanged(ViewHolder viewHolder, LocalMedia imageBean) {
+ viewHolder.tvCheck.setText("");
+ int size = selectData.size();
+ for (int i = 0; i < size; i++) {
+ LocalMedia media = selectData.get(i);
+ if (media.getPath().equals(imageBean.getPath())
+ || media.getId() == imageBean.getId()) {
+ imageBean.setNum(media.getNum());
+ media.setPosition(imageBean.getPosition());
+ viewHolder.tvCheck.setText(String.valueOf(imageBean.getNum()));
+ }
+ }
+ }
+
+ /**
+ * Update the selected status of the image
+ *
+ * @param contentHolder
+ * @param image
+ */
+
+ @SuppressLint("StringFormatMatches")
+ private void changeCheckboxState(ViewHolder contentHolder, LocalMedia image) {
+ boolean isChecked = contentHolder.tvCheck.isSelected();
+ int count = selectData.size();
+ String mimeType = count > 0 ? selectData.get(0).getMimeType() : "";
+ if (config.isWithVideoImage) {
+ // isWithVideoImage mode
+ int videoSize = 0;
+ for (int i = 0; i < count; i++) {
+ LocalMedia media = selectData.get(i);
+ if (PictureMimeType.isHasVideo(media.getMimeType())) {
+ videoSize++;
+ }
+ }
+
+ if (PictureMimeType.isHasVideo(image.getMimeType())) {
+ if (config.maxVideoSelectNum <= 0) {
+ showPromptDialog(context.getString(R.string.picture_rule));
+ return;
+ }
+
+ if (getSelectedSize() >= config.maxSelectNum && !isChecked) {
+ showPromptDialog(context.getString(R.string.picture_message_max_num, config.maxSelectNum));
+ return;
+ }
+
+ if (videoSize >= config.maxVideoSelectNum && !isChecked) {
+ showPromptDialog(StringUtils.getMsg(context, image.getMimeType(), config.maxVideoSelectNum));
+ return;
+ }
+
+ if (!isChecked && config.videoMinSecond > 0 && image.getDuration() < config.videoMinSecond) {
+ showPromptDialog(context.getString(R.string.picture_choose_min_seconds, config.videoMinSecond / 1000));
+ return;
+ }
+
+ if (!isChecked && config.videoMaxSecond > 0 && image.getDuration() > config.videoMaxSecond) {
+ showPromptDialog(context.getString(R.string.picture_choose_max_seconds, config.videoMaxSecond / 1000));
+ return;
+ }
+ }
+
+ if (PictureMimeType.isHasImage(image.getMimeType())) {
+ if (getSelectedSize() >= config.maxSelectNum && !isChecked) {
+ showPromptDialog(context.getString(R.string.picture_message_max_num, config.maxSelectNum));
+ return;
+ }
+ }
+
+ } else {
+ if (!TextUtils.isEmpty(mimeType)) {
+ boolean mimeTypeSame = PictureMimeType.isMimeTypeSame(mimeType, image.getMimeType());
+ if (!mimeTypeSame) {
+ showPromptDialog(context.getString(R.string.picture_rule));
+ return;
+ }
+ }
+ if (PictureMimeType.isHasVideo(mimeType) && config.maxVideoSelectNum > 0) {
+ if (count >= config.maxVideoSelectNum && !isChecked) {
+ showPromptDialog(StringUtils.getMsg(context, mimeType, config.maxVideoSelectNum));
+ return;
+ }
+ if (!isChecked && config.videoMinSecond > 0 && image.getDuration() < config.videoMinSecond) {
+ showPromptDialog(context.getString(R.string.picture_choose_min_seconds, config.videoMinSecond / 1000));
+ return;
+ }
+
+ if (!isChecked && config.videoMaxSecond > 0 && image.getDuration() > config.videoMaxSecond) {
+ showPromptDialog(context.getString(R.string.picture_choose_max_seconds, config.videoMaxSecond / 1000));
+ return;
+ }
+ } else {
+ if (count >= config.maxSelectNum && !isChecked) {
+ showPromptDialog(StringUtils.getMsg(context, mimeType, config.maxSelectNum));
+ return;
+ }
+ if (PictureMimeType.isHasVideo(image.getMimeType())) {
+ if (!isChecked && config.videoMinSecond > 0 && image.getDuration() < config.videoMinSecond) {
+ showPromptDialog(context.getString(R.string.picture_choose_min_seconds, config.videoMinSecond / 1000));
+ return;
+ }
+
+ if (!isChecked && config.videoMaxSecond > 0 && image.getDuration() > config.videoMaxSecond) {
+ showPromptDialog(context.getString(R.string.picture_choose_max_seconds, config.videoMaxSecond / 1000));
+ return;
+ }
+ }
+ }
+ }
+
+ if (isChecked) {
+ for (int i = 0; i < count; i++) {
+ LocalMedia media = selectData.get(i);
+ if (media == null || TextUtils.isEmpty(media.getPath())) {
+ continue;
+ }
+ if (media.getPath().equals(image.getPath())
+ || media.getId() == image.getId()) {
+ selectData.remove(media);
+ subSelectPosition();
+ AnimUtils.disZoom(contentHolder.ivPicture, config.zoomAnim);
+ break;
+ }
+ }
+ } else {
+ // The radio
+ if (config.selectionMode == PictureConfig.SINGLE) {
+ singleRadioMediaImage();
+ }
+
+ // If the width and height are 0, regain the width and height
+ if (image.getWidth() == 0 || image.getHeight() == 0) {
+ int width = 0, height = 0;
+ image.setOrientation(-1);
+ if (PictureMimeType.isContent(image.getPath())) {
+ if (PictureMimeType.isHasVideo(image.getMimeType())) {
+ int[] size = MediaUtils.getVideoSizeForUri(context, Uri.parse(image.getPath()));
+ width = size[0];
+ height = size[1];
+ } else if (PictureMimeType.isHasImage(image.getMimeType())) {
+ int[] size = MediaUtils.getImageSizeForUri(context, Uri.parse(image.getPath()));
+ width = size[0];
+ height = size[1];
+ }
+ } else {
+ if (PictureMimeType.isHasVideo(image.getMimeType())) {
+ int[] size = MediaUtils.getVideoSizeForUrl(image.getPath());
+ width = size[0];
+ height = size[1];
+ } else if (PictureMimeType.isHasImage(image.getMimeType())) {
+ int[] size = MediaUtils.getImageSizeForUrl(image.getPath());
+ width = size[0];
+ height = size[1];
+ }
+ }
+ image.setWidth(width);
+ image.setHeight(height);
+ }
+
+ selectData.add(image);
+ image.setNum(selectData.size());
+ VoiceUtils.getInstance().play();
+ AnimUtils.zoom(contentHolder.ivPicture, config.zoomAnim);
+ contentHolder.tvCheck.startAnimation(AnimationUtils.loadAnimation(context, R.anim.picture_anim_modal_in));
+ }
+
+ boolean isRefreshAll = false;
+ if (config.isMaxSelectEnabledMask) {
+ if (config.chooseMode == PictureMimeType.ofAll()) {
+ // ofAll
+ if (config.isWithVideoImage && config.maxVideoSelectNum > 0) {
+ if (getSelectedSize() >= config.maxSelectNum) {
+ isRefreshAll = true;
+ }
+ if (isChecked) {
+ // delete
+ if (getSelectedSize() == config.maxSelectNum - 1) {
+ isRefreshAll = true;
+ }
+ }
+ } else {
+ if (!isChecked && getSelectedSize() == 1) {
+ // add
+ isRefreshAll = true;
+ }
+ if (isChecked && getSelectedSize() == 0) {
+ // delete
+ isRefreshAll = true;
+ }
+ }
+ } else {
+ // ofImage or ofVideo or ofAudio
+ if (config.chooseMode == PictureMimeType.ofVideo() && config.maxVideoSelectNum > 0) {
+ if (!isChecked && getSelectedSize() == config.maxVideoSelectNum) {
+ // add
+ isRefreshAll = true;
+ }
+ if (isChecked && getSelectedSize() == config.maxVideoSelectNum - 1) {
+ // delete
+ isRefreshAll = true;
+ }
+ } else {
+ if (!isChecked && getSelectedSize() == config.maxSelectNum) {
+ // add
+ isRefreshAll = true;
+ }
+ if (isChecked && getSelectedSize() == config.maxSelectNum - 1) {
+ // delete
+ isRefreshAll = true;
+ }
+ }
+ }
+ }
+
+ if (isRefreshAll) {
+ notifyDataSetChanged();
+ } else {
+ notifyItemChanged(contentHolder.getAdapterPosition());
+ }
+
+ selectImage(contentHolder, !isChecked);
+ if (imageSelectChangedListener != null) {
+ imageSelectChangedListener.onChange(selectData);
+ }
+ }
+
+ /**
+ * Radio mode
+ */
+ private void singleRadioMediaImage() {
+ if (selectData != null
+ && selectData.size() > 0) {
+ LocalMedia media = selectData.get(0);
+ notifyItemChanged(media.position);
+ selectData.clear();
+ }
+ }
+
+ /**
+ * Update the selection order
+ */
+ private void subSelectPosition() {
+ if (config.checkNumMode) {
+ int size = selectData.size();
+ for (int index = 0; index < size; index++) {
+ LocalMedia media = selectData.get(index);
+ media.setNum(index + 1);
+ notifyItemChanged(media.position);
+ }
+ }
+ }
+
+ /**
+ * Select the image and animate it
+ *
+ * @param holder
+ * @param isChecked
+ */
+ public void selectImage(ViewHolder holder, boolean isChecked) {
+ holder.tvCheck.setSelected(isChecked);
+ if (isChecked) {
+ holder.ivPicture.setColorFilter(ContextCompat.getColor
+ (context, R.color.picture_color_80), PorterDuff.Mode.SRC_ATOP);
+ } else {
+ holder.ivPicture.setColorFilter(ContextCompat.getColor
+ (context, R.color.picture_color_20), PorterDuff.Mode.SRC_ATOP);
+ }
+ }
+
+ /**
+ * Tips
+ */
+ private void showPromptDialog(String content) {
+ PictureCustomDialog dialog = new PictureCustomDialog(context, R.layout.picture_prompt_dialog);
+ TextView btnOk = dialog.findViewById(R.id.btnOk);
+ TextView tvContent = dialog.findViewById(R.id.tv_content);
+ tvContent.setText(content);
+ btnOk.setOnClickListener(v -> dialog.dismiss());
+ dialog.show();
+ }
+
+
+ /**
+ * Binding listener
+ *
+ * @param imageSelectChangedListener
+ */
+ public void setOnPhotoSelectChangedListener(OnPhotoSelectChangedListener
+ imageSelectChangedListener) {
+ this.imageSelectChangedListener = imageSelectChangedListener;
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureSimpleFragmentAdapter.java b/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureSimpleFragmentAdapter.java
new file mode 100644
index 0000000..beda694
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureSimpleFragmentAdapter.java
@@ -0,0 +1,225 @@
+package com.luck.picture.lib.adapter;
+
+import android.content.Intent;
+import android.graphics.PointF;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.viewpager.widget.PagerAdapter;
+
+import com.luck.picture.lib.R;
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.config.PictureSelectionConfig;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.photoview.PhotoView;
+import com.luck.picture.lib.tools.JumpUtils;
+import com.luck.picture.lib.tools.MediaUtils;
+import com.luck.picture.lib.widget.longimage.ImageSource;
+import com.luck.picture.lib.widget.longimage.ImageViewState;
+import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @data:2018/1/27 下午7:50
+ * @describe:PictureSimpleFragmentAdapter
+ */
+
+public class PictureSimpleFragmentAdapter extends PagerAdapter {
+ private List data;
+ private OnCallBackActivity onBackPressed;
+ private PictureSelectionConfig config;
+ /**
+ * Maximum number of cached images
+ */
+ private static final int MAX_CACHE_SIZE = 20;
+ /**
+ * To cache the view
+ */
+ private SparseArray mCacheView;
+
+ public void clear() {
+ if (null != mCacheView) {
+ mCacheView.clear();
+ mCacheView = null;
+ }
+ }
+
+ public void removeCacheView(int position) {
+ if (mCacheView != null && position < mCacheView.size()) {
+ mCacheView.removeAt(position);
+ }
+ }
+
+ public interface OnCallBackActivity {
+ /**
+ * Close Activity
+ */
+ void onActivityBackPressed();
+ }
+
+ public PictureSimpleFragmentAdapter(PictureSelectionConfig config,
+ OnCallBackActivity onBackPressed) {
+ super();
+ this.config = config;
+ this.onBackPressed = onBackPressed;
+ this.mCacheView = new SparseArray<>();
+ }
+
+ /**
+ * bind data
+ *
+ * @param data
+ */
+ public void bindData(List data) {
+ this.data = data;
+ }
+
+ /**
+ * get data
+ *
+ * @return
+ */
+ public List getData() {
+ return data == null ? new ArrayList<>() : data;
+ }
+
+ public int getSize() {
+ return data == null ? 0 : data.size();
+ }
+
+ public void remove(int currentItem) {
+ if (getSize() > currentItem) {
+ data.remove(currentItem);
+ }
+ }
+
+ public LocalMedia getItem(int position) {
+ return getSize() > 0 && position < getSize() ? data.get(position) : null;
+ }
+
+ @Override
+ public int getCount() {
+ return data != null ? data.size() : 0;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ (container).removeView((View) object);
+ if (mCacheView.size() > MAX_CACHE_SIZE) {
+ mCacheView.remove(position);
+ }
+ }
+
+ @Override
+ public int getItemPosition(@NonNull Object object) {
+ return POSITION_NONE;
+ }
+
+ @Override
+ public boolean isViewFromObject(@NotNull View view, @NotNull Object object) {
+ return view == object;
+ }
+
+ @NotNull
+ @Override
+ public Object instantiateItem(@NotNull ViewGroup container, int position) {
+ View contentView = mCacheView.get(position);
+ if (contentView == null) {
+ contentView = LayoutInflater.from(container.getContext())
+ .inflate(R.layout.picture_image_preview, container, false);
+ mCacheView.put(position, contentView);
+ }
+ PhotoView imageView = contentView.findViewById(R.id.preview_image);
+ SubsamplingScaleImageView longImg = contentView.findViewById(R.id.longImg);
+ ImageView ivPlay = contentView.findViewById(R.id.iv_play);
+ LocalMedia media = getItem(position);
+ if (media != null) {
+ final String mimeType = media.getMimeType();
+ final String path;
+ if (media.isCut() && !media.isCompressed()) {
+ path = media.getCutPath();
+ } else if (media.isCompressed() || (media.isCut() && media.isCompressed())) {
+ path = media.getCompressPath();
+ } else {
+ path = media.getPath();
+ }
+ boolean isGif = PictureMimeType.isGif(mimeType);
+ boolean isHasVideo = PictureMimeType.isHasVideo(mimeType);
+ ivPlay.setVisibility(isHasVideo ? View.VISIBLE : View.GONE);
+ ivPlay.setOnClickListener(v -> {
+ if (PictureSelectionConfig.customVideoPlayCallback != null) {
+ PictureSelectionConfig.customVideoPlayCallback.startPlayVideo(media);
+ } else {
+ Intent intent = new Intent();
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(PictureConfig.EXTRA_PREVIEW_VIDEO, true);
+ bundle.putString(PictureConfig.EXTRA_VIDEO_PATH, path);
+ intent.putExtras(bundle);
+ JumpUtils.startPictureVideoPlayActivity(container.getContext(), bundle, PictureConfig.PREVIEW_VIDEO_CODE);
+ }
+ });
+ boolean eqLongImg = MediaUtils.isLongImg(media);
+ imageView.setVisibility(eqLongImg && !isGif ? View.GONE : View.VISIBLE);
+ imageView.setOnViewTapListener((view, x, y) -> {
+ if (onBackPressed != null) {
+ onBackPressed.onActivityBackPressed();
+ }
+ });
+ longImg.setVisibility(eqLongImg && !isGif ? View.VISIBLE : View.GONE);
+ longImg.setOnClickListener(v -> {
+ if (onBackPressed != null) {
+ onBackPressed.onActivityBackPressed();
+ }
+ });
+
+ if (isGif && !media.isCompressed()) {
+ if (config != null && PictureSelectionConfig.imageEngine != null) {
+ PictureSelectionConfig.imageEngine.loadAsGifImage
+ (contentView.getContext(), path, imageView);
+ }
+ } else {
+ if (config != null && PictureSelectionConfig.imageEngine != null) {
+ if (eqLongImg) {
+ displayLongPic(PictureMimeType.isContent(path)
+ ? Uri.parse(path) : Uri.fromFile(new File(path)), longImg);
+ } else {
+ PictureSelectionConfig.imageEngine.loadImage
+ (contentView.getContext(), path, imageView);
+ }
+ }
+ }
+ }
+
+ (container).addView(contentView, 0);
+ return contentView;
+ }
+
+ /**
+ * load long image
+ *
+ * @param uri
+ * @param longImg
+ */
+ private void displayLongPic(Uri uri, SubsamplingScaleImageView longImg) {
+ longImg.setQuickScaleEnabled(true);
+ longImg.setZoomEnabled(true);
+ longImg.setPanEnabled(true);
+ longImg.setDoubleTapZoomDuration(100);
+ longImg.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
+ longImg.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER);
+ longImg.setImage(ImageSource.uri(uri), new ImageViewState(0, new PointF(0, 0), 0));
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureWeChatPreviewGalleryAdapter.java b/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureWeChatPreviewGalleryAdapter.java
new file mode 100644
index 0000000..99b9ec1
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/adapter/PictureWeChatPreviewGalleryAdapter.java
@@ -0,0 +1,114 @@
+package com.luck.picture.lib.adapter;
+
+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.luck.picture.lib.R;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.config.PictureSelectionConfig;
+import com.luck.picture.lib.entity.LocalMedia;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2019-11-30 20:50
+ * @describe:WeChat style selected after image preview
+ */
+public class PictureWeChatPreviewGalleryAdapter
+ extends RecyclerView.Adapter {
+ private List list;
+ private PictureSelectionConfig config;
+
+ public PictureWeChatPreviewGalleryAdapter(PictureSelectionConfig config) {
+ super();
+ this.config = config;
+ }
+
+ public void setNewData(List data) {
+ this.list = data == null ? new ArrayList<>() : data;
+ notifyDataSetChanged();
+ }
+
+ public void addSingleMediaToData(LocalMedia media) {
+ if (this.list != null) {
+ list.clear();
+ list.add(media);
+ notifyDataSetChanged();
+ }
+ }
+
+ public void removeMediaToData(LocalMedia media) {
+ if (this.list != null && this.list.size() > 0) {
+ this.list.remove(media);
+ notifyDataSetChanged();
+ }
+ }
+
+ public boolean isDataEmpty() {
+ return list == null || list.size() == 0;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View itemView = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.picture_wechat_preview_gallery, parent, false);
+ return new ViewHolder(itemView);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ LocalMedia item = getItem(position);
+ if (item != null) {
+ holder.viewBorder.setVisibility(item.isChecked() ? View.VISIBLE : View.GONE);
+ if (config != null && PictureSelectionConfig.imageEngine != null) {
+ PictureSelectionConfig.imageEngine.loadImage(holder.itemView.getContext(), item.getPath(), holder.ivImage);
+ }
+ holder.ivPlay.setVisibility(PictureMimeType.isHasVideo(item.getMimeType()) ? View.VISIBLE : View.GONE);
+ holder.itemView.setOnClickListener(v -> {
+ if (listener != null && holder.getAdapterPosition() >= 0) {
+ listener.onItemClick(holder.getAdapterPosition(), getItem(position), v);
+ }
+ });
+ }
+ }
+
+ public LocalMedia getItem(int position) {
+ return list != null && list.size() > 0 ? list.get(position) : null;
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ ImageView ivImage;
+ ImageView ivPlay;
+ View viewBorder;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ ivImage = itemView.findViewById(R.id.ivImage);
+ ivPlay = itemView.findViewById(R.id.ivPlay);
+ viewBorder = itemView.findViewById(R.id.viewBorder);
+ }
+ }
+
+ private OnItemClickListener listener;
+
+ public void setItemClickListener(OnItemClickListener listener) {
+ this.listener = listener;
+ }
+
+ public interface OnItemClickListener {
+ void onItemClick(int position, LocalMedia media, View v);
+ }
+
+ @Override
+ public int getItemCount() {
+ return list != null ? list.size() : 0;
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/animators/AlphaInAnimationAdapter.java b/picture_library/src/main/java/com/luck/picture/lib/animators/AlphaInAnimationAdapter.java
new file mode 100644
index 0000000..51aff95
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/animators/AlphaInAnimationAdapter.java
@@ -0,0 +1,31 @@
+package com.luck.picture.lib.animators;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * @author:luck
+ * @date:2020-04-18 14:11
+ * @describe:AlphaInAnimationAdapter
+ */
+public class AlphaInAnimationAdapter extends BaseAnimationAdapter {
+ private static final float DEFAULT_ALPHA_FROM = 0f;
+ private final float mFrom;
+
+ public AlphaInAnimationAdapter(RecyclerView.Adapter adapter) {
+ this(adapter, DEFAULT_ALPHA_FROM);
+ }
+
+ public AlphaInAnimationAdapter(RecyclerView.Adapter adapter, float from) {
+ super(adapter);
+ mFrom = from;
+ }
+
+ @Override
+ protected Animator[] getAnimators(View view) {
+ return new Animator[]{ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f)};
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/animators/AnimationType.java b/picture_library/src/main/java/com/luck/picture/lib/animators/AnimationType.java
new file mode 100644
index 0000000..9489bde
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/animators/AnimationType.java
@@ -0,0 +1,23 @@
+package com.luck.picture.lib.animators;
+
+/**
+ * @author:luck
+ * @date:2020-04-18 15:21
+ * @describe:AnimationType
+ */
+public class AnimationType {
+
+ /**
+ * default animation
+ */
+ public final static int DEFAULT_ANIMATION = -1;
+
+ /**
+ * alpha animation
+ */
+ public final static int ALPHA_IN_ANIMATION = 1;
+ /**
+ * slide in bottom animation
+ */
+ public final static int SLIDE_IN_BOTTOM_ANIMATION = 2;
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/animators/BaseAnimationAdapter.java b/picture_library/src/main/java/com/luck/picture/lib/animators/BaseAnimationAdapter.java
new file mode 100644
index 0000000..72413d8
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/animators/BaseAnimationAdapter.java
@@ -0,0 +1,127 @@
+package com.luck.picture.lib.animators;
+
+import android.animation.Animator;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * @author:luck
+ * @date:2020-04-18 14:12
+ * @describe:BaseAnimationAdapter
+ */
+public abstract class BaseAnimationAdapter extends RecyclerView.Adapter {
+ private RecyclerView.Adapter mAdapter;
+ private int mDuration = 250;
+ private Interpolator mInterpolator = new LinearInterpolator();
+ private int mLastPosition = -1;
+
+ private boolean isFirstOnly = true;
+
+ public BaseAnimationAdapter(RecyclerView.Adapter adapter) {
+ mAdapter = adapter;
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return mAdapter.onCreateViewHolder(parent, viewType);
+ }
+
+ @Override
+ public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
+ super.registerAdapterDataObserver(observer);
+ mAdapter.registerAdapterDataObserver(observer);
+ }
+
+ @Override
+ public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
+ super.unregisterAdapterDataObserver(observer);
+ mAdapter.unregisterAdapterDataObserver(observer);
+ }
+
+ @Override
+ public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+ super.onAttachedToRecyclerView(recyclerView);
+ mAdapter.onAttachedToRecyclerView(recyclerView);
+ }
+
+ @Override
+ public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+ super.onDetachedFromRecyclerView(recyclerView);
+ mAdapter.onDetachedFromRecyclerView(recyclerView);
+ }
+
+ @Override
+ public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
+ super.onViewAttachedToWindow(holder);
+ mAdapter.onViewAttachedToWindow(holder);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
+ super.onViewDetachedFromWindow(holder);
+ mAdapter.onViewDetachedFromWindow(holder);
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ mAdapter.onBindViewHolder(holder, position);
+
+ int adapterPosition = holder.getAdapterPosition();
+ if (!isFirstOnly || adapterPosition > mLastPosition) {
+ for (Animator anim : getAnimators(holder.itemView)) {
+ anim.setDuration(mDuration).start();
+ anim.setInterpolator(mInterpolator);
+ }
+ mLastPosition = adapterPosition;
+ } else {
+ ViewHelper.clear(holder.itemView);
+ }
+ }
+
+ @Override
+ public void onViewRecycled(RecyclerView.ViewHolder holder) {
+ mAdapter.onViewRecycled(holder);
+ super.onViewRecycled(holder);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mAdapter.getItemCount();
+ }
+
+ public void setDuration(int duration) {
+ mDuration = duration;
+ }
+
+ public void setInterpolator(Interpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ public void setStartPosition(int start) {
+ mLastPosition = start;
+ }
+
+ protected abstract Animator[] getAnimators(View view);
+
+ public void setFirstOnly(boolean firstOnly) {
+ isFirstOnly = firstOnly;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mAdapter.getItemViewType(position);
+ }
+
+ public RecyclerView.Adapter getWrappedAdapter() {
+ return mAdapter;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mAdapter.getItemId(position);
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/animators/SlideInBottomAnimationAdapter.java b/picture_library/src/main/java/com/luck/picture/lib/animators/SlideInBottomAnimationAdapter.java
new file mode 100644
index 0000000..525be63
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/animators/SlideInBottomAnimationAdapter.java
@@ -0,0 +1,26 @@
+package com.luck.picture.lib.animators;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * @author:luck
+ * @date:2020-04-18 14:19
+ * @describe:SlideInBottomAnimationAdapter
+ */
+public class SlideInBottomAnimationAdapter extends BaseAnimationAdapter {
+
+ public SlideInBottomAnimationAdapter(RecyclerView.Adapter adapter) {
+ super(adapter);
+ }
+
+ @Override
+ protected Animator[] getAnimators(View view) {
+ return new Animator[]{
+ ObjectAnimator.ofFloat(view, "translationY", view.getMeasuredHeight(), 0)
+ };
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/animators/ViewHelper.java b/picture_library/src/main/java/com/luck/picture/lib/animators/ViewHelper.java
new file mode 100644
index 0000000..02c65de
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/animators/ViewHelper.java
@@ -0,0 +1,26 @@
+package com.luck.picture.lib.animators;
+
+import android.view.View;
+
+import androidx.core.view.ViewCompat;
+
+/**
+ * @author:luck
+ * @date:2020-04-18 14:13
+ * @describe:ViewHelper
+ */
+public final class ViewHelper {
+ public static void clear(View v) {
+ v.setAlpha(1);
+ v.setScaleY(1);
+ v.setScaleX(1);
+ v.setTranslationY(0);
+ v.setTranslationX(0);
+ v.setRotation(0);
+ v.setRotationY(0);
+ v.setRotationX(0);
+ v.setPivotY(v.getMeasuredHeight() / 2);
+ v.setPivotX(v.getMeasuredWidth() / 2);
+ ViewCompat.animate(v).setInterpolator(null).setStartDelay(0);
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/app/IApp.java b/picture_library/src/main/java/com/luck/picture/lib/app/IApp.java
new file mode 100644
index 0000000..56ec605
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/app/IApp.java
@@ -0,0 +1,26 @@
+package com.luck.picture.lib.app;
+
+import android.content.Context;
+
+import com.luck.picture.lib.engine.PictureSelectorEngine;
+
+/**
+ * @author:luck
+ * @date:2019-12-03 15:14
+ * @describe:IApp
+ */
+public interface IApp {
+ /**
+ * Application
+ *
+ * @return
+ */
+ Context getAppContext();
+
+ /**
+ * PictureSelectorEngine
+ *
+ * @return
+ */
+ PictureSelectorEngine getPictureSelectorEngine();
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/app/PictureAppMaster.java b/picture_library/src/main/java/com/luck/picture/lib/app/PictureAppMaster.java
new file mode 100644
index 0000000..0109a82
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/app/PictureAppMaster.java
@@ -0,0 +1,56 @@
+package com.luck.picture.lib.app;
+
+import android.content.Context;
+
+import com.luck.picture.lib.engine.PictureSelectorEngine;
+
+/**
+ * @author:luck
+ * @date:2019-12-03 15:12
+ * @describe:PictureAppMaster
+ */
+public class PictureAppMaster implements IApp {
+
+
+ @Override
+ public Context getAppContext() {
+ if (app == null) {
+ return null;
+ }
+ return app.getAppContext();
+ }
+
+ @Override
+ public PictureSelectorEngine getPictureSelectorEngine() {
+ if (app == null) {
+ return null;
+ }
+ return app.getPictureSelectorEngine();
+ }
+
+ private PictureAppMaster() {
+ }
+
+ private static PictureAppMaster mInstance;
+
+ public static PictureAppMaster getInstance() {
+ if (mInstance == null) {
+ synchronized (PictureAppMaster.class) {
+ if (mInstance == null) {
+ mInstance = new PictureAppMaster();
+ }
+ }
+ }
+ return mInstance;
+ }
+
+ private IApp app;
+
+ public void setApp(IApp app) {
+ this.app = app;
+ }
+
+ public IApp getApp() {
+ return app;
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/broadcast/BroadcastAction.java b/picture_library/src/main/java/com/luck/picture/lib/broadcast/BroadcastAction.java
new file mode 100644
index 0000000..57f1d52
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/broadcast/BroadcastAction.java
@@ -0,0 +1,14 @@
+package com.luck.picture.lib.broadcast;
+
+/**
+ * @author:luck
+ * @date:2019-11-20 14:01
+ * @describe:广播Action
+ */
+public class BroadcastAction {
+
+ /**
+ * 外部预览界面删除图片Action
+ */
+ public static final String ACTION_DELETE_PREVIEW_POSITION = "com.luck.picture.lib.action.delete_preview_position";
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/broadcast/BroadcastManager.java b/picture_library/src/main/java/com/luck/picture/lib/broadcast/BroadcastManager.java
new file mode 100644
index 0000000..6e34eb8
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/broadcast/BroadcastManager.java
@@ -0,0 +1,250 @@
+package com.luck.picture.lib.broadcast;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2019-11-20 13:45
+ * @describe:本地广播
+ */
+public class BroadcastManager {
+ private static final String TAG = BroadcastManager.class.getSimpleName();
+ private LocalBroadcastManager localBroadcastManager;
+
+ private Intent intent;
+ private String action;
+
+ public static BroadcastManager getInstance(Context ctx) {
+ BroadcastManager broadcastManager = new BroadcastManager();
+ broadcastManager.localBroadcastManager = LocalBroadcastManager.getInstance(ctx.getApplicationContext());
+ return broadcastManager;
+ }
+
+
+ public BroadcastManager intent(Intent intent) {
+ this.intent = intent;
+ return this;
+ }
+
+
+ public BroadcastManager action(String action) {
+ this.action = action;
+ return this;
+ }
+
+ public BroadcastManager extras(Bundle bundle) {
+ createIntent();
+
+ if (intent == null) {
+ Log.e(TAG, "intent create failed");
+ return this;
+ }
+ intent.putExtras(bundle);
+ return this;
+ }
+
+
+ public BroadcastManager put(String key, ArrayList extends Parcelable> value) {
+ createIntent();
+
+ if (intent == null) {
+ Log.e(TAG, "intent create failed");
+ return this;
+ }
+
+ intent.putExtra(key, value);
+ return this;
+ }
+
+ public BroadcastManager put(String key, Parcelable[] value) {
+ createIntent();
+
+ if (intent == null) {
+ Log.e(TAG, "intent create failed");
+ return this;
+ }
+
+ intent.putExtra(key, value);
+ return this;
+ }
+
+
+ public BroadcastManager put(String key, Parcelable value) {
+ createIntent();
+
+ if (intent == null) {
+ Log.e(TAG, "intent create failed");
+ return this;
+ }
+
+ intent.putExtra(key, value);
+ return this;
+ }
+
+ public BroadcastManager put(String key, float value) {
+ createIntent();
+
+ if (intent == null) {
+ Log.e(TAG, "intent create failed");
+ return this;
+ }
+
+ intent.putExtra(key, value);
+ return this;
+ }
+
+ public BroadcastManager put(String key, double value) {
+ createIntent();
+
+ if (intent == null) {
+ Log.e(TAG, "intent create failed");
+ return this;
+ }
+
+ intent.putExtra(key, value);
+ return this;
+ }
+
+ public BroadcastManager put(String key, long value) {
+ createIntent();
+
+ if (intent == null) {
+ Log.e(TAG, "intent create failed");
+ return this;
+ }
+
+ intent.putExtra(key, value);
+ return this;
+ }
+
+ public BroadcastManager put(String key, boolean value) {
+ createIntent();
+
+ if (intent == null) {
+ Log.e(TAG, "intent create failed");
+ return this;
+ }
+
+ intent.putExtra(key, value);
+ return this;
+ }
+
+ public BroadcastManager put(String key, int value) {
+ createIntent();
+
+ if (intent == null) {
+ Log.e(TAG, "intent create failed");
+ return this;
+ }
+
+ intent.putExtra(key, value);
+ return this;
+ }
+
+
+ public BroadcastManager put(String key, String str) {
+ createIntent();
+
+ if (intent == null) {
+ Log.e(TAG, "intent create failed");
+ return this;
+ }
+
+ intent.putExtra(key, str);
+ return this;
+ }
+
+ private void createIntent() {
+ if (intent == null) {
+ Log.d(TAG, "intent is not created");
+ }
+
+ if (intent == null) {
+ if (!TextUtils.isEmpty(action)) {
+ intent = new Intent(action);
+ }
+ Log.d(TAG, "intent created with action");
+ }
+ }
+
+
+ public void broadcast() {
+
+ createIntent();
+
+ if (intent == null) {
+ return;
+ }
+
+ if (action == null) {
+ return;
+ }
+
+ intent.setAction(action);
+
+ if (null != localBroadcastManager) {
+ localBroadcastManager.sendBroadcast(intent);
+ }
+ }
+
+ public void registerReceiver(BroadcastReceiver br, List actions) {
+ if (null == br || null == actions) {
+ return;
+ }
+ IntentFilter iFilter = new IntentFilter();
+ if (actions != null) {
+ for (String action : actions) {
+ iFilter.addAction(action);
+ }
+ }
+ if (null != localBroadcastManager) {
+ localBroadcastManager.registerReceiver(br, iFilter);
+ }
+ }
+
+
+ public void registerReceiver(BroadcastReceiver br, String... actions) {
+ if (actions == null || actions.length <= 0) {
+ return;
+ }
+ registerReceiver(br, Arrays.asList(actions));
+ }
+
+
+ /**
+ * @param br
+ */
+ public void unregisterReceiver(BroadcastReceiver br) {
+ if (null == br) {
+ return;
+ }
+
+ try {
+ localBroadcastManager.unregisterReceiver(br);
+ } catch (Exception e) {
+
+ }
+ }
+
+ /**
+ * @param br
+ * @param actions 至少传入一个
+ */
+ public void unregisterReceiver(BroadcastReceiver br, @NonNull String... actions) {
+ unregisterReceiver(br);
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/camera/CheckPermission.java b/picture_library/src/main/java/com/luck/picture/lib/camera/CheckPermission.java
new file mode 100644
index 0000000..705028f
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/camera/CheckPermission.java
@@ -0,0 +1,79 @@
+package com.luck.picture.lib.camera;
+
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+
+/**
+ * =====================================
+ * 作 者: 陈嘉桐
+ * 版 本:1.1.4
+ * 创建日期:2017/6/8
+ * 描 述:
+ * =====================================
+ */
+public class CheckPermission {
+ public static final int STATE_RECORDING = -1;
+ public static final int STATE_NO_PERMISSION = -2;
+ public static final int STATE_SUCCESS = 1;
+
+ /**
+ * 用于检测是否具有录音权限
+ *
+ * @return
+ */
+ public static int getRecordState() {
+ int minBuffer = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat
+ .ENCODING_PCM_16BIT);
+ AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, 44100, AudioFormat
+ .CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, (minBuffer * 100));
+ short[] point = new short[minBuffer];
+ int readSize = 0;
+ try {
+
+ audioRecord.startRecording();//检测是否可以进入初始化状态
+ } catch (Exception e) {
+ if (audioRecord != null) {
+ audioRecord.release();
+ audioRecord = null;
+ }
+ return STATE_NO_PERMISSION;
+ }
+ if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
+ //6.0以下机型都会返回此状态,故使用时需要判断bulid版本
+ //检测是否在录音中
+ if (audioRecord != null) {
+ audioRecord.stop();
+ audioRecord.release();
+ audioRecord = null;
+ }
+ return STATE_RECORDING;
+ } else {
+ //检测是否可以获取录音结果
+
+ readSize = audioRecord.read(point, 0, point.length);
+
+
+ if (readSize <= 0) {
+ if (audioRecord != null) {
+ audioRecord.stop();
+ audioRecord.release();
+ audioRecord = null;
+
+ }
+ return STATE_NO_PERMISSION;
+
+ } else {
+ if (audioRecord != null) {
+ audioRecord.stop();
+ audioRecord.release();
+ audioRecord = null;
+
+ }
+
+ return STATE_SUCCESS;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/picture_library/src/main/java/com/luck/picture/lib/camera/CustomCameraView.java b/picture_library/src/main/java/com/luck/picture/lib/camera/CustomCameraView.java
new file mode 100644
index 0000000..933583f
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/camera/CustomCameraView.java
@@ -0,0 +1,547 @@
+package com.luck.picture.lib.camera;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageCaptureException;
+import androidx.camera.core.VideoCapture;
+import androidx.camera.view.CameraView;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.LifecycleEventObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+import com.luck.picture.lib.PictureMediaScannerConnection;
+import com.luck.picture.lib.R;
+import com.luck.picture.lib.camera.listener.CameraListener;
+import com.luck.picture.lib.camera.listener.CaptureListener;
+import com.luck.picture.lib.camera.listener.ClickListener;
+import com.luck.picture.lib.camera.listener.ImageCallbackListener;
+import com.luck.picture.lib.camera.listener.TypeListener;
+import com.luck.picture.lib.camera.view.CaptureLayout;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.config.PictureSelectionConfig;
+import com.luck.picture.lib.thread.PictureThreadUtils;
+import com.luck.picture.lib.tools.AndroidQTransformUtils;
+import com.luck.picture.lib.tools.DateUtils;
+import com.luck.picture.lib.tools.MediaUtils;
+import com.luck.picture.lib.tools.PictureFileUtils;
+import com.luck.picture.lib.tools.SdkVersionUtils;
+import com.luck.picture.lib.tools.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+
+/**
+ * @author:luck
+ * @date:2020-01-04 13:41
+ * @describe:自定义相机View
+ */
+public class CustomCameraView extends RelativeLayout {
+ /**
+ * 只能拍照
+ */
+ public static final int BUTTON_STATE_ONLY_CAPTURE = 0x101;
+ /**
+ * 只能录像
+ */
+ public static final int BUTTON_STATE_ONLY_RECORDER = 0x102;
+ /**
+ * 两者都可以
+ */
+ public static final int BUTTON_STATE_BOTH = 0x103;
+ /**
+ * 闪关灯状态
+ */
+ private static final int TYPE_FLASH_AUTO = 0x021;
+ private static final int TYPE_FLASH_ON = 0x022;
+ private static final int TYPE_FLASH_OFF = 0x023;
+ private int type_flash = TYPE_FLASH_OFF;
+ private PictureSelectionConfig mConfig;
+ /**
+ * 回调监听
+ */
+ private CameraListener mCameraListener;
+ private ClickListener mOnClickListener;
+ private ImageCallbackListener mImageCallbackListener;
+ private androidx.camera.view.CameraView mCameraView;
+ private ImageView mImagePreview;
+ private ImageView mSwitchCamera;
+ private ImageView mFlashLamp;
+ private CaptureLayout mCaptureLayout;
+ private MediaPlayer mMediaPlayer;
+ private TextureView mTextureView;
+ private long recordTime = 0;
+ private File mVideoFile;
+ private File mPhotoFile;
+
+ public CustomCameraView(Context context) {
+ this(context, null);
+ }
+
+ public CustomCameraView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CustomCameraView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initView();
+ }
+
+ public void initView() {
+ setWillNotDraw(false);
+ setBackgroundColor(ContextCompat.getColor(getContext(), R.color.picture_color_black));
+ View view = LayoutInflater.from(getContext()).inflate(R.layout.picture_camera_view, this);
+ mCameraView = view.findViewById(R.id.cameraView);
+ mCameraView.enableTorch(true);
+ mTextureView = view.findViewById(R.id.video_play_preview);
+ mImagePreview = view.findViewById(R.id.image_preview);
+ mSwitchCamera = view.findViewById(R.id.image_switch);
+ mSwitchCamera.setImageResource(R.drawable.picture_ic_camera);
+ mFlashLamp = view.findViewById(R.id.image_flash);
+ setFlashRes();
+ mFlashLamp.setOnClickListener(v -> {
+ type_flash++;
+ if (type_flash > 0x023)
+ type_flash = TYPE_FLASH_AUTO;
+ setFlashRes();
+ });
+ mCaptureLayout = view.findViewById(R.id.capture_layout);
+ mCaptureLayout.setDuration(15 * 1000);
+ //切换摄像头
+ mSwitchCamera.setOnClickListener(v -> mCameraView.toggleCamera());
+ //拍照 录像
+ mCaptureLayout.setCaptureListener(new CaptureListener() {
+ @Override
+ public void takePictures() {
+ mSwitchCamera.setVisibility(INVISIBLE);
+ mFlashLamp.setVisibility(INVISIBLE);
+ mCameraView.setCaptureMode(androidx.camera.view.CameraView.CaptureMode.IMAGE);
+ File imageOutFile = createImageFile();
+ if (imageOutFile == null) {
+ return;
+ }
+ mPhotoFile = imageOutFile;
+ mCameraView.takePicture(imageOutFile, ContextCompat.getMainExecutor(getContext()),
+ new MyImageResultCallback(getContext(), mConfig, imageOutFile,
+ mImagePreview, mCaptureLayout, mImageCallbackListener, mCameraListener));
+ }
+
+ @Override
+ public void recordStart() {
+ mSwitchCamera.setVisibility(INVISIBLE);
+ mFlashLamp.setVisibility(INVISIBLE);
+ mCameraView.setCaptureMode(androidx.camera.view.CameraView.CaptureMode.VIDEO);
+ mCameraView.startRecording(createVideoFile(), ContextCompat.getMainExecutor(getContext()),
+ new VideoCapture.OnVideoSavedCallback() {
+ @Override
+ public void onVideoSaved(@NonNull File file) {
+ mVideoFile = file;
+ if (recordTime < 1500 && mVideoFile.exists() && mVideoFile.delete()) {
+ return;
+ }
+ if (SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isContent(mConfig.cameraPath)) {
+ PictureThreadUtils.executeByIo(new PictureThreadUtils.SimpleTask() {
+
+ @Override
+ public Boolean doInBackground() {
+ return AndroidQTransformUtils.copyPathToDCIM(getContext(),
+ file, Uri.parse(mConfig.cameraPath));
+ }
+
+ @Override
+ public void onSuccess(Boolean result) {
+ PictureThreadUtils.cancel(PictureThreadUtils.getIoPool());
+ }
+ });
+ }
+ mTextureView.setVisibility(View.VISIBLE);
+ mCameraView.setVisibility(View.INVISIBLE);
+ if (mTextureView.isAvailable()) {
+ startVideoPlay(mVideoFile);
+ } else {
+ mTextureView.setSurfaceTextureListener(surfaceTextureListener);
+ }
+ }
+
+ @Override
+ public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
+ if (mCameraListener != null) {
+ mCameraListener.onError(videoCaptureError, message, cause);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void recordShort(final long time) {
+ recordTime = time;
+ mSwitchCamera.setVisibility(VISIBLE);
+ mFlashLamp.setVisibility(VISIBLE);
+ mCaptureLayout.resetCaptureLayout();
+ mCaptureLayout.setTextWithAnimation(getContext().getString(R.string.picture_recording_time_is_short));
+ mCameraView.stopRecording();
+ }
+
+ @Override
+ public void recordEnd(long time) {
+ recordTime = time;
+ mCameraView.stopRecording();
+ }
+
+ @Override
+ public void recordZoom(float zoom) {
+
+ }
+
+ @Override
+ public void recordError() {
+ if (mCameraListener != null) {
+ mCameraListener.onError(0, "An unknown error", null);
+ }
+ }
+ });
+ //确认 取消
+ mCaptureLayout.setTypeListener(new TypeListener() {
+ @Override
+ public void cancel() {
+ stopVideoPlay();
+ resetState();
+ }
+
+ @Override
+ public void confirm() {
+ if (mCameraView.getCaptureMode() == androidx.camera.view.CameraView.CaptureMode.VIDEO) {
+ if (mVideoFile == null) {
+ return;
+ }
+ stopVideoPlay();
+ if (mCameraListener != null || !mVideoFile.exists()) {
+ mCameraListener.onRecordSuccess(mVideoFile);
+ }
+ } else {
+ if (mPhotoFile == null || !mPhotoFile.exists()) {
+ return;
+ }
+ mImagePreview.setVisibility(INVISIBLE);
+ if (mCameraListener != null) {
+ mCameraListener.onPictureSuccess(mPhotoFile);
+ }
+ }
+ }
+ });
+ mCaptureLayout.setLeftClickListener(() -> {
+ if (mOnClickListener != null) {
+ mOnClickListener.onClick();
+ }
+ });
+ }
+
+ /**
+ * 拍照回调
+ */
+ private static class MyImageResultCallback implements ImageCapture.OnImageSavedCallback {
+ private WeakReference mContextReference;
+ private WeakReference mConfigReference;
+ private WeakReference mFileReference;
+ private WeakReference mImagePreviewReference;
+ private WeakReference mCaptureLayoutReference;
+ private WeakReference mImageCallbackListenerReference;
+ private WeakReference mCameraListenerReference;
+
+ public MyImageResultCallback(Context context, PictureSelectionConfig config,
+ File imageOutFile, ImageView imagePreview,
+ CaptureLayout captureLayout, ImageCallbackListener imageCallbackListener,
+ CameraListener cameraListener) {
+ super();
+ this.mContextReference = new WeakReference<>(context);
+ this.mConfigReference = new WeakReference<>(config);
+ this.mFileReference = new WeakReference<>(imageOutFile);
+ this.mImagePreviewReference = new WeakReference<>(imagePreview);
+ this.mCaptureLayoutReference = new WeakReference<>(captureLayout);
+ this.mImageCallbackListenerReference = new WeakReference<>(imageCallbackListener);
+ this.mCameraListenerReference = new WeakReference<>(cameraListener);
+ }
+
+ @Override
+ public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
+ if (mConfigReference.get() != null) {
+ if (SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isContent(mConfigReference.get().cameraPath)) {
+ PictureThreadUtils.executeByIo(new PictureThreadUtils.SimpleTask() {
+
+ @Override
+ public Boolean doInBackground() {
+ return AndroidQTransformUtils.copyPathToDCIM(mContextReference.get(),
+ mFileReference.get(), Uri.parse(mConfigReference.get().cameraPath));
+ }
+
+ @Override
+ public void onSuccess(Boolean result) {
+ PictureThreadUtils.cancel(PictureThreadUtils.getIoPool());
+ }
+ });
+ }
+ }
+ if (mImageCallbackListenerReference.get() != null
+ && mFileReference.get() != null
+ && mImagePreviewReference.get() != null) {
+ mImageCallbackListenerReference.get().onLoadImage(mFileReference.get(), mImagePreviewReference.get());
+ }
+ if (mImagePreviewReference.get() != null) {
+ mImagePreviewReference.get().setVisibility(View.VISIBLE);
+ }
+ if (mCaptureLayoutReference.get() != null) {
+ mCaptureLayoutReference.get().startTypeBtnAnimator();
+ }
+ }
+
+ @Override
+ public void onError(@NonNull ImageCaptureException exception) {
+ if (mCameraListenerReference.get() != null) {
+ mCameraListenerReference.get().onError(exception.getImageCaptureError(), exception.getMessage(), exception.getCause());
+ }
+ }
+ }
+
+ private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ startVideoPlay(mVideoFile);
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ return false;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+
+ }
+ };
+
+ public File createImageFile() {
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ String diskCacheDir = PictureFileUtils.getDiskCacheDir(getContext());
+ File rootDir = new File(diskCacheDir);
+ if (!rootDir.exists() && rootDir.mkdirs()) {
+ }
+ boolean isOutFileNameEmpty = TextUtils.isEmpty(mConfig.cameraFileName);
+ String suffix = TextUtils.isEmpty(mConfig.suffixType) ? PictureFileUtils.POSTFIX : mConfig.suffixType;
+ String newFileImageName = isOutFileNameEmpty ? DateUtils.getCreateFileName("IMG_") + suffix : mConfig.cameraFileName;
+ File cameraFile = new File(rootDir, newFileImageName);
+ Uri outUri = getOutUri(PictureMimeType.ofImage());
+ if (outUri != null) {
+ mConfig.cameraPath = outUri.toString();
+ }
+ return cameraFile;
+ } else {
+ String cameraFileName = "";
+ if (!TextUtils.isEmpty(mConfig.cameraFileName)) {
+ boolean isSuffixOfImage = PictureMimeType.isSuffixOfImage(mConfig.cameraFileName);
+ mConfig.cameraFileName = !isSuffixOfImage ? StringUtils.renameSuffix(mConfig.cameraFileName, PictureMimeType.JPEG) : mConfig.cameraFileName;
+ cameraFileName = mConfig.camera ? mConfig.cameraFileName : StringUtils.rename(mConfig.cameraFileName);
+ }
+ File cameraFile = PictureFileUtils.createCameraFile(getContext(),
+ PictureMimeType.ofImage(), cameraFileName, mConfig.suffixType, mConfig.outPutCameraPath);
+ if (cameraFile != null) {
+ mConfig.cameraPath = cameraFile.getAbsolutePath();
+ }
+ return cameraFile;
+ }
+ }
+
+ public File createVideoFile() {
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ String diskCacheDir = PictureFileUtils.getVideoDiskCacheDir(getContext());
+ File rootDir = new File(diskCacheDir);
+ if (!rootDir.exists() && rootDir.mkdirs()) {
+ }
+ boolean isOutFileNameEmpty = TextUtils.isEmpty(mConfig.cameraFileName);
+ String suffix = TextUtils.isEmpty(mConfig.suffixType) ? PictureMimeType.MP4 : mConfig.suffixType;
+ String newFileImageName = isOutFileNameEmpty ? DateUtils.getCreateFileName("VID_") + suffix : mConfig.cameraFileName;
+ File cameraFile = new File(rootDir, newFileImageName);
+ Uri outUri = getOutUri(PictureMimeType.ofVideo());
+ if (outUri != null) {
+ mConfig.cameraPath = outUri.toString();
+ }
+ return cameraFile;
+ } else {
+ String cameraFileName = "";
+ if (!TextUtils.isEmpty(mConfig.cameraFileName)) {
+ boolean isSuffixOfImage = PictureMimeType.isSuffixOfImage(mConfig.cameraFileName);
+ mConfig.cameraFileName = !isSuffixOfImage ? StringUtils
+ .renameSuffix(mConfig.cameraFileName, PictureMimeType.MP4) : mConfig.cameraFileName;
+ cameraFileName = mConfig.camera ? mConfig.cameraFileName : StringUtils.rename(mConfig.cameraFileName);
+ }
+ File cameraFile = PictureFileUtils.createCameraFile(getContext(),
+ PictureMimeType.ofVideo(), cameraFileName, mConfig.suffixType, mConfig.outPutCameraPath);
+ mConfig.cameraPath = cameraFile.getAbsolutePath();
+ return cameraFile;
+ }
+ }
+
+ private Uri getOutUri(int type) {
+ return type == PictureMimeType.ofVideo()
+ ? MediaUtils.createVideoUri(getContext(), mConfig.suffixType) : MediaUtils.createImageUri(getContext(), mConfig.suffixType);
+ }
+
+ public void setCameraListener(CameraListener cameraListener) {
+ this.mCameraListener = cameraListener;
+ }
+
+ public void setPictureSelectionConfig(PictureSelectionConfig config) {
+ this.mConfig = config;
+ }
+
+ public void setBindToLifecycle(LifecycleOwner lifecycleOwner) {
+ mCameraView.bindToLifecycle(lifecycleOwner);
+ lifecycleOwner.getLifecycle().addObserver((LifecycleEventObserver) (source, event) -> {
+
+ });
+ }
+
+ /**
+ * 设置录制视频最大时长 秒
+ */
+ public void setRecordVideoMaxTime(int maxDurationTime) {
+ mCaptureLayout.setDuration(maxDurationTime * 1000);
+ }
+
+ /**
+ * 设置录制视频最小时长 秒
+ */
+ public void setRecordVideoMinTime(int minDurationTime) {
+ mCaptureLayout.setMinDuration(minDurationTime * 1000);
+ }
+
+ /**
+ * 关闭相机界面按钮
+ *
+ * @param clickListener
+ */
+ public void setOnClickListener(ClickListener clickListener) {
+ this.mOnClickListener = clickListener;
+ }
+
+ public void setImageCallbackListener(ImageCallbackListener mImageCallbackListener) {
+ this.mImageCallbackListener = mImageCallbackListener;
+ }
+
+ private void setFlashRes() {
+ switch (type_flash) {
+ case TYPE_FLASH_AUTO:
+ mFlashLamp.setImageResource(R.drawable.picture_ic_flash_auto);
+ mCameraView.setFlash(ImageCapture.FLASH_MODE_AUTO);
+ break;
+ case TYPE_FLASH_ON:
+ mFlashLamp.setImageResource(R.drawable.picture_ic_flash_on);
+ mCameraView.setFlash(ImageCapture.FLASH_MODE_ON);
+ break;
+ case TYPE_FLASH_OFF:
+ mFlashLamp.setImageResource(R.drawable.picture_ic_flash_off);
+ mCameraView.setFlash(ImageCapture.FLASH_MODE_OFF);
+ break;
+ }
+ }
+
+ public CameraView getCameraView() {
+ return mCameraView;
+ }
+
+ public CaptureLayout getCaptureLayout() {
+ return mCaptureLayout;
+ }
+
+ /**
+ * 重置状态
+ */
+ private void resetState() {
+ if (mCameraView.getCaptureMode() == androidx.camera.view.CameraView.CaptureMode.VIDEO) {
+ if (mCameraView.isRecording()) {
+ mCameraView.stopRecording();
+ }
+ if (mVideoFile != null && mVideoFile.exists()) {
+ mVideoFile.delete();
+ if (SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isContent(mConfig.cameraPath)) {
+ getContext().getContentResolver().delete(Uri.parse(mConfig.cameraPath), null, null);
+ } else {
+ new PictureMediaScannerConnection(getContext(), mVideoFile.getAbsolutePath());
+ }
+ }
+ } else {
+ mImagePreview.setVisibility(INVISIBLE);
+ if (mPhotoFile != null && mPhotoFile.exists()) {
+ mPhotoFile.delete();
+ if (SdkVersionUtils.checkedAndroid_Q() && PictureMimeType.isContent(mConfig.cameraPath)) {
+ getContext().getContentResolver().delete(Uri.parse(mConfig.cameraPath), null, null);
+ } else {
+ new PictureMediaScannerConnection(getContext(), mPhotoFile.getAbsolutePath());
+ }
+ }
+ }
+ mSwitchCamera.setVisibility(VISIBLE);
+ mFlashLamp.setVisibility(VISIBLE);
+ mCameraView.setVisibility(View.VISIBLE);
+ mCaptureLayout.resetCaptureLayout();
+ }
+
+ /**
+ * 开始循环播放视频
+ *
+ * @param videoFile
+ */
+ private void startVideoPlay(File videoFile) {
+ try {
+ if (mMediaPlayer == null) {
+ mMediaPlayer = new MediaPlayer();
+ }
+ mMediaPlayer.setDataSource(videoFile.getAbsolutePath());
+ mMediaPlayer.setSurface(new Surface(mTextureView.getSurfaceTexture()));
+ mMediaPlayer.setLooping(true);
+ mMediaPlayer.setOnPreparedListener(mp -> {
+ mp.start();
+
+ float ratio = mp.getVideoWidth() * 1f / mp.getVideoHeight();
+ int width1 = mTextureView.getWidth();
+ ViewGroup.LayoutParams layoutParams = mTextureView.getLayoutParams();
+ layoutParams.height = (int) (width1 / ratio);
+ mTextureView.setLayoutParams(layoutParams);
+ });
+ mMediaPlayer.prepareAsync();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 停止视频播放
+ */
+ private void stopVideoPlay() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+ mTextureView.setVisibility(View.GONE);
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/camera/listener/CameraListener.java b/picture_library/src/main/java/com/luck/picture/lib/camera/listener/CameraListener.java
new file mode 100644
index 0000000..3056be6
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/camera/listener/CameraListener.java
@@ -0,0 +1,33 @@
+package com.luck.picture.lib.camera.listener;
+
+import androidx.annotation.NonNull;
+
+import java.io.File;
+
+/**
+ * @author:luck
+ * @date:2020-01-04 13:38
+ * @describe:相机回调监听
+ */
+public interface CameraListener {
+ /**
+ * 拍照成功返回
+ *
+ * @param file
+ */
+ void onPictureSuccess(@NonNull File file);
+
+ /**
+ * 录像成功返回
+ *
+ * @param file
+ */
+ void onRecordSuccess(@NonNull File file);
+
+ /**
+ * 使用相机出错
+ *
+ * @param file
+ */
+ void onError(int videoCaptureError, String message, Throwable cause);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/camera/listener/CaptureListener.java b/picture_library/src/main/java/com/luck/picture/lib/camera/listener/CaptureListener.java
new file mode 100644
index 0000000..e47fbe6
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/camera/listener/CaptureListener.java
@@ -0,0 +1,19 @@
+package com.luck.picture.lib.camera.listener;
+
+/**
+ * @author:luck
+ * @date:2020-01-04 13:56
+ */
+public interface CaptureListener {
+ void takePictures();
+
+ void recordShort(long time);
+
+ void recordStart();
+
+ void recordEnd(long time);
+
+ void recordZoom(float zoom);
+
+ void recordError();
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/camera/listener/ClickListener.java b/picture_library/src/main/java/com/luck/picture/lib/camera/listener/ClickListener.java
new file mode 100644
index 0000000..3d61c17
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/camera/listener/ClickListener.java
@@ -0,0 +1,10 @@
+package com.luck.picture.lib.camera.listener;
+
+/**
+ * @author:luck
+ * @date:2020-01-04 13:45
+ * @describe:点击事件监听
+ */
+public interface ClickListener {
+ void onClick();
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/camera/listener/ImageCallbackListener.java b/picture_library/src/main/java/com/luck/picture/lib/camera/listener/ImageCallbackListener.java
new file mode 100644
index 0000000..0b0f3f5
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/camera/listener/ImageCallbackListener.java
@@ -0,0 +1,20 @@
+package com.luck.picture.lib.camera.listener;
+
+import android.widget.ImageView;
+
+import java.io.File;
+
+/**
+ * @author:luck
+ * @date:2020-01-04 15:55
+ * @describe:图片加载
+ */
+public interface ImageCallbackListener {
+ /**
+ * 加载图片回调
+ *
+ * @param file
+ * @param imageView
+ */
+ void onLoadImage(File file, ImageView imageView);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/camera/listener/TypeListener.java b/picture_library/src/main/java/com/luck/picture/lib/camera/listener/TypeListener.java
new file mode 100644
index 0000000..59e8a2d
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/camera/listener/TypeListener.java
@@ -0,0 +1,15 @@
+package com.luck.picture.lib.camera.listener;
+
+/**
+ * =====================================
+ * 作 者: 陈嘉桐
+ * 版 本:1.1.4
+ * 创建日期:2017/4/25
+ * 描 述:
+ * =====================================
+ */
+public interface TypeListener {
+ void cancel();
+
+ void confirm();
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/camera/view/CaptureButton.java b/picture_library/src/main/java/com/luck/picture/lib/camera/view/CaptureButton.java
new file mode 100644
index 0000000..8839b3a
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/camera/view/CaptureButton.java
@@ -0,0 +1,379 @@
+package com.luck.picture.lib.camera.view;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.os.CountDownTimer;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.luck.picture.lib.camera.CustomCameraView;
+import com.luck.picture.lib.camera.CheckPermission;
+import com.luck.picture.lib.camera.listener.CaptureListener;
+import com.luck.picture.lib.tools.DoubleUtils;
+
+
+/**
+ * =====================================
+ * 作 者: 陈嘉桐 445263848@qq.com
+ * 版 本:1.1.4
+ * 创建日期:2017/4/25
+ * 描 述:拍照按钮
+ * =====================================
+ */
+public class CaptureButton extends View {
+
+ private int state; //当前按钮状态
+ private int button_state; //按钮可执行的功能状态(拍照,录制,两者)
+
+ public static final int STATE_IDLE = 0x001; //空闲状态
+ public static final int STATE_PRESS = 0x002; //按下状态
+ public static final int STATE_LONG_PRESS = 0x003; //长按状态
+ public static final int STATE_RECORDERING = 0x004; //录制状态
+ public static final int STATE_BAN = 0x005; //禁止状态
+
+ private int progress_color = 0xEE16AE16; //进度条颜色
+ private int outside_color = 0xEEDCDCDC; //外圆背景色
+ private int inside_color = 0xFFFFFFFF; //内圆背景色
+
+
+ private float event_Y; //Touch_Event_Down时候记录的Y值
+
+
+ private Paint mPaint;
+
+ private float strokeWidth; //进度条宽度
+ private int outside_add_size; //长按外圆半径变大的Size
+ private int inside_reduce_size; //长安内圆缩小的Size
+
+ //中心坐标
+ private float center_X;
+ private float center_Y;
+
+ private float button_radius; //按钮半径
+ private float button_outside_radius; //外圆半径
+ private float button_inside_radius; //内圆半径
+ private int button_size; //按钮大小
+
+ private float progress; //录制视频的进度
+ private int duration; //录制视频最大时间长度
+ private int min_duration; //最短录制时间限制
+ private int recorded_time; //记录当前录制的时间
+
+ private RectF rectF;
+
+ private LongPressRunnable longPressRunnable; //长按后处理的逻辑Runnable
+ private CaptureListener captureLisenter; //按钮回调接口
+ private RecordCountDownTimer timer; //计时器
+
+ public CaptureButton(Context context) {
+ super(context);
+ }
+
+ public CaptureButton(Context context, int size) {
+ super(context);
+ this.button_size = size;
+ button_radius = size / 2.0f;
+
+ button_outside_radius = button_radius;
+ button_inside_radius = button_radius * 0.75f;
+
+ strokeWidth = size / 15;
+ outside_add_size = size / 8;
+ inside_reduce_size = size / 8;
+
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+
+ progress = 0;
+ longPressRunnable = new LongPressRunnable();
+
+ state = STATE_IDLE; //初始化为空闲状态
+ button_state = CustomCameraView.BUTTON_STATE_BOTH; //初始化按钮为可录制可拍照
+ duration = 10 * 1000; //默认最长录制时间为10s
+ min_duration = 1500; //默认最短录制时间为1.5s
+
+ center_X = (button_size + outside_add_size * 2) / 2;
+ center_Y = (button_size + outside_add_size * 2) / 2;
+
+ rectF = new RectF(
+ center_X - (button_radius + outside_add_size - strokeWidth / 2),
+ center_Y - (button_radius + outside_add_size - strokeWidth / 2),
+ center_X + (button_radius + outside_add_size - strokeWidth / 2),
+ center_Y + (button_radius + outside_add_size - strokeWidth / 2));
+
+ timer = new RecordCountDownTimer(duration, duration / 360); //录制定时器
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ setMeasuredDimension(button_size + outside_add_size * 2, button_size + outside_add_size * 2);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ mPaint.setStyle(Paint.Style.FILL);
+
+ mPaint.setColor(outside_color); //外圆(半透明灰色)
+ canvas.drawCircle(center_X, center_Y, button_outside_radius, mPaint);
+
+ mPaint.setColor(inside_color); //内圆(白色)
+ canvas.drawCircle(center_X, center_Y, button_inside_radius, mPaint);
+
+ //如果状态为录制状态,则绘制录制进度条
+ if (state == STATE_RECORDERING) {
+ mPaint.setColor(progress_color);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(strokeWidth);
+ canvas.drawArc(rectF, -90, progress, false, mPaint);
+ }
+ }
+
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (event.getPointerCount() > 1 || state != STATE_IDLE)
+ break;
+ event_Y = event.getY(); //记录Y值
+ state = STATE_PRESS; //修改当前状态为点击按下
+
+ //判断按钮状态是否为可录制状态
+ if ((button_state == CustomCameraView.BUTTON_STATE_ONLY_RECORDER || button_state == CustomCameraView.BUTTON_STATE_BOTH))
+ postDelayed(longPressRunnable, 500); //同时延长500启动长按后处理的逻辑Runnable
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (captureLisenter != null
+ && state == STATE_RECORDERING
+ && (button_state == CustomCameraView.BUTTON_STATE_ONLY_RECORDER || button_state == CustomCameraView.BUTTON_STATE_BOTH)) {
+ //记录当前Y值与按下时候Y值的差值,调用缩放回调接口
+ captureLisenter.recordZoom(event_Y - event.getY());
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ //根据当前按钮的状态进行相应的处理 ----CodeReview---抬起瞬间应该重置状态 当前状态可能为按下和正在录制
+ //state = STATE_BAN;
+ handlerPressByState();
+ break;
+ }
+ return true;
+ }
+
+ //当手指松开按钮时候处理的逻辑
+ private void handlerPressByState() {
+ removeCallbacks(longPressRunnable); //移除长按逻辑的Runnable
+ //根据当前状态处理
+ switch (state) {
+ //当前是点击按下
+ case STATE_PRESS:
+ if (captureLisenter != null && (button_state == CustomCameraView.BUTTON_STATE_ONLY_CAPTURE || button_state ==
+ CustomCameraView.BUTTON_STATE_BOTH)) {
+ startCaptureAnimation(button_inside_radius);
+ } else {
+ state = STATE_IDLE;
+ }
+ break;
+ // ---CodeReview---当内外圆动画未结束时已经是长按状态 但还没有置为STATE_RECORDERING时 应该也要结束录制 此处是一个bug
+ case STATE_LONG_PRESS:
+ //当前是长按状态
+ case STATE_RECORDERING:
+ timer.cancel(); //停止计时器
+ recordEnd(); //录制结束
+ break;
+ }
+ state = STATE_IDLE;
+ }
+
+ //录制结束
+ public void recordEnd() {
+ if (captureLisenter != null) {
+ if (recorded_time < min_duration)
+ captureLisenter.recordShort(recorded_time);//回调录制时间过短
+ else
+ captureLisenter.recordEnd(recorded_time); //回调录制结束
+ }
+ resetRecordAnim(); //重制按钮状态
+ }
+
+ //重制状态
+ private void resetRecordAnim() {
+ state = STATE_BAN;
+ progress = 0; //重制进度
+ invalidate();
+ //还原按钮初始状态动画
+ startRecordAnimation(
+ button_outside_radius,
+ button_radius,
+ button_inside_radius,
+ button_radius * 0.75f
+ );
+ }
+
+ //内圆动画
+ private void startCaptureAnimation(float inside_start) {
+ ValueAnimator inside_anim = ValueAnimator.ofFloat(inside_start, inside_start * 0.75f, inside_start);
+ inside_anim.addUpdateListener(animation -> {
+ button_inside_radius = (float) animation.getAnimatedValue();
+ invalidate();
+ });
+ inside_anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ //回调拍照接口
+// if (captureLisenter != null) {
+// captureLisenter.takePictures();
+// }
+ // 为何拍照完成要将状态掷为禁止????此处貌似bug!!!!!!---CodeReview
+ //state = STATE_BAN;
+ //state = STATE_IDLE;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (captureLisenter != null) {
+ captureLisenter.takePictures();
+ }
+ // 防止重复点击 状态重置
+ state = STATE_BAN;
+ }
+ });
+ inside_anim.setDuration(50);
+ inside_anim.start();
+ }
+
+ //内外圆动画
+ private void startRecordAnimation(float outside_start, float outside_end, float inside_start, float inside_end) {
+ ValueAnimator outside_anim = ValueAnimator.ofFloat(outside_start, outside_end);
+ ValueAnimator inside_anim = ValueAnimator.ofFloat(inside_start, inside_end);
+ //外圆动画监听
+ outside_anim.addUpdateListener(animation -> {
+ button_outside_radius = (float) animation.getAnimatedValue();
+ invalidate();
+ });
+ //内圆动画监听
+ inside_anim.addUpdateListener(animation -> {
+ button_inside_radius = (float) animation.getAnimatedValue();
+ invalidate();
+ });
+ AnimatorSet set = new AnimatorSet();
+ //当动画结束后启动录像Runnable并且回调录像开始接口
+ set.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (DoubleUtils.isFastDoubleClick()) {
+ return;
+ }
+ //设置为录制状态
+ if (state == STATE_LONG_PRESS) {
+ if (captureLisenter != null)
+ captureLisenter.recordStart();
+ state = STATE_RECORDERING;
+ timer.start();
+ } else {
+ // 此处动画包括长按起始动画和还原动画 若不是长按状态应该还原状态为空闲????---CodeReview
+ state = STATE_IDLE;
+ }
+ }
+ });
+ set.playTogether(outside_anim, inside_anim);
+ set.setDuration(100);
+ set.start();
+ }
+
+
+ //更新进度条
+ private void updateProgress(long millisUntilFinished) {
+ recorded_time = (int) (duration - millisUntilFinished);
+ progress = 360f - millisUntilFinished / (float) duration * 360f;
+ invalidate();
+ }
+
+ //录制视频计时器
+ private class RecordCountDownTimer extends CountDownTimer {
+ RecordCountDownTimer(long millisInFuture, long countDownInterval) {
+ super(millisInFuture, countDownInterval);
+ }
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ updateProgress(millisUntilFinished);
+ }
+
+ @Override
+ public void onFinish() {
+ //updateProgress(duration);
+ recordEnd();
+ }
+ }
+
+ //长按线程
+ private class LongPressRunnable implements Runnable {
+ @Override
+ public void run() {
+ state = STATE_LONG_PRESS; //如果按下后经过500毫秒则会修改当前状态为长按状态
+ //没有录制权限
+ if (CheckPermission.getRecordState() != CheckPermission.STATE_SUCCESS) {
+ state = STATE_IDLE;
+ if (captureLisenter != null) {
+ captureLisenter.recordError();
+ return;
+ }
+ }
+ //启动按钮动画,外圆变大,内圆缩小
+ startRecordAnimation(
+ button_outside_radius,
+ button_outside_radius + outside_add_size,
+ button_inside_radius,
+ button_inside_radius - inside_reduce_size
+ );
+ }
+ }
+
+
+ //设置最长录制时间
+ public void setDuration(int duration) {
+ this.duration = duration;
+ timer = new RecordCountDownTimer(duration, duration / 360); //录制定时器
+ }
+
+ //设置最短录制时间
+ public void setMinDuration(int duration) {
+ this.min_duration = duration;
+ }
+
+ //设置回调接口
+ public void setCaptureListener(CaptureListener captureListener) {
+ this.captureLisenter = captureListener;
+ }
+
+ //设置按钮功能(拍照和录像)
+ public void setButtonFeatures(int state) {
+ this.button_state = state;
+ }
+
+ //设置按钮功能(拍照和录像)
+ public int getButtonFeatures() {
+ return button_state;
+ }
+
+ //是否空闲状态
+ public boolean isIdle() {
+ return state == STATE_IDLE ? true : false;
+ }
+
+ //设置状态
+ public void resetState() {
+ state = STATE_IDLE;
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/camera/view/CaptureLayout.java b/picture_library/src/main/java/com/luck/picture/lib/camera/view/CaptureLayout.java
new file mode 100644
index 0000000..73a0086
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/camera/view/CaptureLayout.java
@@ -0,0 +1,365 @@
+package com.luck.picture.lib.camera.view;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.luck.picture.lib.R;
+import com.luck.picture.lib.camera.listener.CaptureListener;
+import com.luck.picture.lib.camera.listener.ClickListener;
+import com.luck.picture.lib.camera.listener.TypeListener;
+import com.luck.picture.lib.tools.DoubleUtils;
+
+import static com.luck.picture.lib.camera.CustomCameraView.BUTTON_STATE_ONLY_CAPTURE;
+import static com.luck.picture.lib.camera.CustomCameraView.BUTTON_STATE_ONLY_RECORDER;
+
+/**
+ * =====================================
+ * 作 者: 陈嘉桐 445263848@qq.com
+ * 版 本:1.0.4
+ * 创建日期:2017/4/26
+ * 描 述:集成各个控件的布局
+ * =====================================
+ */
+
+public class CaptureLayout extends FrameLayout {
+
+ private CaptureListener captureListener; //拍照按钮监听
+ private TypeListener typeListener; //拍照或录制后接结果按钮监听
+ private ClickListener leftClickListener; //左边按钮监听
+ private ClickListener rightClickListener; //右边按钮监听
+
+ public void setTypeListener(TypeListener typeListener) {
+ this.typeListener = typeListener;
+ }
+
+ public void setCaptureListener(CaptureListener captureListener) {
+ this.captureListener = captureListener;
+ }
+
+ private CaptureButton btn_capture; //拍照按钮
+ private TypeButton btn_confirm; //确认按钮
+ private TypeButton btn_cancel; //取消按钮
+ private ReturnButton btn_return; //返回按钮
+ private ImageView iv_custom_left; //左边自定义按钮
+ private ImageView iv_custom_right; //右边自定义按钮
+ private TextView txt_tip; //提示文本
+
+ private int layout_width;
+ private int layout_height;
+ private int button_size;
+ private int iconLeft = 0;
+ private int iconRight = 0;
+
+ public CaptureLayout(Context context) {
+ this(context, null);
+ }
+
+ public CaptureLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CaptureLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics outMetrics = new DisplayMetrics();
+ manager.getDefaultDisplay().getMetrics(outMetrics);
+
+ if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ layout_width = outMetrics.widthPixels;
+ } else {
+ layout_width = outMetrics.widthPixels / 2;
+ }
+ button_size = (int) (layout_width / 4.5f);
+ layout_height = button_size + (button_size / 5) * 2 + 100;
+
+ initView();
+ initEvent();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ setMeasuredDimension(layout_width, layout_height);
+ }
+
+ public void initEvent() {
+ //默认TypeButton为隐藏
+ iv_custom_right.setVisibility(GONE);
+ btn_cancel.setVisibility(GONE);
+ btn_confirm.setVisibility(GONE);
+ }
+
+ public void startTypeBtnAnimator() {
+ //拍照录制结果后的动画
+ if (this.iconLeft != 0)
+ iv_custom_left.setVisibility(GONE);
+ else
+ btn_return.setVisibility(GONE);
+ if (this.iconRight != 0)
+ iv_custom_right.setVisibility(GONE);
+ btn_capture.setVisibility(GONE);
+ btn_cancel.setVisibility(VISIBLE);
+ btn_confirm.setVisibility(VISIBLE);
+ btn_cancel.setClickable(false);
+ btn_confirm.setClickable(false);
+ iv_custom_left.setVisibility(GONE);
+ ObjectAnimator animator_cancel = ObjectAnimator.ofFloat(btn_cancel, "translationX", layout_width / 4, 0);
+ ObjectAnimator animator_confirm = ObjectAnimator.ofFloat(btn_confirm, "translationX", -layout_width / 4, 0);
+
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(animator_cancel, animator_confirm);
+ set.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ btn_cancel.setClickable(true);
+ btn_confirm.setClickable(true);
+ }
+ });
+ set.setDuration(500);
+ set.start();
+ }
+
+
+ private void initView() {
+ setWillNotDraw(false);
+ //拍照按钮
+ btn_capture = new CaptureButton(getContext(), button_size);
+ FrameLayout.LayoutParams btn_capture_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ btn_capture_param.gravity = Gravity.CENTER;
+ btn_capture.setLayoutParams(btn_capture_param);
+ btn_capture.setCaptureListener(new CaptureListener() {
+ @Override
+ public void takePictures() {
+ if (captureListener != null) {
+ captureListener.takePictures();
+ }
+ startAlphaAnimation();
+ }
+
+ @Override
+ public void recordShort(long time) {
+ if (captureListener != null) {
+ captureListener.recordShort(time);
+ }
+ }
+
+ @Override
+ public void recordStart() {
+ if (captureListener != null) {
+ captureListener.recordStart();
+ }
+ startAlphaAnimation();
+ }
+
+ @Override
+ public void recordEnd(long time) {
+ if (captureListener != null) {
+ captureListener.recordEnd(time);
+ }
+ startTypeBtnAnimator();
+ }
+
+ @Override
+ public void recordZoom(float zoom) {
+ if (captureListener != null) {
+ captureListener.recordZoom(zoom);
+ }
+ }
+
+ @Override
+ public void recordError() {
+ if (captureListener != null) {
+ captureListener.recordError();
+ }
+ }
+ });
+
+ //取消按钮
+ btn_cancel = new TypeButton(getContext(), TypeButton.TYPE_CANCEL, button_size);
+ final LayoutParams btn_cancel_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ btn_cancel_param.gravity = Gravity.CENTER_VERTICAL;
+ btn_cancel_param.setMargins((layout_width / 4) - button_size / 2, 0, 0, 0);
+ btn_cancel.setLayoutParams(btn_cancel_param);
+ btn_cancel.setOnClickListener(view -> {
+ if (typeListener != null) {
+ typeListener.cancel();
+ }
+ });
+
+ //确认按钮
+ btn_confirm = new TypeButton(getContext(), TypeButton.TYPE_CONFIRM, button_size);
+ LayoutParams btn_confirm_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ btn_confirm_param.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT;
+ btn_confirm_param.setMargins(0, 0, (layout_width / 4) - button_size / 2, 0);
+ btn_confirm.setLayoutParams(btn_confirm_param);
+ btn_confirm.setOnClickListener(view -> {
+ if (typeListener != null) {
+ typeListener.confirm();
+ }
+ });
+
+ //返回按钮
+ btn_return = new ReturnButton(getContext(), (int) (button_size / 2.5f));
+ LayoutParams btn_return_param = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ btn_return_param.gravity = Gravity.CENTER_VERTICAL;
+ btn_return_param.setMargins(layout_width / 6, 0, 0, 0);
+ btn_return.setLayoutParams(btn_return_param);
+ btn_return.setOnClickListener(v -> {
+ if (leftClickListener != null) {
+ leftClickListener.onClick();
+ }
+ });
+ //左边自定义按钮
+ iv_custom_left = new ImageView(getContext());
+ LayoutParams iv_custom_param_left = new LayoutParams((int) (button_size / 2.5f), (int) (button_size / 2.5f));
+ iv_custom_param_left.gravity = Gravity.CENTER_VERTICAL;
+ iv_custom_param_left.setMargins(layout_width / 6, 0, 0, 0);
+ iv_custom_left.setLayoutParams(iv_custom_param_left);
+ iv_custom_left.setOnClickListener(v -> {
+ if (leftClickListener != null) {
+ leftClickListener.onClick();
+ }
+ });
+
+ //右边自定义按钮
+ iv_custom_right = new ImageView(getContext());
+ LayoutParams iv_custom_param_right = new LayoutParams((int) (button_size / 2.5f), (int) (button_size / 2.5f));
+ iv_custom_param_right.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT;
+ iv_custom_param_right.setMargins(0, 0, layout_width / 6, 0);
+ iv_custom_right.setLayoutParams(iv_custom_param_right);
+ iv_custom_right.setOnClickListener(v -> {
+ if (rightClickListener != null) {
+ rightClickListener.onClick();
+ }
+ });
+
+ txt_tip = new TextView(getContext());
+ LayoutParams txt_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ txt_param.gravity = Gravity.CENTER_HORIZONTAL;
+ txt_param.setMargins(0, 0, 0, 0);
+
+ txt_tip.setText(getCaptureTip());
+
+ txt_tip.setTextColor(0xFFFFFFFF);
+ txt_tip.setGravity(Gravity.CENTER);
+ txt_tip.setLayoutParams(txt_param);
+
+ this.addView(btn_capture);
+ this.addView(btn_cancel);
+ this.addView(btn_confirm);
+ this.addView(btn_return);
+ this.addView(iv_custom_left);
+ this.addView(iv_custom_right);
+ this.addView(txt_tip);
+
+ }
+
+ private String getCaptureTip() {
+ int buttonFeatures = btn_capture.getButtonFeatures();
+ switch (buttonFeatures) {
+ case BUTTON_STATE_ONLY_CAPTURE:
+ return getContext().getString(R.string.picture_photo_pictures);
+ case BUTTON_STATE_ONLY_RECORDER:
+ return getContext().getString(R.string.picture_photo_recording);
+ default:
+ return getContext().getString(R.string.picture_photo_camera);
+ }
+ }
+
+ public void resetCaptureLayout() {
+ btn_capture.resetState();
+ btn_cancel.setVisibility(GONE);
+ btn_confirm.setVisibility(GONE);
+ btn_capture.setVisibility(VISIBLE);
+ txt_tip.setText(getCaptureTip());
+ txt_tip.setVisibility(View.VISIBLE);
+ if (this.iconLeft != 0)
+ iv_custom_left.setVisibility(VISIBLE);
+ else
+ btn_return.setVisibility(VISIBLE);
+ if (this.iconRight != 0)
+ iv_custom_right.setVisibility(VISIBLE);
+ }
+
+
+ public void startAlphaAnimation() {
+ txt_tip.setVisibility(View.INVISIBLE);
+ }
+
+ public void setTextWithAnimation(String tip) {
+ txt_tip.setText(tip);
+ ObjectAnimator animator_txt_tip = ObjectAnimator.ofFloat(txt_tip, "alpha", 0f, 1f, 1f, 0f);
+ animator_txt_tip.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ txt_tip.setText(getCaptureTip());
+ txt_tip.setAlpha(1f);
+ }
+ });
+ animator_txt_tip.setDuration(2500);
+ animator_txt_tip.start();
+ }
+
+ public void setDuration(int duration) {
+ btn_capture.setDuration(duration);
+ }
+
+ public void setMinDuration(int duration) {
+ btn_capture.setMinDuration(duration);
+ }
+
+ public void setButtonFeatures(int state) {
+ btn_capture.setButtonFeatures(state);
+ txt_tip.setText(getCaptureTip());
+ }
+
+ public void setTip(String tip) {
+ txt_tip.setText(tip);
+ }
+
+ public void showTip() {
+ txt_tip.setVisibility(VISIBLE);
+ }
+
+ public void setIconSrc(int iconLeft, int iconRight) {
+ this.iconLeft = iconLeft;
+ this.iconRight = iconRight;
+ if (this.iconLeft != 0) {
+ iv_custom_left.setImageResource(iconLeft);
+ iv_custom_left.setVisibility(VISIBLE);
+ btn_return.setVisibility(GONE);
+ } else {
+ iv_custom_left.setVisibility(GONE);
+ btn_return.setVisibility(VISIBLE);
+ }
+ if (this.iconRight != 0) {
+ iv_custom_right.setImageResource(iconRight);
+ iv_custom_right.setVisibility(VISIBLE);
+ } else {
+ iv_custom_right.setVisibility(GONE);
+ }
+ }
+
+ public void setLeftClickListener(ClickListener leftClickListener) {
+ this.leftClickListener = leftClickListener;
+ }
+
+ public void setRightClickListener(ClickListener rightClickListener) {
+ this.rightClickListener = rightClickListener;
+ }
+}
\ No newline at end of file
diff --git a/picture_library/src/main/java/com/luck/picture/lib/camera/view/ReturnButton.java b/picture_library/src/main/java/com/luck/picture/lib/camera/view/ReturnButton.java
new file mode 100644
index 0000000..f811d63
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/camera/view/ReturnButton.java
@@ -0,0 +1,63 @@
+package com.luck.picture.lib.camera.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.view.View;
+
+/**
+ * =====================================
+ * 作 者: 陈嘉桐 445263848@qq.com
+ * 版 本:1.0.4
+ * 创建日期:2017/4/26
+ * 描 述:向下箭头的退出按钮
+ * =====================================
+ */
+public class ReturnButton extends View {
+
+ private int size;
+
+ private int center_X;
+ private int center_Y;
+ private float strokeWidth;
+
+ private Paint paint;
+ Path path;
+
+ public ReturnButton(Context context, int size) {
+ this(context);
+ this.size = size;
+ center_X = size / 2;
+ center_Y = size / 2;
+
+ strokeWidth = size / 15f;
+
+ paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setColor(Color.WHITE);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeWidth(strokeWidth);
+
+ path = new Path();
+ }
+
+ public ReturnButton(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(size, size / 2);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ path.moveTo(strokeWidth, strokeWidth / 2);
+ path.lineTo(center_X, center_Y - strokeWidth / 2);
+ path.lineTo(size - strokeWidth, strokeWidth / 2);
+ canvas.drawPath(path, paint);
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/camera/view/TypeButton.java b/picture_library/src/main/java/com/luck/picture/lib/camera/view/TypeButton.java
new file mode 100644
index 0000000..bcab339
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/camera/view/TypeButton.java
@@ -0,0 +1,109 @@
+package com.luck.picture.lib.camera.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.view.View;
+
+/**
+ * =====================================
+ * 作 者: 陈嘉桐 445263848@qq.com
+ * 版 本:1.0.4
+ * 创建日期:2017/4/26
+ * 描 述:拍照或录制完成后弹出的确认和返回按钮
+ * =====================================
+ */
+public class TypeButton extends View{
+ public static final int TYPE_CANCEL = 0x001;
+ public static final int TYPE_CONFIRM = 0x002;
+ private int button_type;
+ private int button_size;
+
+ private float center_X;
+ private float center_Y;
+ private float button_radius;
+
+ private Paint mPaint;
+ private Path path;
+ private float strokeWidth;
+
+ private float index;
+ private RectF rectF;
+
+ public TypeButton(Context context) {
+ super(context);
+ }
+
+ public TypeButton(Context context, int type, int size) {
+ super(context);
+ this.button_type = type;
+ button_size = size;
+ button_radius = size / 2.0f;
+ center_X = size / 2.0f;
+ center_Y = size / 2.0f;
+
+ mPaint = new Paint();
+ path = new Path();
+ strokeWidth = size / 50f;
+ index = button_size / 12f;
+ rectF = new RectF(center_X, center_Y - index, center_X + index * 2, center_Y + index);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ setMeasuredDimension(button_size, button_size);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ //如果类型为取消,则绘制内部为返回箭头
+ if (button_type == TYPE_CANCEL) {
+ mPaint.setAntiAlias(true);
+ mPaint.setColor(0xEEDCDCDC);
+ mPaint.setStyle(Paint.Style.FILL);
+ canvas.drawCircle(center_X, center_Y, button_radius, mPaint);
+
+ mPaint.setColor(Color.BLACK);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(strokeWidth);
+
+ path.moveTo(center_X - index / 7, center_Y + index);
+ path.lineTo(center_X + index, center_Y + index);
+
+ path.arcTo(rectF, 90, -180);
+ path.lineTo(center_X - index, center_Y - index);
+ canvas.drawPath(path, mPaint);
+ mPaint.setStyle(Paint.Style.FILL);
+ path.reset();
+ path.moveTo(center_X - index, (float) (center_Y - index * 1.5));
+ path.lineTo(center_X - index, (float) (center_Y - index / 2.3));
+ path.lineTo((float) (center_X - index * 1.6), center_Y - index);
+ path.close();
+ canvas.drawPath(path, mPaint);
+
+ }
+ //如果类型为确认,则绘制绿色勾
+ if (button_type == TYPE_CONFIRM) {
+ mPaint.setAntiAlias(true);
+ mPaint.setColor(0xFFFFFFFF);
+ mPaint.setStyle(Paint.Style.FILL);
+ canvas.drawCircle(center_X, center_Y, button_radius, mPaint);
+ mPaint.setAntiAlias(true);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setColor(0xFF00CC00);
+ mPaint.setStrokeWidth(strokeWidth);
+
+ path.moveTo(center_X - button_size / 6f, center_Y);
+ path.lineTo(center_X - button_size / 21.2f, center_Y + button_size / 7.7f);
+ path.lineTo(center_X + button_size / 4.0f, center_Y - button_size / 8.5f);
+ path.lineTo(center_X - button_size / 21.2f, center_Y + button_size / 9.4f);
+ path.close();
+ canvas.drawPath(path, mPaint);
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/compress/Checker.java b/picture_library/src/main/java/com/luck/picture/lib/compress/Checker.java
new file mode 100644
index 0000000..02b75a6
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/compress/Checker.java
@@ -0,0 +1,237 @@
+package com.luck.picture.lib.compress;
+
+import android.graphics.BitmapFactory;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+enum Checker {
+ SINGLE;
+
+ public final static String MIME_TYPE_JPEG = "image/jpeg";
+
+ public final static String MIME_TYPE_JPG = "image/jpg";
+
+ private static final String TAG = "Luban";
+
+ private static final String JPG = ".jpg";
+
+ private final byte[] JPEG_SIGNATURE = new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF};
+
+ /**
+ * Determine if it is JPG.
+ *
+ * @param is image file input stream
+ */
+ boolean isJPG(InputStream is) {
+ return isJPG(toByteArray(is));
+ }
+
+ /**
+ * Determine if it is JPG.
+ *
+ * @param is image file mimeType
+ */
+ boolean isJPG(String mimeType) {
+ if (TextUtils.isEmpty(mimeType)) {
+ return false;
+ }
+ return mimeType.startsWith(MIME_TYPE_JPEG) || mimeType.startsWith(MIME_TYPE_JPG);
+ }
+
+ /**
+ * Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
+ */
+ int getOrientation(InputStream is) {
+ return getOrientation(toByteArray(is));
+ }
+
+ private boolean isJPG(byte[] data) {
+ if (data == null || data.length < 3) {
+ return false;
+ }
+ byte[] signatureB = new byte[]{data[0], data[1], data[2]};
+ return Arrays.equals(JPEG_SIGNATURE, signatureB);
+ }
+
+ private int getOrientation(byte[] jpeg) {
+ if (jpeg == null) {
+ return 0;
+ }
+
+ int offset = 0;
+ int length = 0;
+
+ // ISO/IEC 10918-1:1993(E)
+ while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
+ int marker = jpeg[offset] & 0xFF;
+
+ // Check if the marker is a padding.
+ if (marker == 0xFF) {
+ continue;
+ }
+ offset++;
+
+ // Check if the marker is SOI or TEM.
+ if (marker == 0xD8 || marker == 0x01) {
+ continue;
+ }
+ // Check if the marker is EOI or SOS.
+ if (marker == 0xD9 || marker == 0xDA) {
+ break;
+ }
+
+ // Get the length and check if it is reasonable.
+ length = pack(jpeg, offset, 2, false);
+ if (length < 2 || offset + length > jpeg.length) {
+ Log.e(TAG, "Invalid length");
+ return 0;
+ }
+
+ // Break if the marker is EXIF in APP1.
+ if (marker == 0xE1 && length >= 8
+ && pack(jpeg, offset + 2, 4, false) == 0x45786966
+ && pack(jpeg, offset + 6, 2, false) == 0) {
+ offset += 8;
+ length -= 8;
+ break;
+ }
+
+ // Skip other markers.
+ offset += length;
+ length = 0;
+ }
+
+ // JEITA CP-3451 Exif Version 2.2
+ if (length > 8) {
+ // Identify the byte order.
+ int tag = pack(jpeg, offset, 4, false);
+ if (tag != 0x49492A00 && tag != 0x4D4D002A) {
+ Log.e(TAG, "Invalid byte order");
+ return 0;
+ }
+ boolean littleEndian = (tag == 0x49492A00);
+
+ // Get the offset and check if it is reasonable.
+ int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
+ if (count < 10 || count > length) {
+ Log.e(TAG, "Invalid offset");
+ return 0;
+ }
+ offset += count;
+ length -= count;
+
+ // Get the count and go through all the elements.
+ count = pack(jpeg, offset - 2, 2, littleEndian);
+ while (count-- > 0 && length >= 12) {
+ // Get the tag and check if it is orientation.
+ tag = pack(jpeg, offset, 2, littleEndian);
+ if (tag == 0x0112) {
+ int orientation = pack(jpeg, offset + 8, 2, littleEndian);
+ switch (orientation) {
+ case 1:
+ return 0;
+ case 3:
+ return 180;
+ case 6:
+ return 90;
+ case 8:
+ return 270;
+ }
+ Log.e(TAG, "Unsupported orientation");
+ return 0;
+ }
+ offset += 12;
+ length -= 12;
+ }
+ }
+
+ Log.e(TAG, "Orientation not found");
+ return 0;
+ }
+
+ String extSuffix(InputStreamProvider input) {
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(input.open(), null, options);
+ return options.outMimeType.replace("image/", ".");
+ } catch (Exception e) {
+ return JPG;
+ }
+ }
+
+ String extSuffix(String mimeType) {
+ try {
+ if (TextUtils.isEmpty(mimeType)) {
+ return JPG;
+ }
+ return mimeType.startsWith("video") ? mimeType.replace("video/", ".")
+ : mimeType.replace("image/", ".");
+ } catch (Exception e) {
+ return JPG;
+ }
+ }
+
+ boolean needCompress(int leastCompressSize, String path) {
+ if (leastCompressSize > 0) {
+ File source = new File(path);
+ return source.exists() && source.length() > (leastCompressSize << 10);
+ }
+ return true;
+ }
+
+ boolean needCompressToLocalMedia(int leastCompressSize, String path) {
+ if (leastCompressSize > 0 && !TextUtils.isEmpty(path)) {
+ File source = new File(path);
+ return source.exists() && source.length() > (leastCompressSize << 10);
+ }
+ return true;
+ }
+
+ private int pack(byte[] bytes, int offset, int length, boolean littleEndian) {
+ int step = 1;
+ if (littleEndian) {
+ offset += length - 1;
+ step = -1;
+ }
+
+ int value = 0;
+ while (length-- > 0) {
+ value = (value << 8) | (bytes[offset] & 0xFF);
+ offset += step;
+ }
+ return value;
+ }
+
+ private byte[] toByteArray(InputStream is) {
+ if (is == null) {
+ return new byte[0];
+ }
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ int read;
+ byte[] data = new byte[4096];
+
+ try {
+ while ((read = is.read(data, 0, data.length)) != -1) {
+ buffer.write(data, 0, read);
+ }
+ } catch (Exception ignored) {
+ return new byte[0];
+ } finally {
+ try {
+ buffer.close();
+ } catch (IOException ignored) {
+ }
+ }
+
+ return buffer.toByteArray();
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/compress/CompressionPredicate.java b/picture_library/src/main/java/com/luck/picture/lib/compress/CompressionPredicate.java
new file mode 100644
index 0000000..8e6ceeb
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/compress/CompressionPredicate.java
@@ -0,0 +1,19 @@
+package com.luck.picture.lib.compress;
+
+/**
+ * Created on 2018/1/3 19:43
+ *
+ * @author andy
+ *
+ * A functional interface (callback) that returns true or false for the given input path should be compressed.
+ */
+
+public interface CompressionPredicate {
+
+ /**
+ * Determine the given input path should be compressed and return a boolean.
+ * @param path input path
+ * @return the boolean result
+ */
+ boolean apply(String path);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/compress/Engine.java b/picture_library/src/main/java/com/luck/picture/lib/compress/Engine.java
new file mode 100644
index 0000000..e590a86
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/compress/Engine.java
@@ -0,0 +1,117 @@
+package com.luck.picture.lib.compress;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.media.ExifInterface;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Responsible for starting compress and managing active and cached resources.
+ */
+class Engine {
+ private InputStreamProvider srcImg;
+ private File tagImg;
+ private int srcWidth;
+ private int srcHeight;
+ private boolean focusAlpha;
+ private static final int DEFAULT_QUALITY = 80;
+ private int compressQuality;
+
+ Engine(InputStreamProvider srcImg, File tagImg, boolean focusAlpha, int compressQuality) throws IOException {
+ this.tagImg = tagImg;
+ this.srcImg = srcImg;
+ this.focusAlpha = focusAlpha;
+ this.compressQuality = compressQuality <= 0 ? DEFAULT_QUALITY : compressQuality;
+
+ if (srcImg.getMedia() != null
+ && srcImg.getMedia().getWidth() > 0
+ && srcImg.getMedia().getHeight() > 0) {
+ this.srcWidth = srcImg.getMedia().getWidth();
+ this.srcHeight = srcImg.getMedia().getHeight();
+ } else {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ options.inSampleSize = 1;
+ BitmapFactory.decodeStream(srcImg.open(), null, options);
+ this.srcWidth = options.outWidth;
+ this.srcHeight = options.outHeight;
+ }
+ }
+
+ private int computeSize() {
+ srcWidth = srcWidth % 2 == 1 ? srcWidth + 1 : srcWidth;
+ srcHeight = srcHeight % 2 == 1 ? srcHeight + 1 : srcHeight;
+
+ int longSide = Math.max(srcWidth, srcHeight);
+ int shortSide = Math.min(srcWidth, srcHeight);
+
+ float scale = ((float) shortSide / longSide);
+ if (scale <= 1 && scale > 0.5625) {
+ if (longSide < 1664) {
+ return 1;
+ } else if (longSide < 4990) {
+ return 2;
+ } else if (longSide > 4990 && longSide < 10240) {
+ return 4;
+ } else {
+ return longSide / 1280;
+ }
+ } else if (scale <= 0.5625 && scale > 0.5) {
+ return longSide / 1280 == 0 ? 1 : longSide / 1280;
+ } else {
+ return (int) Math.ceil(longSide / (1280.0 / scale));
+ }
+ }
+
+ private Bitmap rotatingImage(Bitmap bitmap, int angle) {
+ Matrix matrix = new Matrix();
+
+ matrix.postRotate(angle);
+
+ return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+ }
+
+ File compress() throws IOException {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = computeSize();
+ Bitmap tagBitmap = BitmapFactory.decodeStream(srcImg.open(), null, options);
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ if (srcImg.getMedia() != null && !srcImg.getMedia().isCut()) {
+ if (Checker.SINGLE.isJPG(srcImg.getMedia().getMimeType())) {
+ int orientation = srcImg.getMedia().getOrientation();
+ if (orientation > 0) {
+ switch (orientation) {
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ orientation = 90;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ orientation = 180;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ orientation = 270;
+ break;
+ default:
+ break;
+ }
+ tagBitmap = rotatingImage(tagBitmap, orientation);
+ }
+ }
+ }
+ if (tagBitmap != null) {
+ compressQuality = compressQuality <= 0 || compressQuality > 100 ? DEFAULT_QUALITY : compressQuality;
+ tagBitmap.compress(focusAlpha ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG, compressQuality, stream);
+ tagBitmap.recycle();
+ }
+ FileOutputStream fos = new FileOutputStream(tagImg);
+ fos.write(stream.toByteArray());
+ fos.flush();
+ fos.close();
+ stream.close();
+ return tagImg;
+ }
+}
\ No newline at end of file
diff --git a/picture_library/src/main/java/com/luck/picture/lib/compress/InputStreamAdapter.java b/picture_library/src/main/java/com/luck/picture/lib/compress/InputStreamAdapter.java
new file mode 100644
index 0000000..607ce30
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/compress/InputStreamAdapter.java
@@ -0,0 +1,34 @@
+package com.luck.picture.lib.compress;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Automatically close the previous InputStream when opening a new InputStream,
+ * and finally need to manually call {@link #close()} to release the resource.
+ */
+public abstract class InputStreamAdapter implements InputStreamProvider {
+
+ private InputStream inputStream;
+
+ @Override
+ public InputStream open() throws IOException {
+ close();
+ inputStream = openInternal();
+ return inputStream;
+ }
+
+ public abstract InputStream openInternal() throws IOException;
+
+ @Override
+ public void close() {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException ignore) {
+ }finally {
+ inputStream = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/picture_library/src/main/java/com/luck/picture/lib/compress/InputStreamProvider.java b/picture_library/src/main/java/com/luck/picture/lib/compress/InputStreamProvider.java
new file mode 100644
index 0000000..b3d1b4b
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/compress/InputStreamProvider.java
@@ -0,0 +1,23 @@
+package com.luck.picture.lib.compress;
+
+import com.luck.picture.lib.entity.LocalMedia;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 通过此接口获取输入流,以兼容文件、FileProvider方式获取到的图片
+ *
+ * Get the input stream through this interface, and obtain the picture using compatible files and FileProvider
+ */
+public interface InputStreamProvider {
+
+ InputStream open() throws IOException;
+
+ void close();
+
+ String getPath();
+
+ LocalMedia getMedia();
+
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/compress/Luban.java b/picture_library/src/main/java/com/luck/picture/lib/compress/Luban.java
new file mode 100644
index 0000000..d01fa46
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/compress/Luban.java
@@ -0,0 +1,638 @@
+package com.luck.picture.lib.compress;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.tools.AndroidQTransformUtils;
+import com.luck.picture.lib.tools.DateUtils;
+import com.luck.picture.lib.tools.SdkVersionUtils;
+import com.luck.picture.lib.tools.StringUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class Luban implements Handler.Callback {
+ private static final String TAG = "Luban";
+
+ private static final int MSG_COMPRESS_SUCCESS = 0;
+ private static final int MSG_COMPRESS_START = 1;
+ private static final int MSG_COMPRESS_ERROR = 2;
+
+ private String mTargetDir;
+ private String mNewFileName;
+ private boolean focusAlpha;
+ private boolean isCamera;
+ private int mLeastCompressSize;
+ private OnRenameListener mRenameListener;
+ private OnCompressListener mCompressListener;
+ private CompressionPredicate mCompressionPredicate;
+ private List mStreamProviders;
+ private List mPaths;
+ private List mediaList;
+ private int index = -1;
+ private int compressQuality;
+ private Handler mHandler;
+ private int dataCount;
+
+ private Luban(Builder builder) {
+ this.mPaths = builder.mPaths;
+ this.mediaList = builder.mediaList;
+ this.dataCount = builder.dataCount;
+ this.mTargetDir = builder.mTargetDir;
+ this.mNewFileName = builder.mNewFileName;
+ this.mRenameListener = builder.mRenameListener;
+ this.mStreamProviders = builder.mStreamProviders;
+ this.mCompressListener = builder.mCompressListener;
+ this.mLeastCompressSize = builder.mLeastCompressSize;
+ this.mCompressionPredicate = builder.mCompressionPredicate;
+ this.compressQuality = builder.compressQuality;
+ this.focusAlpha = builder.focusAlpha;
+ this.isCamera = builder.isCamera;
+ this.mHandler = new Handler(Looper.getMainLooper(), this);
+ }
+
+ public static Builder with(Context context) {
+ return new Builder(context);
+ }
+
+ /**
+ * Returns a file with a cache image name in the private cache directory.
+ *
+ * @param context A context.
+ */
+ private File getImageCacheFile(Context context, InputStreamProvider provider, String suffix) {
+ if (TextUtils.isEmpty(mTargetDir)) {
+ File imageCacheDir = getImageCacheDir(context);
+ if (imageCacheDir != null) {
+ mTargetDir = imageCacheDir.getAbsolutePath();
+ }
+ }
+ String cacheBuilder = "";
+ try {
+ LocalMedia media = provider.getMedia();
+ String encryptionValue = StringUtils.getEncryptionValue(media.getPath(), media.getWidth(), media.getHeight());
+ if (!TextUtils.isEmpty(encryptionValue) && !media.isCut()) {
+ cacheBuilder = mTargetDir + "/" +
+ "IMG_CMP_" +
+ encryptionValue +
+ (TextUtils.isEmpty(suffix) ? ".jpg" : suffix);
+ } else {
+ cacheBuilder = mTargetDir +
+ "/" +
+ DateUtils.getCreateFileName("IMG_CMP_") +
+ (TextUtils.isEmpty(suffix) ? ".jpg" : suffix);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return new File(cacheBuilder);
+ }
+
+ private File getImageCustomFile(Context context, String filename) {
+ if (TextUtils.isEmpty(mTargetDir)) {
+ mTargetDir = getImageCacheDir(context).getAbsolutePath();
+ }
+
+ String cacheBuilder = mTargetDir + "/" + filename;
+
+ return new File(cacheBuilder);
+ }
+
+ /**
+ * Returns a directory with the given name in the private cache directory of the application to
+ * use to store retrieved media and thumbnails.
+ *
+ * @param context A context.
+ * @see #getImageCacheDir(Context)
+ */
+ private static File getImageCacheDir(Context context) {
+ File cacheDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+ if (cacheDir != null) {
+ if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) {
+ // File wasn't able to create a directory, or the result exists but not a directory
+ return null;
+ }
+ return cacheDir;
+ }
+ if (Log.isLoggable(TAG, Log.ERROR)) {
+ Log.e(TAG, "default disk cache dir is null");
+ }
+ return null;
+ }
+
+ /**
+ * start asynchronous compress thread
+ */
+ private void launch(final Context context) {
+ if (mStreamProviders == null || mPaths == null || mStreamProviders.size() == 0 && mCompressListener != null) {
+ mCompressListener.onError(new NullPointerException("image file cannot be null"));
+ }
+ Iterator iterator = mStreamProviders.iterator();
+ // 当前压缩下标
+ index = -1;
+ while (iterator.hasNext()) {
+ final InputStreamProvider path = iterator.next();
+ AsyncTask.SERIAL_EXECUTOR.execute(() -> {
+ try {
+ index++;
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_START));
+ String newPath;
+ if (path.open() != null) {
+ if (path.getMedia().isCompressed()
+ && !TextUtils.isEmpty(path.getMedia().getCompressPath())) {
+ // 压缩过的图片不重复压缩 注意:如果是开启了裁剪 就算压缩过也要重新压缩
+ boolean exists = !path.getMedia().isCut() && new File(path.getMedia().getCompressPath()).exists();
+
+ File result = exists ? new File(path.getMedia().getCompressPath())
+ : compress(context, path);
+ newPath = result.getAbsolutePath();
+ } else {
+ File result = PictureMimeType.isHasVideo(path.getMedia().getMimeType())
+ ? new File(path.getPath()) : compress(context, path);
+ newPath = result.getAbsolutePath();
+ }
+ } else {
+ // error
+ newPath = path.getPath();
+ }
+ if (mediaList != null && mediaList.size() > 0) {
+ LocalMedia media = mediaList.get(index);
+ boolean isHasHttp = PictureMimeType.isHasHttp(newPath);
+ boolean isHasVideo = PictureMimeType.isHasVideo(media.getMimeType());
+ media.setCompressed(!isHasHttp && !isHasVideo);
+ media.setCompressPath(isHasHttp || isHasVideo ? null : newPath);
+ media.setAndroidQToPath(SdkVersionUtils.checkedAndroid_Q() ? media.getCompressPath() : null);
+ boolean isLast = index == mediaList.size() - 1;
+ if (isLast) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_SUCCESS, mediaList));
+ }
+ } else {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_ERROR, new IOException()));
+ }
+ } catch (IOException e) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_ERROR, e));
+ }
+ });
+
+ iterator.remove();
+ }
+ }
+
+ /**
+ * start compress and return the file
+ */
+ private File get(InputStreamProvider input, Context context) throws IOException {
+ try {
+ return new Engine(input, getImageCacheFile(context, input, Checker.SINGLE.extSuffix(input)), focusAlpha, compressQuality).compress();
+ } finally {
+ input.close();
+ }
+ }
+
+ private List get(Context context) throws IOException {
+ List results = new ArrayList<>();
+ Iterator iterator = mStreamProviders.iterator();
+
+ while (iterator.hasNext()) {
+ InputStreamProvider provider = iterator.next();
+ InputStream inputStream = provider.open();
+ if (inputStream != null) {
+ if (provider.getMedia().isCompressed()
+ && !TextUtils.isEmpty(provider.getMedia().getCompressPath())) {
+ // 压缩过的图片不重复压缩 注意:如果是开启了裁剪 就算压缩过也要重新压缩
+ boolean exists = !provider.getMedia().isCut() && new File(provider.getMedia().getCompressPath()).exists();
+ File oldFile = exists ? new File(provider.getMedia().getCompressPath())
+ : compress(context, provider);
+ results.add(oldFile);
+ } else {
+ boolean hasVideo = PictureMimeType.isHasVideo(provider.getMedia().getMimeType());
+ results.add(hasVideo ? new File(provider.getMedia().getPath()) : compress(context, provider));
+ }
+ } else {
+ // error
+ results.add(new File(provider.getMedia().getPath()));
+ }
+ iterator.remove();
+ }
+
+ return results;
+ }
+
+ private File compress(Context context, InputStreamProvider path) throws IOException {
+ try {
+ return compressRealLocalMedia(context, path);
+ } finally {
+ path.close();
+ }
+ }
+
+ private File compressReal(Context context, InputStreamProvider path) throws IOException {
+ File result;
+ String suffix = Checker.SINGLE.extSuffix(path.getMedia() != null ? path.getMedia().getMimeType() : "");
+ File outFile = getImageCacheFile(context, path, TextUtils.isEmpty(suffix) ? Checker.SINGLE.extSuffix(path) : suffix);
+ if (mRenameListener != null) {
+ String filename = mRenameListener.rename(path.getPath());
+ outFile = getImageCustomFile(context, filename);
+ }
+
+ if (mCompressionPredicate != null) {
+ if (mCompressionPredicate.apply(path.getPath())
+ && Checker.SINGLE.needCompress(mLeastCompressSize, path.getPath())) {
+ result = new Engine(path, outFile, focusAlpha, compressQuality).compress();
+ } else {
+ result = new File(path.getPath());
+ }
+ } else {
+ if (Checker.SINGLE.extSuffix(path).startsWith(".gif")) {
+ // GIF without compression
+ result = new File(path.getPath());
+ } else {
+ result = Checker.SINGLE.needCompress(mLeastCompressSize, path.getPath()) ?
+ new Engine(path, outFile, focusAlpha, compressQuality).compress() :
+ new File(path.getPath());
+ }
+ }
+ return result;
+ }
+
+ private File compressRealLocalMedia(Context context, InputStreamProvider path) throws IOException {
+ File result = null;
+ LocalMedia media = path.getMedia();
+ if (media == null) {
+ throw new NullPointerException("Luban Compress LocalMedia Can't be empty");
+ }
+ String newPath = media.isCut() && !TextUtils.isEmpty(media.getCutPath()) ? media.getCutPath() : media.getRealPath();
+ String suffix = Checker.SINGLE.extSuffix(media.getMimeType());
+ File outFile = getImageCacheFile(context, path, TextUtils.isEmpty(suffix) ? Checker.SINGLE.extSuffix(path) : suffix);
+ String filename = "";
+ if (!TextUtils.isEmpty(mNewFileName)) {
+ filename = isCamera || dataCount == 1 ? mNewFileName : StringUtils.rename(mNewFileName);
+ outFile = getImageCustomFile(context, filename);
+ }
+ // 如果文件存在直接返回不处理
+ if (outFile.exists()) {
+ return outFile;
+ }
+
+ if (mCompressionPredicate != null) {
+ if (Checker.SINGLE.extSuffix(path).startsWith(".gif")) {
+ // GIF without compression
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ if (media.isCut() && !TextUtils.isEmpty(media.getCutPath())) {
+ result = new File(media.getCutPath());
+ } else {
+ String androidQToPath = AndroidQTransformUtils.copyPathToAndroidQ(context, path.getPath(),
+ media.getWidth(), media.getHeight(), media.getMimeType(), filename);
+ if (!TextUtils.isEmpty(androidQToPath)) {
+ result = new File(androidQToPath);
+ }
+ }
+ } else {
+ result = new File(newPath);
+ }
+ } else {
+ boolean isCompress = Checker.SINGLE.needCompressToLocalMedia(mLeastCompressSize, newPath);
+ if (mCompressionPredicate.apply(newPath) && isCompress) {
+ // 压缩
+ result = new Engine(path, outFile, focusAlpha, compressQuality).compress();
+ } else {
+ if (isCompress) {
+ // 压缩
+ result = new Engine(path, outFile, focusAlpha, compressQuality).compress();
+ }
+ }
+ }
+ } else {
+ if (Checker.SINGLE.extSuffix(path).startsWith(".gif")) {
+ // GIF without compression
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ String newFilePath = media.isCut() ? media.getCutPath() :
+ AndroidQTransformUtils.copyPathToAndroidQ(context,
+ path.getPath(), media.getWidth(), media.getHeight(), media.getMimeType(), filename);
+ if (!TextUtils.isEmpty(newFilePath)) {
+ result = new File(newFilePath);
+ }
+ } else {
+ result = new File(newPath);
+ }
+ } else {
+ boolean isCompress = Checker.SINGLE.needCompressToLocalMedia(mLeastCompressSize, newPath);
+ if (isCompress) {
+ // 压缩
+ result = new Engine(path, outFile, focusAlpha, compressQuality).compress();
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (mCompressListener == null) return false;
+
+ switch (msg.what) {
+ case MSG_COMPRESS_START:
+ mCompressListener.onStart();
+ break;
+ case MSG_COMPRESS_SUCCESS:
+ mCompressListener.onSuccess((List) msg.obj);
+ break;
+ case MSG_COMPRESS_ERROR:
+ mCompressListener.onError((Throwable) msg.obj);
+ break;
+ }
+ return false;
+ }
+
+ public static class Builder {
+ private Context context;
+ private String mTargetDir;
+ private String mNewFileName;
+ private boolean focusAlpha;
+ private boolean isCamera;
+ private int compressQuality;
+ private int mLeastCompressSize = 100;
+ private OnRenameListener mRenameListener;
+ private OnCompressListener mCompressListener;
+ private CompressionPredicate mCompressionPredicate;
+ private List mStreamProviders;
+ private List mPaths;
+ private List mediaList;
+ private int dataCount;
+ private boolean isAndroidQ;
+
+ Builder(Context context) {
+ this.context = context;
+ this.mPaths = new ArrayList<>();
+ this.mediaList = new ArrayList<>();
+ this.mStreamProviders = new ArrayList<>();
+ this.isAndroidQ = SdkVersionUtils.checkedAndroid_Q();
+ }
+
+ private Luban build() {
+ return new Luban(this);
+ }
+
+ public Builder load(InputStreamProvider inputStreamProvider) {
+ mStreamProviders.add(inputStreamProvider);
+ return this;
+ }
+
+ /**
+ * 扩展符合PictureSelector的压缩策略
+ *
+ * @param list LocalMedia集合
+ * @param
+ * @return
+ */
+ public Builder loadMediaData(List list) {
+ this.mediaList = list;
+ this.dataCount = list.size();
+ for (LocalMedia src : list) {
+ load(src);
+ }
+ return this;
+ }
+
+
+ /**
+ * 扩展符合PictureSelector的压缩策略
+ *
+ * @param media LocalMedia对象
+ * @param
+ * @return
+ */
+ private Builder load(final LocalMedia media) {
+ mStreamProviders.add(new InputStreamAdapter() {
+ @Override
+ public InputStream openInternal() throws IOException {
+ if (PictureMimeType.isContent(media.getPath()) && !media.isCut()) {
+ if (!TextUtils.isEmpty(media.getAndroidQToPath())) {
+ return new FileInputStream(media.getAndroidQToPath());
+ }
+ return context.getContentResolver().openInputStream(Uri.parse(media.getPath()));
+ } else {
+ return PictureMimeType.isHasHttp(media.getPath()) ? null : new FileInputStream(media.isCut() ? media.getCutPath() : media.getPath());
+ }
+ }
+
+ @Override
+ public String getPath() {
+ if (media.isCut()) {
+ return media.getCutPath();
+ } else {
+ return TextUtils.isEmpty(media.getAndroidQToPath()) ? media.getPath() : media.getAndroidQToPath();
+ }
+ }
+
+ @Override
+ public LocalMedia getMedia() {
+ return media;
+ }
+ });
+ return this;
+ }
+
+ public Builder load(final Uri uri) {
+ mStreamProviders.add(new InputStreamAdapter() {
+ @Override
+ public InputStream openInternal() throws IOException {
+ return context.getContentResolver().openInputStream(uri);
+ }
+
+ @Override
+ public String getPath() {
+ return uri.getPath();
+ }
+
+ @Override
+ public LocalMedia getMedia() {
+ return null;
+ }
+ });
+ return this;
+ }
+
+ public Builder load(final File file) {
+ mStreamProviders.add(new InputStreamAdapter() {
+ @Override
+ public InputStream openInternal() throws IOException {
+ return new FileInputStream(file);
+ }
+
+ @Override
+ public String getPath() {
+ return file.getAbsolutePath();
+ }
+
+ @Override
+ public LocalMedia getMedia() {
+ return null;
+ }
+
+ });
+ return this;
+ }
+
+ public Builder load(final String string) {
+ mStreamProviders.add(new InputStreamAdapter() {
+ @Override
+ public InputStream openInternal() throws IOException {
+ return new FileInputStream(string);
+ }
+
+ @Override
+ public String getPath() {
+ return string;
+ }
+
+ @Override
+ public LocalMedia getMedia() {
+ return null;
+ }
+
+ });
+ return this;
+ }
+
+ public Builder load(List list) {
+ for (T src : list) {
+ if (src instanceof String) {
+ load((String) src);
+ } else if (src instanceof File) {
+ load((File) src);
+ } else if (src instanceof Uri) {
+ load((Uri) src);
+ } else {
+ throw new IllegalArgumentException("Incoming data type exception, it must be String, File, Uri or Bitmap");
+ }
+ }
+ return this;
+ }
+
+ public Builder putGear(int gear) {
+ return this;
+ }
+
+ @Deprecated
+ public Builder setRenameListener(OnRenameListener listener) {
+ this.mRenameListener = listener;
+ return this;
+ }
+
+ public Builder setCompressListener(OnCompressListener listener) {
+ this.mCompressListener = listener;
+ return this;
+ }
+
+ public Builder setTargetDir(String targetDir) {
+ this.mTargetDir = targetDir;
+ return this;
+ }
+
+ public Builder setNewCompressFileName(String newFileName) {
+ this.mNewFileName = newFileName;
+ return this;
+ }
+
+ public Builder isCamera(boolean isCamera) {
+ this.isCamera = isCamera;
+ return this;
+ }
+
+ /**
+ * Do I need to keep the image's alpha channel
+ *
+ * @param focusAlpha true - to keep alpha channel, the compress speed will be slow.
+ * false - don't keep alpha channel, it might have a black background.
+ */
+ public Builder setFocusAlpha(boolean focusAlpha) {
+ this.focusAlpha = focusAlpha;
+ return this;
+ }
+
+ /**
+ * Image compressed output quality
+ *
+ * @param compressQuality The quality is better than
+ */
+ public Builder setCompressQuality(int compressQuality) {
+ this.compressQuality = compressQuality;
+ return this;
+ }
+
+
+ /**
+ * do not compress when the origin image file size less than one value
+ *
+ * @param size the value of file size, unit KB, default 100K
+ */
+ public Builder ignoreBy(int size) {
+ this.mLeastCompressSize = size;
+ return this;
+ }
+
+ /**
+ * do compress image when return value was true, otherwise, do not compress the image file
+ *
+ * @param compressionPredicate A predicate callback that returns true or false for the given input path should be compressed.
+ */
+ public Builder filter(CompressionPredicate compressionPredicate) {
+ this.mCompressionPredicate = compressionPredicate;
+ return this;
+ }
+
+
+ /**
+ * begin compress image with asynchronous
+ */
+ public void launch() {
+ build().launch(context);
+ }
+
+ public File get(final String path) throws IOException {
+ return build().get(new InputStreamAdapter() {
+ @Override
+ public InputStream openInternal() throws IOException {
+ return new FileInputStream(path);
+ }
+
+ @Override
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public LocalMedia getMedia() {
+ return null;
+ }
+
+ }, context);
+ }
+
+ /**
+ * begin compress image with synchronize
+ *
+ * @return the thumb image file list
+ */
+ public List get() throws IOException {
+ return build().get(context);
+ }
+ }
+}
\ No newline at end of file
diff --git a/picture_library/src/main/java/com/luck/picture/lib/compress/OnCompressListener.java b/picture_library/src/main/java/com/luck/picture/lib/compress/OnCompressListener.java
new file mode 100644
index 0000000..d910e31
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/compress/OnCompressListener.java
@@ -0,0 +1,23 @@
+package com.luck.picture.lib.compress;
+
+import com.luck.picture.lib.entity.LocalMedia;
+
+import java.util.List;
+
+public interface OnCompressListener {
+
+ /**
+ * Fired when the compression is started, override to handle in your own code
+ */
+ void onStart();
+
+ /**
+ * Fired when a compression returns successfully, override to handle in your own code
+ */
+ void onSuccess(List list);
+
+ /**
+ * Fired when a compression fails to complete, override to handle in your own code
+ */
+ void onError(Throwable e);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/compress/OnRenameListener.java b/picture_library/src/main/java/com/luck/picture/lib/compress/OnRenameListener.java
new file mode 100644
index 0000000..efaea00
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/compress/OnRenameListener.java
@@ -0,0 +1,22 @@
+package com.luck.picture.lib.compress;
+
+/**
+ * Author: zibin
+ * Datetime: 2018/5/18
+ *
+ * 提供修改压缩图片命名接口
+ *
+ * A functional interface (callback) that used to rename the file after compress.
+ */
+public interface OnRenameListener {
+
+ /**
+ * 压缩前调用该方法用于修改压缩后文件名
+ *
+ * Call before compression begins.
+ *
+ * @param filePath 传入文件路径/ file path
+ * @return 返回重命名后的字符串/ file name
+ */
+ String rename(String filePath);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/config/PictureConfig.java b/picture_library/src/main/java/com/luck/picture/lib/config/PictureConfig.java
new file mode 100644
index 0000000..7908090
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/config/PictureConfig.java
@@ -0,0 +1,71 @@
+package com.luck.picture.lib.config;
+
+/**
+ * @author:luck
+ * @data:2017/5/24 1:00
+ * @describe : constant
+ */
+public final class PictureConfig {
+ public final static int APPLY_STORAGE_PERMISSIONS_CODE = 1;
+ public final static int APPLY_CAMERA_PERMISSIONS_CODE = 2;
+ public final static int APPLY_AUDIO_PERMISSIONS_CODE = 3;
+ public final static int APPLY_RECORD_AUDIO_PERMISSIONS_CODE = 4;
+ public final static int APPLY_CAMERA_STORAGE_PERMISSIONS_CODE = 5;
+
+ public final static String EXTRA_MEDIA_KEY = "mediaKey";
+ public final static String EXTRA_MEDIA_PATH = "mediaPath";
+ public final static String EXTRA_AUDIO_PATH = "audioPath";
+ public final static String EXTRA_VIDEO_PATH = "videoPath";
+ public final static String EXTRA_PREVIEW_VIDEO = "isExternalPreviewVideo";
+ public final static String EXTRA_PREVIEW_DELETE_POSITION = "position";
+ public final static String EXTRA_FC_TAG = "picture";
+ public final static String EXTRA_RESULT_SELECTION = "extra_result_media";
+ public final static String EXTRA_PREVIEW_SELECT_LIST = "previewSelectList";
+ public final static String EXTRA_SELECT_LIST = "selectList";
+ public final static String EXTRA_COMPLETE_SELECTED = "isCompleteOrSelected";
+ public final static String EXTRA_CHANGE_SELECTED_DATA = "isChangeSelectedData";
+ public final static String EXTRA_CHANGE_ORIGINAL = "isOriginal";
+ public final static String EXTRA_POSITION = "position";
+ public final static String EXTRA_OLD_CURRENT_LIST_SIZE = "oldCurrentListSize";
+ public final static String EXTRA_DIRECTORY_PATH = "directory_path";
+ public final static String EXTRA_BOTTOM_PREVIEW = "bottom_preview";
+ public final static String EXTRA_CONFIG = "PictureSelectorConfig";
+ public final static String EXTRA_SHOW_CAMERA = "isShowCamera";
+ public final static String EXTRA_IS_CURRENT_DIRECTORY = "currentDirectory";
+ public final static String EXTRA_BUCKET_ID = "bucket_id";
+ public final static String EXTRA_PAGE = "page";
+ public final static String EXTRA_DATA_COUNT = "count";
+ public final static String CAMERA_FACING = "android.intent.extras.CAMERA_FACING";
+
+ public final static String EXTRA_ALL_FOLDER_SIZE = "all_folder_size";
+ public final static String EXTRA_QUICK_CAPTURE = "android.intent.extra.quickCapture";
+
+ public final static int MAX_PAGE_SIZE = 60;
+
+ public final static int MIN_PAGE_SIZE = 10;
+
+ public final static int LOADED = 0;
+
+ public final static int NORMAL = -1;
+
+ public final static int CAMERA_BEFORE = 1;
+
+ public final static int TYPE_ALL = 0;
+ public final static int TYPE_IMAGE = 1;
+ public final static int TYPE_VIDEO = 2;
+
+ @Deprecated
+ public final static int TYPE_AUDIO = 3;
+
+ public final static int MAX_COMPRESS_SIZE = 100;
+
+ public final static int TYPE_CAMERA = 1;
+ public final static int TYPE_PICTURE = 2;
+
+ public final static int SINGLE = 1;
+ public final static int MULTIPLE = 2;
+
+ public final static int PREVIEW_VIDEO_CODE = 166;
+ public final static int CHOOSE_REQUEST = 188;
+ public final static int REQUEST_CAMERA = 909;
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/config/PictureMimeType.java b/picture_library/src/main/java/com/luck/picture/lib/config/PictureMimeType.java
new file mode 100644
index 0000000..bbfc235
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/config/PictureMimeType.java
@@ -0,0 +1,345 @@
+package com.luck.picture.lib.config;
+
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.luck.picture.lib.R;
+
+import java.io.File;
+
+/**
+ * @author:luck
+ * @date:2017-5-24 17:02
+ * @describe:PictureMimeType
+ */
+
+public final class PictureMimeType {
+ public static int ofAll() {
+ return PictureConfig.TYPE_ALL;
+ }
+
+ public static int ofImage() {
+ return PictureConfig.TYPE_IMAGE;
+ }
+
+ public static int ofVideo() {
+ return PictureConfig.TYPE_VIDEO;
+ }
+
+ /**
+ * # No longer maintain audio related functions,
+ * but can continue to use but there will be phone compatibility issues
+ *
+ * 不再维护音频相关功能,但可以继续使用但会有机型兼容性问题
+ */
+ @Deprecated
+ public static int ofAudio() {
+ return PictureConfig.TYPE_AUDIO;
+ }
+
+
+ public static String ofPNG() {
+ return MIME_TYPE_PNG;
+ }
+
+ public static String ofJPEG() {
+ return MIME_TYPE_JPEG;
+ }
+
+ public static String ofBMP() {
+ return MIME_TYPE_BMP;
+ }
+
+ public static String ofGIF() {
+ return MIME_TYPE_GIF;
+ }
+
+ public static String ofWEBP() {
+ return MIME_TYPE_WEBP;
+ }
+
+ public static String of3GP() {
+ return MIME_TYPE_3GP;
+ }
+
+ public static String ofMP4() {
+ return MIME_TYPE_MP4;
+ }
+
+ public static String ofMPEG() {
+ return MIME_TYPE_MPEG;
+ }
+
+ public static String ofAVI() {
+ return MIME_TYPE_AVI;
+ }
+
+ private final static String MIME_TYPE_PNG = "image/png";
+ public final static String MIME_TYPE_JPEG = "image/jpeg";
+ private final static String MIME_TYPE_JPG = "image/jpg";
+ private final static String MIME_TYPE_BMP = "image/bmp";
+ private final static String MIME_TYPE_GIF = "image/gif";
+ private final static String MIME_TYPE_WEBP = "image/webp";
+
+ private final static String MIME_TYPE_3GP = "video/3gp";
+ private final static String MIME_TYPE_MP4 = "video/mp4";
+ private final static String MIME_TYPE_MPEG = "video/mpeg";
+ private final static String MIME_TYPE_AVI = "video/avi";
+
+
+ /**
+ * isGif
+ *
+ * @param mimeType
+ * @return
+ */
+ public static boolean isGif(String mimeType) {
+ return mimeType != null && (mimeType.equals("image/gif") || mimeType.equals("image/GIF"));
+ }
+
+
+ /**
+ * isVideo
+ *
+ * @param mimeType
+ * @return
+ */
+ public static boolean isHasVideo(String mimeType) {
+ return mimeType != null && mimeType.startsWith(MIME_TYPE_PREFIX_VIDEO);
+ }
+
+ /**
+ * isVideo
+ *
+ * @param url
+ * @return
+ */
+ public static boolean isUrlHasVideo(String url) {
+ return url.endsWith(".mp4");
+ }
+
+ /**
+ * isAudio
+ *
+ * @param mimeType
+ * @return
+ */
+ public static boolean isHasAudio(String mimeType) {
+ return mimeType != null && mimeType.startsWith(MIME_TYPE_PREFIX_AUDIO);
+ }
+
+ /**
+ * isImage
+ *
+ * @param mimeType
+ * @return
+ */
+ public static boolean isHasImage(String mimeType) {
+ return mimeType != null && mimeType.startsWith(MIME_TYPE_PREFIX_IMAGE);
+ }
+
+ /**
+ * Determine if it is JPG.
+ *
+ * @param is image file mimeType
+ */
+ public static boolean isJPEG(String mimeType) {
+ if (TextUtils.isEmpty(mimeType)) {
+ return false;
+ }
+ return mimeType.startsWith(MIME_TYPE_JPEG) || mimeType.startsWith(MIME_TYPE_JPG);
+ }
+
+ /**
+ * Determine if it is JPG.
+ *
+ * @param is image file mimeType
+ */
+ public static boolean isJPG(String mimeType) {
+ if (TextUtils.isEmpty(mimeType)) {
+ return false;
+ }
+ return mimeType.startsWith(MIME_TYPE_JPG);
+ }
+
+
+ /**
+ * is Network image
+ *
+ * @param path
+ * @return
+ */
+ public static boolean isHasHttp(String path) {
+ if (TextUtils.isEmpty(path)) {
+ return false;
+ }
+ return path.startsWith("http")
+ || path.startsWith("https")
+ || path.startsWith("/http")
+ || path.startsWith("/https");
+ }
+
+ /**
+ * Determine whether the file type is an image or a video
+ *
+ * @param cameraMimeType
+ * @return
+ */
+ public static String getMimeType(int cameraMimeType) {
+ switch (cameraMimeType) {
+ case PictureConfig.TYPE_VIDEO:
+ return MIME_TYPE_VIDEO;
+ case PictureConfig.TYPE_AUDIO:
+ return MIME_TYPE_AUDIO;
+ default:
+ return MIME_TYPE_IMAGE;
+ }
+ }
+
+ /**
+ * Determines if the file name is a picture
+ *
+ * @param name
+ * @return
+ */
+ public static boolean isSuffixOfImage(String name) {
+ return !TextUtils.isEmpty(name) && name.endsWith(".PNG") || name.endsWith(".png") || name.endsWith(".jpeg")
+ || name.endsWith(".gif") || name.endsWith(".GIF") || name.endsWith(".jpg")
+ || name.endsWith(".webp") || name.endsWith(".WEBP") || name.endsWith(".JPEG")
+ || name.endsWith(".bmp");
+ }
+
+ /**
+ * Is it the same type
+ *
+ * @param oldMimeType
+ * @param newMimeType
+ * @return
+ */
+ public static boolean isMimeTypeSame(String oldMimeType, String newMimeType) {
+
+ return getMimeType(oldMimeType) == getMimeType(newMimeType);
+ }
+
+ /**
+ * Get Image mimeType
+ *
+ * @param path
+ * @return
+ */
+ public static String getImageMimeType(String path) {
+ try {
+ if (!TextUtils.isEmpty(path)) {
+ File file = new File(path);
+ String fileName = file.getName();
+ int last = fileName.lastIndexOf(".") + 1;
+ String temp = fileName.substring(last);
+ return "image/" + temp;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return MIME_TYPE_IMAGE;
+ }
+ return MIME_TYPE_IMAGE;
+ }
+
+
+ /**
+ * Picture or video
+ *
+ * @return
+ */
+ public static int getMimeType(String mimeType) {
+ if (TextUtils.isEmpty(mimeType)) {
+ return PictureConfig.TYPE_IMAGE;
+ }
+ if (mimeType.startsWith(MIME_TYPE_PREFIX_VIDEO)) {
+ return PictureConfig.TYPE_VIDEO;
+ } else if (mimeType.startsWith(MIME_TYPE_PREFIX_AUDIO)) {
+ return PictureConfig.TYPE_AUDIO;
+ } else {
+ return PictureConfig.TYPE_IMAGE;
+ }
+ }
+
+ /**
+ * Get image suffix
+ *
+ * @param mineType
+ * @return
+ */
+ public static String getLastImgSuffix(String mineType) {
+ String defaultSuffix = PNG;
+ try {
+ int index = mineType.lastIndexOf("/") + 1;
+ if (index > 0) {
+ return "." + mineType.substring(index);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return defaultSuffix;
+ }
+ return defaultSuffix;
+ }
+
+
+ /**
+ * is content://
+ *
+ * @param url
+ * @return
+ */
+ public static boolean isContent(String url) {
+ if (TextUtils.isEmpty(url)) {
+ return false;
+ }
+ return url.startsWith("content://");
+ }
+
+ /**
+ * Returns an error message by type
+ *
+ * @param context
+ * @param mimeType
+ * @return
+ */
+ public static String s(Context context, String mimeType) {
+ Context ctx = context.getApplicationContext();
+ if (isHasVideo(mimeType)) {
+ return ctx.getString(R.string.picture_video_error);
+ } else if (isHasAudio(mimeType)) {
+ return ctx.getString(R.string.picture_audio_error);
+ } else {
+ return ctx.getString(R.string.picture_error);
+ }
+ }
+
+ public final static String JPEG = ".jpg";
+
+ private final static String PNG = ".png";
+
+ public final static String MP4 = ".mp4";
+
+ public final static String JPEG_Q = "image/jpeg";
+
+ public final static String PNG_Q = "image/png";
+
+ public final static String MP4_Q = "video/mp4";
+
+ public final static String AVI_Q = "video/avi";
+
+ public final static String DCIM = "DCIM/Camera";
+
+ public final static String CAMERA = "Camera";
+
+ public final static String MIME_TYPE_IMAGE = "image/jpeg";
+ public final static String MIME_TYPE_VIDEO = "video/mp4";
+ public final static String MIME_TYPE_AUDIO = "audio/mpeg";
+
+
+ private final static String MIME_TYPE_PREFIX_IMAGE = "image";
+ private final static String MIME_TYPE_PREFIX_VIDEO = "video";
+ private final static String MIME_TYPE_PREFIX_AUDIO = "audio";
+
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/config/PictureSelectionConfig.java b/picture_library/src/main/java/com/luck/picture/lib/config/PictureSelectionConfig.java
new file mode 100644
index 0000000..36fdd31
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/config/PictureSelectionConfig.java
@@ -0,0 +1,521 @@
+package com.luck.picture.lib.config;
+
+import android.content.pm.ActivityInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.StyleRes;
+
+import com.luck.picture.lib.R;
+import com.luck.picture.lib.camera.CustomCameraView;
+import com.luck.picture.lib.engine.CacheResourcesEngine;
+import com.luck.picture.lib.engine.ImageEngine;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.listener.OnCustomCameraInterfaceListener;
+import com.luck.picture.lib.listener.OnResultCallbackListener;
+import com.luck.picture.lib.listener.OnVideoSelectedPlayCallback;
+import com.luck.picture.lib.style.PictureCropParameterStyle;
+import com.luck.picture.lib.style.PictureParameterStyle;
+import com.luck.picture.lib.style.PictureWindowAnimationStyle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2017-05-24 17:02
+ * @describe:PictureSelector Config
+ */
+
+public final class PictureSelectionConfig implements Parcelable {
+ public int chooseMode;
+ public boolean camera;
+ public boolean isSingleDirectReturn;
+ public PictureParameterStyle style;
+ public PictureCropParameterStyle cropStyle;
+ public PictureWindowAnimationStyle windowAnimationStyle;
+ public String compressSavePath;
+ public String suffixType;
+ public boolean focusAlpha;
+ public String renameCompressFileName;
+ public String renameCropFileName;
+ public String specifiedFormat;
+ public int requestedOrientation;
+ public int buttonFeatures;
+ public boolean isCameraAroundState;
+ public boolean isAndroidQTransform;
+ @StyleRes
+ public int themeStyleId;
+ public int selectionMode;
+ public int maxSelectNum;
+ public int minSelectNum;
+ public int maxVideoSelectNum;
+ public int minVideoSelectNum;
+ public int videoQuality;
+ public int cropCompressQuality;
+ public int videoMaxSecond;
+ public int videoMinSecond;
+ public int recordVideoSecond;
+ public int recordVideoMinSecond;
+ public int minimumCompressSize;
+ public int imageSpanCount;
+ public int aspect_ratio_x;
+ public int aspect_ratio_y;
+ public int cropWidth;
+ public int cropHeight;
+ public int compressQuality;
+ public float filterFileSize;
+ public int language;
+ public boolean isMultipleRecyclerAnimation;
+ public boolean isMultipleSkipCrop;
+ public boolean isWeChatStyle;
+ public boolean isUseCustomCamera;
+ public boolean zoomAnim;
+ public boolean isCompress;
+ public boolean isOriginalControl;
+ public boolean isCamera;
+ public boolean isGif;
+ public boolean enablePreview;
+ public boolean enPreviewVideo;
+ public boolean enablePreviewAudio;
+ public boolean checkNumMode;
+ public boolean openClickSound;
+ public boolean enableCrop;
+ public boolean freeStyleCropEnabled;
+ public boolean circleDimmedLayer;
+ @ColorInt
+ public int circleDimmedColor;
+ @ColorInt
+ public int circleDimmedBorderColor;
+ public int circleStrokeWidth;
+ public boolean showCropFrame;
+ public boolean showCropGrid;
+ public boolean hideBottomControls;
+ public boolean rotateEnabled;
+ public boolean scaleEnabled;
+ public boolean previewEggs;
+ public boolean synOrAsy;
+ public boolean returnEmpty;
+ public boolean isDragFrame;
+ public boolean isNotPreviewDownload;
+ public boolean isWithVideoImage;
+ public UCropOptions uCropOptions;
+ public static ImageEngine imageEngine;
+ public static CacheResourcesEngine cacheResourcesEngine;
+ public static OnResultCallbackListener listener;
+ public static OnVideoSelectedPlayCallback customVideoPlayCallback;
+ public static OnCustomCameraInterfaceListener onCustomCameraInterfaceListener;
+ public List selectionMedias;
+ public String cameraFileName;
+ public boolean isCheckOriginalImage;
+ @Deprecated
+ public int overrideWidth;
+ @Deprecated
+ public int overrideHeight;
+ @Deprecated
+ public float sizeMultiplier;
+ @Deprecated
+ public boolean isChangeStatusBarFontColor;
+ @Deprecated
+ public boolean isOpenStyleNumComplete;
+ @Deprecated
+ public boolean isOpenStyleCheckNumMode;
+ @Deprecated
+ public int titleBarBackgroundColor;
+ @Deprecated
+ public int pictureStatusBarColor;
+ @Deprecated
+ public int cropTitleBarBackgroundColor;
+ @Deprecated
+ public int cropStatusBarColorPrimaryDark;
+ @Deprecated
+ public int cropTitleColor;
+ @Deprecated
+ public int upResId;
+ @Deprecated
+ public int downResId;
+ public String outPutCameraPath;
+
+ public String originalPath;
+ public String cameraPath;
+ public int cameraMimeType;
+ public int pageSize;
+ public boolean isPageStrategy;
+ public boolean isFilterInvalidFile;
+ public boolean isMaxSelectEnabledMask;
+ public int animationMode;
+ public boolean isAutomaticTitleRecyclerTop;
+ public boolean isCallbackMode;
+ public boolean isAndroidQChangeWH;
+ public boolean isAndroidQChangeVideoWH;
+ public boolean isQuickCapture;
+ /**
+ * 内测专用###########
+ */
+ public boolean isFallbackVersion;
+ public boolean isFallbackVersion2;
+ public boolean isFallbackVersion3;
+
+ protected void initDefaultValue() {
+ chooseMode = PictureMimeType.ofImage();
+ camera = false;
+ themeStyleId = R.style.picture_default_style;
+ selectionMode = PictureConfig.MULTIPLE;
+ maxSelectNum = 9;
+ minSelectNum = 0;
+ maxVideoSelectNum = 0;
+ minVideoSelectNum = 0;
+ videoQuality = 1;
+ language = -1;
+ cropCompressQuality = 90;
+ videoMaxSecond = 0;
+ videoMinSecond = 0;
+ filterFileSize = -1;
+ recordVideoSecond = 60;
+ recordVideoMinSecond = 0;
+ compressQuality = 80;
+ minimumCompressSize = PictureConfig.MAX_COMPRESS_SIZE;
+ imageSpanCount = 4;
+ isCompress = false;
+ isOriginalControl = false;
+ aspect_ratio_x = 0;
+ aspect_ratio_y = 0;
+ cropWidth = 0;
+ cropHeight = 0;
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR;
+ buttonFeatures = CustomCameraView.BUTTON_STATE_BOTH; //初始化按钮为可录制可拍照
+ isCameraAroundState = false;
+ isWithVideoImage = false;
+ isAndroidQTransform = true;
+ isCamera = true;
+ isGif = false;
+ focusAlpha = false;
+ isCheckOriginalImage = false;
+ isSingleDirectReturn = false;
+ enablePreview = true;
+ enPreviewVideo = true;
+ enablePreviewAudio = true;
+ checkNumMode = false;
+ isNotPreviewDownload = false;
+ openClickSound = false;
+ isFallbackVersion = false;
+ isFallbackVersion2 = true;
+ isFallbackVersion3 = true;
+ enableCrop = false;
+ isWeChatStyle = false;
+ isUseCustomCamera = false;
+ isMultipleSkipCrop = true;
+ isMultipleRecyclerAnimation = true;
+ freeStyleCropEnabled = false;
+ circleDimmedLayer = false;
+ showCropFrame = true;
+ showCropGrid = true;
+ hideBottomControls = true;
+ rotateEnabled = true;
+ scaleEnabled = true;
+ previewEggs = false;
+ returnEmpty = false;
+ synOrAsy = true;
+ zoomAnim = true;
+ circleDimmedColor = 0;
+ circleDimmedBorderColor = 0;
+ circleStrokeWidth = 1;
+ isDragFrame = true;
+ compressSavePath = "";
+ suffixType = "";
+ cameraFileName = "";
+ specifiedFormat = "";
+ renameCompressFileName = "";
+ renameCropFileName = "";
+ selectionMedias = new ArrayList<>();
+ uCropOptions = null;
+ style = null;
+ cropStyle = null;
+ windowAnimationStyle = null;
+ titleBarBackgroundColor = 0;
+ pictureStatusBarColor = 0;
+ cropTitleBarBackgroundColor = 0;
+ cropStatusBarColorPrimaryDark = 0;
+ cropTitleColor = 0;
+ upResId = 0;
+ downResId = 0;
+ isChangeStatusBarFontColor = false;
+ isOpenStyleNumComplete = false;
+ isOpenStyleCheckNumMode = false;
+ outPutCameraPath = "";
+ sizeMultiplier = 0.5f;
+ overrideWidth = 0;
+ overrideHeight = 0;
+ originalPath = "";
+ cameraPath = "";
+ cameraMimeType = -1;
+ pageSize = PictureConfig.MAX_PAGE_SIZE;
+ isPageStrategy = true;
+ isFilterInvalidFile = false;
+ isMaxSelectEnabledMask = false;
+ animationMode = -1;
+ isAutomaticTitleRecyclerTop = true;
+ isCallbackMode = false;
+ isAndroidQChangeWH = true;
+ isAndroidQChangeVideoWH = false;
+ isQuickCapture = true;
+ }
+
+ public static PictureSelectionConfig getInstance() {
+ return InstanceHolder.INSTANCE;
+ }
+
+ public static PictureSelectionConfig getCleanInstance() {
+ PictureSelectionConfig selectionSpec = getInstance();
+ selectionSpec.initDefaultValue();
+ return selectionSpec;
+ }
+
+ private static final class InstanceHolder {
+ private static final PictureSelectionConfig INSTANCE = new PictureSelectionConfig();
+ }
+
+ public PictureSelectionConfig() {
+ }
+
+ /**
+ * 释放监听器
+ */
+ public static void destroy() {
+ PictureSelectionConfig.listener = null;
+ PictureSelectionConfig.customVideoPlayCallback = null;
+ PictureSelectionConfig.onCustomCameraInterfaceListener = null;
+ PictureSelectionConfig.onCustomCameraInterfaceListener = null;
+ PictureSelectionConfig.cacheResourcesEngine = null;
+ }
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(this.chooseMode);
+ dest.writeByte(this.camera ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isSingleDirectReturn ? (byte) 1 : (byte) 0);
+ dest.writeParcelable(this.style, flags);
+ dest.writeParcelable(this.cropStyle, flags);
+ dest.writeParcelable(this.windowAnimationStyle, flags);
+ dest.writeString(this.compressSavePath);
+ dest.writeString(this.suffixType);
+ dest.writeByte(this.focusAlpha ? (byte) 1 : (byte) 0);
+ dest.writeString(this.renameCompressFileName);
+ dest.writeString(this.renameCropFileName);
+ dest.writeString(this.specifiedFormat);
+ dest.writeInt(this.requestedOrientation);
+ dest.writeInt(this.buttonFeatures);
+ dest.writeByte(this.isCameraAroundState ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isAndroidQTransform ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.themeStyleId);
+ dest.writeInt(this.selectionMode);
+ dest.writeInt(this.maxSelectNum);
+ dest.writeInt(this.minSelectNum);
+ dest.writeInt(this.maxVideoSelectNum);
+ dest.writeInt(this.minVideoSelectNum);
+ dest.writeInt(this.videoQuality);
+ dest.writeInt(this.cropCompressQuality);
+ dest.writeInt(this.videoMaxSecond);
+ dest.writeInt(this.videoMinSecond);
+ dest.writeInt(this.recordVideoSecond);
+ dest.writeInt(this.recordVideoMinSecond);
+ dest.writeInt(this.minimumCompressSize);
+ dest.writeInt(this.imageSpanCount);
+ dest.writeInt(this.aspect_ratio_x);
+ dest.writeInt(this.aspect_ratio_y);
+ dest.writeInt(this.cropWidth);
+ dest.writeInt(this.cropHeight);
+ dest.writeInt(this.compressQuality);
+ dest.writeFloat(this.filterFileSize);
+ dest.writeInt(this.language);
+ dest.writeByte(this.isMultipleRecyclerAnimation ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isMultipleSkipCrop ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isWeChatStyle ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isUseCustomCamera ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.zoomAnim ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isCompress ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isOriginalControl ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isCamera ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isGif ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.enablePreview ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.enPreviewVideo ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.enablePreviewAudio ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.checkNumMode ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.openClickSound ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.enableCrop ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.freeStyleCropEnabled ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.circleDimmedLayer ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.circleDimmedColor);
+ dest.writeInt(this.circleDimmedBorderColor);
+ dest.writeInt(this.circleStrokeWidth);
+ dest.writeByte(this.showCropFrame ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.showCropGrid ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.hideBottomControls ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.rotateEnabled ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.scaleEnabled ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.previewEggs ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.synOrAsy ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.returnEmpty ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isDragFrame ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isNotPreviewDownload ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isWithVideoImage ? (byte) 1 : (byte) 0);
+ dest.writeParcelable(this.uCropOptions, flags);
+ dest.writeTypedList(this.selectionMedias);
+ dest.writeString(this.cameraFileName);
+ dest.writeByte(this.isCheckOriginalImage ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.overrideWidth);
+ dest.writeInt(this.overrideHeight);
+ dest.writeFloat(this.sizeMultiplier);
+ dest.writeByte(this.isChangeStatusBarFontColor ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isOpenStyleNumComplete ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isOpenStyleCheckNumMode ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.titleBarBackgroundColor);
+ dest.writeInt(this.pictureStatusBarColor);
+ dest.writeInt(this.cropTitleBarBackgroundColor);
+ dest.writeInt(this.cropStatusBarColorPrimaryDark);
+ dest.writeInt(this.cropTitleColor);
+ dest.writeInt(this.upResId);
+ dest.writeInt(this.downResId);
+ dest.writeString(this.outPutCameraPath);
+ dest.writeString(this.originalPath);
+ dest.writeString(this.cameraPath);
+ dest.writeInt(this.cameraMimeType);
+ dest.writeInt(this.pageSize);
+ dest.writeByte(this.isPageStrategy ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isFilterInvalidFile ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isMaxSelectEnabledMask ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.animationMode);
+ dest.writeByte(this.isAutomaticTitleRecyclerTop ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isCallbackMode ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isAndroidQChangeWH ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isAndroidQChangeVideoWH ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isQuickCapture ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isFallbackVersion ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isFallbackVersion2 ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isFallbackVersion3 ? (byte) 1 : (byte) 0);
+ }
+
+ protected PictureSelectionConfig(Parcel in) {
+ this.chooseMode = in.readInt();
+ this.camera = in.readByte() != 0;
+ this.isSingleDirectReturn = in.readByte() != 0;
+ this.style = in.readParcelable(PictureParameterStyle.class.getClassLoader());
+ this.cropStyle = in.readParcelable(PictureCropParameterStyle.class.getClassLoader());
+ this.windowAnimationStyle = in.readParcelable(PictureWindowAnimationStyle.class.getClassLoader());
+ this.compressSavePath = in.readString();
+ this.suffixType = in.readString();
+ this.focusAlpha = in.readByte() != 0;
+ this.renameCompressFileName = in.readString();
+ this.renameCropFileName = in.readString();
+ this.specifiedFormat = in.readString();
+ this.requestedOrientation = in.readInt();
+ this.buttonFeatures = in.readInt();
+ this.isCameraAroundState = in.readByte() != 0;
+ this.isAndroidQTransform = in.readByte() != 0;
+ this.themeStyleId = in.readInt();
+ this.selectionMode = in.readInt();
+ this.maxSelectNum = in.readInt();
+ this.minSelectNum = in.readInt();
+ this.maxVideoSelectNum = in.readInt();
+ this.minVideoSelectNum = in.readInt();
+ this.videoQuality = in.readInt();
+ this.cropCompressQuality = in.readInt();
+ this.videoMaxSecond = in.readInt();
+ this.videoMinSecond = in.readInt();
+ this.recordVideoSecond = in.readInt();
+ this.recordVideoMinSecond = in.readInt();
+ this.minimumCompressSize = in.readInt();
+ this.imageSpanCount = in.readInt();
+ this.aspect_ratio_x = in.readInt();
+ this.aspect_ratio_y = in.readInt();
+ this.cropWidth = in.readInt();
+ this.cropHeight = in.readInt();
+ this.compressQuality = in.readInt();
+ this.filterFileSize = in.readFloat();
+ this.language = in.readInt();
+ this.isMultipleRecyclerAnimation = in.readByte() != 0;
+ this.isMultipleSkipCrop = in.readByte() != 0;
+ this.isWeChatStyle = in.readByte() != 0;
+ this.isUseCustomCamera = in.readByte() != 0;
+ this.zoomAnim = in.readByte() != 0;
+ this.isCompress = in.readByte() != 0;
+ this.isOriginalControl = in.readByte() != 0;
+ this.isCamera = in.readByte() != 0;
+ this.isGif = in.readByte() != 0;
+ this.enablePreview = in.readByte() != 0;
+ this.enPreviewVideo = in.readByte() != 0;
+ this.enablePreviewAudio = in.readByte() != 0;
+ this.checkNumMode = in.readByte() != 0;
+ this.openClickSound = in.readByte() != 0;
+ this.enableCrop = in.readByte() != 0;
+ this.freeStyleCropEnabled = in.readByte() != 0;
+ this.circleDimmedLayer = in.readByte() != 0;
+ this.circleDimmedColor = in.readInt();
+ this.circleDimmedBorderColor = in.readInt();
+ this.circleStrokeWidth = in.readInt();
+ this.showCropFrame = in.readByte() != 0;
+ this.showCropGrid = in.readByte() != 0;
+ this.hideBottomControls = in.readByte() != 0;
+ this.rotateEnabled = in.readByte() != 0;
+ this.scaleEnabled = in.readByte() != 0;
+ this.previewEggs = in.readByte() != 0;
+ this.synOrAsy = in.readByte() != 0;
+ this.returnEmpty = in.readByte() != 0;
+ this.isDragFrame = in.readByte() != 0;
+ this.isNotPreviewDownload = in.readByte() != 0;
+ this.isWithVideoImage = in.readByte() != 0;
+ this.uCropOptions = in.readParcelable(UCropOptions.class.getClassLoader());
+ this.selectionMedias = in.createTypedArrayList(LocalMedia.CREATOR);
+ this.cameraFileName = in.readString();
+ this.isCheckOriginalImage = in.readByte() != 0;
+ this.overrideWidth = in.readInt();
+ this.overrideHeight = in.readInt();
+ this.sizeMultiplier = in.readFloat();
+ this.isChangeStatusBarFontColor = in.readByte() != 0;
+ this.isOpenStyleNumComplete = in.readByte() != 0;
+ this.isOpenStyleCheckNumMode = in.readByte() != 0;
+ this.titleBarBackgroundColor = in.readInt();
+ this.pictureStatusBarColor = in.readInt();
+ this.cropTitleBarBackgroundColor = in.readInt();
+ this.cropStatusBarColorPrimaryDark = in.readInt();
+ this.cropTitleColor = in.readInt();
+ this.upResId = in.readInt();
+ this.downResId = in.readInt();
+ this.outPutCameraPath = in.readString();
+ this.originalPath = in.readString();
+ this.cameraPath = in.readString();
+ this.cameraMimeType = in.readInt();
+ this.pageSize = in.readInt();
+ this.isPageStrategy = in.readByte() != 0;
+ this.isFilterInvalidFile = in.readByte() != 0;
+ this.isMaxSelectEnabledMask = in.readByte() != 0;
+ this.animationMode = in.readInt();
+ this.isAutomaticTitleRecyclerTop = in.readByte() != 0;
+ this.isCallbackMode = in.readByte() != 0;
+ this.isAndroidQChangeWH = in.readByte() != 0;
+ this.isAndroidQChangeVideoWH = in.readByte() != 0;
+ this.isQuickCapture = in.readByte() != 0;
+ this.isFallbackVersion = in.readByte() != 0;
+ this.isFallbackVersion2 = in.readByte() != 0;
+ this.isFallbackVersion3 = in.readByte() != 0;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public PictureSelectionConfig createFromParcel(Parcel source) {
+ return new PictureSelectionConfig(source);
+ }
+
+ @Override
+ public PictureSelectionConfig[] newArray(int size) {
+ return new PictureSelectionConfig[size];
+ }
+ };
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/config/UCropOptions.java b/picture_library/src/main/java/com/luck/picture/lib/config/UCropOptions.java
new file mode 100644
index 0000000..e5bcf24
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/config/UCropOptions.java
@@ -0,0 +1,41 @@
+package com.luck.picture.lib.config;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.yalantis.ucrop.UCrop;
+
+/**
+ * @author:luck
+ * @date:2020-01-09 13:33
+ * @describe: UCrop Configuration items
+ */
+public class UCropOptions extends UCrop.Options implements Parcelable {
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ public UCropOptions() {
+ }
+
+ protected UCropOptions(Parcel in) {
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ @Override
+ public UCropOptions createFromParcel(Parcel source) {
+ return new UCropOptions(source);
+ }
+
+ @Override
+ public UCropOptions[] newArray(int size) {
+ return new UCropOptions[size];
+ }
+ };
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/crash/PictureSelectorCrashUtils.java b/picture_library/src/main/java/com/luck/picture/lib/crash/PictureSelectorCrashUtils.java
new file mode 100644
index 0000000..3004243
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/crash/PictureSelectorCrashUtils.java
@@ -0,0 +1,207 @@
+package com.luck.picture.lib.crash;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Environment;
+
+import androidx.annotation.NonNull;
+
+import com.luck.picture.lib.app.PictureAppMaster;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * @author:luck
+ * @date:2019-12-03 14:53
+ * @describe:PictureSelector Crash Log collection class
+ */
+public class PictureSelectorCrashUtils {
+ private static boolean mInitialized;
+ private static String defaultDir;
+ private static String dir;
+ private static String versionName;
+ private static int versionCode;
+
+ private static final String FILE_SEP = System.getProperty("file.separator");
+ private static final Format FORMAT = new SimpleDateFormat("MM-dd HH-mm-ss", Locale.getDefault());
+
+ private static final String CRASH_HEAD;
+
+ private static final Thread.UncaughtExceptionHandler UNCAUGHT_EXCEPTION_HANDLER;
+
+
+ static {
+ try {
+ PackageInfo pi = PictureAppMaster.getInstance().getAppContext()
+ .getPackageManager()
+ .getPackageInfo(PictureAppMaster.getInstance().getAppContext().getPackageName(), 0);
+ if (pi != null) {
+ versionName = pi.versionName;
+ versionCode = pi.versionCode;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ CRASH_HEAD = "\n************* Crash Log Head ****************" +
+ "\nDevice Manufacturer: " + Build.MANUFACTURER + // 设备厂商
+ "\nDevice Model : " + Build.MODEL + // 设备型号
+ "\nAndroid Version : " + Build.VERSION.RELEASE + // 系统版本
+ "\nAndroid SDK : " + Build.VERSION.SDK_INT + // SDK版本
+ "\nApp VersionName : " + versionName +
+ "\nApp VersionCode : " + versionCode +
+ "\n************* Crash Log Head ****************\n\n";
+
+
+ UNCAUGHT_EXCEPTION_HANDLER = new Thread.UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(final Thread t, final Throwable e) {
+
+ if (mFinishAppListener != null) {
+ mFinishAppListener.onFinishApp(t, e);
+ }
+
+ Date now = new Date(System.currentTimeMillis());
+ String fileName = FORMAT.format(now) + ".txt";
+ final String fullPath = (dir == null ? defaultDir : dir) + fileName;
+ if (createOrExistsFile(fullPath)) {
+ PrintWriter pw = null;
+ try {
+ pw = new PrintWriter(new FileWriter(fullPath, false));
+ pw.write(CRASH_HEAD);
+ e.printStackTrace(pw);
+ Throwable cause = e.getCause();
+ while (cause != null) {
+ cause.printStackTrace(pw);
+ cause = cause.getCause();
+ }
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ } finally {
+ if (pw != null) {
+ pw.close();
+ }
+ }
+ }
+ android.os.Process.killProcess(android.os.Process.myPid());
+ System.exit(0);
+ }
+ };
+ }
+
+
+ private PictureSelectorCrashUtils() {
+ throw new UnsupportedOperationException("u can't instantiate me...");
+ }
+
+ /**
+ * 初始化
+ * 需添加权限 {@code }
+ *
+ * @return {@code true}: 初始化成功
{@code false}: 初始化失败
+ */
+ public static boolean init() {
+ return init("", null);
+ }
+
+
+ /**
+ * 初始化
+ * 需添加权限 {@code }
+ *
+ * @return {@code true}: 初始化成功
{@code false}: 初始化失败
+ */
+ public static boolean init(CrashAppListener listener) {
+ return init("", listener);
+ }
+
+
+ /**
+ * 初始化
+ * 需添加权限 {@code }
+ *
+ * @param crashDir 崩溃文件存储目录
+ * @return {@code true}: 初始化成功
{@code false}: 初始化失败
+ */
+ public static boolean init(@NonNull final File crashDir) {
+ return init(crashDir.getAbsolutePath() + FILE_SEP, null);
+ }
+
+ /**
+ * 初始化
+ * 需添加权限 {@code }
+ *
+ * @param crashDir 崩溃文件存储目录
+ * @return {@code true}: 初始化成功
{@code false}: 初始化失败
+ */
+ public static boolean init(final String crashDir, CrashAppListener listener) {
+ mFinishAppListener = listener;
+ if (isSpace(crashDir)) {
+ dir = null;
+ } else {
+ dir = crashDir.endsWith(FILE_SEP) ? dir : dir + FILE_SEP;
+ }
+ if (mInitialized) {
+ return true;
+ }
+ if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
+ && PictureAppMaster.getInstance().getAppContext().getExternalCacheDir() != null) {
+ defaultDir = PictureAppMaster.getInstance().getAppContext().getExternalCacheDir() + FILE_SEP + "crash" + FILE_SEP;
+ } else {
+ defaultDir = PictureAppMaster.getInstance().getAppContext().getCacheDir() + FILE_SEP + "crash" + FILE_SEP;
+ }
+ Thread.setDefaultUncaughtExceptionHandler(UNCAUGHT_EXCEPTION_HANDLER);
+ return mInitialized = true;
+ }
+
+ private static boolean createOrExistsFile(String filePath) {
+ File file = new File(filePath);
+ if (file.exists()) {
+ return file.isFile();
+ }
+ if (!createOrExistsDir(file.getParentFile())) {
+ return false;
+ }
+ try {
+ return file.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ private static boolean createOrExistsDir(File file) {
+ return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());
+ }
+
+ private static boolean isSpace(String s) {
+ if (s == null) {
+ return true;
+ }
+ for (int i = 0, len = s.length(); i < len; ++i) {
+ if (!Character.isWhitespace(s.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ private static CrashAppListener mFinishAppListener = null;
+
+ public static void setCrashListener(CrashAppListener crashListener) {
+ mFinishAppListener = crashListener;
+ }
+
+ public interface CrashAppListener {
+ void onFinishApp(final Thread t, final Throwable e);
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/decoration/GridSpacingItemDecoration.java b/picture_library/src/main/java/com/luck/picture/lib/decoration/GridSpacingItemDecoration.java
new file mode 100644
index 0000000..8c31327
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/decoration/GridSpacingItemDecoration.java
@@ -0,0 +1,46 @@
+package com.luck.picture.lib.decoration;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * @author:luck
+ * @data:2016/12/27 下午23:50
+ * @describe:GridSpacingItemDecoration
+ */
+
+public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
+
+ private int spanCount;
+ private int spacing;
+ private boolean includeEdge;
+
+ public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
+ this.spanCount = spanCount;
+ this.spacing = spacing;
+ this.includeEdge = includeEdge;
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+ int position = parent.getChildAdapterPosition(view);
+ int column = position % spanCount;
+ if (includeEdge) {
+ outRect.left = spacing - column * spacing / spanCount;
+ outRect.right = (column + 1) * spacing / spanCount;
+ if (position < spanCount) {
+ outRect.top = spacing;
+ }
+ outRect.bottom = spacing;
+ } else {
+ outRect.left = column * spacing / spanCount;
+ outRect.right = spacing - (column + 1) * spacing / spanCount;
+ if (position < spanCount) {
+ outRect.top = spacing;
+ }
+ outRect.bottom = spacing;
+ }
+ }
+}
\ No newline at end of file
diff --git a/picture_library/src/main/java/com/luck/picture/lib/decoration/WrapContentLinearLayoutManager.java b/picture_library/src/main/java/com/luck/picture/lib/decoration/WrapContentLinearLayoutManager.java
new file mode 100644
index 0000000..24d4abf
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/decoration/WrapContentLinearLayoutManager.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018.
+ * Author:Zhao
+ * Email:joeyzhao1005@gmail.com
+ */
+
+package com.luck.picture.lib.decoration;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Created by luck on 2017/12/4.
+ *
+ * RecyclerView Bug:IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter的解决方案
+ */
+
+public class WrapContentLinearLayoutManager extends LinearLayoutManager {
+ public WrapContentLinearLayoutManager(Context context) {
+ super(context);
+ }
+
+ public WrapContentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
+ super(context, orientation, reverseLayout);
+ }
+
+ public WrapContentLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ try {
+ super.onLayoutChildren(recycler, state);
+ } catch (IndexOutOfBoundsException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/dialog/PhotoItemSelectedDialog.java b/picture_library/src/main/java/com/luck/picture/lib/dialog/PhotoItemSelectedDialog.java
new file mode 100644
index 0000000..1600078
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/dialog/PhotoItemSelectedDialog.java
@@ -0,0 +1,110 @@
+package com.luck.picture.lib.dialog;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+
+import com.luck.picture.lib.R;
+import com.luck.picture.lib.listener.OnItemClickListener;
+import com.luck.picture.lib.tools.ScreenUtils;
+
+/**
+ * @author:luck
+ * @date:2019-12-12 16:39
+ * @describe:PhotoSelectedDialog
+ */
+public class PhotoItemSelectedDialog extends DialogFragment implements View.OnClickListener {
+ public static final int IMAGE_CAMERA = 0;
+ public static final int VIDEO_CAMERA = 1;
+ private TextView tvPicturePhoto, tvPictureVideo, tvPictureCancel;
+
+ public static PhotoItemSelectedDialog newInstance() {
+ PhotoItemSelectedDialog selectedDialog = new PhotoItemSelectedDialog();
+ return selectedDialog;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ if (getDialog() != null) {
+ getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
+ if (getDialog().getWindow() != null) {
+ getDialog().getWindow().setBackgroundDrawableResource(android.R.color.transparent);
+ }
+ }
+ return inflater.inflate(R.layout.picture_dialog_camera_selected, container);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ tvPicturePhoto = view.findViewById(R.id.picture_tv_photo);
+ tvPictureVideo = view.findViewById(R.id.picture_tv_video);
+ tvPictureCancel = view.findViewById(R.id.picture_tv_cancel);
+ tvPictureVideo.setOnClickListener(this);
+ tvPicturePhoto.setOnClickListener(this);
+ tvPictureCancel.setOnClickListener(this);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ initDialogStyle();
+ }
+
+ /**
+ * DialogFragment Style
+ */
+ private void initDialogStyle() {
+ Dialog dialog = getDialog();
+ if (dialog != null) {
+ Window window = dialog.getWindow();
+ if (window != null) {
+ window.setLayout(ScreenUtils.getScreenWidth(getContext()), RelativeLayout.LayoutParams.WRAP_CONTENT);
+ window.setGravity(Gravity.BOTTOM);
+ window.setWindowAnimations(R.style.PictureThemeDialogFragmentAnim);
+ }
+ }
+ }
+
+ private OnItemClickListener onItemClickListener;
+
+ public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
+ this.onItemClickListener = onItemClickListener;
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ if (onItemClickListener != null) {
+ if (id == R.id.picture_tv_photo) {
+ onItemClickListener.onItemClick(v, IMAGE_CAMERA);
+ }
+ if (id == R.id.picture_tv_video) {
+ onItemClickListener.onItemClick(v, VIDEO_CAMERA);
+ }
+ }
+
+ dismissAllowingStateLoss();
+ }
+
+ @Override
+ public void show(FragmentManager manager, String tag) {
+ FragmentTransaction ft = manager.beginTransaction();
+ ft.add(this, tag);
+ ft.commitAllowingStateLoss();
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/dialog/PictureCustomDialog.java b/picture_library/src/main/java/com/luck/picture/lib/dialog/PictureCustomDialog.java
new file mode 100644
index 0000000..82aea91
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/dialog/PictureCustomDialog.java
@@ -0,0 +1,24 @@
+package com.luck.picture.lib.dialog;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.luck.picture.lib.R;
+
+public class PictureCustomDialog extends Dialog {
+
+ public PictureCustomDialog(Context context, int layout) {
+ super(context, R.style.Picture_Theme_Dialog);
+ setContentView(layout);
+ Window window = getWindow();
+ WindowManager.LayoutParams params = window.getAttributes();
+ params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ params.gravity = Gravity.CENTER;
+ window.setAttributes(params);
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/dialog/PictureLoadingDialog.java b/picture_library/src/main/java/com/luck/picture/lib/dialog/PictureLoadingDialog.java
new file mode 100644
index 0000000..9554ecb
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/dialog/PictureLoadingDialog.java
@@ -0,0 +1,26 @@
+package com.luck.picture.lib.dialog;
+
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Window;
+
+import com.luck.picture.lib.R;
+
+public class PictureLoadingDialog extends Dialog {
+
+ public PictureLoadingDialog(Context context) {
+ super(context, R.style.Picture_Theme_AlertDialog);
+ setCancelable(true);
+ setCanceledOnTouchOutside(false);
+ Window window = getWindow();
+ window.setWindowAnimations(R.style.PictureThemeDialogWindowStyle);
+ }
+
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.picture_alert_dialog);
+ }
+}
\ No newline at end of file
diff --git a/picture_library/src/main/java/com/luck/picture/lib/engine/CacheResourcesEngine.java b/picture_library/src/main/java/com/luck/picture/lib/engine/CacheResourcesEngine.java
new file mode 100644
index 0000000..07dea2d
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/engine/CacheResourcesEngine.java
@@ -0,0 +1,18 @@
+package com.luck.picture.lib.engine;
+
+import android.content.Context;
+
+/**
+ * @author:luck
+ * @date:2020-03-24 09:36
+ * @describe:CacheResourcesEngine
+ */
+public interface CacheResourcesEngine {
+ /**
+ * Get the cache path
+ *
+ * @param context
+ * @param url
+ */
+ String onCachePath(Context context, String url);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/engine/ImageEngine.java b/picture_library/src/main/java/com/luck/picture/lib/engine/ImageEngine.java
new file mode 100644
index 0000000..9fa7ac5
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/engine/ImageEngine.java
@@ -0,0 +1,72 @@
+package com.luck.picture.lib.engine;
+
+import android.content.Context;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+
+import com.luck.picture.lib.listener.OnImageCompleteCallback;
+import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView;
+
+/**
+ * @author:luck
+ * @date:2019-11-13 16:59
+ * @describe:ImageEngine
+ */
+public interface ImageEngine {
+ /**
+ * Loading image
+ *
+ * @param context
+ * @param url
+ * @param imageView
+ */
+ void loadImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView);
+
+ /**
+ * Loading image
+ *
+ * @param context
+ * @param url
+ * @param imageView
+ */
+ void loadImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView, SubsamplingScaleImageView longImageView, OnImageCompleteCallback callback);
+
+ /**
+ * Load network long graph adaption
+ *
+ * @param context
+ * @param url
+ * @param imageView
+ */
+ @Deprecated
+ void loadImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView, SubsamplingScaleImageView longImageView);
+
+
+ /**
+ * Load album catalog pictures
+ *
+ * @param context
+ * @param url
+ * @param imageView
+ */
+ void loadFolderImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView);
+
+ /**
+ * Load GIF image
+ *
+ * @param context
+ * @param url
+ * @param imageView
+ */
+ void loadAsGifImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView);
+
+ /**
+ * Load picture list picture
+ *
+ * @param context
+ * @param url
+ * @param imageView
+ */
+ void loadGridImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/engine/PictureSelectorEngine.java b/picture_library/src/main/java/com/luck/picture/lib/engine/PictureSelectorEngine.java
new file mode 100644
index 0000000..bc20a85
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/engine/PictureSelectorEngine.java
@@ -0,0 +1,26 @@
+package com.luck.picture.lib.engine;
+
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.listener.OnResultCallbackListener;
+
+/**
+ * @author:luck
+ * @date:2020/4/22 11:36 AM
+ * @describe:PictureSelectorEngine
+ */
+public interface PictureSelectorEngine {
+
+ /**
+ * Create ImageLoad Engine
+ *
+ * @return
+ */
+ ImageEngine createEngine();
+
+ /**
+ * Create Result Listener
+ *
+ * @return
+ */
+ OnResultCallbackListener getResultCallbackListener();
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/entity/LocalMedia.java b/picture_library/src/main/java/com/luck/picture/lib/entity/LocalMedia.java
new file mode 100644
index 0000000..09b1ed4
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/entity/LocalMedia.java
@@ -0,0 +1,471 @@
+package com.luck.picture.lib.entity;
+
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.luck.picture.lib.config.PictureConfig;
+
+/**
+ * @author:luck
+ * @date:2017-5-24 16:21
+ * @describe:Media Entity
+ */
+
+public class LocalMedia implements Parcelable {
+ /**
+ * file to ID
+ */
+ private long id;
+ /**
+ * original path
+ */
+ private String path;
+
+ /**
+ * The real path,But you can't get access from AndroidQ
+ *
+ * It could be empty
+ *
+ */
+ private String realPath;
+
+ /**
+ * # Check the original button to get the return value
+ * original path
+ */
+ private String originalPath;
+ /**
+ * compress path
+ */
+ private String compressPath;
+ /**
+ * cut path
+ */
+ private String cutPath;
+
+ /**
+ * Note: this field is only returned in Android Q version
+ *
+ * Android Q image or video path
+ */
+ private String androidQToPath;
+ /**
+ * video duration
+ */
+ private long duration;
+ /**
+ * If the selected
+ * # Internal use
+ */
+ private boolean isChecked;
+ /**
+ * If the cut
+ */
+ private boolean isCut;
+ /**
+ * media position of list
+ */
+ public int position;
+ /**
+ * The media number of qq choose styles
+ */
+ private int num;
+ /**
+ * The media resource type
+ */
+ private String mimeType;
+
+ /**
+ * Gallery selection mode
+ */
+ private int chooseModel;
+
+ /**
+ * If the compressed
+ */
+ private boolean compressed;
+ /**
+ * image or video width
+ *
+ * # If zero occurs, the developer needs to handle it extra
+ */
+ private int width;
+ /**
+ * image or video height
+ *
+ * # If zero occurs, the developer needs to handle it extra
+ */
+ private int height;
+
+ /**
+ * file size
+ */
+ private long size;
+
+ /**
+ * Whether the original image is displayed
+ */
+ private boolean isOriginal;
+
+ /**
+ * file name
+ */
+ private String fileName;
+
+ /**
+ * Parent Folder Name
+ */
+ private String parentFolderName;
+
+ /**
+ * orientation info
+ * # For internal use only
+ */
+ private int orientation = -1;
+
+ /**
+ * loadLongImageStatus
+ * # For internal use only
+ */
+ public int loadLongImageStatus = PictureConfig.NORMAL;
+
+ /**
+ * isLongImage
+ * # For internal use only
+ */
+ public boolean isLongImage;
+
+ /**
+ * bucketId
+ */
+ private long bucketId = -1;
+
+ /**
+ * isMaxSelectEnabledMask
+ * # For internal use only
+ */
+ private boolean isMaxSelectEnabledMask;
+
+ public LocalMedia() {
+
+ }
+
+ public LocalMedia(String path, long duration, int chooseModel, String mimeType) {
+ this.path = path;
+ this.duration = duration;
+ this.chooseModel = chooseModel;
+ this.mimeType = mimeType;
+ }
+
+ public LocalMedia(long id, String path, String fileName, String parentFolderName, long duration, int chooseModel,
+ String mimeType, int width, int height, long size) {
+ this.id = id;
+ this.path = path;
+ this.fileName = fileName;
+ this.parentFolderName = parentFolderName;
+ this.duration = duration;
+ this.chooseModel = chooseModel;
+ this.mimeType = mimeType;
+ this.width = width;
+ this.height = height;
+ this.size = size;
+ }
+
+ public LocalMedia(long id, String path, String absolutePath, String fileName, String parentFolderName, long duration, int chooseModel,
+ String mimeType, int width, int height, long size, long bucketId) {
+ this.id = id;
+ this.path = path;
+ this.realPath = absolutePath;
+ this.fileName = fileName;
+ this.parentFolderName = parentFolderName;
+ this.duration = duration;
+ this.chooseModel = chooseModel;
+ this.mimeType = mimeType;
+ this.width = width;
+ this.height = height;
+ this.size = size;
+ this.bucketId = bucketId;
+ }
+
+ public LocalMedia(String path, long duration,
+ boolean isChecked, int position, int num, int chooseModel) {
+ this.path = path;
+ this.duration = duration;
+ this.isChecked = isChecked;
+ this.position = position;
+ this.num = num;
+ this.chooseModel = chooseModel;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public String getCompressPath() {
+ return compressPath;
+ }
+
+ public void setCompressPath(String compressPath) {
+ this.compressPath = compressPath;
+ }
+
+ public String getCutPath() {
+ return cutPath;
+ }
+
+ public void setCutPath(String cutPath) {
+ this.cutPath = cutPath;
+ }
+
+ public String getAndroidQToPath() {
+ return androidQToPath;
+ }
+
+ public void setAndroidQToPath(String androidQToPath) {
+ this.androidQToPath = androidQToPath;
+ }
+
+ public long getDuration() {
+ return duration;
+ }
+
+ public void setDuration(long duration) {
+ this.duration = duration;
+ }
+
+ public String getRealPath() {
+ return realPath;
+ }
+
+ public void setRealPath(String realPath) {
+ this.realPath = realPath;
+ }
+
+ public boolean isChecked() {
+ return isChecked;
+ }
+
+ public void setChecked(boolean checked) {
+ isChecked = checked;
+ }
+
+ public boolean isCut() {
+ return isCut;
+ }
+
+ public void setCut(boolean cut) {
+ isCut = cut;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ public void setPosition(int position) {
+ this.position = position;
+ }
+
+ public int getNum() {
+ return num;
+ }
+
+ public void setNum(int num) {
+ this.num = num;
+ }
+
+ public String getMimeType() {
+ return TextUtils.isEmpty(mimeType) ? "image/jpeg" : mimeType;
+ }
+
+ public void setMimeType(String mimeType) {
+ this.mimeType = mimeType;
+ }
+
+ public boolean isCompressed() {
+ return compressed;
+ }
+
+ public void setCompressed(boolean compressed) {
+ this.compressed = compressed;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public int getChooseModel() {
+ return chooseModel;
+ }
+
+ public void setChooseModel(int chooseModel) {
+ this.chooseModel = chooseModel;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public void setSize(long size) {
+ this.size = size;
+ }
+
+ public boolean isOriginal() {
+ return isOriginal;
+ }
+
+ public void setOriginal(boolean original) {
+ isOriginal = original;
+ }
+
+ public String getOriginalPath() {
+ return originalPath;
+ }
+
+ public void setOriginalPath(String originalPath) {
+ this.originalPath = originalPath;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getParentFolderName() {
+ return parentFolderName;
+ }
+
+ public void setParentFolderName(String parentFolderName) {
+ this.parentFolderName = parentFolderName;
+ }
+
+ public int getOrientation() {
+ return orientation;
+ }
+
+ public void setOrientation(int orientation) {
+ this.orientation = orientation;
+ }
+
+ public long getBucketId() {
+ return bucketId;
+ }
+
+ public void setBucketId(long bucketId) {
+ this.bucketId = bucketId;
+ }
+
+ public boolean isMaxSelectEnabledMask() {
+ return isMaxSelectEnabledMask;
+ }
+
+ public void setMaxSelectEnabledMask(boolean maxSelectEnabledMask) {
+ isMaxSelectEnabledMask = maxSelectEnabledMask;
+ }
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(this.id);
+ dest.writeString(this.path);
+ dest.writeString(this.realPath);
+ dest.writeString(this.originalPath);
+ dest.writeString(this.compressPath);
+ dest.writeString(this.cutPath);
+ dest.writeString(this.androidQToPath);
+ dest.writeLong(this.duration);
+ dest.writeByte(this.isChecked ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isCut ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.position);
+ dest.writeInt(this.num);
+ dest.writeString(this.mimeType);
+ dest.writeInt(this.chooseModel);
+ dest.writeByte(this.compressed ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.width);
+ dest.writeInt(this.height);
+ dest.writeLong(this.size);
+ dest.writeByte(this.isOriginal ? (byte) 1 : (byte) 0);
+ dest.writeString(this.fileName);
+ dest.writeString(this.parentFolderName);
+ dest.writeInt(this.orientation);
+ dest.writeInt(this.loadLongImageStatus);
+ dest.writeByte(this.isLongImage ? (byte) 1 : (byte) 0);
+ dest.writeLong(this.bucketId);
+ dest.writeByte(this.isMaxSelectEnabledMask ? (byte) 1 : (byte) 0);
+ }
+
+ protected LocalMedia(Parcel in) {
+ this.id = in.readLong();
+ this.path = in.readString();
+ this.realPath = in.readString();
+ this.originalPath = in.readString();
+ this.compressPath = in.readString();
+ this.cutPath = in.readString();
+ this.androidQToPath = in.readString();
+ this.duration = in.readLong();
+ this.isChecked = in.readByte() != 0;
+ this.isCut = in.readByte() != 0;
+ this.position = in.readInt();
+ this.num = in.readInt();
+ this.mimeType = in.readString();
+ this.chooseModel = in.readInt();
+ this.compressed = in.readByte() != 0;
+ this.width = in.readInt();
+ this.height = in.readInt();
+ this.size = in.readLong();
+ this.isOriginal = in.readByte() != 0;
+ this.fileName = in.readString();
+ this.parentFolderName = in.readString();
+ this.orientation = in.readInt();
+ this.loadLongImageStatus = in.readInt();
+ this.isLongImage = in.readByte() != 0;
+ this.bucketId = in.readLong();
+ this.isMaxSelectEnabledMask = in.readByte() != 0;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public LocalMedia createFromParcel(Parcel source) {
+ return new LocalMedia(source);
+ }
+
+ @Override
+ public LocalMedia[] newArray(int size) {
+ return new LocalMedia[size];
+ }
+ };
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/entity/LocalMediaFolder.java b/picture_library/src/main/java/com/luck/picture/lib/entity/LocalMediaFolder.java
new file mode 100644
index 0000000..7daa09b
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/entity/LocalMediaFolder.java
@@ -0,0 +1,204 @@
+package com.luck.picture.lib.entity;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2016-12-31 15:21
+ * @describe:MediaFolder Entity
+ */
+
+public class LocalMediaFolder implements Parcelable {
+ /**
+ * bucketId
+ */
+ private long bucketId = -1;
+ /**
+ * Folder name
+ */
+ private String name;
+ /**
+ * Folder first path
+ */
+ private String firstImagePath;
+ /**
+ * Folder media num
+ */
+ private int imageNum;
+ /**
+ * If the selected num
+ */
+ private int checkedNum;
+ /**
+ * If the selected
+ */
+ private boolean isChecked;
+
+ /**
+ * type
+ */
+ private int ofAllType = -1;
+ /**
+ * Whether or not the camera
+ */
+ private boolean isCameraFolder;
+
+ /**
+ * data
+ */
+ private List data = new ArrayList<>();
+
+ /**
+ * # Internal use
+ * setCurrentDataPage
+ */
+ private int currentDataPage;
+
+ /**
+ * # Internal use
+ * is load more
+ */
+ private boolean isHasMore;
+
+ public long getBucketId() {
+ return bucketId;
+ }
+
+ public void setBucketId(long bucketId) {
+ this.bucketId = bucketId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getFirstImagePath() {
+ return firstImagePath;
+ }
+
+ public void setFirstImagePath(String firstImagePath) {
+ this.firstImagePath = firstImagePath;
+ }
+
+ public int getImageNum() {
+ return imageNum;
+ }
+
+ public void setImageNum(int imageNum) {
+ this.imageNum = imageNum;
+ }
+
+ public int getCheckedNum() {
+ return checkedNum;
+ }
+
+ public void setCheckedNum(int checkedNum) {
+ this.checkedNum = checkedNum;
+ }
+
+ public boolean isChecked() {
+ return isChecked;
+ }
+
+ public void setChecked(boolean checked) {
+ isChecked = checked;
+ }
+
+ public int getOfAllType() {
+ return ofAllType;
+ }
+
+ public void setOfAllType(int ofAllType) {
+ this.ofAllType = ofAllType;
+ }
+
+ public boolean isCameraFolder() {
+ return isCameraFolder;
+ }
+
+ public void setCameraFolder(boolean cameraFolder) {
+ isCameraFolder = cameraFolder;
+ }
+
+ public List getData() {
+ return data;
+ }
+
+ public void setData(List data) {
+ this.data = data;
+ }
+
+ public int getCurrentDataPage() {
+ return currentDataPage;
+ }
+
+ public void setCurrentDataPage(int currentDataPage) {
+ this.currentDataPage = currentDataPage;
+ }
+
+ public boolean isHasMore() {
+ return isHasMore;
+ }
+
+ public void setHasMore(boolean hasMore) {
+ isHasMore = hasMore;
+ }
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(this.bucketId);
+ dest.writeString(this.name);
+ dest.writeString(this.firstImagePath);
+ dest.writeInt(this.imageNum);
+ dest.writeInt(this.checkedNum);
+ dest.writeByte(this.isChecked ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.ofAllType);
+ dest.writeByte(this.isCameraFolder ? (byte) 1 : (byte) 0);
+ dest.writeTypedList(this.data);
+ dest.writeInt(this.currentDataPage);
+ dest.writeByte(this.isHasMore ? (byte) 1 : (byte) 0);
+ }
+
+ public LocalMediaFolder() {
+ }
+
+ protected LocalMediaFolder(Parcel in) {
+ this.bucketId = in.readLong();
+ this.name = in.readString();
+ this.firstImagePath = in.readString();
+ this.imageNum = in.readInt();
+ this.checkedNum = in.readInt();
+ this.isChecked = in.readByte() != 0;
+ this.ofAllType = in.readInt();
+ this.isCameraFolder = in.readByte() != 0;
+ this.data = in.createTypedArrayList(LocalMedia.CREATOR);
+ this.currentDataPage = in.readInt();
+ this.isHasMore = in.readByte() != 0;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public LocalMediaFolder createFromParcel(Parcel source) {
+ return new LocalMediaFolder(source);
+ }
+
+ @Override
+ public LocalMediaFolder[] newArray(int size) {
+ return new LocalMediaFolder[size];
+ }
+ };
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/entity/MediaData.java b/picture_library/src/main/java/com/luck/picture/lib/entity/MediaData.java
new file mode 100644
index 0000000..2b7980d
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/entity/MediaData.java
@@ -0,0 +1,32 @@
+package com.luck.picture.lib.entity;
+
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2020-04-17 13:52
+ * @describe:MediaData
+ */
+public class MediaData {
+
+ /**
+ * Is there more
+ */
+ public boolean isHasNextMore;
+
+ /**
+ * data
+ */
+ public List data;
+
+
+ public MediaData() {
+ super();
+ }
+
+ public MediaData(boolean isHasNextMore, List data) {
+ super();
+ this.isHasNextMore = isHasNextMore;
+ this.data = data;
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/immersive/ImmersiveManage.java b/picture_library/src/main/java/com/luck/picture/lib/immersive/ImmersiveManage.java
new file mode 100644
index 0000000..e532b5e
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/immersive/ImmersiveManage.java
@@ -0,0 +1,95 @@
+package com.luck.picture.lib.immersive;
+
+import android.graphics.Color;
+import android.os.Build;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+/**
+ * @author:luck
+ * @data:2018/3/28 下午1:00
+ * @描述: 沉浸式相关
+ */
+
+public class ImmersiveManage {
+
+ /**
+ * 注意:使用最好将布局xml 跟布局加入 android:fitsSystemWindows="true" ,这样可以避免有些手机上布局顶边的问题
+ *
+ * @param baseActivity 这个会留出来状态栏和底栏的空白
+ * @param statusBarColor 状态栏的颜色
+ * @param navigationBarColor 导航栏的颜色
+ * @param isDarkStatusBarIcon 状态栏图标颜色是否是深(黑)色 false状态栏图标颜色为白色
+ */
+ public static void immersiveAboveAPI23(AppCompatActivity baseActivity, int statusBarColor, int navigationBarColor, boolean isDarkStatusBarIcon) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ immersiveAboveAPI23(baseActivity, false, false, statusBarColor, navigationBarColor, isDarkStatusBarIcon);
+ }
+ }
+
+
+ /**
+ * @param baseActivity
+ * @param statusBarColor 状态栏的颜色
+ * @param navigationBarColor 导航栏的颜色
+ */
+ public static void immersiveAboveAPI23(AppCompatActivity baseActivity, boolean isMarginStatusBar
+ , boolean isMarginNavigationBar, int statusBarColor, int navigationBarColor, boolean isDarkStatusBarIcon) {
+ try {
+ Window window = baseActivity.getWindow();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ //4.4版本及以上 5.0版本及以下
+ window.setFlags(
+ WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
+ WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (isMarginStatusBar && isMarginNavigationBar) {
+ //5.0版本及以上
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+ LightStatusBarUtils.setLightStatusBar(baseActivity, isMarginStatusBar
+ , isMarginNavigationBar
+ , statusBarColor == Color.TRANSPARENT
+ , isDarkStatusBarIcon);
+
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ } else if (!isMarginStatusBar && !isMarginNavigationBar) {
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+
+ LightStatusBarUtils.setLightStatusBar(baseActivity, isMarginStatusBar
+ , isMarginNavigationBar
+ , statusBarColor == Color.TRANSPARENT
+ , isDarkStatusBarIcon);
+
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+
+
+ } else if (!isMarginStatusBar && isMarginNavigationBar) {
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+ LightStatusBarUtils.setLightStatusBar(baseActivity, isMarginStatusBar
+ , isMarginNavigationBar
+ , statusBarColor == Color.TRANSPARENT
+ , isDarkStatusBarIcon);
+
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+
+
+ } else {
+ //留出来状态栏 不留出来导航栏 没找到办法。。
+ return;
+ }
+
+ window.setStatusBarColor(statusBarColor);
+ window.setNavigationBarColor(navigationBarColor);
+
+ }
+ } catch (Exception e) {
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/immersive/LightStatusBarUtils.java b/picture_library/src/main/java/com/luck/picture/lib/immersive/LightStatusBarUtils.java
new file mode 100644
index 0000000..3f2b408
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/immersive/LightStatusBarUtils.java
@@ -0,0 +1,189 @@
+package com.luck.picture.lib.immersive;
+
+import android.annotation.TargetApi;
+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.Method;
+
+/**
+ * @author:luck
+ * @data:2018/3/28 下午1:01
+ * @描述: 沉浸式
+ */
+
+public class LightStatusBarUtils {
+ public static void setLightStatusBarAboveAPI23(Activity activity, boolean isMarginStatusBar
+ , boolean isMarginNavigationBar, boolean isTransStatusBar, boolean dark) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ setLightStatusBar(activity, isMarginStatusBar, isMarginNavigationBar, isTransStatusBar, dark);
+ }
+ }
+
+ public static void setLightStatusBar(Activity activity, boolean dark) {
+ setLightStatusBar(activity, false, false, false, dark);
+ }
+
+ public static void setLightStatusBar(Activity activity, boolean isMarginStatusBar
+ , boolean isMarginNavigationBar, boolean isTransStatusBar, boolean dark) {
+ switch (RomUtils.getLightStatausBarAvailableRomType()) {
+ case RomUtils.AvailableRomType.MIUI:
+ if (RomUtils.getMIUIVersionCode() >= 7) {
+ setAndroidNativeLightStatusBar(activity, isMarginStatusBar, isMarginNavigationBar, isTransStatusBar, dark);
+ } else {
+ setMIUILightStatusBar(activity, isMarginStatusBar, isMarginNavigationBar, isTransStatusBar, dark);
+ }
+ break;
+
+ case RomUtils.AvailableRomType.FLYME:
+ setFlymeLightStatusBar(activity, isMarginStatusBar, isMarginNavigationBar, isTransStatusBar, dark);
+ break;
+
+ case RomUtils.AvailableRomType.ANDROID_NATIVE:
+ setAndroidNativeLightStatusBar(activity, isMarginStatusBar, isMarginNavigationBar, isTransStatusBar, dark);
+ break;
+
+ case RomUtils.AvailableRomType.NA:
+ // N/A do nothing
+ break;
+ }
+ }
+
+
+ private static boolean setMIUILightStatusBar(Activity activity, boolean isMarginStatusBar
+ , boolean isMarginNavigationBar, boolean isTransStatusBar, boolean darkmode) {
+ initStatusBarStyle(activity, isMarginStatusBar, isMarginNavigationBar);
+
+ Class extends Window> 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);
+ return true;
+ } catch (Exception e) {
+ setAndroidNativeLightStatusBar(activity, isMarginStatusBar, isMarginNavigationBar, isTransStatusBar, darkmode);
+ }
+ return false;
+ }
+
+ private static boolean setFlymeLightStatusBar(Activity activity, boolean isMarginStatusBar
+ , boolean isMarginNavigationBar, boolean isTransStatusBar, boolean dark) {
+ boolean result = false;
+ if (activity != null) {
+ initStatusBarStyle(activity, isMarginStatusBar, isMarginNavigationBar);
+ try {
+ WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
+ Field darkFlag = WindowManager.LayoutParams.class
+ .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
+ Field meizuFlags = WindowManager.LayoutParams.class
+ .getDeclaredField("meizuFlags");
+ darkFlag.setAccessible(true);
+ meizuFlags.setAccessible(true);
+ int bit = darkFlag.getInt(null);
+ int value = meizuFlags.getInt(lp);
+ if (dark) {
+ value |= bit;
+ } else {
+ value &= ~bit;
+ }
+ meizuFlags.setInt(lp, value);
+ activity.getWindow().setAttributes(lp);
+ result = true;
+
+ if (RomUtils.getFlymeVersion() >= 7) {
+ setAndroidNativeLightStatusBar(activity, isMarginStatusBar, isMarginNavigationBar, isTransStatusBar, dark);
+ }
+ } catch (Exception e) {
+ setAndroidNativeLightStatusBar(activity, isMarginStatusBar, isMarginNavigationBar, isTransStatusBar, dark);
+ }
+ }
+ return result;
+ }
+
+ @TargetApi(11)
+ private static void setAndroidNativeLightStatusBar(Activity activity, boolean isMarginStatusBar
+ , boolean isMarginNavigationBar, boolean isTransStatusBar, boolean isDarkStatusBarIcon) {
+
+ try {
+ if (isTransStatusBar) {
+ Window window = activity.getWindow();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (isMarginStatusBar && isMarginNavigationBar) {
+ //5.0版本及以上
+ if (isDarkStatusBarIcon && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ } else {
+ window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
+ } else if (!isMarginStatusBar && !isMarginNavigationBar) {
+
+ if (isDarkStatusBarIcon && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+// | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ } else {
+ window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+// | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
+
+
+ } else if (!isMarginStatusBar && isMarginNavigationBar) {
+ if (isDarkStatusBarIcon && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ } else {
+ window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
+
+
+ } else {
+ //留出来状态栏 不留出来导航栏 没找到办法。。
+ return;
+ }
+ }
+ } else {
+ View decor = activity.getWindow().getDecorView();
+ if (isDarkStatusBarIcon && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ } else {
+ // We want to change tint color to white again.
+ // You can also record the flags in advance so that you can turn UI back completely if
+ // you have set other flags before, such as translucent or full screen.
+ decor.setSystemUiVisibility(0);
+ }
+ }
+ } catch (Exception e) {
+ }
+ }
+
+ private static void initStatusBarStyle(Activity activity, boolean isMarginStatusBar
+ , boolean isMarginNavigationBar) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ if (isMarginStatusBar && isMarginNavigationBar) {
+ activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ } else if (!isMarginStatusBar && !isMarginNavigationBar) {
+ activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ } else if (!isMarginStatusBar && isMarginNavigationBar) {
+
+ activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ } else {
+ //留出来状态栏 不留出来导航栏 没找到办法。。
+ }
+ }
+
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/immersive/NavBarUtils.java b/picture_library/src/main/java/com/luck/picture/lib/immersive/NavBarUtils.java
new file mode 100644
index 0000000..6e6c929
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/immersive/NavBarUtils.java
@@ -0,0 +1,31 @@
+package com.luck.picture.lib.immersive;
+
+import android.app.Activity;
+import android.os.Build;
+import android.view.Window;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+
+/**
+ * @author:luck
+ * @date:2019-11-25 20:58
+ * @describe:NavBar工具类
+ */
+public class NavBarUtils {
+ /**
+ * 动态设置 NavBar 色值
+ *
+ * @param activity
+ * @param color
+ */
+ public static void setNavBarColor(@NonNull final Activity activity, @ColorInt final int color) {
+ setNavBarColor(activity.getWindow(), color);
+ }
+
+ public static void setNavBarColor(@NonNull final Window window, @ColorInt final int color) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ window.setNavigationBarColor(color);
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/immersive/RomUtils.java b/picture_library/src/main/java/com/luck/picture/lib/immersive/RomUtils.java
new file mode 100644
index 0000000..373533d
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/immersive/RomUtils.java
@@ -0,0 +1,139 @@
+package com.luck.picture.lib.immersive;
+
+import android.os.Build;
+import android.text.TextUtils;
+import com.luck.picture.lib.tools.StringUtils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * @author:luck
+ * @data:2018/3/28 下午1:02
+ * @描述: Rom版本管理
+ */
+
+public class RomUtils {
+ public class AvailableRomType {
+ public static final int MIUI = 1;
+ public static final int FLYME = 2;
+ public static final int ANDROID_NATIVE = 3;
+ public static final int NA = 4;
+ }
+
+
+ private static Integer romType;
+
+ public static int getLightStatausBarAvailableRomType() {
+ if (romType != null) {
+ return romType;
+ }
+
+ if (isMIUIV6OrAbove()) {
+ romType = AvailableRomType.MIUI;
+ return romType;
+ }
+
+ if (isFlymeV4OrAbove()) {
+ romType = AvailableRomType.FLYME;
+ return romType;
+ }
+
+ if (isAndroid5OrAbove()) {
+ romType = AvailableRomType.ANDROID_NATIVE;
+ return romType;
+ }
+
+ romType = AvailableRomType.NA;
+ return romType;
+ }
+
+ //Flyme V4的displayId格式为 [Flyme OS 4.x.x.xA]
+ //Flyme V5的displayId格式为 [Flyme 5.x.x.x beta]
+ private static boolean isFlymeV4OrAbove() {
+ return (getFlymeVersion() >= 4);
+ }
+
+
+ //Flyme V4的displayId格式为 [Flyme OS 4.x.x.xA]
+ //Flyme V5的displayId格式为 [Flyme 5.x.x.x beta]
+ public static int getFlymeVersion() {
+ String displayId = Build.DISPLAY;
+ if (!TextUtils.isEmpty(displayId) && displayId.contains("Flyme")) {
+ displayId = displayId.replaceAll("Flyme", "");
+ displayId = displayId.replaceAll("OS", "");
+ displayId = displayId.replaceAll(" ", "");
+
+
+ String version = displayId.substring(0, 1);
+
+ if (version != null) {
+ return StringUtils.stringToInt(version);
+ }
+ }
+ return 0;
+ }
+
+ //MIUI V6对应的versionCode是4
+ //MIUI V7对应的versionCode是5
+ private static 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;
+ }
+
+
+ public static int getMIUIVersionCode() {
+ String miuiVersionCodeStr = getSystemProperty("ro.miui.ui.version.code");
+ int miuiVersionCode = 0;
+ if (!TextUtils.isEmpty(miuiVersionCodeStr)) {
+ try {
+ miuiVersionCode = Integer.parseInt(miuiVersionCodeStr);
+ return miuiVersionCode;
+ } catch (Exception e) {
+ }
+ }
+ return miuiVersionCode;
+ }
+
+
+ //Android Api 23以上
+ private static boolean isAndroid5OrAbove() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return true;
+ }
+ return false;
+ }
+
+
+ public static 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/picture_library/src/main/java/com/luck/picture/lib/language/LanguageConfig.java b/picture_library/src/main/java/com/luck/picture/lib/language/LanguageConfig.java
new file mode 100644
index 0000000..17f0e99
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/language/LanguageConfig.java
@@ -0,0 +1,46 @@
+package com.luck.picture.lib.language;
+
+/**
+ * @author:luck
+ * @date:2019-11-25 21:50
+ * @describe:语言配制
+ */
+public class LanguageConfig {
+ /**
+ * 简体中文
+ */
+ public static final int CHINESE = 0;
+ /**
+ * 繁体
+ */
+ public static final int TRADITIONAL_CHINESE = 1;
+
+ // 英语
+ public static final int ENGLISH = 2;
+
+ /**
+ * 韩语
+ */
+ public static final int KOREA = 3;
+
+ /**
+ * 德语
+ */
+ public static final int GERMANY = 4;
+
+ /**
+ * 法语
+ */
+ public static final int FRANCE = 5;
+
+ /**
+ * 日语
+ */
+ public static final int JAPAN = 6;
+
+ /**
+ * 越语
+ */
+ public static final int VIETNAM = 7;
+
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/language/LocaleTransform.java b/picture_library/src/main/java/com/luck/picture/lib/language/LocaleTransform.java
new file mode 100644
index 0000000..684eeac
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/language/LocaleTransform.java
@@ -0,0 +1,39 @@
+package com.luck.picture.lib.language;
+
+import java.util.Locale;
+
+/**
+ * @author:luck
+ * @date:2019-11-25 21:58
+ * @describe:语言转换
+ */
+public class LocaleTransform {
+ public static Locale getLanguage(int language) {
+ switch (language) {
+ case LanguageConfig.ENGLISH:
+ // 英语-美国
+ return Locale.ENGLISH;
+ case LanguageConfig.TRADITIONAL_CHINESE:
+ // 繁体中文
+ return Locale.TRADITIONAL_CHINESE;
+ case LanguageConfig.KOREA:
+ // 韩语
+ return Locale.KOREA;
+ case LanguageConfig.GERMANY:
+ // 德语
+ return Locale.GERMANY;
+ case LanguageConfig.FRANCE:
+ // 法语
+ return Locale.FRANCE;
+ case LanguageConfig.JAPAN:
+ // 日语
+ return Locale.JAPAN;
+ case LanguageConfig.VIETNAM:
+ // 越南语
+ return new Locale("vi");
+ default:
+ // 简体中文
+ return Locale.CHINESE;
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/language/PictureLanguageUtils.java b/picture_library/src/main/java/com/luck/picture/lib/language/PictureLanguageUtils.java
new file mode 100644
index 0000000..cf5aaae
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/language/PictureLanguageUtils.java
@@ -0,0 +1,118 @@
+package com.luck.picture.lib.language;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.util.DisplayMetrics;
+
+import androidx.annotation.NonNull;
+
+import com.luck.picture.lib.tools.SPUtils;
+
+import java.lang.ref.WeakReference;
+import java.util.Locale;
+
+/**
+ * @author:luck
+ * @data:2018/3/28 下午1:00
+ * @描述: PictureLanguageUtils
+ */
+public class PictureLanguageUtils {
+
+ private static final String KEY_LOCALE = "KEY_LOCALE";
+ private static final String VALUE_FOLLOW_SYSTEM = "VALUE_FOLLOW_SYSTEM";
+
+ private PictureLanguageUtils() {
+ throw new UnsupportedOperationException("u can't instantiate me...");
+ }
+
+ /**
+ * init app the language
+ *
+ * @param context
+ * @param languageId
+ */
+ public static void setAppLanguage(Context context, int languageId) {
+ WeakReference contextWeakReference = new WeakReference<>(context);
+ if (languageId >= 0) {
+ applyLanguage(contextWeakReference.get(), LocaleTransform.getLanguage(languageId));
+ } else {
+ setDefaultLanguage(contextWeakReference.get());
+ }
+ }
+
+ /**
+ * Apply the language.
+ *
+ * @param locale The language of locale.
+ */
+ private static void applyLanguage(@NonNull Context context, @NonNull final Locale locale) {
+ applyLanguage(context, locale, false);
+ }
+
+
+ private static void applyLanguage(@NonNull Context context, @NonNull final Locale locale,
+ final boolean isFollowSystem) {
+ if (isFollowSystem) {
+ SPUtils.getPictureSpUtils().put(KEY_LOCALE, VALUE_FOLLOW_SYSTEM);
+ } else {
+ String localLanguage = locale.getLanguage();
+ String localCountry = locale.getCountry();
+ SPUtils.getPictureSpUtils().put(KEY_LOCALE, localLanguage + "$" + localCountry);
+ }
+
+ updateLanguage(context, locale);
+ }
+
+
+ private static void updateLanguage(Context context, Locale locale) {
+ Resources resources = context.getResources();
+ Configuration config = resources.getConfiguration();
+ Locale contextLocale = config.locale;
+ if (equals(contextLocale.getLanguage(), locale.getLanguage())
+ && equals(contextLocale.getCountry(), locale.getCountry())) {
+ return;
+ }
+ DisplayMetrics dm = resources.getDisplayMetrics();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ config.setLocale(locale);
+ context.createConfigurationContext(config);
+ } else {
+ config.locale = locale;
+ }
+ resources.updateConfiguration(config, dm);
+ }
+
+ /**
+ * set default language
+ *
+ * @param context
+ */
+ private static void setDefaultLanguage(Context context) {
+ Resources resources = context.getResources();
+ Configuration config = resources.getConfiguration();
+ DisplayMetrics dm = resources.getDisplayMetrics();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ config.setLocale(config.locale);
+ context.createConfigurationContext(config);
+ }
+ resources.updateConfiguration(config, dm);
+ }
+
+ private static boolean equals(final CharSequence s1, final CharSequence s2) {
+ if (s1 == s2) return true;
+ int length;
+ if (s1 != null && s2 != null && (length = s1.length()) == s2.length()) {
+ if (s1 instanceof String && s2 instanceof String) {
+ return s1.equals(s2);
+ } else {
+ for (int i = 0; i < length; i++) {
+ if (s1.charAt(i) != s2.charAt(i)) return false;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/listener/OnAlbumItemClickListener.java b/picture_library/src/main/java/com/luck/picture/lib/listener/OnAlbumItemClickListener.java
new file mode 100644
index 0000000..bd79ceb
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/listener/OnAlbumItemClickListener.java
@@ -0,0 +1,24 @@
+package com.luck.picture.lib.listener;
+
+import com.luck.picture.lib.entity.LocalMedia;
+
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2020-03-26 10:57
+ * @describe:OnAlbumItemClickListener
+ */
+public interface OnAlbumItemClickListener {
+ /**
+ * Album catalog item click event
+ *
+ * @param position
+ * @param isCameraFolder
+ * @param bucketId
+ * @param folderName
+ * @param data
+ */
+ void onItemClick(int position, boolean isCameraFolder,
+ long bucketId, String folderName, List data);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/listener/OnCallbackListener.java b/picture_library/src/main/java/com/luck/picture/lib/listener/OnCallbackListener.java
new file mode 100644
index 0000000..b9093f3
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/listener/OnCallbackListener.java
@@ -0,0 +1,13 @@
+package com.luck.picture.lib.listener;
+
+/**
+ * @author:luck
+ * @date:2020/4/24 11:48 AM
+ * @describe:OnCallbackListener
+ */
+public interface OnCallbackListener {
+ /**
+ * @param data
+ */
+ void onCall(T data);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/listener/OnCustomCameraInterfaceListener.java b/picture_library/src/main/java/com/luck/picture/lib/listener/OnCustomCameraInterfaceListener.java
new file mode 100644
index 0000000..ce9604a
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/listener/OnCustomCameraInterfaceListener.java
@@ -0,0 +1,21 @@
+package com.luck.picture.lib.listener;
+
+import android.content.Context;
+
+import com.luck.picture.lib.config.PictureSelectionConfig;
+
+/**
+ * @author:luck
+ * @date:2020/4/27 3:24 PM
+ * @describe:OnCustomCameraInterfaceListener
+ */
+public interface OnCustomCameraInterfaceListener {
+ /**
+ * Camera Menu
+ *
+ * @param context
+ * @param config
+ * @param type
+ */
+ void onCameraClick(Context context, PictureSelectionConfig config, int type);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/listener/OnImageCompleteCallback.java b/picture_library/src/main/java/com/luck/picture/lib/listener/OnImageCompleteCallback.java
new file mode 100644
index 0000000..cc4b18f
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/listener/OnImageCompleteCallback.java
@@ -0,0 +1,18 @@
+package com.luck.picture.lib.listener;
+
+/**
+ * @author:luck
+ * @date:2020-01-03 16:43
+ * @describe:Image load complete callback
+ */
+public interface OnImageCompleteCallback {
+ /**
+ * Start loading
+ */
+ void onShowLoading();
+
+ /**
+ * Stop loading
+ */
+ void onHideLoading();
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/listener/OnItemClickListener.java b/picture_library/src/main/java/com/luck/picture/lib/listener/OnItemClickListener.java
new file mode 100644
index 0000000..a0b4a8b
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/listener/OnItemClickListener.java
@@ -0,0 +1,18 @@
+package com.luck.picture.lib.listener;
+
+import android.view.View;
+
+/**
+ * @author:luck
+ * @date:2020-03-26 10:50
+ * @describe:OnItemClickListener
+ */
+public interface OnItemClickListener {
+ /**
+ * Item click event
+ *
+ * @param v
+ * @param position
+ */
+ void onItemClick(View v, int position);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/listener/OnPhotoSelectChangedListener.java b/picture_library/src/main/java/com/luck/picture/lib/listener/OnPhotoSelectChangedListener.java
new file mode 100644
index 0000000..3279127
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/listener/OnPhotoSelectChangedListener.java
@@ -0,0 +1,30 @@
+package com.luck.picture.lib.listener;
+
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2020-03-26 10:34
+ * @describe:OnPhotoSelectChangedListener
+ */
+public interface OnPhotoSelectChangedListener {
+ /**
+ * Photo callback
+ */
+ void onTakePhoto();
+
+ /**
+ * Selected LocalMedia callback
+ *
+ * @param data
+ */
+ void onChange(List data);
+
+ /**
+ * Image preview callback
+ *
+ * @param data
+ * @param position
+ */
+ void onPictureClick(T data, int position);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/listener/OnQueryDataResultListener.java b/picture_library/src/main/java/com/luck/picture/lib/listener/OnQueryDataResultListener.java
new file mode 100644
index 0000000..146f1cb
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/listener/OnQueryDataResultListener.java
@@ -0,0 +1,19 @@
+package com.luck.picture.lib.listener;
+
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2020-04-16 12:42
+ * @describe:OnQueryMediaResultListener
+ */
+public interface OnQueryDataResultListener {
+ /**
+ * Query to complete The callback listener
+ *
+ * @param data The data source
+ * @param currentPage The page number
+ * @param isHasMore Is there more
+ */
+ void onComplete(List data, int currentPage, boolean isHasMore);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/listener/OnRecyclerViewPreloadMoreListener.java b/picture_library/src/main/java/com/luck/picture/lib/listener/OnRecyclerViewPreloadMoreListener.java
new file mode 100644
index 0000000..d863610
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/listener/OnRecyclerViewPreloadMoreListener.java
@@ -0,0 +1,13 @@
+package com.luck.picture.lib.listener;
+
+/**
+ * @author:luck
+ * @date:2020-04-14 18:44
+ * @describe:OnRecyclerViewPreloadMoreListener
+ */
+public interface OnRecyclerViewPreloadMoreListener {
+ /**
+ * load more
+ */
+ void onRecyclerViewPreloadMore();
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/listener/OnResultCallbackListener.java b/picture_library/src/main/java/com/luck/picture/lib/listener/OnResultCallbackListener.java
new file mode 100644
index 0000000..7f24fee
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/listener/OnResultCallbackListener.java
@@ -0,0 +1,22 @@
+package com.luck.picture.lib.listener;
+
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2020-01-14 17:08
+ * @describe:onResult Callback Listener
+ */
+public interface OnResultCallbackListener {
+ /**
+ * return LocalMedia result
+ *
+ * @param result
+ */
+ void onResult(List result);
+
+ /**
+ * Cancel
+ */
+ void onCancel();
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/listener/OnVideoSelectedPlayCallback.java b/picture_library/src/main/java/com/luck/picture/lib/listener/OnVideoSelectedPlayCallback.java
new file mode 100644
index 0000000..6032ee0
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/listener/OnVideoSelectedPlayCallback.java
@@ -0,0 +1,16 @@
+package com.luck.picture.lib.listener;
+
+
+/**
+ * @author:luck
+ * @date:2020-01-15 14:38
+ * @describe:Custom video playback callback
+ */
+public interface OnVideoSelectedPlayCallback {
+ /**
+ * Play the video
+ *
+ * @param data
+ */
+ void startPlayVideo(T data);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/model/LocalMediaLoader.java b/picture_library/src/main/java/com/luck/picture/lib/model/LocalMediaLoader.java
new file mode 100644
index 0000000..6139d1e
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/model/LocalMediaLoader.java
@@ -0,0 +1,407 @@
+package com.luck.picture.lib.model;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.luck.picture.lib.R;
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.config.PictureSelectionConfig;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.entity.LocalMediaFolder;
+import com.luck.picture.lib.tools.PictureFileUtils;
+import com.luck.picture.lib.tools.SdkVersionUtils;
+import com.luck.picture.lib.tools.ValueOf;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * @author:luck
+ * @data:2016/12/31 19:12
+ * @describe: Local media database query class
+ */
+@Deprecated
+public final class LocalMediaLoader {
+ private static final String TAG = LocalMediaLoader.class.getSimpleName();
+ private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");
+ private static final String ORDER_BY = MediaStore.Files.FileColumns._ID + " DESC";
+ private static final String NOT_GIF = "!='image/gif'";
+ /**
+ * Filter out recordings that are less than 500 milliseconds long
+ */
+ private static final int AUDIO_DURATION = 500;
+ private Context mContext;
+ private boolean isAndroidQ;
+ private PictureSelectionConfig config;
+ /**
+ * unit
+ */
+ private static final long FILE_SIZE_UNIT = 1024 * 1024L;
+ /**
+ * Media file database field
+ */
+ private static final String[] PROJECTION = {
+ MediaStore.Files.FileColumns._ID,
+ MediaStore.MediaColumns.DATA,
+ MediaStore.MediaColumns.MIME_TYPE,
+ MediaStore.MediaColumns.WIDTH,
+ MediaStore.MediaColumns.HEIGHT,
+ MediaStore.MediaColumns.DURATION,
+ MediaStore.MediaColumns.SIZE,
+ MediaStore.MediaColumns.BUCKET_DISPLAY_NAME,
+ MediaStore.MediaColumns.DISPLAY_NAME,
+ MediaStore.MediaColumns.BUCKET_ID};
+
+ /**
+ * Image
+ */
+ private static final String SELECTION = MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + " AND " + MediaStore.MediaColumns.SIZE + ">0";
+
+ private static final String SELECTION_NOT_GIF = MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + " AND " + MediaStore.MediaColumns.SIZE + ">0"
+ + " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF;
+ /**
+ * Queries for images with the specified suffix
+ */
+ private static final String SELECTION_SPECIFIED_FORMAT = MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + " AND " + MediaStore.MediaColumns.SIZE + ">0"
+ + " AND " + MediaStore.MediaColumns.MIME_TYPE;
+
+ /**
+ * Query criteria (audio and video)
+ *
+ * @param time_condition
+ * @return
+ */
+ private static String getSelectionArgsForSingleMediaCondition(String time_condition) {
+ return MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + " AND " + MediaStore.MediaColumns.SIZE + ">0"
+ + " AND " + time_condition;
+ }
+
+ /**
+ * Query (video)
+ *
+ * @return
+ */
+ private static String getSelectionArgsForSingleMediaCondition() {
+ return MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + " AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+
+ /**
+ * Query conditions in all modes
+ *
+ * @param time_condition
+ * @param isGif
+ * @return
+ */
+ private static String getSelectionArgsForAllMediaCondition(String time_condition, boolean isGif) {
+ String condition = "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + (isGif ? "" : " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF)
+ + " OR "
+ + (MediaStore.Files.FileColumns.MEDIA_TYPE + "=? AND " + time_condition) + ")"
+ + " AND " + MediaStore.MediaColumns.SIZE + ">0";
+ return condition;
+ }
+
+ /**
+ * Get pictures or videos
+ */
+ private static final String[] SELECTION_ALL_ARGS = {
+ String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
+ String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
+ };
+
+ /**
+ * Gets a file of the specified type
+ *
+ * @param mediaType
+ * @return
+ */
+ private static String[] getSelectionArgsForSingleMediaType(int mediaType) {
+ return new String[]{String.valueOf(mediaType)};
+ }
+
+
+ public LocalMediaLoader(Context context, PictureSelectionConfig config) {
+ this.mContext = context.getApplicationContext();
+ this.isAndroidQ = SdkVersionUtils.checkedAndroid_Q();
+ this.config = config;
+ }
+
+ /**
+ * Query the local gallery data
+ *
+ * @return
+ */
+ public List loadAllMedia() {
+ Cursor data = mContext.getContentResolver().query(QUERY_URI, PROJECTION, getSelection(), getSelectionArgs(), ORDER_BY);
+ try {
+ if (data != null) {
+ List imageFolders = new ArrayList<>();
+ LocalMediaFolder allImageFolder = new LocalMediaFolder();
+ List latelyImages = new ArrayList<>();
+ int count = data.getCount();
+ if (count > 0) {
+ data.moveToFirst();
+ do {
+ long id = data.getLong
+ (data.getColumnIndexOrThrow(PROJECTION[0]));
+
+ String absolutePath = data.getString
+ (data.getColumnIndexOrThrow(PROJECTION[1]));
+
+ String url = isAndroidQ ? getRealPathAndroid_Q(id) : absolutePath;
+
+ String mimeType = data.getString
+ (data.getColumnIndexOrThrow(PROJECTION[2]));
+
+ mimeType = TextUtils.isEmpty(mimeType) ? PictureMimeType.ofJPEG() : mimeType;
+ // Here, it is solved that some models obtain mimeType and return the format of image / *,
+ // which makes it impossible to distinguish the specific type, such as mi 8,9,10 and other models
+ if (mimeType.endsWith("image/*")) {
+ if (PictureMimeType.isContent(url)) {
+ mimeType = PictureMimeType.getImageMimeType(absolutePath);
+ } else {
+ mimeType = PictureMimeType.getImageMimeType(url);
+ }
+ if (!config.isGif) {
+ boolean isGif = PictureMimeType.isGif(mimeType);
+ if (isGif) {
+ continue;
+ }
+ }
+ }
+ int width = data.getInt
+ (data.getColumnIndexOrThrow(PROJECTION[3]));
+
+ int height = data.getInt
+ (data.getColumnIndexOrThrow(PROJECTION[4]));
+
+ long duration = data.getLong
+ (data.getColumnIndexOrThrow(PROJECTION[5]));
+
+ long size = data.getLong
+ (data.getColumnIndexOrThrow(PROJECTION[6]));
+
+ String folderName = data.getString
+ (data.getColumnIndexOrThrow(PROJECTION[7]));
+
+ String fileName = data.getString
+ (data.getColumnIndexOrThrow(PROJECTION[8]));
+
+ long bucketId = data.getLong(data.getColumnIndexOrThrow(PROJECTION[9]));
+
+ if (config.filterFileSize > 0) {
+ if (size > config.filterFileSize * FILE_SIZE_UNIT) {
+ continue;
+ }
+ }
+ if (PictureMimeType.isHasVideo(mimeType)) {
+ if (config.videoMinSecond > 0 && duration < config.videoMinSecond) {
+ // If you set the minimum number of seconds of video to display
+ continue;
+ }
+ if (config.videoMaxSecond > 0 && duration > config.videoMaxSecond) {
+ // If you set the maximum number of seconds of video to display
+ continue;
+ }
+ if (duration == 0) {
+ //If the length is 0, the corrupted video is processed and filtered out
+ continue;
+ }
+ if (size <= 0) {
+ // The video size is 0 to filter out
+ continue;
+ }
+ }
+ LocalMedia image = new LocalMedia
+ (id, url, absolutePath, fileName, folderName, duration, config.chooseMode, mimeType, width, height, size, bucketId);
+ LocalMediaFolder folder = getImageFolder(url, folderName, imageFolders);
+ folder.setBucketId(image.getBucketId());
+ List images = folder.getData();
+ images.add(image);
+ folder.setImageNum(folder.getImageNum() + 1);
+ folder.setBucketId(image.getBucketId());
+ latelyImages.add(image);
+ int imageNum = allImageFolder.getImageNum();
+ allImageFolder.setImageNum(imageNum + 1);
+
+ } while (data.moveToNext());
+
+ if (latelyImages.size() > 0) {
+ sortFolder(imageFolders);
+ imageFolders.add(0, allImageFolder);
+ allImageFolder.setFirstImagePath
+ (latelyImages.get(0).getPath());
+ String title = config.chooseMode == PictureMimeType.ofAudio() ?
+ mContext.getString(R.string.picture_all_audio)
+ : mContext.getString(R.string.picture_camera_roll);
+ allImageFolder.setName(title);
+ allImageFolder.setBucketId(-1);
+ allImageFolder.setOfAllType(config.chooseMode);
+ allImageFolder.setCameraFolder(true);
+ allImageFolder.setData(latelyImages);
+ }
+ }
+ return imageFolders;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.i(TAG, "loadAllMedia Data Error: " + e.getMessage());
+ return null;
+ } finally {
+ if (data != null && !data.isClosed()) {
+ data.close();
+ }
+ }
+ return null;
+ }
+
+ private String getSelection() {
+ switch (config.chooseMode) {
+ case PictureConfig.TYPE_ALL:
+ // Get all, not including audio
+ return getSelectionArgsForAllMediaCondition(getDurationCondition(0, 0), config.isGif);
+ case PictureConfig.TYPE_IMAGE:
+ if (!TextUtils.isEmpty(config.specifiedFormat)) {
+ // Gets the image of the specified type
+ return SELECTION_SPECIFIED_FORMAT + "='" + config.specifiedFormat + "'";
+ }
+ return config.isGif ? SELECTION : SELECTION_NOT_GIF;
+ case PictureConfig.TYPE_VIDEO:
+ // Access to video
+ if (!TextUtils.isEmpty(config.specifiedFormat)) {
+ // Gets the image of the specified type
+ return SELECTION_SPECIFIED_FORMAT + "='" + config.specifiedFormat + "'";
+ }
+ return getSelectionArgsForSingleMediaCondition();
+ case PictureConfig.TYPE_AUDIO:
+ // Access to the audio
+ if (!TextUtils.isEmpty(config.specifiedFormat)) {
+ // Gets the image of the specified type
+ return SELECTION_SPECIFIED_FORMAT + "='" + config.specifiedFormat + "'";
+ }
+ return getSelectionArgsForSingleMediaCondition(getDurationCondition(0, AUDIO_DURATION));
+ }
+ return null;
+ }
+
+ private String[] getSelectionArgs() {
+ switch (config.chooseMode) {
+ case PictureConfig.TYPE_ALL:
+ return SELECTION_ALL_ARGS;
+ case PictureConfig.TYPE_IMAGE:
+ // Get Image
+ return getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
+ case PictureConfig.TYPE_VIDEO:
+ // Get Video
+ return getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
+ case PictureConfig.TYPE_AUDIO:
+ return getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO);
+ }
+ return null;
+ }
+
+ /**
+ * Sort by the number of files
+ *
+ * @param imageFolders
+ */
+ private void sortFolder(List imageFolders) {
+ Collections.sort(imageFolders, (lhs, rhs) -> {
+ if (lhs.getData() == null || rhs.getData() == null) {
+ return 0;
+ }
+ int lSize = lhs.getImageNum();
+ int rSize = rhs.getImageNum();
+ return Integer.compare(rSize, lSize);
+ });
+ }
+
+ /**
+ * Android Q
+ *
+ * @param id
+ * @return
+ */
+ private String getRealPathAndroid_Q(long id) {
+ return QUERY_URI.buildUpon().appendPath(ValueOf.toString(id)).build().toString();
+ }
+
+ /**
+ * Create folder
+ *
+ * @param path
+ * @param imageFolders
+ * @param folderName
+ * @return
+ */
+ private LocalMediaFolder getImageFolder(String path, String folderName, List imageFolders) {
+ if (!config.isFallbackVersion) {
+ for (LocalMediaFolder folder : imageFolders) {
+ // Under the same folder, return yourself, otherwise create a new folder
+ String name = folder.getName();
+ if (TextUtils.isEmpty(name)) {
+ continue;
+ }
+ if (name.equals(folderName)) {
+ return folder;
+ }
+ }
+ LocalMediaFolder newFolder = new LocalMediaFolder();
+ newFolder.setName(folderName);
+ newFolder.setFirstImagePath(path);
+ imageFolders.add(newFolder);
+ return newFolder;
+ } else {
+ // Fault-tolerant processing
+ File imageFile = new File(path);
+ File folderFile = imageFile.getParentFile();
+ for (LocalMediaFolder folder : imageFolders) {
+ // Under the same folder, return yourself, otherwise create a new folder
+ String name = folder.getName();
+ if (TextUtils.isEmpty(name)) {
+ continue;
+ }
+ if (folderFile != null && name.equals(folderFile.getName())) {
+ return folder;
+ }
+ }
+ LocalMediaFolder newFolder = new LocalMediaFolder();
+ newFolder.setName(folderFile != null ? folderFile.getName() : "");
+ newFolder.setFirstImagePath(path);
+ imageFolders.add(newFolder);
+ return newFolder;
+ }
+ }
+
+ /**
+ * Get video (maximum or minimum time)
+ *
+ * @param exMaxLimit
+ * @param exMinLimit
+ * @return
+ */
+ private String getDurationCondition(long exMaxLimit, long exMinLimit) {
+ long maxS = config.videoMaxSecond == 0 ? Long.MAX_VALUE : config.videoMaxSecond;
+ if (exMaxLimit != 0) {
+ maxS = Math.min(maxS, exMaxLimit);
+ }
+ return String.format(Locale.CHINA, "%d <%s " + MediaStore.MediaColumns.DURATION + " and " + MediaStore.MediaColumns.DURATION + " <= %d",
+ Math.max(exMinLimit, config.videoMinSecond),
+ Math.max(exMinLimit, config.videoMinSecond) == 0 ? "" : "=",
+ maxS);
+ }
+
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/model/LocalMediaPageLoader.java b/picture_library/src/main/java/com/luck/picture/lib/model/LocalMediaPageLoader.java
new file mode 100644
index 0000000..cd97ae3
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/model/LocalMediaPageLoader.java
@@ -0,0 +1,713 @@
+package com.luck.picture.lib.model;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.luck.picture.lib.R;
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.config.PictureSelectionConfig;
+import com.luck.picture.lib.entity.LocalMedia;
+import com.luck.picture.lib.entity.LocalMediaFolder;
+import com.luck.picture.lib.entity.MediaData;
+import com.luck.picture.lib.listener.OnQueryDataResultListener;
+import com.luck.picture.lib.thread.PictureThreadUtils;
+import com.luck.picture.lib.tools.PictureFileUtils;
+import com.luck.picture.lib.tools.SdkVersionUtils;
+import com.luck.picture.lib.tools.ValueOf;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author:luck
+ * @date:2020-04-13 15:06
+ * @describe:Local media database query class,Support paging
+ */
+public final class LocalMediaPageLoader {
+ private static final String TAG = LocalMediaPageLoader.class.getSimpleName();
+
+ private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");
+ private static final String ORDER_BY = MediaStore.Files.FileColumns._ID + " DESC";
+ private static final String NOT_GIF_UNKNOWN = "!='image/*'";
+ private static final String NOT_GIF = "!='image/gif' AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF_UNKNOWN;
+ private static final String GROUP_BY_BUCKET_Id = " GROUP BY (bucket_id";
+ private static final String COLUMN_COUNT = "count";
+ private static final String COLUMN_BUCKET_ID = "bucket_id";
+ private static final String COLUMN_BUCKET_DISPLAY_NAME = "bucket_display_name";
+
+ /**
+ * Filter out recordings that are less than 500 milliseconds long
+ */
+ private static final int AUDIO_DURATION = 500;
+ private Context mContext;
+ private PictureSelectionConfig config;
+ /**
+ * unit
+ */
+ private static final long FILE_SIZE_UNIT = 1024 * 1024L;
+ /**
+ * Image
+ */
+ private static final String SELECTION = "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=? )"
+ + " AND " + MediaStore.MediaColumns.SIZE + ">0)" + GROUP_BY_BUCKET_Id;
+
+ private static final String SELECTION_29 = MediaStore.Files.FileColumns.MEDIA_TYPE + "=? "
+ + " AND " + MediaStore.MediaColumns.SIZE + ">0";
+
+ private static final String SELECTION_NOT_GIF = "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF + ") AND " + MediaStore.MediaColumns.SIZE + ">0)" + GROUP_BY_BUCKET_Id;
+
+ private static final String SELECTION_NOT_GIF_29 = MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF + " AND " + MediaStore.MediaColumns.SIZE + ">0";
+ /**
+ * Queries for images with the specified suffix
+ */
+ private static final String SELECTION_SPECIFIED_FORMAT = "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + " AND " + MediaStore.MediaColumns.MIME_TYPE;
+
+ /**
+ * Queries for images with the specified suffix targetSdk>=29
+ */
+ private static final String SELECTION_SPECIFIED_FORMAT_29 = MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + " AND " + MediaStore.MediaColumns.MIME_TYPE;
+
+ /**
+ * Query criteria (audio and video)
+ *
+ * @param timeCondition
+ * @return
+ */
+ private static String getSelectionArgsForSingleMediaCondition(String timeCondition) {
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ return MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + " AND " + MediaStore.MediaColumns.SIZE + ">0"
+ + " AND " + timeCondition;
+ }
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + ") AND " + MediaStore.MediaColumns.SIZE + ">0"
+ + " AND " + timeCondition + ")" + GROUP_BY_BUCKET_Id;
+ }
+
+ /**
+ * All mode conditions
+ *
+ * @param timeCondition
+ * @param isGif
+ * @return
+ */
+ private static String getSelectionArgsForAllMediaCondition(String timeCondition, boolean isGif) {
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + (isGif ? "" : " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF)
+ + " OR " + MediaStore.Files.FileColumns.MEDIA_TYPE + "=? AND " + timeCondition + ") AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + (isGif ? "" : " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF)
+ + " OR " + (MediaStore.Files.FileColumns.MEDIA_TYPE + "=? AND " + timeCondition) + ")" + " AND " + MediaStore.MediaColumns.SIZE + ">0)" + GROUP_BY_BUCKET_Id;
+ }
+
+ /**
+ * Get pictures or videos
+ */
+ private static final String[] SELECTION_ALL_ARGS = {
+ String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
+ String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
+ };
+
+ /**
+ * Gets a file of the specified type
+ *
+ * @param mediaType
+ * @return
+ */
+ private static String[] getSelectionArgsForSingleMediaType(int mediaType) {
+ return new String[]{String.valueOf(mediaType)};
+ }
+
+ /**
+ * Gets a file of the specified type
+ *
+ * @param mediaType
+ * @return
+ */
+ private static String[] getSelectionArgsForPageSingleMediaType(int mediaType, long bucketId) {
+ return bucketId == -1 ? new String[]{String.valueOf(mediaType)} : new String[]{String.valueOf(mediaType), ValueOf.toString(bucketId)};
+ }
+
+
+ public LocalMediaPageLoader(Context context, PictureSelectionConfig config) {
+ this.mContext = context;
+ this.config = config;
+ }
+
+ private static final String[] PROJECTION_29 = {
+ MediaStore.Files.FileColumns._ID,
+ COLUMN_BUCKET_ID,
+ COLUMN_BUCKET_DISPLAY_NAME,
+ MediaStore.MediaColumns.MIME_TYPE};
+
+ private static final String[] PROJECTION = {
+ MediaStore.Files.FileColumns._ID,
+ MediaStore.MediaColumns.DATA,
+ COLUMN_BUCKET_ID,
+ COLUMN_BUCKET_DISPLAY_NAME,
+ MediaStore.MediaColumns.MIME_TYPE,
+ "COUNT(*) AS " + COLUMN_COUNT};
+
+ /**
+ * Media file database field
+ */
+ private static final String[] PROJECTION_PAGE = {
+ MediaStore.Files.FileColumns._ID,
+ MediaStore.MediaColumns.DATA,
+ MediaStore.MediaColumns.MIME_TYPE,
+ MediaStore.MediaColumns.WIDTH,
+ MediaStore.MediaColumns.HEIGHT,
+ MediaStore.MediaColumns.DURATION,
+ MediaStore.MediaColumns.SIZE,
+ MediaStore.MediaColumns.BUCKET_DISPLAY_NAME,
+ MediaStore.MediaColumns.DISPLAY_NAME,
+ COLUMN_BUCKET_ID};
+
+ /**
+ * Get the latest cover of an album catalog
+ *
+ * @param bucketId
+ * @return
+ */
+ public String getFirstCover(long bucketId) {
+ Cursor data = null;
+ try {
+ String orderBy = MediaStore.Files.FileColumns._ID + " DESC limit 1 offset 0";
+ data = mContext.getContentResolver().query(QUERY_URI, new String[]{
+ MediaStore.Files.FileColumns._ID,
+ MediaStore.MediaColumns.DATA}, getPageSelection(bucketId), getPageSelectionArgs(bucketId), orderBy);
+ if (data != null && data.getCount() > 0) {
+ if (data.moveToFirst()) {
+ long id = data.getLong(data.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID));
+ return SdkVersionUtils.checkedAndroid_Q() ? getRealPathAndroid_Q(id) : data.getString
+ (data.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA));
+ }
+ return null;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (data != null && !data.isClosed()) {
+ data.close();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Queries for data in the specified directory
+ *
+ * @param bucketId
+ * @param page
+ * @param limit
+ * @param listener
+ * @return
+ */
+ public void loadPageMediaData(long bucketId, int page, int limit, OnQueryDataResultListener listener) {
+ loadPageMediaData(bucketId, page, limit, config.pageSize, listener);
+ }
+
+ /**
+ * Queries for data in the specified directory
+ *
+ * @param bucketId
+ * @param listener
+ * @return
+ */
+ public void loadPageMediaData(long bucketId, int page, OnQueryDataResultListener listener) {
+ loadPageMediaData(bucketId, page, config.pageSize, config.pageSize, listener);
+ }
+
+ /**
+ * Queries for data in the specified directory (page)
+ *
+ * @param bucketId
+ * @param page
+ * @param limit
+ * @param pageSize
+ * @return
+ */
+ public void loadPageMediaData(long bucketId, int page, int limit, int pageSize, OnQueryDataResultListener listener) {
+ PictureThreadUtils.executeByIo(new PictureThreadUtils.SimpleTask() {
+
+ @Override
+ public MediaData doInBackground() {
+ Cursor data = null;
+ try {
+ String orderBy = page == -1 ? MediaStore.Files.FileColumns._ID + " DESC" : MediaStore.Files.FileColumns._ID + " DESC limit " + limit + " offset " + (page - 1) * pageSize;
+ data = mContext.getContentResolver().query(QUERY_URI, PROJECTION_PAGE, getPageSelection(bucketId), getPageSelectionArgs(bucketId), orderBy);
+ if (data != null) {
+ List result = new ArrayList<>();
+ if (data.getCount() > 0) {
+ data.moveToFirst();
+ do {
+ long id = data.getLong
+ (data.getColumnIndexOrThrow(PROJECTION_PAGE[0]));
+
+ String absolutePath = data.getString
+ (data.getColumnIndexOrThrow(PROJECTION_PAGE[1]));
+
+ String url = SdkVersionUtils.checkedAndroid_Q() ? getRealPathAndroid_Q(id) : absolutePath;
+
+ if (config.isFilterInvalidFile) {
+ if (!PictureFileUtils.isFileExists(absolutePath)) {
+ continue;
+ }
+ }
+ String mimeType = data.getString
+ (data.getColumnIndexOrThrow(PROJECTION_PAGE[2]));
+
+ mimeType = TextUtils.isEmpty(mimeType) ? PictureMimeType.ofJPEG() : mimeType;
+ // Here, it is solved that some models obtain mimeType and return the format of image / *,
+ // which makes it impossible to distinguish the specific type, such as mi 8,9,10 and other models
+ if (mimeType.endsWith("image/*")) {
+ if (PictureMimeType.isContent(url)) {
+ mimeType = PictureMimeType.getImageMimeType(absolutePath);
+ } else {
+ mimeType = PictureMimeType.getImageMimeType(url);
+ }
+ if (!config.isGif) {
+ boolean isGif = PictureMimeType.isGif(mimeType);
+ if (isGif) {
+ continue;
+ }
+ }
+ }
+ int width = data.getInt
+ (data.getColumnIndexOrThrow(PROJECTION_PAGE[3]));
+
+ int height = data.getInt
+ (data.getColumnIndexOrThrow(PROJECTION_PAGE[4]));
+
+ long duration = data.getLong
+ (data.getColumnIndexOrThrow(PROJECTION_PAGE[5]));
+
+ long size = data.getLong
+ (data.getColumnIndexOrThrow(PROJECTION_PAGE[6]));
+
+ String folderName = data.getString
+ (data.getColumnIndexOrThrow(PROJECTION_PAGE[7]));
+
+ String fileName = data.getString
+ (data.getColumnIndexOrThrow(PROJECTION_PAGE[8]));
+
+ long bucket_id = data.getLong
+ (data.getColumnIndexOrThrow(PROJECTION_PAGE[9]));
+
+ if (config.filterFileSize > 0) {
+ if (size > config.filterFileSize * FILE_SIZE_UNIT) {
+ continue;
+ }
+ }
+
+ if (PictureMimeType.isHasVideo(mimeType)) {
+ if (config.videoMinSecond > 0 && duration < config.videoMinSecond) {
+ // If you set the minimum number of seconds of video to display
+ continue;
+ }
+ if (config.videoMaxSecond > 0 && duration > config.videoMaxSecond) {
+ // If you set the maximum number of seconds of video to display
+ continue;
+ }
+ if (duration == 0) {
+ //If the length is 0, the corrupted video is processed and filtered out
+ continue;
+ }
+ if (size <= 0) {
+ // The video size is 0 to filter out
+ continue;
+ }
+ }
+
+ LocalMedia image = new LocalMedia
+ (id, url, absolutePath, fileName, folderName, duration, config.chooseMode, mimeType, width, height, size, bucket_id);
+
+ result.add(image);
+
+ } while (data.moveToNext());
+ }
+ return new MediaData(data.getCount() > 0, result);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.i(TAG, "loadMedia Page Data Error: " + e.getMessage());
+ return null;
+ } finally {
+ if (data != null && !data.isClosed()) {
+ data.close();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onSuccess(MediaData result) {
+ if (listener != null && result != null) {
+ listener.onComplete(result.data, page, result.isHasNextMore);
+ }
+ }
+ });
+ }
+
+ /**
+ * Query the local gallery data
+ *
+ * @param listener
+ */
+ public void loadAllMedia(OnQueryDataResultListener listener) {
+ PictureThreadUtils.executeByIo(new PictureThreadUtils.SimpleTask>() {
+ @Override
+ public List doInBackground() {
+ Cursor data = mContext.getContentResolver().query(QUERY_URI,
+ SdkVersionUtils.checkedAndroid_Q() ? PROJECTION_29 : PROJECTION,
+ getSelection(), getSelectionArgs(), ORDER_BY);
+ try {
+ if (data != null) {
+ int count = data.getCount();
+ int totalCount = 0;
+ List mediaFolders = new ArrayList<>();
+ if (count > 0) {
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ Map countMap = new HashMap<>();
+ while (data.moveToNext()) {
+ long bucketId = data.getLong(data.getColumnIndex(COLUMN_BUCKET_ID));
+ Long newCount = countMap.get(bucketId);
+ if (newCount == null) {
+ newCount = 1L;
+ } else {
+ newCount++;
+ }
+ countMap.put(bucketId, newCount);
+ }
+
+ if (data.moveToFirst()) {
+ Set hashSet = new HashSet<>();
+ do {
+ long bucketId = data.getLong(data.getColumnIndex(COLUMN_BUCKET_ID));
+ if (hashSet.contains(bucketId)) {
+ continue;
+ }
+ LocalMediaFolder mediaFolder = new LocalMediaFolder();
+ mediaFolder.setBucketId(bucketId);
+ String bucketDisplayName = data.getString(
+ data.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME));
+ long size = countMap.get(bucketId);
+ long id = data.getLong(data.getColumnIndex(MediaStore.Files.FileColumns._ID));
+ mediaFolder.setName(bucketDisplayName);
+ mediaFolder.setImageNum(ValueOf.toInt(size));
+ mediaFolder.setFirstImagePath(getRealPathAndroid_Q(id));
+ mediaFolders.add(mediaFolder);
+ hashSet.add(bucketId);
+ totalCount += size;
+ } while (data.moveToNext());
+ }
+
+ } else {
+ data.moveToFirst();
+ do {
+ LocalMediaFolder mediaFolder = new LocalMediaFolder();
+ long bucketId = data.getLong(data.getColumnIndex(COLUMN_BUCKET_ID));
+ String bucketDisplayName = data.getString(data.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME));
+ int size = data.getInt(data.getColumnIndex(COLUMN_COUNT));
+ mediaFolder.setBucketId(bucketId);
+ String url = data.getString(data.getColumnIndex(MediaStore.MediaColumns.DATA));
+ mediaFolder.setFirstImagePath(url);
+ mediaFolder.setName(bucketDisplayName);
+ mediaFolder.setImageNum(size);
+ mediaFolders.add(mediaFolder);
+ totalCount += size;
+ } while (data.moveToNext());
+ }
+
+ sortFolder(mediaFolders);
+
+ // 相机胶卷
+ LocalMediaFolder allMediaFolder = new LocalMediaFolder();
+ allMediaFolder.setImageNum(totalCount);
+ allMediaFolder.setChecked(true);
+ allMediaFolder.setBucketId(-1);
+ if (data.moveToFirst()) {
+ String firstUrl = SdkVersionUtils.checkedAndroid_Q() ? getFirstUri(data) : getFirstUrl(data);
+ allMediaFolder.setFirstImagePath(firstUrl);
+ }
+ String bucketDisplayName = config.chooseMode == PictureMimeType.ofAudio() ?
+ mContext.getString(R.string.picture_all_audio)
+ : mContext.getString(R.string.picture_camera_roll);
+ allMediaFolder.setName(bucketDisplayName);
+ allMediaFolder.setOfAllType(config.chooseMode);
+ allMediaFolder.setCameraFolder(true);
+ mediaFolders.add(0, allMediaFolder);
+
+ return mediaFolders;
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.i(TAG, "loadAllMedia Data Error: " + e.getMessage());
+ return null;
+ } finally {
+ if (data != null && !data.isClosed()) {
+ data.close();
+ }
+ }
+ return new ArrayList<>();
+ }
+
+ @Override
+ public void onSuccess(List result) {
+ if (listener != null && result != null) {
+ listener.onComplete(result, 1, false);
+ }
+ }
+ });
+ }
+
+ /**
+ * Get cover uri
+ *
+ * @param cursor
+ * @return
+ */
+ private static String getFirstUri(Cursor cursor) {
+ long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID));
+ return getRealPathAndroid_Q(id);
+ }
+
+ /**
+ * Get cover url
+ *
+ * @param cursor
+ * @return
+ */
+ private static String getFirstUrl(Cursor cursor) {
+ return cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA));
+ }
+
+
+ private String getPageSelection(long bucketId) {
+ String durationCondition = getDurationCondition(0, 0);
+ boolean isSpecifiedFormat = !TextUtils.isEmpty(config.specifiedFormat);
+ switch (config.chooseMode) {
+ case PictureConfig.TYPE_ALL:
+ if (bucketId == -1) {
+ // ofAll
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + (config.isGif ? "" : " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF)
+ + " OR " + MediaStore.Files.FileColumns.MEDIA_TYPE + "=? AND " + durationCondition + ") AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+ // Gets the specified album directory
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + (config.isGif ? "" : " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF)
+ + " OR " + MediaStore.Files.FileColumns.MEDIA_TYPE + "=? AND " + durationCondition + ") AND " + COLUMN_BUCKET_ID + "=? AND " + MediaStore.MediaColumns.SIZE + ">0";
+
+ case PictureConfig.TYPE_IMAGE:
+ // Gets the image of the specified type
+ if (bucketId == -1) {
+ // ofAll
+ if (isSpecifiedFormat) {
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + (config.isGif ? "" : " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF + " AND " + MediaStore.MediaColumns.MIME_TYPE + "='" + config.specifiedFormat + "'")
+ + ") AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + (config.isGif ? "" : " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF)
+ + ") AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+ // Gets the specified album directory
+ if (isSpecifiedFormat) {
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + (config.isGif ? "" : " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF + " AND " + MediaStore.MediaColumns.MIME_TYPE + "='" + config.specifiedFormat + "'")
+ + ") AND " + COLUMN_BUCKET_ID + "=? AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ + (config.isGif ? "" : " AND " + MediaStore.MediaColumns.MIME_TYPE + NOT_GIF)
+ + ") AND " + COLUMN_BUCKET_ID + "=? AND " + MediaStore.MediaColumns.SIZE + ">0";
+ case PictureConfig.TYPE_VIDEO:
+ case PictureConfig.TYPE_AUDIO:
+ if (bucketId == -1) {
+ // ofAll
+ if (isSpecifiedFormat) {
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=? AND " + MediaStore.MediaColumns.MIME_TYPE + "='" + config.specifiedFormat + "'" + " AND " + durationCondition + ") AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=? AND " + durationCondition + ") AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+ // Gets the specified album directory
+ if (isSpecifiedFormat) {
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=? AND " + MediaStore.MediaColumns.MIME_TYPE + "='" + config.specifiedFormat + "'" + " AND " + durationCondition + ") AND " + COLUMN_BUCKET_ID + "=? AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+ return "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=? AND " + durationCondition + ") AND " + COLUMN_BUCKET_ID + "=? AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+ return null;
+ }
+
+ private String[] getPageSelectionArgs(long bucketId) {
+ switch (config.chooseMode) {
+ case PictureConfig.TYPE_ALL:
+ if (bucketId == -1) {
+ // ofAll
+ return new String[]{
+ String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
+ String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
+ };
+ }
+ // Gets the specified album directory
+ return new String[]{
+ String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
+ String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
+ ValueOf.toString(bucketId)
+ };
+ case PictureConfig.TYPE_IMAGE:
+ // Get photo
+ return getSelectionArgsForPageSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE, bucketId);
+ case PictureConfig.TYPE_VIDEO:
+ // Get video
+ return getSelectionArgsForPageSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO, bucketId);
+ case PictureConfig.TYPE_AUDIO:
+ // Get audio
+ return getSelectionArgsForPageSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO, bucketId);
+ }
+ return null;
+ }
+
+
+ private String getSelection() {
+ switch (config.chooseMode) {
+ case PictureConfig.TYPE_ALL:
+ // Get all, not including audio
+ return getSelectionArgsForAllMediaCondition(getDurationCondition(0, 0), config.isGif);
+ case PictureConfig.TYPE_IMAGE:
+ if (!TextUtils.isEmpty(config.specifiedFormat)) {
+ // 获取指定类型的图片
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ return SELECTION_SPECIFIED_FORMAT_29 + "='" + config.specifiedFormat + "' AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+ return SELECTION_SPECIFIED_FORMAT + "='" + config.specifiedFormat + "') AND " + MediaStore.MediaColumns.SIZE + ">0)" + GROUP_BY_BUCKET_Id;
+ }
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ return config.isGif ? SELECTION_29 : SELECTION_NOT_GIF_29;
+ }
+ return config.isGif ? SELECTION : SELECTION_NOT_GIF;
+ case PictureConfig.TYPE_VIDEO:
+ // 获取视频
+ if (!TextUtils.isEmpty(config.specifiedFormat)) {
+ // Gets the specified album directory
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ return SELECTION_SPECIFIED_FORMAT_29 + "='" + config.specifiedFormat + "' AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+ return SELECTION_SPECIFIED_FORMAT + "='" + config.specifiedFormat + "') AND " + MediaStore.MediaColumns.SIZE + ">0)" + GROUP_BY_BUCKET_Id;
+ }
+ return getSelectionArgsForSingleMediaCondition(getDurationCondition(0, 0));
+ case PictureConfig.TYPE_AUDIO:
+ // Get Audio
+ if (!TextUtils.isEmpty(config.specifiedFormat)) {
+ // Gets the specified album directory
+ if (SdkVersionUtils.checkedAndroid_Q()) {
+ return SELECTION_SPECIFIED_FORMAT_29 + "='" + config.specifiedFormat + "' AND " + MediaStore.MediaColumns.SIZE + ">0";
+ }
+ return SELECTION_SPECIFIED_FORMAT + "='" + config.specifiedFormat + "') AND " + MediaStore.MediaColumns.SIZE + ">0)" + GROUP_BY_BUCKET_Id;
+ }
+ return getSelectionArgsForSingleMediaCondition(getDurationCondition(0, AUDIO_DURATION));
+ }
+ return null;
+ }
+
+ private String[] getSelectionArgs() {
+ switch (config.chooseMode) {
+ case PictureConfig.TYPE_ALL:
+ return SELECTION_ALL_ARGS;
+ case PictureConfig.TYPE_IMAGE:
+ // Get photo
+ return getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
+ case PictureConfig.TYPE_VIDEO:
+ // Get video
+ return getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
+ case PictureConfig.TYPE_AUDIO:
+ return getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO);
+ }
+ return null;
+ }
+
+ /**
+ * Sort by number of files
+ *
+ * @param imageFolders
+ */
+ private void sortFolder(List imageFolders) {
+ Collections.sort(imageFolders, (lhs, rhs) -> {
+ if (lhs.getData() == null || rhs.getData() == null) {
+ return 0;
+ }
+ int lSize = lhs.getImageNum();
+ int rSize = rhs.getImageNum();
+ return Integer.compare(rSize, lSize);
+ });
+ }
+
+ /**
+ * Android Q
+ *
+ * @param id
+ * @return
+ */
+ private static String getRealPathAndroid_Q(long id) {
+ return QUERY_URI.buildUpon().appendPath(ValueOf.toString(id)).build().toString();
+ }
+
+ /**
+ * Get video (maximum or minimum time)
+ *
+ * @param exMaxLimit
+ * @param exMinLimit
+ * @return
+ */
+ private String getDurationCondition(long exMaxLimit, long exMinLimit) {
+ long maxS = config.videoMaxSecond == 0 ? Long.MAX_VALUE : config.videoMaxSecond;
+ if (exMaxLimit != 0) {
+ maxS = Math.min(maxS, exMaxLimit);
+ }
+ return String.format(Locale.CHINA, "%d <%s " + MediaStore.MediaColumns.DURATION + " and " + MediaStore.MediaColumns.DURATION + " <= %d",
+ Math.max(exMinLimit, config.videoMinSecond),
+ Math.max(exMinLimit, config.videoMinSecond) == 0 ? "" : "=",
+ maxS);
+ }
+
+
+ private static LocalMediaPageLoader instance;
+
+ public static LocalMediaPageLoader getInstance(Context context, PictureSelectionConfig
+ config) {
+ if (instance == null) {
+ synchronized (LocalMediaPageLoader.class) {
+ if (instance == null) {
+ instance = new LocalMediaPageLoader(context.getApplicationContext(), config);
+ }
+ }
+ }
+ return instance;
+ }
+
+ /**
+ * set empty
+ */
+ public static void setInstanceNull() {
+ instance = null;
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/observable/ImagesObservable.java b/picture_library/src/main/java/com/luck/picture/lib/observable/ImagesObservable.java
new file mode 100644
index 0000000..81efaac
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/observable/ImagesObservable.java
@@ -0,0 +1,52 @@
+package com.luck.picture.lib.observable;
+
+import com.luck.picture.lib.entity.LocalMedia;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author:luck
+ * @date:2017-1-12 21:30
+ * @describe:解决预览时传值过大问题
+ */
+public class ImagesObservable {
+ private List data;
+ private static ImagesObservable sObserver;
+
+ public static ImagesObservable getInstance() {
+ if (sObserver == null) {
+ synchronized (ImagesObservable.class) {
+ if (sObserver == null) {
+ sObserver = new ImagesObservable();
+ }
+ }
+ }
+ return sObserver;
+ }
+
+ /**
+ * 存储图片用于预览时用
+ *
+ * @param data
+ */
+ public void savePreviewMediaData(List data) {
+ this.data = data;
+ }
+
+ /**
+ * 读取预览的图片
+ */
+ public List readPreviewMediaData() {
+ return data == null ? new ArrayList<>() : data;
+ }
+
+ /**
+ * 清空预览的图片
+ */
+ public void clearPreviewMediaData() {
+ if (data != null) {
+ data.clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/picture_library/src/main/java/com/luck/picture/lib/permissions/PermissionChecker.java b/picture_library/src/main/java/com/luck/picture/lib/permissions/PermissionChecker.java
new file mode 100644
index 0000000..9773ae4
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/permissions/PermissionChecker.java
@@ -0,0 +1,62 @@
+package com.luck.picture.lib.permissions;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+/**
+ * @author:luck
+ * @date:2019-11-20 19:07
+ * @describe:权限检查
+ */
+public class PermissionChecker {
+
+ /**
+ * 检查是否有某个权限
+ *
+ * @param ctx
+ * @param permission
+ * @return
+ */
+ public static boolean checkSelfPermission(Context ctx, String permission) {
+ return ContextCompat.checkSelfPermission(ctx.getApplicationContext(), permission)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+
+ /**
+ * 动态申请多个权限
+ *
+ * @param activity
+ * @param code
+ */
+ public static void requestPermissions(Activity activity, @NonNull String[] permissions, int code) {
+ ActivityCompat.requestPermissions(activity, permissions, code);
+ }
+
+
+ /**
+ * Launch the application's details settings.
+ */
+ public static void launchAppDetailsSettings(Context context) {
+ Context applicationContext = context.getApplicationContext();
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ intent.setData(Uri.parse("package:" + applicationContext.getPackageName()));
+ if (!isIntentAvailable(context, intent)) return;
+ applicationContext.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ private static boolean isIntentAvailable(Context context, final Intent intent) {
+ return context.getApplicationContext()
+ .getPackageManager()
+ .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
+ .size() > 0;
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/Compat.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/Compat.java
new file mode 100644
index 0000000..66ab719
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/Compat.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.luck.picture.lib.photoview;
+
+import android.annotation.TargetApi;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.view.View;
+
+class Compat {
+
+ private static final int SIXTY_FPS_INTERVAL = 1000 / 60;
+
+ public static void postOnAnimation(View view, Runnable runnable) {
+ if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
+ postOnAnimationJellyBean(view, runnable);
+ } else {
+ view.postDelayed(runnable, SIXTY_FPS_INTERVAL);
+ }
+ }
+
+ @TargetApi(16)
+ private static void postOnAnimationJellyBean(View view, Runnable runnable) {
+ view.postOnAnimation(runnable);
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/CustomGestureDetector.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/CustomGestureDetector.java
new file mode 100644
index 0000000..26a5f3f
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/CustomGestureDetector.java
@@ -0,0 +1,206 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.luck.picture.lib.photoview;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+/**
+ * Does a whole lot of gesture detecting.
+ */
+class CustomGestureDetector {
+
+ private static final int INVALID_POINTER_ID = -1;
+
+ private int mActivePointerId = INVALID_POINTER_ID;
+ private int mActivePointerIndex = 0;
+ private final ScaleGestureDetector mDetector;
+
+ private VelocityTracker mVelocityTracker;
+ private boolean mIsDragging;
+ private float mLastTouchX;
+ private float mLastTouchY;
+ private final float mTouchSlop;
+ private final float mMinimumVelocity;
+ private OnGestureListener mListener;
+
+ CustomGestureDetector(Context context, OnGestureListener listener) {
+ final ViewConfiguration configuration = ViewConfiguration
+ .get(context);
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mTouchSlop = configuration.getScaledTouchSlop();
+
+ mListener = listener;
+ ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ float scaleFactor = detector.getScaleFactor();
+
+ if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))
+ return false;
+
+ if (scaleFactor >= 0) {
+ mListener.onScale(scaleFactor,
+ detector.getFocusX(), detector.getFocusY());
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ // NO-OP
+ }
+ };
+ mDetector = new ScaleGestureDetector(context, mScaleListener);
+ }
+
+ private float getActiveX(MotionEvent ev) {
+ try {
+ return ev.getX(mActivePointerIndex);
+ } catch (Exception e) {
+ return ev.getX();
+ }
+ }
+
+ private float getActiveY(MotionEvent ev) {
+ try {
+ return ev.getY(mActivePointerIndex);
+ } catch (Exception e) {
+ return ev.getY();
+ }
+ }
+
+ public boolean isScaling() {
+ return mDetector.isInProgress();
+ }
+
+ public boolean isDragging() {
+ return mIsDragging;
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ try {
+ mDetector.onTouchEvent(ev);
+ return processTouchEvent(ev);
+ } catch (IllegalArgumentException e) {
+ // Fix for support lib bug, happening when onDestroy is called
+ return true;
+ }
+ }
+
+ private boolean processTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = ev.getPointerId(0);
+
+ mVelocityTracker = VelocityTracker.obtain();
+ if (null != mVelocityTracker) {
+ mVelocityTracker.addMovement(ev);
+ }
+
+ mLastTouchX = getActiveX(ev);
+ mLastTouchY = getActiveY(ev);
+ mIsDragging = false;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final float x = getActiveX(ev);
+ final float y = getActiveY(ev);
+ final float dx = x - mLastTouchX, dy = y - mLastTouchY;
+
+ if (!mIsDragging) {
+ // Use Pythagoras to see if drag length is larger than
+ // touch slop
+ mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
+ }
+
+ if (mIsDragging) {
+ mListener.onDrag(dx, dy);
+ mLastTouchX = x;
+ mLastTouchY = y;
+
+ if (null != mVelocityTracker) {
+ mVelocityTracker.addMovement(ev);
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ mActivePointerId = INVALID_POINTER_ID;
+ // Recycle Velocity Tracker
+ if (null != mVelocityTracker) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ mActivePointerId = INVALID_POINTER_ID;
+ if (mIsDragging) {
+ if (null != mVelocityTracker) {
+ mLastTouchX = getActiveX(ev);
+ mLastTouchY = getActiveY(ev);
+
+ // Compute velocity within the last 1000ms
+ mVelocityTracker.addMovement(ev);
+ mVelocityTracker.computeCurrentVelocity(1000);
+
+ final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker
+ .getYVelocity();
+
+ // If the velocity is greater than minVelocity, call
+ // listener
+ if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
+ mListener.onFling(mLastTouchX, mLastTouchY, -vX,
+ -vY);
+ }
+ }
+ }
+
+ // Recycle Velocity Tracker
+ if (null != mVelocityTracker) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ final int pointerIndex = Util.getPointerIndex(ev.getAction());
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ mLastTouchX = ev.getX(newPointerIndex);
+ mLastTouchY = ev.getY(newPointerIndex);
+ }
+ break;
+ }
+
+ mActivePointerIndex = ev
+ .findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId
+ : 0);
+ return true;
+ }
+}
+
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/OnGestureListener.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnGestureListener.java
new file mode 100644
index 0000000..6dbc9cc
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnGestureListener.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.luck.picture.lib.photoview;
+
+interface OnGestureListener {
+
+ void onDrag(float dx, float dy);
+
+ void onFling(float startX, float startY, float velocityX,
+ float velocityY);
+
+ void onScale(float scaleFactor, float focusX, float focusY);
+
+}
\ No newline at end of file
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/OnMatrixChangedListener.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnMatrixChangedListener.java
new file mode 100644
index 0000000..1391942
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnMatrixChangedListener.java
@@ -0,0 +1,18 @@
+package com.luck.picture.lib.photoview;
+
+import android.graphics.RectF;
+
+/**
+ * Interface definition for a callback to be invoked when the internal Matrix has changed for
+ * this View.
+ */
+public interface OnMatrixChangedListener {
+
+ /**
+ * Callback for when the Matrix displaying the Drawable has changed. This could be because
+ * the View's bounds have changed, or the user has zoomed.
+ *
+ * @param rect - Rectangle displaying the Drawable's new bounds.
+ */
+ void onMatrixChanged(RectF rect);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/OnOutsidePhotoTapListener.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnOutsidePhotoTapListener.java
new file mode 100644
index 0000000..be187c5
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnOutsidePhotoTapListener.java
@@ -0,0 +1,14 @@
+package com.luck.picture.lib.photoview;
+
+import android.widget.ImageView;
+
+/**
+ * Callback when the user tapped outside of the photo
+ */
+public interface OnOutsidePhotoTapListener {
+
+ /**
+ * The outside of the photo has been tapped
+ */
+ void onOutsidePhotoTap(ImageView imageView);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/OnPhotoTapListener.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnPhotoTapListener.java
new file mode 100644
index 0000000..8db0570
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnPhotoTapListener.java
@@ -0,0 +1,22 @@
+package com.luck.picture.lib.photoview;
+
+import android.widget.ImageView;
+
+/**
+ * A callback to be invoked when the Photo is tapped with a single
+ * tap.
+ */
+public interface OnPhotoTapListener {
+
+ /**
+ * A callback to receive where the user taps on a photo. You will only receive a callback if
+ * the user taps on the actual photo, tapping on 'whitespace' will be ignored.
+ *
+ * @param view ImageView the user tapped.
+ * @param x where the user tapped from the of the Drawable, as percentage of the
+ * Drawable width.
+ * @param y where the user tapped from the top of the Drawable, as percentage of the
+ * Drawable height.
+ */
+ void onPhotoTap(ImageView view, float x, float y);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/OnScaleChangedListener.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnScaleChangedListener.java
new file mode 100644
index 0000000..815f341
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnScaleChangedListener.java
@@ -0,0 +1,17 @@
+package com.luck.picture.lib.photoview;
+
+
+/**
+ * Interface definition for callback to be invoked when attached ImageView scale changes
+ */
+public interface OnScaleChangedListener {
+
+ /**
+ * Callback for when the scale changes
+ *
+ * @param scaleFactor the scale factor (less than 1 for zoom out, greater than 1 for zoom in)
+ * @param focusX focal point X position
+ * @param focusY focal point Y position
+ */
+ void onScaleChange(float scaleFactor, float focusX, float focusY);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/OnSingleFlingListener.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnSingleFlingListener.java
new file mode 100644
index 0000000..bd60f6b
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnSingleFlingListener.java
@@ -0,0 +1,21 @@
+package com.luck.picture.lib.photoview;
+
+import android.view.MotionEvent;
+
+/**
+ * A callback to be invoked when the ImageView is flung with a single
+ * touch
+ */
+public interface OnSingleFlingListener {
+
+ /**
+ * A callback to receive where the user flings on a ImageView. You will receive a callback if
+ * the user flings anywhere on the view.
+ *
+ * @param e1 MotionEvent the user first touch.
+ * @param e2 MotionEvent the user last touch.
+ * @param velocityX distance of user's horizontal fling.
+ * @param velocityY distance of user's vertical fling.
+ */
+ boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/OnViewDragListener.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnViewDragListener.java
new file mode 100644
index 0000000..4023f9c
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnViewDragListener.java
@@ -0,0 +1,16 @@
+package com.luck.picture.lib.photoview;
+
+/**
+ * Interface definition for a callback to be invoked when the photo is experiencing a drag event
+ */
+public interface OnViewDragListener {
+
+ /**
+ * Callback for when the photo is experiencing a drag event. This cannot be invoked when the
+ * user is scaling.
+ *
+ * @param dx The change of the coordinates in the x-direction
+ * @param dy The change of the coordinates in the y-direction
+ */
+ void onDrag(float dx, float dy);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/OnViewTapListener.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnViewTapListener.java
new file mode 100644
index 0000000..8f918cc
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/OnViewTapListener.java
@@ -0,0 +1,16 @@
+package com.luck.picture.lib.photoview;
+
+import android.view.View;
+
+public interface OnViewTapListener {
+
+ /**
+ * A callback to receive where the user taps on a ImageView. You will receive a callback if
+ * the user taps anywhere on the view, tapping on 'whitespace' will not be ignored.
+ *
+ * @param view - View the user tapped.
+ * @param x - where the user tapped from the left of the View.
+ * @param y - where the user tapped from the top of the View.
+ */
+ void onViewTap(View view, float x, float y);
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/PhotoView.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/PhotoView.java
new file mode 100644
index 0000000..72455ac
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/PhotoView.java
@@ -0,0 +1,257 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.luck.picture.lib.photoview;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.widget.ImageView;
+
+import androidx.appcompat.widget.AppCompatImageView;
+
+/**
+ * A zoomable {@link ImageView}. See {@link PhotoViewAttacher} for most of the details on how the zooming
+ * is accomplished
+ */
+public class PhotoView extends AppCompatImageView {
+
+ private PhotoViewAttacher attacher;
+ private ScaleType pendingScaleType;
+
+ public PhotoView(Context context) {
+ this(context, null);
+ }
+
+ public PhotoView(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public PhotoView(Context context, AttributeSet attr, int defStyle) {
+ super(context, attr, defStyle);
+ init();
+ }
+
+ private void init() {
+ attacher = new PhotoViewAttacher(this);
+ //We always pose as a Matrix scale type, though we can change to another scale type
+ //via the attacher
+ super.setScaleType(ScaleType.MATRIX);
+ //apply the previously applied scale type
+ if (pendingScaleType != null) {
+ setScaleType(pendingScaleType);
+ pendingScaleType = null;
+ }
+ }
+
+ /**
+ * Get the current {@link PhotoViewAttacher} for this view. Be wary of holding on to references
+ * to this attacher, as it has a reference to this view, which, if a reference is held in the
+ * wrong place, can cause memory leaks.
+ *
+ * @return the attacher.
+ */
+ public PhotoViewAttacher getAttacher() {
+ return attacher;
+ }
+
+ @Override
+ public ScaleType getScaleType() {
+ return attacher.getScaleType();
+ }
+
+ @Override
+ public Matrix getImageMatrix() {
+ return attacher.getImageMatrix();
+ }
+
+ @Override
+ public void setOnLongClickListener(OnLongClickListener l) {
+ attacher.setOnLongClickListener(l);
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener l) {
+ attacher.setOnClickListener(l);
+ }
+
+ @Override
+ public void setScaleType(ScaleType scaleType) {
+ if (attacher == null) {
+ pendingScaleType = scaleType;
+ } else {
+ attacher.setScaleType(scaleType);
+ }
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ super.setImageDrawable(drawable);
+ // setImageBitmap calls through to this method
+ if (attacher != null) {
+ attacher.update();
+ }
+ }
+
+ @Override
+ public void setImageResource(int resId) {
+ super.setImageResource(resId);
+ if (attacher != null) {
+ attacher.update();
+ }
+ }
+
+ @Override
+ public void setImageURI(Uri uri) {
+ super.setImageURI(uri);
+ if (attacher != null) {
+ attacher.update();
+ }
+ }
+
+ @Override
+ protected boolean setFrame(int l, int t, int r, int b) {
+ boolean changed = super.setFrame(l, t, r, b);
+ if (changed) {
+ attacher.update();
+ }
+ return changed;
+ }
+
+ public void setRotationTo(float rotationDegree) {
+ attacher.setRotationTo(rotationDegree);
+ }
+
+ public void setRotationBy(float rotationDegree) {
+ attacher.setRotationBy(rotationDegree);
+ }
+
+ public boolean isZoomable() {
+ return attacher.isZoomable();
+ }
+
+ public void setZoomable(boolean zoomable) {
+ attacher.setZoomable(zoomable);
+ }
+
+ public RectF getDisplayRect() {
+ return attacher.getDisplayRect();
+ }
+
+ public void getDisplayMatrix(Matrix matrix) {
+ attacher.getDisplayMatrix(matrix);
+ }
+
+ @SuppressWarnings("UnusedReturnValue")
+ public boolean setDisplayMatrix(Matrix finalRectangle) {
+ return attacher.setDisplayMatrix(finalRectangle);
+ }
+
+ public void getSuppMatrix(Matrix matrix) {
+ attacher.getSuppMatrix(matrix);
+ }
+
+ public boolean setSuppMatrix(Matrix matrix) {
+ return attacher.setDisplayMatrix(matrix);
+ }
+
+ public float getMinimumScale() {
+ return attacher.getMinimumScale();
+ }
+
+ public float getMediumScale() {
+ return attacher.getMediumScale();
+ }
+
+ public float getMaximumScale() {
+ return attacher.getMaximumScale();
+ }
+
+ public float getScale() {
+ return attacher.getScale();
+ }
+
+ public void setAllowParentInterceptOnEdge(boolean allow) {
+ attacher.setAllowParentInterceptOnEdge(allow);
+ }
+
+ public void setMinimumScale(float minimumScale) {
+ attacher.setMinimumScale(minimumScale);
+ }
+
+ public void setMediumScale(float mediumScale) {
+ attacher.setMediumScale(mediumScale);
+ }
+
+ public void setMaximumScale(float maximumScale) {
+ attacher.setMaximumScale(maximumScale);
+ }
+
+ public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
+ attacher.setScaleLevels(minimumScale, mediumScale, maximumScale);
+ }
+
+ public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
+ attacher.setOnMatrixChangeListener(listener);
+ }
+
+ public void setOnPhotoTapListener(OnPhotoTapListener listener) {
+ attacher.setOnPhotoTapListener(listener);
+ }
+
+ public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener listener) {
+ attacher.setOnOutsidePhotoTapListener(listener);
+ }
+
+ public void setOnViewTapListener(OnViewTapListener listener) {
+ attacher.setOnViewTapListener(listener);
+ }
+
+ public void setOnViewDragListener(OnViewDragListener listener) {
+ attacher.setOnViewDragListener(listener);
+ }
+
+ public void setScale(float scale) {
+ attacher.setScale(scale);
+ }
+
+ public void setScale(float scale, boolean animate) {
+ attacher.setScale(scale, animate);
+ }
+
+ public void setScale(float scale, float focalX, float focalY, boolean animate) {
+ attacher.setScale(scale, focalX, focalY, animate);
+ }
+
+ public void setZoomTransitionDuration(int milliseconds) {
+ attacher.setZoomTransitionDuration(milliseconds);
+ }
+
+ public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener onDoubleTapListener) {
+ attacher.setOnDoubleTapListener(onDoubleTapListener);
+ }
+
+ public void setOnScaleChangeListener(OnScaleChangedListener onScaleChangedListener) {
+ attacher.setOnScaleChangeListener(onScaleChangedListener);
+ }
+
+ public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) {
+ attacher.setOnSingleFlingListener(onSingleFlingListener);
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/PhotoViewAttacher.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/PhotoViewAttacher.java
new file mode 100644
index 0000000..aa36b43
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/PhotoViewAttacher.java
@@ -0,0 +1,817 @@
+/*******************************************************************************
+ * Copyright 2011, 2012 Chris Banes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.luck.picture.lib.photoview;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnLongClickListener;
+import android.view.ViewParent;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.OverScroller;
+
+/**
+ * The component of {@link PhotoView} which does the work allowing for zooming, scaling, panning, etc.
+ * It is made public in case you need to subclass something other than {@link ImageView} and still
+ * gain the functionality that {@link PhotoView} offers
+ */
+public class PhotoViewAttacher implements View.OnTouchListener,
+ View.OnLayoutChangeListener {
+
+ private static float DEFAULT_MAX_SCALE = 3.0f;
+ private static float DEFAULT_MID_SCALE = 1.75f;
+ private static float DEFAULT_MIN_SCALE = 1.0f;
+ private static int DEFAULT_ZOOM_DURATION = 200;
+
+ private static final int HORIZONTAL_EDGE_NONE = -1;
+ private static final int HORIZONTAL_EDGE_LEFT = 0;
+ private static final int HORIZONTAL_EDGE_RIGHT = 1;
+ private static final int HORIZONTAL_EDGE_BOTH = 2;
+ private static final int VERTICAL_EDGE_NONE = -1;
+ private static final int VERTICAL_EDGE_TOP = 0;
+ private static final int VERTICAL_EDGE_BOTTOM = 1;
+ private static final int VERTICAL_EDGE_BOTH = 2;
+ private static int SINGLE_TOUCH = 1;
+
+ private Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
+ private int mZoomDuration = DEFAULT_ZOOM_DURATION;
+ private float mMinScale = DEFAULT_MIN_SCALE;
+ private float mMidScale = DEFAULT_MID_SCALE;
+ private float mMaxScale = DEFAULT_MAX_SCALE;
+
+ private boolean mAllowParentInterceptOnEdge = true;
+ private boolean mBlockParentIntercept = false;
+
+ private ImageView mImageView;
+
+ // Gesture Detectors
+ private GestureDetector mGestureDetector;
+ private CustomGestureDetector mScaleDragDetector;
+
+ // These are set so we don't keep allocating them on the heap
+ private final Matrix mBaseMatrix = new Matrix();
+ private final Matrix mDrawMatrix = new Matrix();
+ private final Matrix mSuppMatrix = new Matrix();
+ private final RectF mDisplayRect = new RectF();
+ private final float[] mMatrixValues = new float[9];
+
+ // Listeners
+ private OnMatrixChangedListener mMatrixChangeListener;
+ private OnPhotoTapListener mPhotoTapListener;
+ private OnOutsidePhotoTapListener mOutsidePhotoTapListener;
+ private OnViewTapListener mViewTapListener;
+ private View.OnClickListener mOnClickListener;
+ private OnLongClickListener mLongClickListener;
+ private OnScaleChangedListener mScaleChangeListener;
+ private OnSingleFlingListener mSingleFlingListener;
+ private OnViewDragListener mOnViewDragListener;
+
+ private FlingRunnable mCurrentFlingRunnable;
+ private int mHorizontalScrollEdge = HORIZONTAL_EDGE_BOTH;
+ private int mVerticalScrollEdge = VERTICAL_EDGE_BOTH;
+ private float mBaseRotation;
+
+ private boolean mZoomEnabled = true;
+ private ScaleType mScaleType = ScaleType.FIT_CENTER;
+
+ private OnGestureListener onGestureListener = new OnGestureListener() {
+ @Override
+ public void onDrag(float dx, float dy) {
+ if (mScaleDragDetector.isScaling()) {
+ return; // Do not drag if we are already scaling
+ }
+ if (mOnViewDragListener != null) {
+ mOnViewDragListener.onDrag(dx, dy);
+ }
+ mSuppMatrix.postTranslate(dx, dy);
+ checkAndDisplayMatrix();
+
+ /*
+ * Here we decide whether to let the ImageView's parent to start taking
+ * over the touch event.
+ *
+ * First we check whether this function is enabled. We never want the
+ * parent to take over if we're scaling. We then check the edge we're
+ * on, and the direction of the scroll (i.e. if we're pulling against
+ * the edge, aka 'overscrolling', let the parent take over).
+ */
+ ViewParent parent = mImageView.getParent();
+ if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) {
+ if (mHorizontalScrollEdge == HORIZONTAL_EDGE_BOTH
+ || (mHorizontalScrollEdge == HORIZONTAL_EDGE_LEFT && dx >= 1f)
+ || (mHorizontalScrollEdge == HORIZONTAL_EDGE_RIGHT && dx <= -1f)
+ || (mVerticalScrollEdge == VERTICAL_EDGE_TOP && dy >= 1f)
+ || (mVerticalScrollEdge == VERTICAL_EDGE_BOTTOM && dy <= -1f)) {
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(false);
+ }
+ }
+ } else {
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+ }
+
+ @Override
+ public void onFling(float startX, float startY, float velocityX, float velocityY) {
+ mCurrentFlingRunnable = new FlingRunnable(mImageView.getContext());
+ mCurrentFlingRunnable.fling(getImageViewWidth(mImageView),
+ getImageViewHeight(mImageView), (int) velocityX, (int) velocityY);
+ mImageView.post(mCurrentFlingRunnable);
+ }
+
+ @Override
+ public void onScale(float scaleFactor, float focusX, float focusY) {
+ if (getScale() < mMaxScale || scaleFactor < 1f) {
+ if (mScaleChangeListener != null) {
+ mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
+ }
+ mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
+ checkAndDisplayMatrix();
+ }
+ }
+ };
+
+ public PhotoViewAttacher(ImageView imageView) {
+ mImageView = imageView;
+ imageView.setOnTouchListener(this);
+ imageView.addOnLayoutChangeListener(this);
+ if (imageView.isInEditMode()) {
+ return;
+ }
+ mBaseRotation = 0.0f;
+ // Create Gesture Detectors...
+ mScaleDragDetector = new CustomGestureDetector(imageView.getContext(), onGestureListener);
+ mGestureDetector = new GestureDetector(imageView.getContext(), new GestureDetector.SimpleOnGestureListener() {
+
+ // forward long click listener
+ @Override
+ public void onLongPress(MotionEvent e) {
+ if (mLongClickListener != null) {
+ mLongClickListener.onLongClick(mImageView);
+ }
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2,
+ float velocityX, float velocityY) {
+ if (mSingleFlingListener != null) {
+ if (getScale() > DEFAULT_MIN_SCALE) {
+ return false;
+ }
+ if (e1.getPointerCount() > SINGLE_TOUCH
+ || e2.getPointerCount() > SINGLE_TOUCH) {
+ return false;
+ }
+ return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY);
+ }
+ return false;
+ }
+ });
+ mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ if (mOnClickListener != null) {
+ mOnClickListener.onClick(mImageView);
+ }
+ final RectF displayRect = getDisplayRect();
+ final float x = e.getX(), y = e.getY();
+ if (mViewTapListener != null) {
+ mViewTapListener.onViewTap(mImageView, x, y);
+ }
+ if (displayRect != null) {
+ // Check to see if the user tapped on the photo
+ if (displayRect.contains(x, y)) {
+ float xResult = (x - displayRect.left)
+ / displayRect.width();
+ float yResult = (y - displayRect.top)
+ / displayRect.height();
+ if (mPhotoTapListener != null) {
+ mPhotoTapListener.onPhotoTap(mImageView, xResult, yResult);
+ }
+ return true;
+ } else {
+ if (mOutsidePhotoTapListener != null) {
+ mOutsidePhotoTapListener.onOutsidePhotoTap(mImageView);
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent ev) {
+ try {
+ float scale = getScale();
+ float x = ev.getX();
+ float y = ev.getY();
+ if (scale < getMediumScale()) {
+ setScale(getMediumScale(), x, y, true);
+ } else if (scale >= getMediumScale() && scale < getMaximumScale()) {
+ setScale(getMaximumScale(), x, y, true);
+ } else {
+ setScale(getMinimumScale(), x, y, true);
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // Can sometimes happen when getX() and getY() is called
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ // Wait for the confirmed onDoubleTap() instead
+ return false;
+ }
+ });
+ }
+
+ public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
+ this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener);
+ }
+
+ public void setOnScaleChangeListener(OnScaleChangedListener onScaleChangeListener) {
+ this.mScaleChangeListener = onScaleChangeListener;
+ }
+
+ public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) {
+ this.mSingleFlingListener = onSingleFlingListener;
+ }
+
+ @Deprecated
+ public boolean isZoomEnabled() {
+ return mZoomEnabled;
+ }
+
+ public RectF getDisplayRect() {
+ checkMatrixBounds();
+ return getDisplayRect(getDrawMatrix());
+ }
+
+ public boolean setDisplayMatrix(Matrix finalMatrix) {
+ if (finalMatrix == null) {
+ throw new IllegalArgumentException("Matrix cannot be null");
+ }
+ if (mImageView.getDrawable() == null) {
+ return false;
+ }
+ mSuppMatrix.set(finalMatrix);
+ checkAndDisplayMatrix();
+ return true;
+ }
+
+ public void setBaseRotation(final float degrees) {
+ mBaseRotation = degrees % 360;
+ update();
+ setRotationBy(mBaseRotation);
+ checkAndDisplayMatrix();
+ }
+
+ public void setRotationTo(float degrees) {
+ mSuppMatrix.setRotate(degrees % 360);
+ checkAndDisplayMatrix();
+ }
+
+ public void setRotationBy(float degrees) {
+ mSuppMatrix.postRotate(degrees % 360);
+ checkAndDisplayMatrix();
+ }
+
+ public float getMinimumScale() {
+ return mMinScale;
+ }
+
+ public float getMediumScale() {
+ return mMidScale;
+ }
+
+ public float getMaximumScale() {
+ return mMaxScale;
+ }
+
+ public float getScale() {
+ return (float) Math.sqrt((float) Math.pow(getValue(mSuppMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow
+ (getValue(mSuppMatrix, Matrix.MSKEW_Y), 2));
+ }
+
+ public ScaleType getScaleType() {
+ return mScaleType;
+ }
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int
+ oldRight, int oldBottom) {
+ // Update our base matrix, as the bounds have changed
+ if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
+ updateBaseMatrix(mImageView.getDrawable());
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ boolean handled = false;
+ if (mZoomEnabled && Util.hasDrawable((ImageView) v)) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ ViewParent parent = v.getParent();
+ // First, disable the Parent from intercepting the touch
+ // event
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ // If we're flinging, and the user presses down, cancel
+ // fling
+ cancelFling();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ // If the user has zoomed less than min scale, zoom back
+ // to min scale
+ if (getScale() < mMinScale) {
+ RectF rect = getDisplayRect();
+ if (rect != null) {
+ v.post(new AnimatedZoomRunnable(getScale(), mMinScale,
+ rect.centerX(), rect.centerY()));
+ handled = true;
+ }
+ } else if (getScale() > mMaxScale) {
+ RectF rect = getDisplayRect();
+ if (rect != null) {
+ v.post(new AnimatedZoomRunnable(getScale(), mMaxScale,
+ rect.centerX(), rect.centerY()));
+ handled = true;
+ }
+ }
+ break;
+ }
+ // Try the Scale/Drag detector
+ if (mScaleDragDetector != null) {
+ boolean wasScaling = mScaleDragDetector.isScaling();
+ boolean wasDragging = mScaleDragDetector.isDragging();
+ handled = mScaleDragDetector.onTouchEvent(ev);
+ boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling();
+ boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging();
+ mBlockParentIntercept = didntScale && didntDrag;
+ }
+ // Check to see if the user double tapped
+ if (mGestureDetector != null && mGestureDetector.onTouchEvent(ev)) {
+ handled = true;
+ }
+
+ }
+ return handled;
+ }
+
+ public void setAllowParentInterceptOnEdge(boolean allow) {
+ mAllowParentInterceptOnEdge = allow;
+ }
+
+ public void setMinimumScale(float minimumScale) {
+ Util.checkZoomLevels(minimumScale, mMidScale, mMaxScale);
+ mMinScale = minimumScale;
+ }
+
+ public void setMediumScale(float mediumScale) {
+ Util.checkZoomLevels(mMinScale, mediumScale, mMaxScale);
+ mMidScale = mediumScale;
+ }
+
+ public void setMaximumScale(float maximumScale) {
+ Util.checkZoomLevels(mMinScale, mMidScale, maximumScale);
+ mMaxScale = maximumScale;
+ }
+
+ public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
+ Util.checkZoomLevels(minimumScale, mediumScale, maximumScale);
+ mMinScale = minimumScale;
+ mMidScale = mediumScale;
+ mMaxScale = maximumScale;
+ }
+
+ public void setOnLongClickListener(OnLongClickListener listener) {
+ mLongClickListener = listener;
+ }
+
+ public void setOnClickListener(View.OnClickListener listener) {
+ mOnClickListener = listener;
+ }
+
+ public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
+ mMatrixChangeListener = listener;
+ }
+
+ public void setOnPhotoTapListener(OnPhotoTapListener listener) {
+ mPhotoTapListener = listener;
+ }
+
+ public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener mOutsidePhotoTapListener) {
+ this.mOutsidePhotoTapListener = mOutsidePhotoTapListener;
+ }
+
+ public void setOnViewTapListener(OnViewTapListener listener) {
+ mViewTapListener = listener;
+ }
+
+ public void setOnViewDragListener(OnViewDragListener listener) {
+ mOnViewDragListener = listener;
+ }
+
+ public void setScale(float scale) {
+ setScale(scale, false);
+ }
+
+ public void setScale(float scale, boolean animate) {
+ setScale(scale,
+ (mImageView.getRight()) / 2,
+ (mImageView.getBottom()) / 2,
+ animate);
+ }
+
+ public void setScale(float scale, float focalX, float focalY,
+ boolean animate) {
+ // Check to see if the scale is within bounds
+ if (scale < mMinScale || scale > mMaxScale) {
+ throw new IllegalArgumentException("Scale must be within the range of minScale and maxScale");
+ }
+ if (animate) {
+ mImageView.post(new AnimatedZoomRunnable(getScale(), scale,
+ focalX, focalY));
+ } else {
+ mSuppMatrix.setScale(scale, scale, focalX, focalY);
+ checkAndDisplayMatrix();
+ }
+ }
+
+ /**
+ * Set the zoom interpolator
+ *
+ * @param interpolator the zoom interpolator
+ */
+ public void setZoomInterpolator(Interpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ public void setScaleType(ScaleType scaleType) {
+ if (Util.isSupportedScaleType(scaleType) && scaleType != mScaleType) {
+ mScaleType = scaleType;
+ update();
+ }
+ }
+
+ public boolean isZoomable() {
+ return mZoomEnabled;
+ }
+
+ public void setZoomable(boolean zoomable) {
+ mZoomEnabled = zoomable;
+ update();
+ }
+
+ public void update() {
+ if (mZoomEnabled) {
+ // Update the base matrix using the current drawable
+ updateBaseMatrix(mImageView.getDrawable());
+ } else {
+ // Reset the Matrix...
+ resetMatrix();
+ }
+ }
+
+ /**
+ * Get the display matrix
+ *
+ * @param matrix target matrix to copy to
+ */
+ public void getDisplayMatrix(Matrix matrix) {
+ matrix.set(getDrawMatrix());
+ }
+
+ /**
+ * Get the current support matrix
+ */
+ public void getSuppMatrix(Matrix matrix) {
+ matrix.set(mSuppMatrix);
+ }
+
+ private Matrix getDrawMatrix() {
+ mDrawMatrix.set(mBaseMatrix);
+ mDrawMatrix.postConcat(mSuppMatrix);
+ return mDrawMatrix;
+ }
+
+ public Matrix getImageMatrix() {
+ return mDrawMatrix;
+ }
+
+ public void setZoomTransitionDuration(int milliseconds) {
+ this.mZoomDuration = milliseconds;
+ }
+
+ /**
+ * Helper method that 'unpacks' a Matrix and returns the required value
+ *
+ * @param matrix Matrix to unpack
+ * @param whichValue Which value from Matrix.M* to return
+ * @return returned value
+ */
+ private float getValue(Matrix matrix, int whichValue) {
+ matrix.getValues(mMatrixValues);
+ return mMatrixValues[whichValue];
+ }
+
+ /**
+ * Resets the Matrix back to FIT_CENTER, and then displays its contents
+ */
+ private void resetMatrix() {
+ mSuppMatrix.reset();
+ setRotationBy(mBaseRotation);
+ setImageViewMatrix(getDrawMatrix());
+ checkMatrixBounds();
+ }
+
+ private void setImageViewMatrix(Matrix matrix) {
+ mImageView.setImageMatrix(matrix);
+ // Call MatrixChangedListener if needed
+ if (mMatrixChangeListener != null) {
+ RectF displayRect = getDisplayRect(matrix);
+ if (displayRect != null) {
+ mMatrixChangeListener.onMatrixChanged(displayRect);
+ }
+ }
+ }
+
+ /**
+ * Helper method that simply checks the Matrix, and then displays the result
+ */
+ private void checkAndDisplayMatrix() {
+ if (checkMatrixBounds()) {
+ setImageViewMatrix(getDrawMatrix());
+ }
+ }
+
+ /**
+ * Helper method that maps the supplied Matrix to the current Drawable
+ *
+ * @param matrix - Matrix to map Drawable against
+ * @return RectF - Displayed Rectangle
+ */
+ private RectF getDisplayRect(Matrix matrix) {
+ Drawable d = mImageView.getDrawable();
+ if (d != null) {
+ mDisplayRect.set(0, 0, d.getIntrinsicWidth(),
+ d.getIntrinsicHeight());
+ matrix.mapRect(mDisplayRect);
+ return mDisplayRect;
+ }
+ return null;
+ }
+
+ /**
+ * Calculate Matrix for FIT_CENTER
+ *
+ * @param drawable - Drawable being displayed
+ */
+ private void updateBaseMatrix(Drawable drawable) {
+ if (drawable == null) {
+ return;
+ }
+ final float viewWidth = getImageViewWidth(mImageView);
+ final float viewHeight = getImageViewHeight(mImageView);
+ final int drawableWidth = drawable.getIntrinsicWidth();
+ final int drawableHeight = drawable.getIntrinsicHeight();
+ mBaseMatrix.reset();
+ final float widthScale = viewWidth / drawableWidth;
+ final float heightScale = viewHeight / drawableHeight;
+ if (mScaleType == ScaleType.CENTER) {
+ mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F,
+ (viewHeight - drawableHeight) / 2F);
+
+ } else if (mScaleType == ScaleType.CENTER_CROP) {
+ float scale = Math.max(widthScale, heightScale);
+ mBaseMatrix.postScale(scale, scale);
+ mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
+ (viewHeight - drawableHeight * scale) / 2F);
+
+ } else if (mScaleType == ScaleType.CENTER_INSIDE) {
+ float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
+ mBaseMatrix.postScale(scale, scale);
+ mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
+ (viewHeight - drawableHeight * scale) / 2F);
+
+ } else {
+ RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
+ RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);
+ if ((int) mBaseRotation % 180 != 0) {
+ mTempSrc = new RectF(0, 0, drawableHeight, drawableWidth);
+ }
+ switch (mScaleType) {
+ case FIT_CENTER:
+ mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
+ break;
+ case FIT_START:
+ mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
+ break;
+ case FIT_END:
+ mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
+ break;
+ case FIT_XY:
+ mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
+ break;
+ default:
+ break;
+ }
+ }
+ resetMatrix();
+ }
+
+ private boolean checkMatrixBounds() {
+ final RectF rect = getDisplayRect(getDrawMatrix());
+ if (rect == null) {
+ return false;
+ }
+ final float height = rect.height(), width = rect.width();
+ float deltaX = 0, deltaY = 0;
+ final int viewHeight = getImageViewHeight(mImageView);
+ if (height <= viewHeight) {
+ switch (mScaleType) {
+ case FIT_START:
+ deltaY = -rect.top;
+ break;
+ case FIT_END:
+ deltaY = viewHeight - height - rect.top;
+ break;
+ default:
+ deltaY = (viewHeight - height) / 2 - rect.top;
+ break;
+ }
+ mVerticalScrollEdge = VERTICAL_EDGE_BOTH;
+ } else if (rect.top > 0) {
+ mVerticalScrollEdge = VERTICAL_EDGE_TOP;
+ deltaY = -rect.top;
+ } else if (rect.bottom < viewHeight) {
+ mVerticalScrollEdge = VERTICAL_EDGE_BOTTOM;
+ deltaY = viewHeight - rect.bottom;
+ } else {
+ mVerticalScrollEdge = VERTICAL_EDGE_NONE;
+ }
+ final int viewWidth = getImageViewWidth(mImageView);
+ if (width <= viewWidth) {
+ switch (mScaleType) {
+ case FIT_START:
+ deltaX = -rect.left;
+ break;
+ case FIT_END:
+ deltaX = viewWidth - width - rect.left;
+ break;
+ default:
+ deltaX = (viewWidth - width) / 2 - rect.left;
+ break;
+ }
+ mHorizontalScrollEdge = HORIZONTAL_EDGE_BOTH;
+ } else if (rect.left > 0) {
+ mHorizontalScrollEdge = HORIZONTAL_EDGE_LEFT;
+ deltaX = -rect.left;
+ } else if (rect.right < viewWidth) {
+ deltaX = viewWidth - rect.right;
+ mHorizontalScrollEdge = HORIZONTAL_EDGE_RIGHT;
+ } else {
+ mHorizontalScrollEdge = HORIZONTAL_EDGE_NONE;
+ }
+ // Finally actually translate the matrix
+ mSuppMatrix.postTranslate(deltaX, deltaY);
+ return true;
+ }
+
+ private int getImageViewWidth(ImageView imageView) {
+ return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight();
+ }
+
+ private int getImageViewHeight(ImageView imageView) {
+ return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom();
+ }
+
+ private void cancelFling() {
+ if (mCurrentFlingRunnable != null) {
+ mCurrentFlingRunnable.cancelFling();
+ mCurrentFlingRunnable = null;
+ }
+ }
+
+ private class AnimatedZoomRunnable implements Runnable {
+
+ private final float mFocalX, mFocalY;
+ private final long mStartTime;
+ private final float mZoomStart, mZoomEnd;
+
+ public AnimatedZoomRunnable(final float currentZoom, final float targetZoom,
+ final float focalX, final float focalY) {
+ mFocalX = focalX;
+ mFocalY = focalY;
+ mStartTime = System.currentTimeMillis();
+ mZoomStart = currentZoom;
+ mZoomEnd = targetZoom;
+ }
+
+ @Override
+ public void run() {
+ float t = interpolate();
+ float scale = mZoomStart + t * (mZoomEnd - mZoomStart);
+ float deltaScale = scale / getScale();
+ onGestureListener.onScale(deltaScale, mFocalX, mFocalY);
+ // We haven't hit our target scale yet, so post ourselves again
+ if (t < 1f) {
+ Compat.postOnAnimation(mImageView, this);
+ }
+ }
+
+ private float interpolate() {
+ float t = 1f * (System.currentTimeMillis() - mStartTime) / mZoomDuration;
+ t = Math.min(1f, t);
+ t = mInterpolator.getInterpolation(t);
+ return t;
+ }
+ }
+
+ private class FlingRunnable implements Runnable {
+
+ private final OverScroller mScroller;
+ private int mCurrentX, mCurrentY;
+
+ public FlingRunnable(Context context) {
+ mScroller = new OverScroller(context);
+ }
+
+ public void cancelFling() {
+ mScroller.forceFinished(true);
+ }
+
+ public void fling(int viewWidth, int viewHeight, int velocityX,
+ int velocityY) {
+ final RectF rect = getDisplayRect();
+ if (rect == null) {
+ return;
+ }
+ final int startX = Math.round(-rect.left);
+ final int minX, maxX, minY, maxY;
+ if (viewWidth < rect.width()) {
+ minX = 0;
+ maxX = Math.round(rect.width() - viewWidth);
+ } else {
+ minX = maxX = startX;
+ }
+ final int startY = Math.round(-rect.top);
+ if (viewHeight < rect.height()) {
+ minY = 0;
+ maxY = Math.round(rect.height() - viewHeight);
+ } else {
+ minY = maxY = startY;
+ }
+ mCurrentX = startX;
+ mCurrentY = startY;
+ // If we actually can move, fling the scroller
+ if (startX != maxX || startY != maxY) {
+ mScroller.fling(startX, startY, velocityX, velocityY, minX,
+ maxX, minY, maxY, 0, 0);
+ }
+ }
+
+ @Override
+ public void run() {
+ if (mScroller.isFinished()) {
+ return; // remaining post that should not be handled
+ }
+ if (mScroller.computeScrollOffset()) {
+ final int newX = mScroller.getCurrX();
+ final int newY = mScroller.getCurrY();
+ mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
+ checkAndDisplayMatrix();
+ mCurrentX = newX;
+ mCurrentY = newY;
+ // Post On animation
+ Compat.postOnAnimation(mImageView, this);
+ }
+ }
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/photoview/Util.java b/picture_library/src/main/java/com/luck/picture/lib/photoview/Util.java
new file mode 100644
index 0000000..58175c5
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/photoview/Util.java
@@ -0,0 +1,37 @@
+package com.luck.picture.lib.photoview;
+
+import android.view.MotionEvent;
+import android.widget.ImageView;
+
+class Util {
+
+ static void checkZoomLevels(float minZoom, float midZoom,
+ float maxZoom) {
+ if (minZoom >= midZoom) {
+ throw new IllegalArgumentException(
+ "Minimum zoom has to be less than Medium zoom. Call setMinimumZoom() with a more appropriate value");
+ } else if (midZoom >= maxZoom) {
+ throw new IllegalArgumentException(
+ "Medium zoom has to be less than Maximum zoom. Call setMaximumZoom() with a more appropriate value");
+ }
+ }
+
+ static boolean hasDrawable(ImageView imageView) {
+ return imageView.getDrawable() != null;
+ }
+
+ static boolean isSupportedScaleType(final ImageView.ScaleType scaleType) {
+ if (scaleType == null) {
+ return false;
+ }
+ switch (scaleType) {
+ case MATRIX:
+ throw new IllegalStateException("Matrix scale type is not supported");
+ }
+ return true;
+ }
+
+ static int getPointerIndex(int action) {
+ return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ }
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/style/PictureCropParameterStyle.java b/picture_library/src/main/java/com/luck/picture/lib/style/PictureCropParameterStyle.java
new file mode 100644
index 0000000..f307f0a
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/style/PictureCropParameterStyle.java
@@ -0,0 +1,104 @@
+package com.luck.picture.lib.style;
+
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.ColorInt;
+
+/**
+ * @author:luck
+ * @date:2019-11-22 17:24
+ * @describe:裁剪动态样式参数设置
+ */
+public class PictureCropParameterStyle implements Parcelable {
+ /**
+ * 是否改变状态栏字体颜色 黑白切换
+ */
+ public boolean isChangeStatusBarFontColor;
+
+ /**
+ * 裁剪页标题背景颜色
+ */
+ @ColorInt
+ public int cropTitleBarBackgroundColor;
+ /**
+ * 裁剪页状态栏颜色
+ */
+ @ColorInt
+ public int cropStatusBarColorPrimaryDark;
+ /**
+ * 裁剪页标题栏字体颜色
+ */
+ @ColorInt
+ public int cropTitleColor;
+
+ /**
+ * # SDK Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP有效
+ * 裁剪导航条颜色
+ */
+ @ColorInt
+ public int cropNavBarColor;
+
+
+ public PictureCropParameterStyle() {
+ super();
+ }
+
+ public PictureCropParameterStyle(int cropTitleBarBackgroundColor,
+ int cropStatusBarColorPrimaryDark,
+ int cropTitleColor,
+ boolean isChangeStatusBarFontColor) {
+ this.cropTitleBarBackgroundColor = cropTitleBarBackgroundColor;
+ this.cropStatusBarColorPrimaryDark = cropStatusBarColorPrimaryDark;
+ this.cropTitleColor = cropTitleColor;
+ this.isChangeStatusBarFontColor = isChangeStatusBarFontColor;
+ }
+
+ public PictureCropParameterStyle(int cropTitleBarBackgroundColor,
+ int cropStatusBarColorPrimaryDark,
+ int cropNavBarColor,
+ int cropTitleColor,
+ boolean isChangeStatusBarFontColor) {
+ this.cropTitleBarBackgroundColor = cropTitleBarBackgroundColor;
+ this.cropNavBarColor = cropNavBarColor;
+ this.cropStatusBarColorPrimaryDark = cropStatusBarColorPrimaryDark;
+ this.cropTitleColor = cropTitleColor;
+ this.isChangeStatusBarFontColor = isChangeStatusBarFontColor;
+ }
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByte(this.isChangeStatusBarFontColor ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.cropTitleBarBackgroundColor);
+ dest.writeInt(this.cropStatusBarColorPrimaryDark);
+ dest.writeInt(this.cropTitleColor);
+ dest.writeInt(this.cropNavBarColor);
+ }
+
+ protected PictureCropParameterStyle(Parcel in) {
+ this.isChangeStatusBarFontColor = in.readByte() != 0;
+ this.cropTitleBarBackgroundColor = in.readInt();
+ this.cropStatusBarColorPrimaryDark = in.readInt();
+ this.cropTitleColor = in.readInt();
+ this.cropNavBarColor = in.readInt();
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public PictureCropParameterStyle createFromParcel(Parcel source) {
+ return new PictureCropParameterStyle(source);
+ }
+
+ @Override
+ public PictureCropParameterStyle[] newArray(int size) {
+ return new PictureCropParameterStyle[size];
+ }
+ };
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/style/PictureParameterStyle.java b/picture_library/src/main/java/com/luck/picture/lib/style/PictureParameterStyle.java
new file mode 100644
index 0000000..f91b227
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/style/PictureParameterStyle.java
@@ -0,0 +1,396 @@
+package com.luck.picture.lib.style;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+
+/**
+ * @author:luck
+ * @date:2019-11-22 17:24
+ * @describe:相册动态样式参数设置
+ */
+public class PictureParameterStyle implements Parcelable {
+ /**
+ * 是否改变状态栏字体颜色 黑白切换
+ */
+ public boolean isChangeStatusBarFontColor;
+ /**
+ * 是否开启 已完成(0/9) 模式
+ */
+ public boolean isOpenCompletedNumStyle;
+ /**
+ * 是否开启QQ 数字选择风格
+ */
+ public boolean isOpenCheckNumStyle;
+
+ /**
+ * 状态栏色值
+ */
+ @ColorInt
+ public int pictureStatusBarColor;
+
+ /**
+ * 标题栏背景色
+ */
+ @ColorInt
+ public int pictureTitleBarBackgroundColor;
+
+ /**
+ * 相册父容器背景色
+ */
+ @ColorInt
+ public int pictureContainerBackgroundColor;
+
+ /**
+ * 相册标题色值
+ */
+ @ColorInt
+ public int pictureTitleTextColor;
+
+ /**
+ * 相册标题字体大小
+ */
+ public int pictureTitleTextSize;
+
+ /**
+ * 相册取消按钮色值
+ */
+ @ColorInt
+ @Deprecated
+ public int pictureCancelTextColor;
+
+ /**
+ * 相册右侧按钮色值
+ */
+ @ColorInt
+ public int pictureRightDefaultTextColor;
+
+ /**
+ * 相册右侧文字字体大小
+ */
+ public int pictureRightTextSize;
+
+ /**
+ * 相册右侧按钮文本
+ */
+ public String pictureRightDefaultText;
+
+ /**
+ * 相册右侧按钮色值
+ */
+ @ColorInt
+ public int pictureRightSelectedTextColor;
+
+ /**
+ * 相册列表底部背景色
+ */
+ @ColorInt
+ public int pictureBottomBgColor;
+
+ /**
+ * 相册列表已完成按钮色值
+ */
+ @ColorInt
+ public int pictureCompleteTextColor;
+
+ /**
+ * 相册列表未完成按钮色值
+ */
+ @ColorInt
+ public int pictureUnCompleteTextColor;
+
+ /**
+ * 相册列表完成按钮字体大小
+ */
+ public int pictureCompleteTextSize;
+
+ /**
+ * 相册列表不可预览文字颜色
+ */
+ @ColorInt
+ public int pictureUnPreviewTextColor;
+
+ /**
+ * 相册列表预览文字大小
+ */
+ public int picturePreviewTextSize;
+
+ /**
+ * 相册列表未完成按钮文本
+ */
+ public String pictureUnCompleteText;
+
+ /**
+ * 相册列表已完成按钮文本
+ */
+ public String pictureCompleteText;
+
+ /**
+ * 相册列表预览文字颜色
+ */
+ @ColorInt
+ public int picturePreviewTextColor;
+
+ /**
+ * 相册列表不可预览文字
+ */
+ public String pictureUnPreviewText;
+
+ /**
+ * 相册列表预览文字
+ */
+ public String picturePreviewText;
+
+ /**
+ * 相册列表预览界面底部背景色
+ */
+ @ColorInt
+ public int picturePreviewBottomBgColor;
+
+ /**
+ * # SDK Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP有效
+ * 相册导航条颜色
+ */
+ @ColorInt
+ public int pictureNavBarColor;
+
+ /**
+ * 原图字体颜色
+ */
+ @ColorInt
+ public int pictureOriginalFontColor;
+
+ /**
+ * 原图字体大小
+ */
+ public int pictureOriginalTextSize;
+
+ /**
+ * 相册右侧按钮不可点击背景样式
+ */
+ @DrawableRes
+ public int pictureUnCompleteBackgroundStyle;
+
+ /**
+ * 相册右侧按钮可点击背景样式
+ */
+ @DrawableRes
+ public int pictureCompleteBackgroundStyle;
+
+ /**
+ * 相册标题右侧箭头
+ */
+ @DrawableRes
+ public int pictureTitleUpResId;
+ /**
+ * 相册标题右侧箭头
+ */
+ @DrawableRes
+ public int pictureTitleDownResId;
+
+ /**
+ * 相册返回图标
+ */
+ @DrawableRes
+ public int pictureLeftBackIcon;
+
+ /**
+ * 相册勾选CheckBox drawable样式
+ */
+ @DrawableRes
+ public int pictureCheckedStyle;
+
+ /**
+ * 是否使用(%1$d/%2$d)字符串
+ */
+ public boolean isCompleteReplaceNum;
+
+ /**
+ * WeChatStyle 预览右下角 勾选CheckBox drawable样式
+ */
+ @DrawableRes
+ public int pictureWeChatChooseStyle;
+
+ /**
+ * WeChatStyle 预览界面返回键样式
+ */
+ @DrawableRes
+ public int pictureWeChatLeftBackStyle;
+
+ /**
+ * WeChatStyle 相册界面标题背景样式
+ */
+ @DrawableRes
+ public int pictureWeChatTitleBackgroundStyle;
+
+ /**
+ * WeChatStyle 自定义预览页右下角选择文字大小
+ */
+ public int pictureWeChatPreviewSelectedTextSize;
+
+ /**
+ * WeChatStyle 自定义预览页右下角选择文字文案
+ */
+ public String pictureWeChatPreviewSelectedText;
+
+ /**
+ * 图片已选数量圆点背景色
+ */
+ @DrawableRes
+ public int pictureCheckNumBgStyle;
+
+ /**
+ * 相册文件夹列表选中圆点
+ */
+ @DrawableRes
+ public int pictureFolderCheckedDotStyle;
+
+ /**
+ * 外部预览图片删除按钮样式
+ */
+ @DrawableRes
+ public int pictureExternalPreviewDeleteStyle;
+
+ /**
+ * 原图勾选样式
+ */
+ @DrawableRes
+ public int pictureOriginalControlStyle;
+
+
+ /**
+ * 外部预览图片是否显示删除按钮
+ */
+ public boolean pictureExternalPreviewGonePreviewDelete;
+
+
+ /**
+ * 选择相册目录背景样式
+ */
+ @DrawableRes
+ public int pictureAlbumStyle;
+
+
+ public PictureParameterStyle() {
+ super();
+ }
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByte(this.isChangeStatusBarFontColor ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isOpenCompletedNumStyle ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.isOpenCheckNumStyle ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.pictureStatusBarColor);
+ dest.writeInt(this.pictureTitleBarBackgroundColor);
+ dest.writeInt(this.pictureContainerBackgroundColor);
+ dest.writeInt(this.pictureTitleTextColor);
+ dest.writeInt(this.pictureTitleTextSize);
+ dest.writeInt(this.pictureCancelTextColor);
+ dest.writeInt(this.pictureRightDefaultTextColor);
+ dest.writeInt(this.pictureRightTextSize);
+ dest.writeString(this.pictureRightDefaultText);
+ dest.writeInt(this.pictureRightSelectedTextColor);
+ dest.writeInt(this.pictureBottomBgColor);
+ dest.writeInt(this.pictureCompleteTextColor);
+ dest.writeInt(this.pictureUnCompleteTextColor);
+ dest.writeInt(this.pictureCompleteTextSize);
+ dest.writeInt(this.pictureUnPreviewTextColor);
+ dest.writeInt(this.picturePreviewTextSize);
+ dest.writeString(this.pictureUnCompleteText);
+ dest.writeString(this.pictureCompleteText);
+ dest.writeInt(this.picturePreviewTextColor);
+ dest.writeString(this.pictureUnPreviewText);
+ dest.writeString(this.picturePreviewText);
+ dest.writeInt(this.picturePreviewBottomBgColor);
+ dest.writeInt(this.pictureNavBarColor);
+ dest.writeInt(this.pictureOriginalFontColor);
+ dest.writeInt(this.pictureOriginalTextSize);
+ dest.writeInt(this.pictureUnCompleteBackgroundStyle);
+ dest.writeInt(this.pictureCompleteBackgroundStyle);
+ dest.writeInt(this.pictureTitleUpResId);
+ dest.writeInt(this.pictureTitleDownResId);
+ dest.writeInt(this.pictureLeftBackIcon);
+ dest.writeInt(this.pictureCheckedStyle);
+ dest.writeByte(this.isCompleteReplaceNum ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.pictureWeChatChooseStyle);
+ dest.writeInt(this.pictureWeChatLeftBackStyle);
+ dest.writeInt(this.pictureWeChatTitleBackgroundStyle);
+ dest.writeInt(this.pictureWeChatPreviewSelectedTextSize);
+ dest.writeString(this.pictureWeChatPreviewSelectedText);
+ dest.writeInt(this.pictureCheckNumBgStyle);
+ dest.writeInt(this.pictureFolderCheckedDotStyle);
+ dest.writeInt(this.pictureExternalPreviewDeleteStyle);
+ dest.writeInt(this.pictureOriginalControlStyle);
+ dest.writeByte(this.pictureExternalPreviewGonePreviewDelete ? (byte) 1 : (byte) 0);
+ dest.writeInt(this.pictureAlbumStyle);
+ }
+
+ protected PictureParameterStyle(Parcel in) {
+ this.isChangeStatusBarFontColor = in.readByte() != 0;
+ this.isOpenCompletedNumStyle = in.readByte() != 0;
+ this.isOpenCheckNumStyle = in.readByte() != 0;
+ this.pictureStatusBarColor = in.readInt();
+ this.pictureTitleBarBackgroundColor = in.readInt();
+ this.pictureContainerBackgroundColor = in.readInt();
+ this.pictureTitleTextColor = in.readInt();
+ this.pictureTitleTextSize = in.readInt();
+ this.pictureCancelTextColor = in.readInt();
+ this.pictureRightDefaultTextColor = in.readInt();
+ this.pictureRightTextSize = in.readInt();
+ this.pictureRightDefaultText = in.readString();
+ this.pictureRightSelectedTextColor = in.readInt();
+ this.pictureBottomBgColor = in.readInt();
+ this.pictureCompleteTextColor = in.readInt();
+ this.pictureUnCompleteTextColor = in.readInt();
+ this.pictureCompleteTextSize = in.readInt();
+ this.pictureUnPreviewTextColor = in.readInt();
+ this.picturePreviewTextSize = in.readInt();
+ this.pictureUnCompleteText = in.readString();
+ this.pictureCompleteText = in.readString();
+ this.picturePreviewTextColor = in.readInt();
+ this.pictureUnPreviewText = in.readString();
+ this.picturePreviewText = in.readString();
+ this.picturePreviewBottomBgColor = in.readInt();
+ this.pictureNavBarColor = in.readInt();
+ this.pictureOriginalFontColor = in.readInt();
+ this.pictureOriginalTextSize = in.readInt();
+ this.pictureUnCompleteBackgroundStyle = in.readInt();
+ this.pictureCompleteBackgroundStyle = in.readInt();
+ this.pictureTitleUpResId = in.readInt();
+ this.pictureTitleDownResId = in.readInt();
+ this.pictureLeftBackIcon = in.readInt();
+ this.pictureCheckedStyle = in.readInt();
+ this.isCompleteReplaceNum = in.readByte() != 0;
+ this.pictureWeChatChooseStyle = in.readInt();
+ this.pictureWeChatLeftBackStyle = in.readInt();
+ this.pictureWeChatTitleBackgroundStyle = in.readInt();
+ this.pictureWeChatPreviewSelectedTextSize = in.readInt();
+ this.pictureWeChatPreviewSelectedText = in.readString();
+ this.pictureCheckNumBgStyle = in.readInt();
+ this.pictureFolderCheckedDotStyle = in.readInt();
+ this.pictureExternalPreviewDeleteStyle = in.readInt();
+ this.pictureOriginalControlStyle = in.readInt();
+ this.pictureExternalPreviewGonePreviewDelete = in.readByte() != 0;
+ this.pictureAlbumStyle = in.readInt();
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public PictureParameterStyle createFromParcel(Parcel source) {
+ return new PictureParameterStyle(source);
+ }
+
+ @Override
+ public PictureParameterStyle[] newArray(int size) {
+ return new PictureParameterStyle[size];
+ }
+ };
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/style/PictureWindowAnimationStyle.java b/picture_library/src/main/java/com/luck/picture/lib/style/PictureWindowAnimationStyle.java
new file mode 100644
index 0000000..4478f2f
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/style/PictureWindowAnimationStyle.java
@@ -0,0 +1,126 @@
+package com.luck.picture.lib.style;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.AnimRes;
+
+/**
+ * @author:luck
+ * @date:2019-11-25 18:17
+ * @describe:PictureSelector Activity动画管理Style
+ */
+public class PictureWindowAnimationStyle implements Parcelable {
+ /**
+ * 相册启动动画
+ */
+ @AnimRes
+ public int activityEnterAnimation;
+
+ /**
+ * 相册退出动画
+ */
+ @AnimRes
+ public int activityExitAnimation;
+
+ /**
+ * 预览界面启动动画
+ */
+ @AnimRes
+ public int activityPreviewEnterAnimation;
+
+ /**
+ * 预览界面退出动画
+ */
+ @AnimRes
+ public int activityPreviewExitAnimation;
+
+ /**
+ * 裁剪界面启动动画
+ */
+ @AnimRes
+ public int activityCropEnterAnimation;
+
+ /**
+ * 裁剪界面退出动画
+ */
+ @AnimRes
+ public int activityCropExitAnimation;
+
+
+ public PictureWindowAnimationStyle() {
+ super();
+ }
+
+ public PictureWindowAnimationStyle(@AnimRes int activityEnterAnimation,
+ @AnimRes int activityExitAnimation) {
+ super();
+ this.activityEnterAnimation = activityEnterAnimation;
+ this.activityExitAnimation = activityExitAnimation;
+ }
+
+ public PictureWindowAnimationStyle(@AnimRes int activityEnterAnimation,
+ @AnimRes int activityExitAnimation,
+ @AnimRes int activityPreviewEnterAnimation,
+ @AnimRes int activityPreviewExitAnimation) {
+ super();
+ this.activityEnterAnimation = activityEnterAnimation;
+ this.activityExitAnimation = activityExitAnimation;
+ this.activityPreviewEnterAnimation = activityPreviewEnterAnimation;
+ this.activityPreviewExitAnimation = activityPreviewExitAnimation;
+ }
+
+ /**
+ * 全局所有动画样式
+ *
+ * @param enterAnimation
+ * @param exitAnimation
+ */
+ public void ofAllAnimation(int enterAnimation, int exitAnimation) {
+ this.activityEnterAnimation = enterAnimation;
+ this.activityExitAnimation = exitAnimation;
+
+ this.activityPreviewEnterAnimation = enterAnimation;
+ this.activityPreviewExitAnimation = exitAnimation;
+
+ this.activityCropEnterAnimation = enterAnimation;
+ this.activityCropExitAnimation = exitAnimation;
+
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(this.activityEnterAnimation);
+ dest.writeInt(this.activityExitAnimation);
+ dest.writeInt(this.activityPreviewEnterAnimation);
+ dest.writeInt(this.activityPreviewExitAnimation);
+ dest.writeInt(this.activityCropEnterAnimation);
+ dest.writeInt(this.activityCropExitAnimation);
+ }
+
+ protected PictureWindowAnimationStyle(Parcel in) {
+ this.activityEnterAnimation = in.readInt();
+ this.activityExitAnimation = in.readInt();
+ this.activityPreviewEnterAnimation = in.readInt();
+ this.activityPreviewExitAnimation = in.readInt();
+ this.activityCropEnterAnimation = in.readInt();
+ this.activityCropExitAnimation = in.readInt();
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public PictureWindowAnimationStyle createFromParcel(Parcel source) {
+ return new PictureWindowAnimationStyle(source);
+ }
+
+ @Override
+ public PictureWindowAnimationStyle[] newArray(int size) {
+ return new PictureWindowAnimationStyle[size];
+ }
+ };
+}
diff --git a/picture_library/src/main/java/com/luck/picture/lib/thread/PictureThreadUtils.java b/picture_library/src/main/java/com/luck/picture/lib/thread/PictureThreadUtils.java
new file mode 100644
index 0000000..90cdaf8
--- /dev/null
+++ b/picture_library/src/main/java/com/luck/picture/lib/thread/PictureThreadUtils.java
@@ -0,0 +1,1335 @@
+package com.luck.picture.lib.thread;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ *
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2018/05/08
+ * desc : utils about thread
+ *
+ */
+public final class PictureThreadUtils {
+
+ private static final Map> TYPE_PRIORITY_POOLS = new HashMap<>();
+
+ private static final Map TASK_TASKINFO_MAP = new ConcurrentHashMap<>();
+
+ private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+ private static final Timer TIMER = new Timer();
+
+ private static final byte TYPE_SINGLE = -1;
+ private static final byte TYPE_CACHED = -2;
+ private static final byte TYPE_IO = -4;
+ private static final byte TYPE_CPU = -8;
+
+ private static Executor sDeliver;
+
+ /**
+ * Return whether the thread is the main thread.
+ *
+ * @return {@code true}: yes
{@code false}: no
+ */
+ public static boolean isMainThread() {
+ return Looper.myLooper() == Looper.getMainLooper();
+ }
+
+ /**
+ * Return a thread pool that reuses a fixed number of threads
+ * operating off a shared unbounded queue, using the provided
+ * ThreadFactory to create new threads when needed.
+ *
+ * @param size The size of thread in the pool.
+ * @return a fixed thread pool
+ */
+ public static ExecutorService getFixedPool(@IntRange(from = 1) final int size) {
+ return getPoolByTypeAndPriority(size);
+ }
+
+ /**
+ * Return a thread pool that reuses a fixed number of threads
+ * operating off a shared unbounded queue, using the provided
+ * ThreadFactory to create new threads when needed.
+ *
+ * @param size The size of thread in the pool.
+ * @param priority The priority of thread in the poll.
+ * @return a fixed thread pool
+ */
+ public static ExecutorService getFixedPool(@IntRange(from = 1) final int size,
+ @IntRange(from = 1, to = 10) final int priority) {
+ return getPoolByTypeAndPriority(size, priority);
+ }
+
+ /**
+ * Return a thread pool that uses a single worker thread operating
+ * off an unbounded queue, and uses the provided ThreadFactory to
+ * create a new thread when needed.
+ *
+ * @return a single thread pool
+ */
+ public static ExecutorService getSinglePool() {
+ return getPoolByTypeAndPriority(TYPE_SINGLE);
+ }
+
+ /**
+ * Return a thread pool that uses a single worker thread operating
+ * off an unbounded queue, and uses the provided ThreadFactory to
+ * create a new thread when needed.
+ *
+ * @param priority The priority of thread in the poll.
+ * @return a single thread pool
+ */
+ public static ExecutorService getSinglePool(@IntRange(from = 1, to = 10) final int priority) {
+ return getPoolByTypeAndPriority(TYPE_SINGLE, priority);
+ }
+
+ /**
+ * Return a thread pool that creates new threads as needed, but
+ * will reuse previously constructed threads when they are
+ * available.
+ *
+ * @return a cached thread pool
+ */
+ public static ExecutorService getCachedPool() {
+ return getPoolByTypeAndPriority(TYPE_CACHED);
+ }
+
+ /**
+ * Return a thread pool that creates new threads as needed, but
+ * will reuse previously constructed threads when they are
+ * available.
+ *
+ * @param priority The priority of thread in the poll.
+ * @return a cached thread pool
+ */
+ public static ExecutorService getCachedPool(@IntRange(from = 1, to = 10) final int priority) {
+ return getPoolByTypeAndPriority(TYPE_CACHED, priority);
+ }
+
+ /**
+ * Return a thread pool that creates (2 * CPU_COUNT + 1) threads
+ * operating off a queue which size is 128.
+ *
+ * @return a IO thread pool
+ */
+ public static ExecutorService getIoPool() {
+ return getPoolByTypeAndPriority(TYPE_IO);
+ }
+
+ /**
+ * Return a thread pool that creates (2 * CPU_COUNT + 1) threads
+ * operating off a queue which size is 128.
+ *
+ * @param priority The priority of thread in the poll.
+ * @return a IO thread pool
+ */
+ public static ExecutorService getIoPool(@IntRange(from = 1, to = 10) final int priority) {
+ return getPoolByTypeAndPriority(TYPE_IO, priority);
+ }
+
+ /**
+ * Return a thread pool that creates (CPU_COUNT + 1) threads
+ * operating off a queue which size is 128 and the maximum
+ * number of threads equals (2 * CPU_COUNT + 1).
+ *
+ * @return a cpu thread pool for
+ */
+ public static ExecutorService getCpuPool() {
+ return getPoolByTypeAndPriority(TYPE_CPU);
+ }
+
+ /**
+ * Return a thread pool that creates (CPU_COUNT + 1) threads
+ * operating off a queue which size is 128 and the maximum
+ * number of threads equals (2 * CPU_COUNT + 1).
+ *
+ * @param priority The priority of thread in the poll.
+ * @return a cpu thread pool for
+ */
+ public static ExecutorService getCpuPool(@IntRange(from = 1, to = 10) final int priority) {
+ return getPoolByTypeAndPriority(TYPE_CPU, priority);
+ }
+
+ /**
+ * Executes the given task in a fixed thread pool.
+ *
+ * @param size The size of thread in the fixed thread pool.
+ * @param task The task to execute.
+ * @param The type of the task's result.
+ */
+ public static void executeByFixed(@IntRange(from = 1) final int size, final Task task) {
+ execute(getPoolByTypeAndPriority(size), task);
+ }
+
+ /**
+ * Executes the given task in a fixed thread pool.
+ *
+ * @param size The size of thread in the fixed thread pool.
+ * @param task The task to execute.
+ * @param priority The priority of thread in the poll.
+ * @param The type of the task's result.
+ */
+ public static void executeByFixed(@IntRange(from = 1) final int size,
+ final Task task,
+ @IntRange(from = 1, to = 10) final int priority) {
+ execute(getPoolByTypeAndPriority(size, priority), task);
+ }
+
+ /**
+ * Executes the given task in a fixed thread pool after the given delay.
+ *
+ * @param size The size of thread in the fixed thread pool.
+ * @param task The task to execute.
+ * @param delay The time from now to delay execution.
+ * @param unit The time unit of the delay parameter.
+ * @param The type of the task's result.
+ */
+ public static void executeByFixedWithDelay(@IntRange(from = 1) final int size,
+ final Task task,
+ final long delay,
+ final TimeUnit unit) {
+ executeWithDelay(getPoolByTypeAndPriority(size), task, delay, unit);
+ }
+
+ /**
+ * Executes the given task in a fixed thread pool after the given delay.
+ *
+ * @param size The size of thread in the fixed thread pool.
+ * @param task The task to execute.
+ * @param delay The time from now to delay execution.
+ * @param unit The time unit of the delay parameter.
+ * @param priority The priority of thread in the poll.
+ * @param The type of the task's result.
+ */
+ public static void executeByFixedWithDelay(@IntRange(from = 1) final int size,
+ final Task task,
+ final long delay,
+ final TimeUnit unit,
+ @IntRange(from = 1, to = 10) final int priority) {
+ executeWithDelay(getPoolByTypeAndPriority(size, priority), task, delay, unit);
+ }
+
+ /**
+ * Executes the given task in a fixed thread pool at fix rate.
+ *
+ * @param size The size of thread in the fixed thread pool.
+ * @param task The task to execute.
+ * @param period The period between successive executions.
+ * @param unit The time unit of the period parameter.
+ * @param The type of the task's result.
+ */
+ public static void executeByFixedAtFixRate(@IntRange(from = 1) final int size,
+ final Task task,
+ final long period,
+ final TimeUnit unit) {
+ executeAtFixedRate(getPoolByTypeAndPriority(size), task, 0, period, unit);
+ }
+
+ /**
+ * Executes the given task in a fixed thread pool at fix rate.
+ *
+ * @param size The size of thread in the fixed thread pool.
+ * @param task The task to execute.
+ * @param period The period between successive executions.
+ * @param unit The time unit of the period parameter.
+ * @param priority The priority of thread in the poll.
+ * @param The type of the task's result.
+ */
+ public static void executeByFixedAtFixRate(@IntRange(from = 1) final int size,
+ final Task task,
+ final long period,
+ final TimeUnit unit,
+ @IntRange(from = 1, to = 10) final int priority) {
+ executeAtFixedRate(getPoolByTypeAndPriority(size, priority), task, 0, period, unit);
+ }
+
+ /**
+ * Executes the given task in a fixed thread pool at fix rate.
+ *
+ * @param size The size of thread in the fixed thread pool.
+ * @param task The task to execute.
+ * @param initialDelay The time to delay first execution.
+ * @param period The period between successive executions.
+ * @param unit The time unit of the initialDelay and period parameters.
+ * @param The type of the task's result.
+ */
+ public static void executeByFixedAtFixRate(@IntRange(from = 1) final int size,
+ final Task task,
+ long initialDelay,
+ final long period,
+ final TimeUnit unit) {
+ executeAtFixedRate(getPoolByTypeAndPriority(size), task, initialDelay, period, unit);
+ }
+
+ /**
+ * Executes the given task in a fixed thread pool at fix rate.
+ *
+ * @param size The size of thread in the fixed thread pool.
+ * @param task The task to execute.
+ * @param initialDelay The time to delay first execution.
+ * @param period The period between successive executions.
+ * @param unit The time unit of the initialDelay and period parameters.
+ * @param priority The priority of thread in the poll.
+ * @param The type of the task's result.
+ */
+ public static void executeByFixedAtFixRate(@IntRange(from = 1) final int size,
+ final Task task,
+ long initialDelay,
+ final long period,
+ final TimeUnit unit,
+ @IntRange(from = 1, to = 10) final int priority) {
+ executeAtFixedRate(getPoolByTypeAndPriority(size, priority), task, initialDelay, period, unit);
+ }
+
+ /**
+ * Executes the given task in a single thread pool.
+ *
+ * @param task The task to execute.
+ * @param The type of the task's result.
+ */
+ public static void executeBySingle(final Task task) {
+ execute(getPoolByTypeAndPriority(TYPE_SINGLE), task);
+ }
+
+ /**
+ * Executes the given task in a single thread pool.
+ *
+ * @param task The task to execute.
+ * @param priority The priority of thread in the poll.
+ * @param The type of the task's result.
+ */
+ public static void executeBySingle(final Task task,
+ @IntRange(from = 1, to = 10) final int priority) {
+ execute(getPoolByTypeAndPriority(TYPE_SINGLE, priority), task);
+ }
+
+ /**
+ * Executes the given task in a single thread pool after the given delay.
+ *
+ * @param task The task to execute.
+ * @param delay The time from now to delay execution.
+ * @param unit The time unit of the delay parameter.
+ * @param