pref: 优化图片保存
This commit is contained in:
parent
254c844ed2
commit
90cebdb90d
|
|
@ -30,6 +30,7 @@ import com.huanchengfly.tieba.post.models.PhotoViewBean
|
||||||
import com.huanchengfly.tieba.post.ui.common.theme.utils.ThemeUtils
|
import com.huanchengfly.tieba.post.ui.common.theme.utils.ThemeUtils
|
||||||
import com.huanchengfly.tieba.post.utils.AnimUtil
|
import com.huanchengfly.tieba.post.utils.AnimUtil
|
||||||
import com.huanchengfly.tieba.post.utils.ImageUtil
|
import com.huanchengfly.tieba.post.utils.ImageUtil
|
||||||
|
import com.huanchengfly.tieba.post.utils.download
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
|
@ -95,7 +96,7 @@ class PhotoViewActivity : BaseActivity(), OnChangeBottomBarVisibilityListener,
|
||||||
}
|
}
|
||||||
picBeans.addAll(data.picList)
|
picBeans.addAll(data.picList)
|
||||||
picBeans.forEach {
|
picBeans.forEach {
|
||||||
it.img.original?.let { it1 -> imgInfoBeans.add(it1) }
|
it.img.original.let { it1 -> imgInfoBeans.add(it1) }
|
||||||
}
|
}
|
||||||
lastIndex = picBeans.first().overAllIndex.toInt()
|
lastIndex = picBeans.first().overAllIndex.toInt()
|
||||||
for (photoViewBean in photoViewBeans) {
|
for (photoViewBean in photoViewBeans) {
|
||||||
|
|
@ -277,7 +278,7 @@ class PhotoViewActivity : BaseActivity(), OnChangeBottomBarVisibilityListener,
|
||||||
this,
|
this,
|
||||||
mAdapter.getBean(mViewPager.currentItem).originUrl,
|
mAdapter.getBean(mViewPager.currentItem).originUrl,
|
||||||
true
|
true
|
||||||
) { uri: Uri? ->
|
) { uri: Uri ->
|
||||||
val intent = Intent(Intent.ACTION_SEND)
|
val intent = Intent(Intent.ACTION_SEND)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ import com.huanchengfly.tieba.post.toastShort
|
||||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||||
import com.huanchengfly.tieba.post.utils.ImageUtil
|
import com.huanchengfly.tieba.post.utils.ImageUtil
|
||||||
|
import com.huanchengfly.tieba.post.utils.download
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
|
||||||
|
|
@ -1,677 +0,0 @@
|
||||||
package com.huanchengfly.tieba.post.utils;
|
|
||||||
|
|
||||||
import static com.huanchengfly.tieba.post.utils.FileUtil.FILE_FOLDER;
|
|
||||||
import static com.huanchengfly.tieba.post.utils.FileUtil.changeFileExtension;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.ColorMatrix;
|
|
||||||
import android.graphics.ColorMatrixColorFilter;
|
|
||||||
import android.graphics.PixelFormat;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.graphics.drawable.GradientDrawable;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.provider.MediaStore;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Base64;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.URLUtil;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.widget.PopupMenu;
|
|
||||||
import androidx.core.content.FileProvider;
|
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
|
||||||
import com.bumptech.glide.RequestBuilder;
|
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
|
||||||
import com.bumptech.glide.request.FutureTarget;
|
|
||||||
import com.bumptech.glide.request.RequestOptions;
|
|
||||||
import com.bumptech.glide.request.target.Target;
|
|
||||||
import com.huanchengfly.tieba.post.App;
|
|
||||||
import com.huanchengfly.tieba.post.ExtensionsKt;
|
|
||||||
import com.huanchengfly.tieba.post.R;
|
|
||||||
import com.huanchengfly.tieba.post.activities.PhotoViewActivity;
|
|
||||||
import com.huanchengfly.tieba.post.components.transformations.RadiusTransformation;
|
|
||||||
import com.huanchengfly.tieba.post.models.PhotoViewBean;
|
|
||||||
import com.zhihu.matisse.MimeType;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.nio.channels.FileChannel;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ImageUtil {
|
|
||||||
/**
|
|
||||||
* 智能省流
|
|
||||||
*/
|
|
||||||
public static final int SETTINGS_SMART_ORIGIN = 0;
|
|
||||||
/**
|
|
||||||
* 智能无图
|
|
||||||
*/
|
|
||||||
public static final int SETTINGS_SMART_LOAD = 1;
|
|
||||||
/**
|
|
||||||
* 始终高质量
|
|
||||||
*/
|
|
||||||
public static final int SETTINGS_ALL_ORIGIN = 2;
|
|
||||||
/**
|
|
||||||
* 始终无图
|
|
||||||
*/
|
|
||||||
public static final int SETTINGS_ALL_NO = 3;
|
|
||||||
|
|
||||||
public static final int LOAD_TYPE_SMALL_PIC = 0;
|
|
||||||
public static final int LOAD_TYPE_AVATAR = 1;
|
|
||||||
public static final int LOAD_TYPE_NO_RADIUS = 2;
|
|
||||||
public static final int LOAD_TYPE_ALWAYS_ROUND = 3;
|
|
||||||
public static final String TAG = "ImageUtil";
|
|
||||||
|
|
||||||
private static boolean isGifFile(File file) {
|
|
||||||
if (file == null) return false;
|
|
||||||
try {
|
|
||||||
return isGifFile(new FileInputStream(file));
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//判断是否为GIF文件
|
|
||||||
private static boolean isGifFile(InputStream inputStream) {
|
|
||||||
if (inputStream == null) return false;
|
|
||||||
byte[] bytes = new byte[4];
|
|
||||||
try {
|
|
||||||
inputStream.read(bytes);
|
|
||||||
inputStream.close();
|
|
||||||
String str = new String(bytes);
|
|
||||||
return str.equalsIgnoreCase("GIF8");
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File compressImage(Bitmap bitmap, File output, int maxSize) {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
int quality = 100;
|
|
||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
|
|
||||||
while (baos.toByteArray().length / 1024 > maxSize && quality > 0) { //循环判断如果压缩后图片是否大于20kb,大于继续压缩 友盟缩略图要求不大于18kb
|
|
||||||
baos.reset();//重置baos即清空baos
|
|
||||||
quality -= 5;//每次都减少5
|
|
||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);//这里压缩options%,把压缩后的数据存放到baos中
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
FileOutputStream fos = new FileOutputStream(output);
|
|
||||||
try {
|
|
||||||
fos.write(baos.toByteArray());
|
|
||||||
fos.flush();
|
|
||||||
fos.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File bitmapToFile(Bitmap bitmap, File output) {
|
|
||||||
return bitmapToFile(bitmap, output, Bitmap.CompressFormat.JPEG);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File bitmapToFile(Bitmap bitmap, File output, Bitmap.CompressFormat format) {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
bitmap.compress(format, 100, baos);
|
|
||||||
try {
|
|
||||||
FileOutputStream fos = new FileOutputStream(output);
|
|
||||||
try {
|
|
||||||
fos.write(baos.toByteArray());
|
|
||||||
fos.flush();
|
|
||||||
fos.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File compressImage(Bitmap bitmap, File output) {
|
|
||||||
return compressImage(bitmap, output, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Bitmap drawableToBitmap(Drawable drawable) {
|
|
||||||
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
|
|
||||||
drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
|
|
||||||
Canvas canvas = new Canvas(bitmap);
|
|
||||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
|
||||||
drawable.draw(canvas);
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean copyFile(FileInputStream src, FileOutputStream dest) {
|
|
||||||
boolean result = false;
|
|
||||||
if ((src == null) || (dest == null)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileChannel srcChannel = null;
|
|
||||||
FileChannel dstChannel = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
srcChannel = src.getChannel();
|
|
||||||
dstChannel = dest.getChannel();
|
|
||||||
srcChannel.transferTo(0, srcChannel.size(), dstChannel);
|
|
||||||
result = true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
srcChannel.close();
|
|
||||||
dstChannel.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean copyFile(File src, File dest) {
|
|
||||||
boolean result = false;
|
|
||||||
if ((src == null) || (dest == null)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (dest.exists()) {
|
|
||||||
dest.delete();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
dest.createNewFile();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
FileChannel srcChannel = null;
|
|
||||||
FileChannel dstChannel = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
srcChannel = new FileInputStream(src).getChannel();
|
|
||||||
dstChannel = new FileOutputStream(dest).getChannel();
|
|
||||||
srcChannel.transferTo(0, srcChannel.size(), dstChannel);
|
|
||||||
result = true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
srcChannel.close();
|
|
||||||
dstChannel.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void changeBrightness(ImageView imageView, int brightness) {
|
|
||||||
ColorMatrix cMatrix = new ColorMatrix();
|
|
||||||
cMatrix.set(new float[]{1, 0, 0, 0, brightness, 0, 1, 0, 0, brightness, // 改变亮度
|
|
||||||
0, 0, 1, 0, brightness, 0, 0, 0, 1, 0});
|
|
||||||
imageView.setColorFilter(new ColorMatrixColorFilter(cMatrix));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
public static void download(Context context, String url) {
|
|
||||||
download(context, url, false, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void downloadForShare(Context context, String url, @NonNull ShareTaskCallback taskCallback) {
|
|
||||||
new DownloadAsyncTask(context, url, file -> {
|
|
||||||
File pictureFolder = new File(context.getCacheDir(), ".shareTemp");
|
|
||||||
if (pictureFolder.exists() || pictureFolder.mkdirs()) {
|
|
||||||
String fileName = "share_" + System.currentTimeMillis();
|
|
||||||
File destFile = new File(pictureFolder, fileName);
|
|
||||||
if (!destFile.exists()) {
|
|
||||||
copyFile(file, destFile);
|
|
||||||
}
|
|
||||||
Uri shareUri;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
shareUri = FileProvider.getUriForFile(
|
|
||||||
context,
|
|
||||||
context.getPackageName() + ".share.FileProvider",
|
|
||||||
destFile
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
shareUri = Uri.fromFile(destFile);
|
|
||||||
}
|
|
||||||
taskCallback.onGetUri(shareUri);
|
|
||||||
}
|
|
||||||
}).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
public static void download(Context context, String url, boolean forShare, @Nullable ShareTaskCallback taskCallback) {
|
|
||||||
if (forShare) {
|
|
||||||
if (taskCallback != null) downloadForShare(context, url, taskCallback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
downloadAboveQ(context, url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PermissionUtils.INSTANCE.askPermission(
|
|
||||||
context,
|
|
||||||
new PermissionUtils.PermissionData(
|
|
||||||
Arrays.asList(PermissionUtils.READ_EXTERNAL_STORAGE, PermissionUtils.WRITE_EXTERNAL_STORAGE),
|
|
||||||
context.getString(R.string.tip_permission_storage)
|
|
||||||
),
|
|
||||||
R.string.toast_no_permission_save_photo,
|
|
||||||
() -> {
|
|
||||||
downloadBelowQ(context, url);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void downloadAboveQ(Context context, String url) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
new DownloadAsyncTask(context, url, file -> {
|
|
||||||
String mimeType = MimeType.JPEG.toString();
|
|
||||||
String fileName = URLUtil.guessFileName(url, null, MimeType.JPEG.toString());
|
|
||||||
if (isGifFile(file)) {
|
|
||||||
fileName = changeFileExtension(fileName, ".gif");
|
|
||||||
mimeType = MimeType.GIF.toString();
|
|
||||||
}
|
|
||||||
Log.i(TAG, "download: fileName = " + fileName);
|
|
||||||
String relativePath = Environment.DIRECTORY_PICTURES + File.separator + FILE_FOLDER;
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(MediaStore.Images.Media.RELATIVE_PATH, relativePath);
|
|
||||||
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
|
|
||||||
values.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
|
|
||||||
values.put(MediaStore.Images.Media.DESCRIPTION, fileName);
|
|
||||||
Uri uri = null;
|
|
||||||
ContentResolver cr = context.getContentResolver();
|
|
||||||
try {
|
|
||||||
uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
|
|
||||||
if (uri == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ParcelFileDescriptor descriptor = cr.openFileDescriptor(uri, "w");
|
|
||||||
FileOutputStream outputStream = new FileOutputStream(descriptor.getFileDescriptor());
|
|
||||||
FileInputStream inputStream = new FileInputStream(file);
|
|
||||||
copyFile(inputStream, outputStream);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
if (uri != null) {
|
|
||||||
cr.delete(uri, null, null);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Toast.makeText(context, context.getString(R.string.toast_photo_saved, relativePath), Toast.LENGTH_SHORT).show();
|
|
||||||
}).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void downloadBelowQ(Context context, String url) {
|
|
||||||
new DownloadAsyncTask(context, url, file -> {
|
|
||||||
File pictureFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsoluteFile();
|
|
||||||
File appDir;
|
|
||||||
appDir = new File(pictureFolder, FILE_FOLDER);
|
|
||||||
if (appDir.exists() || appDir.mkdirs()) {
|
|
||||||
String fileName = URLUtil.guessFileName(url, null, MimeType.JPEG.toString());
|
|
||||||
if (isGifFile(file)) {
|
|
||||||
fileName = changeFileExtension(fileName, ".gif");
|
|
||||||
}
|
|
||||||
File destFile = new File(appDir, fileName);
|
|
||||||
if (destFile.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
copyFile(file, destFile);
|
|
||||||
checkGifFile(destFile);
|
|
||||||
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(new File(destFile.getPath()))));
|
|
||||||
Toast.makeText(context, context.getString(R.string.toast_photo_saved, destFile.getPath()), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkGifFile(File file) {
|
|
||||||
if (isGifFile(file)) {
|
|
||||||
File gifFile = new File(file.getParentFile(), FileUtil.changeFileExtension(file.getName(), ".gif"));
|
|
||||||
if (gifFile.exists()) {
|
|
||||||
file.delete();
|
|
||||||
} else {
|
|
||||||
file.renameTo(gifFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static String getPicId(String picUrl) {
|
|
||||||
String fileName = URLUtil.guessFileName(picUrl, null, MimeType.JPEG.toString());
|
|
||||||
return fileName.replace(".jpg", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void initImageView(@NonNull ImageView view, List<PhotoViewBean> photoViewBeans, int position, String forumName, String forumId, String threadId, boolean seeLz, String objType) {
|
|
||||||
view.setOnClickListener(v -> {
|
|
||||||
Object tag = view.getTag(R.id.image_load_tag);
|
|
||||||
if (tag != null) {
|
|
||||||
boolean loaded = (boolean) tag;
|
|
||||||
if (loaded) {
|
|
||||||
PhotoViewActivity.launch(v.getContext(), photoViewBeans.toArray(new PhotoViewBean[0]), position, forumName, forumId, threadId, seeLz, objType);
|
|
||||||
} else {
|
|
||||||
load(view, LOAD_TYPE_SMALL_PIC, photoViewBeans.get(position).getUrl(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
view.setOnLongClickListener(v -> {
|
|
||||||
PopupMenu popupMenu = PopupUtil.create(view);
|
|
||||||
popupMenu.getMenuInflater().inflate(R.menu.menu_image_long_click, popupMenu.getMenu());
|
|
||||||
popupMenu.setOnMenuItemClickListener(item -> {
|
|
||||||
if (item.getItemId() == R.id.menu_save_image) {
|
|
||||||
download(view.getContext(), photoViewBeans.get(position).getOriginUrl());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
popupMenu.show();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void initImageView(ImageView view, List<PhotoViewBean> photoViewBeans, int position) {
|
|
||||||
view.setOnClickListener(v -> {
|
|
||||||
Object tag = view.getTag(R.id.image_load_tag);
|
|
||||||
if (tag != null) {
|
|
||||||
boolean loaded = (boolean) tag;
|
|
||||||
if (loaded) {
|
|
||||||
PhotoViewActivity.launch(v.getContext(), photoViewBeans, position);
|
|
||||||
} else {
|
|
||||||
load(view, LOAD_TYPE_SMALL_PIC, photoViewBeans.get(position).getUrl(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
view.setOnLongClickListener(v -> {
|
|
||||||
PopupMenu popupMenu = PopupUtil.create(view);
|
|
||||||
popupMenu.getMenuInflater().inflate(R.menu.menu_image_long_click, popupMenu.getMenu());
|
|
||||||
popupMenu.setOnMenuItemClickListener(item -> {
|
|
||||||
if (item.getItemId() == R.id.menu_save_image) {
|
|
||||||
download(view.getContext(), photoViewBeans.get(position).getOriginUrl());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
popupMenu.show();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void initImageView(ImageView view, PhotoViewBean photoViewBean) {
|
|
||||||
List<PhotoViewBean> photoViewBeans = new ArrayList<>();
|
|
||||||
photoViewBeans.add(photoViewBean);
|
|
||||||
initImageView(view, photoViewBeans, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getNonNullString(String... strings) {
|
|
||||||
for (String url : strings) {
|
|
||||||
if (!TextUtils.isEmpty(url)) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getRadiusPx(Context context) {
|
|
||||||
return DisplayUtil.dp2px(context, getRadiusDp(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getRadiusDp(Context context) {
|
|
||||||
return AppPreferencesUtilsKt.getAppPreferences(context).getRadius();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void load(ImageView imageView, @LoadType int type, String url) {
|
|
||||||
load(imageView, type, url, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clear(ImageView imageView) {
|
|
||||||
Glide.with(imageView).clear(imageView);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static Drawable getPlaceHolder(Context context, int radius) {
|
|
||||||
GradientDrawable drawable = new GradientDrawable();
|
|
||||||
int color = ThemeUtil.isNightMode() ?
|
|
||||||
context.getResources().getColor(R.color.color_place_holder_night) :
|
|
||||||
context.getResources().getColor(R.color.color_place_holder);
|
|
||||||
drawable.setColor(color);
|
|
||||||
drawable.setCornerRadius(DisplayUtil.dp2px(context, radius));
|
|
||||||
return drawable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
public static void load(
|
|
||||||
ImageView imageView,
|
|
||||||
@LoadType int type,
|
|
||||||
String url,
|
|
||||||
boolean skipNetworkCheck,
|
|
||||||
boolean noTransition
|
|
||||||
) {
|
|
||||||
if (!Util.canLoadGlide(imageView.getContext())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int radius = getRadiusDp(imageView.getContext());
|
|
||||||
RequestBuilder<Drawable> requestBuilder;
|
|
||||||
if (skipNetworkCheck ||
|
|
||||||
type == LOAD_TYPE_AVATAR ||
|
|
||||||
getImageLoadSettings() == SETTINGS_ALL_ORIGIN ||
|
|
||||||
getImageLoadSettings() == SETTINGS_SMART_ORIGIN ||
|
|
||||||
(getImageLoadSettings() == SETTINGS_SMART_LOAD && NetworkUtil.isWifiConnected(imageView.getContext()))) {
|
|
||||||
imageView.setTag(R.id.image_load_tag, true);
|
|
||||||
requestBuilder = Glide.with(imageView).load(url);
|
|
||||||
} else {
|
|
||||||
imageView.setTag(R.id.image_load_tag, false);
|
|
||||||
requestBuilder = Glide.with(imageView).load(getPlaceHolder(imageView.getContext(), type == LOAD_TYPE_SMALL_PIC ? radius : 0));
|
|
||||||
}
|
|
||||||
if (ThemeUtil.isNightMode()) {
|
|
||||||
changeBrightness(imageView, -35);
|
|
||||||
} else {
|
|
||||||
imageView.clearColorFilter();
|
|
||||||
}
|
|
||||||
switch (type) {
|
|
||||||
case LOAD_TYPE_SMALL_PIC:
|
|
||||||
requestBuilder.apply(RequestOptions.bitmapTransform(new RadiusTransformation(radius))
|
|
||||||
.placeholder(getPlaceHolder(imageView.getContext(), radius))
|
|
||||||
.skipMemoryCache(true));
|
|
||||||
break;
|
|
||||||
case LOAD_TYPE_AVATAR:
|
|
||||||
requestBuilder.apply(RequestOptions.bitmapTransform(new RadiusTransformation(6))
|
|
||||||
.placeholder(getPlaceHolder(imageView.getContext(), 6))
|
|
||||||
.skipMemoryCache(true));
|
|
||||||
break;
|
|
||||||
case LOAD_TYPE_NO_RADIUS:
|
|
||||||
requestBuilder.apply(new RequestOptions()
|
|
||||||
.placeholder(getPlaceHolder(imageView.getContext(), 0))
|
|
||||||
.skipMemoryCache(true));
|
|
||||||
break;
|
|
||||||
case LOAD_TYPE_ALWAYS_ROUND:
|
|
||||||
requestBuilder.apply(new RequestOptions()
|
|
||||||
.circleCrop()
|
|
||||||
.placeholder(getPlaceHolder(imageView.getContext(), ExtensionsKt.dpToPx(100)))
|
|
||||||
.skipMemoryCache(true));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!noTransition) {
|
|
||||||
requestBuilder.transition(DrawableTransitionOptions.withCrossFade());
|
|
||||||
}
|
|
||||||
requestBuilder.into(imageView);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void load(
|
|
||||||
ImageView imageView,
|
|
||||||
@LoadType int type,
|
|
||||||
String url,
|
|
||||||
boolean skipNetworkCheck
|
|
||||||
) {
|
|
||||||
load(imageView, type, url, skipNetworkCheck, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取要加载的图片 Url
|
|
||||||
*
|
|
||||||
* @param isSmallPic 加载的是否为缩略图
|
|
||||||
* @param originUrl 原图 Url
|
|
||||||
* @param smallPicUrls 缩略图 Url,按照画质从好到差排序
|
|
||||||
* @return 要加载的图片 Url
|
|
||||||
*/
|
|
||||||
public static String getUrl(Context context, boolean isSmallPic, @NonNull String originUrl, @NonNull String... smallPicUrls) {
|
|
||||||
List<String> urls = Arrays.asList(smallPicUrls);
|
|
||||||
if (isSmallPic) {
|
|
||||||
if (needReverse(context)) {
|
|
||||||
Collections.reverse(urls);
|
|
||||||
}
|
|
||||||
for (String url : urls) {
|
|
||||||
if (!TextUtils.isEmpty(url)) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return originUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean needReverse(Context context) {
|
|
||||||
if (getImageLoadSettings() == SETTINGS_SMART_ORIGIN && NetworkUtil.isWifiConnected(context)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return getImageLoadSettings() != SETTINGS_ALL_ORIGIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ImageLoadSettings
|
|
||||||
private static int getImageLoadSettings() {
|
|
||||||
return Integer.parseInt(AppPreferencesUtilsKt.getAppPreferences(App.getINSTANCE()).getImageLoadType());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String imageToBase64(InputStream is) {
|
|
||||||
if (is == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
byte[] data = null;
|
|
||||||
String result = null;
|
|
||||||
try {
|
|
||||||
//创建一个字符流大小的数组。
|
|
||||||
data = new byte[is.available()];
|
|
||||||
//写入数组
|
|
||||||
is.read(data);
|
|
||||||
//用默认的编码格式进行编码
|
|
||||||
result = Base64.encodeToString(data, Base64.DEFAULT);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
if (null != is) {
|
|
||||||
try {
|
|
||||||
is.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String imageToBase64(File file) {
|
|
||||||
if (file == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String result = null;
|
|
||||||
try {
|
|
||||||
InputStream is = new FileInputStream(file);
|
|
||||||
result = imageToBase64(is);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ShareTaskCallback {
|
|
||||||
void onGetUri(Uri uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@IntDef({LOAD_TYPE_SMALL_PIC, LOAD_TYPE_AVATAR, LOAD_TYPE_NO_RADIUS, LOAD_TYPE_ALWAYS_ROUND})
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
public @interface LoadType {
|
|
||||||
}
|
|
||||||
|
|
||||||
@IntDef({SETTINGS_SMART_ORIGIN, SETTINGS_SMART_LOAD, SETTINGS_ALL_ORIGIN, SETTINGS_ALL_NO})
|
|
||||||
public @interface ImageLoadSettings {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DownloadAsyncTask extends AsyncTask<Void, Integer, File> {
|
|
||||||
private final WeakReference<Context> contextWeakReference;
|
|
||||||
private final TaskCallback callback;
|
|
||||||
private final String url;
|
|
||||||
|
|
||||||
public DownloadAsyncTask(Context context, String url, TaskCallback callback) {
|
|
||||||
this.contextWeakReference = new WeakReference<>(context);
|
|
||||||
this.url = url;
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskCallback getCallback() {
|
|
||||||
return callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Context getContext() {
|
|
||||||
return contextWeakReference.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected File doInBackground(Void... voids) {
|
|
||||||
File file = null;
|
|
||||||
try {
|
|
||||||
FutureTarget<File> future = Glide.with(getContext())
|
|
||||||
.asFile()
|
|
||||||
.load(getUrl())
|
|
||||||
.submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
|
|
||||||
file = future.get();
|
|
||||||
return file;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(File file) {
|
|
||||||
super.onPostExecute(file);
|
|
||||||
getCallback().onPostExecute(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface TaskCallback {
|
|
||||||
void onPostExecute(File file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,701 @@
|
||||||
|
package com.huanchengfly.tieba.post.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Bitmap.CompressFormat
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.ColorMatrix
|
||||||
|
import android.graphics.ColorMatrixColorFilter
|
||||||
|
import android.graphics.PixelFormat
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.Base64
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.webkit.URLUtil
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.IntDef
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import com.github.panpf.sketch.request.DownloadRequest
|
||||||
|
import com.github.panpf.sketch.request.DownloadResult
|
||||||
|
import com.huanchengfly.tieba.post.App.Companion.INSTANCE
|
||||||
|
import com.huanchengfly.tieba.post.R
|
||||||
|
import com.huanchengfly.tieba.post.activities.PhotoViewActivity.Companion.launch
|
||||||
|
import com.huanchengfly.tieba.post.components.transformations.RadiusTransformation
|
||||||
|
import com.huanchengfly.tieba.post.dpToPx
|
||||||
|
import com.huanchengfly.tieba.post.models.PhotoViewBean
|
||||||
|
import com.huanchengfly.tieba.post.utils.PermissionUtils.PermissionData
|
||||||
|
import com.huanchengfly.tieba.post.utils.PermissionUtils.askPermission
|
||||||
|
import com.huanchengfly.tieba.post.utils.ThemeUtil.isNightMode
|
||||||
|
import com.zhihu.matisse.MimeType
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.nio.channels.FileChannel
|
||||||
|
|
||||||
|
object ImageUtil {
|
||||||
|
/**
|
||||||
|
* 智能省流
|
||||||
|
*/
|
||||||
|
const val SETTINGS_SMART_ORIGIN = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能无图
|
||||||
|
*/
|
||||||
|
const val SETTINGS_SMART_LOAD = 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 始终高质量
|
||||||
|
*/
|
||||||
|
const val SETTINGS_ALL_ORIGIN = 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 始终无图
|
||||||
|
*/
|
||||||
|
const val SETTINGS_ALL_NO = 3
|
||||||
|
const val LOAD_TYPE_SMALL_PIC = 0
|
||||||
|
const val LOAD_TYPE_AVATAR = 1
|
||||||
|
const val LOAD_TYPE_NO_RADIUS = 2
|
||||||
|
const val LOAD_TYPE_ALWAYS_ROUND = 3
|
||||||
|
const val TAG = "ImageUtil"
|
||||||
|
private fun isGifFile(file: File?): Boolean {
|
||||||
|
if (file == null) return false
|
||||||
|
try {
|
||||||
|
return FileInputStream(file).use { isGifFile(it) }
|
||||||
|
} catch (e: FileNotFoundException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断是否为GIF文件
|
||||||
|
private fun isGifFile(inputStream: InputStream?): Boolean {
|
||||||
|
if (inputStream == null) return false
|
||||||
|
val bytes = ByteArray(4)
|
||||||
|
try {
|
||||||
|
inputStream.mark(0)
|
||||||
|
inputStream.read(bytes)
|
||||||
|
inputStream.reset()
|
||||||
|
val str = String(bytes)
|
||||||
|
return str.equals("GIF8", ignoreCase = true)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun compressImage(bitmap: Bitmap, output: File, maxSize: Int = 100): File {
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
var quality = 100
|
||||||
|
bitmap.compress(CompressFormat.JPEG, quality, baos) //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
|
||||||
|
while (baos.toByteArray().size / 1024 > maxSize && quality > 0) { //循环判断如果压缩后图片是否大于20kb,大于继续压缩 友盟缩略图要求不大于18kb
|
||||||
|
baos.reset() //重置baos即清空baos
|
||||||
|
quality -= 5 //每次都减少5
|
||||||
|
bitmap.compress(CompressFormat.JPEG, quality, baos) //这里压缩options%,把压缩后的数据存放到baos中
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val fos = FileOutputStream(output)
|
||||||
|
try {
|
||||||
|
fos.write(baos.toByteArray())
|
||||||
|
fos.flush()
|
||||||
|
fos.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
} catch (e: FileNotFoundException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun bitmapToFile(
|
||||||
|
bitmap: Bitmap,
|
||||||
|
output: File,
|
||||||
|
format: CompressFormat? = CompressFormat.JPEG
|
||||||
|
): File {
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
bitmap.compress(format, 100, baos)
|
||||||
|
try {
|
||||||
|
val fos = FileOutputStream(output)
|
||||||
|
try {
|
||||||
|
fos.write(baos.toByteArray())
|
||||||
|
fos.flush()
|
||||||
|
fos.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
} catch (e: FileNotFoundException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
fun drawableToBitmap(drawable: Drawable): Bitmap {
|
||||||
|
val bitmap = Bitmap.createBitmap(
|
||||||
|
drawable.intrinsicWidth, drawable.intrinsicHeight,
|
||||||
|
if (drawable.opacity != PixelFormat.OPAQUE) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565
|
||||||
|
)
|
||||||
|
val canvas = Canvas(bitmap)
|
||||||
|
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||||
|
drawable.draw(canvas)
|
||||||
|
return bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyFile(src: FileInputStream?, dest: FileOutputStream?): Boolean {
|
||||||
|
if (src == null || dest == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val srcChannel: FileChannel?
|
||||||
|
val dstChannel: FileChannel?
|
||||||
|
try {
|
||||||
|
srcChannel = src.channel
|
||||||
|
dstChannel = dest.channel
|
||||||
|
srcChannel.transferTo(0, srcChannel.size(), dstChannel)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
srcChannel.close()
|
||||||
|
dstChannel.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyFile(src: File?, dest: File?): Boolean {
|
||||||
|
if (src == null || dest == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (dest.exists()) {
|
||||||
|
dest.delete()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
dest.createNewFile()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
val srcChannel: FileChannel?
|
||||||
|
val dstChannel: FileChannel?
|
||||||
|
try {
|
||||||
|
srcChannel = FileInputStream(src).channel
|
||||||
|
dstChannel = FileOutputStream(dest).channel
|
||||||
|
srcChannel.transferTo(0, srcChannel.size(), dstChannel)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
srcChannel.close()
|
||||||
|
dstChannel.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun changeBrightness(imageView: ImageView, brightness: Int) {
|
||||||
|
val cMatrix = ColorMatrix()
|
||||||
|
cMatrix.set(
|
||||||
|
floatArrayOf(
|
||||||
|
1f, 0f, 0f, 0f, brightness.toFloat(), 0f, 1f, 0f, 0f, brightness.toFloat(), // 改变亮度
|
||||||
|
0f, 0f, 1f, 0f, brightness.toFloat(), 0f, 0f, 0f, 1f, 0f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
imageView.colorFilter = ColorMatrixColorFilter(cMatrix)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
fun download(context: Context, url: String?) {
|
||||||
|
download(context, url, false, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadForShare(context: Context, url: String?, taskCallback: ShareTaskCallback) {
|
||||||
|
if (url == null) return
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val downloadResult = DownloadRequest(context, url).execute()
|
||||||
|
if (downloadResult is DownloadResult.Success) {
|
||||||
|
val inputStream = downloadResult.data.data.newInputStream()
|
||||||
|
val pictureFolder = File(context.cacheDir, ".shareTemp")
|
||||||
|
if (pictureFolder.exists() || pictureFolder.mkdirs()) {
|
||||||
|
val fileName = "share_" + System.currentTimeMillis()
|
||||||
|
val destFile = File(pictureFolder, fileName)
|
||||||
|
if (!destFile.exists()) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
destFile.createNewFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inputStream.use { input ->
|
||||||
|
if (destFile.canWrite()) {
|
||||||
|
destFile.outputStream().use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val shareUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
context.packageName + ".share.FileProvider",
|
||||||
|
destFile
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Uri.fromFile(destFile)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
taskCallback.onGetUri(shareUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
fun download(
|
||||||
|
context: Context,
|
||||||
|
url: String?,
|
||||||
|
forShare: Boolean,
|
||||||
|
taskCallback: ShareTaskCallback?
|
||||||
|
) {
|
||||||
|
if (forShare) {
|
||||||
|
if (taskCallback != null) downloadForShare(context, url, taskCallback)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
downloadAboveQ(context, url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
askPermission(
|
||||||
|
context,
|
||||||
|
PermissionData(
|
||||||
|
listOf(
|
||||||
|
PermissionUtils.READ_EXTERNAL_STORAGE,
|
||||||
|
PermissionUtils.WRITE_EXTERNAL_STORAGE
|
||||||
|
),
|
||||||
|
context.getString(R.string.tip_permission_storage)
|
||||||
|
),
|
||||||
|
R.string.toast_no_permission_save_photo
|
||||||
|
) {
|
||||||
|
downloadBelowQ(context, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadAboveQ(context: Context, url: String?) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val downloadResult = DownloadRequest(context, url).execute()
|
||||||
|
if (downloadResult is DownloadResult.Success) {
|
||||||
|
var mimeType = MimeType.JPEG.toString()
|
||||||
|
var fileName = URLUtil.guessFileName(url, null, mimeType)
|
||||||
|
downloadResult.data.data.newInputStream().use { inputStream ->
|
||||||
|
if (isGifFile(inputStream)) {
|
||||||
|
mimeType = MimeType.GIF.toString()
|
||||||
|
fileName = FileUtil.changeFileExtension(fileName, ".gif")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
downloadResult.data.data.newInputStream().use { inputStream ->
|
||||||
|
val relativePath =
|
||||||
|
Environment.DIRECTORY_PICTURES + File.separator + FileUtil.FILE_FOLDER
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(MediaStore.Images.Media.RELATIVE_PATH, relativePath)
|
||||||
|
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
|
||||||
|
put(MediaStore.Images.Media.MIME_TYPE, mimeType)
|
||||||
|
put(MediaStore.Images.Media.DESCRIPTION, fileName)
|
||||||
|
}
|
||||||
|
val cr = context.contentResolver
|
||||||
|
val uri: Uri = runCatching {
|
||||||
|
cr.insert(
|
||||||
|
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||||
|
values
|
||||||
|
)
|
||||||
|
}.getOrNull() ?: return@launch
|
||||||
|
try {
|
||||||
|
cr.openFileDescriptor(uri, "w")?.use {
|
||||||
|
FileOutputStream(it.fileDescriptor).use { outputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.toast_photo_saved, relativePath),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
cr.delete(uri, null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadBelowQ(context: Context, url: String?) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val downloadResult = DownloadRequest(context, url).execute()
|
||||||
|
if (downloadResult is DownloadResult.Success) {
|
||||||
|
var fileName = URLUtil.guessFileName(url, null, MimeType.JPEG.toString())
|
||||||
|
downloadResult.data.data.newInputStream().use { inputStream ->
|
||||||
|
if (isGifFile(inputStream)) {
|
||||||
|
fileName = FileUtil.changeFileExtension(fileName, ".gif")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
downloadResult.data.data.newInputStream().use { inputStream ->
|
||||||
|
val pictureFolder =
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
||||||
|
val appDir = File(pictureFolder, FileUtil.FILE_FOLDER)
|
||||||
|
val dirExists =
|
||||||
|
withContext(Dispatchers.IO) { appDir.exists() || appDir.mkdirs() }
|
||||||
|
if (dirExists) {
|
||||||
|
val destFile = File(appDir, fileName)
|
||||||
|
if (!destFile.exists()) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
destFile.createNewFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destFile.outputStream().use { outputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
context.sendBroadcast(
|
||||||
|
Intent(
|
||||||
|
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
|
||||||
|
Uri.fromFile(File(destFile.path))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.toast_photo_saved, destFile.path),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkGifFile(file: File) {
|
||||||
|
if (isGifFile(file)) {
|
||||||
|
val gifFile = File(file.parentFile, FileUtil.changeFileExtension(file.name, ".gif"))
|
||||||
|
if (gifFile.exists()) {
|
||||||
|
file.delete()
|
||||||
|
} else {
|
||||||
|
file.renameTo(gifFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPicId(picUrl: String?): String {
|
||||||
|
val fileName = URLUtil.guessFileName(picUrl, null, MimeType.JPEG.toString())
|
||||||
|
return fileName.replace(".jpg", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun initImageView(
|
||||||
|
view: ImageView,
|
||||||
|
photoViewBeans: List<PhotoViewBean?>,
|
||||||
|
position: Int,
|
||||||
|
forumName: String?,
|
||||||
|
forumId: String?,
|
||||||
|
threadId: String?,
|
||||||
|
seeLz: Boolean,
|
||||||
|
objType: String?
|
||||||
|
) {
|
||||||
|
view.setOnClickListener { v: View ->
|
||||||
|
val tag = view.getTag(R.id.image_load_tag)
|
||||||
|
if (tag != null) {
|
||||||
|
val loaded = tag as Boolean
|
||||||
|
if (loaded) {
|
||||||
|
launch(
|
||||||
|
v.context,
|
||||||
|
photoViewBeans.toTypedArray(),
|
||||||
|
position,
|
||||||
|
forumName,
|
||||||
|
forumId,
|
||||||
|
threadId,
|
||||||
|
seeLz,
|
||||||
|
objType
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
load(view, LOAD_TYPE_SMALL_PIC, photoViewBeans[position]!!.url, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.setOnLongClickListener {
|
||||||
|
val popupMenu = PopupUtil.create(view)
|
||||||
|
popupMenu.menuInflater.inflate(R.menu.menu_image_long_click, popupMenu.menu)
|
||||||
|
popupMenu.setOnMenuItemClickListener { item: MenuItem ->
|
||||||
|
if (item.itemId == R.id.menu_save_image) {
|
||||||
|
download(view.context, photoViewBeans[position]!!.originUrl)
|
||||||
|
return@setOnMenuItemClickListener true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
popupMenu.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun initImageView(view: ImageView, photoViewBeans: List<PhotoViewBean>, position: Int) {
|
||||||
|
view.setOnClickListener { v: View ->
|
||||||
|
val tag = view.getTag(R.id.image_load_tag)
|
||||||
|
if (tag != null) {
|
||||||
|
val loaded = tag as Boolean
|
||||||
|
if (loaded) {
|
||||||
|
launch(v.context, photoViewBeans, position)
|
||||||
|
} else {
|
||||||
|
load(view, LOAD_TYPE_SMALL_PIC, photoViewBeans[position].url, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.setOnLongClickListener {
|
||||||
|
val popupMenu = PopupUtil.create(view)
|
||||||
|
popupMenu.menuInflater.inflate(R.menu.menu_image_long_click, popupMenu.menu)
|
||||||
|
popupMenu.setOnMenuItemClickListener { item: MenuItem ->
|
||||||
|
if (item.itemId == R.id.menu_save_image) {
|
||||||
|
download(view.context, photoViewBeans[position].originUrl)
|
||||||
|
return@setOnMenuItemClickListener true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
popupMenu.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initImageView(view: ImageView, photoViewBean: PhotoViewBean) {
|
||||||
|
val photoViewBeans: MutableList<PhotoViewBean> = ArrayList()
|
||||||
|
photoViewBeans.add(photoViewBean)
|
||||||
|
initImageView(view, photoViewBeans, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getNonNullString(vararg strings: String?): String? {
|
||||||
|
for (url in strings) {
|
||||||
|
if (!TextUtils.isEmpty(url)) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getRadiusPx(context: Context): Int {
|
||||||
|
return DisplayUtil.dp2px(context, getRadiusDp(context).toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRadiusDp(context: Context): Int {
|
||||||
|
return context.appPreferences.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear(imageView: ImageView?) {
|
||||||
|
Glide.with(imageView!!).clear(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPlaceHolder(context: Context, radius: Int): Drawable {
|
||||||
|
val drawable = GradientDrawable()
|
||||||
|
val colorResId =
|
||||||
|
if (isNightMode()) R.color.color_place_holder_night else R.color.color_place_holder
|
||||||
|
val color = ContextCompat.getColor(context, colorResId)
|
||||||
|
drawable.setColor(color)
|
||||||
|
drawable.cornerRadius =
|
||||||
|
DisplayUtil.dp2px(context, radius.toFloat()).toFloat()
|
||||||
|
return drawable
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
fun load(
|
||||||
|
imageView: ImageView,
|
||||||
|
@LoadType type: Int,
|
||||||
|
url: String?,
|
||||||
|
skipNetworkCheck: Boolean,
|
||||||
|
noTransition: Boolean
|
||||||
|
) {
|
||||||
|
if (!Util.canLoadGlide(imageView.context)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val radius = getRadiusDp(imageView.context)
|
||||||
|
val requestBuilder =
|
||||||
|
if (skipNetworkCheck ||
|
||||||
|
type == LOAD_TYPE_AVATAR ||
|
||||||
|
imageLoadSettings == SETTINGS_ALL_ORIGIN ||
|
||||||
|
imageLoadSettings == SETTINGS_SMART_ORIGIN ||
|
||||||
|
(imageLoadSettings == SETTINGS_SMART_LOAD && NetworkUtil.isWifiConnected(imageView.context))
|
||||||
|
) {
|
||||||
|
imageView.setTag(R.id.image_load_tag, true)
|
||||||
|
Glide.with(imageView).load(url)
|
||||||
|
} else {
|
||||||
|
imageView.setTag(R.id.image_load_tag, false)
|
||||||
|
Glide.with(imageView).load(
|
||||||
|
getPlaceHolder(
|
||||||
|
imageView.context,
|
||||||
|
if (type == LOAD_TYPE_SMALL_PIC) radius else 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isNightMode()) {
|
||||||
|
changeBrightness(imageView, -35)
|
||||||
|
} else {
|
||||||
|
imageView.clearColorFilter()
|
||||||
|
}
|
||||||
|
when (type) {
|
||||||
|
LOAD_TYPE_SMALL_PIC -> requestBuilder.apply(
|
||||||
|
RequestOptions.bitmapTransform(RadiusTransformation(radius))
|
||||||
|
.placeholder(getPlaceHolder(imageView.context, radius))
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
)
|
||||||
|
|
||||||
|
LOAD_TYPE_AVATAR -> requestBuilder.apply(
|
||||||
|
RequestOptions.bitmapTransform(RadiusTransformation(6))
|
||||||
|
.placeholder(getPlaceHolder(imageView.context, 6))
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
)
|
||||||
|
|
||||||
|
LOAD_TYPE_NO_RADIUS -> requestBuilder.apply(
|
||||||
|
RequestOptions()
|
||||||
|
.placeholder(getPlaceHolder(imageView.context, 0))
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
)
|
||||||
|
|
||||||
|
LOAD_TYPE_ALWAYS_ROUND -> requestBuilder.apply(
|
||||||
|
RequestOptions()
|
||||||
|
.circleCrop()
|
||||||
|
.placeholder(getPlaceHolder(imageView.context, 100.dpToPx()))
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!noTransition) {
|
||||||
|
requestBuilder.transition(DrawableTransitionOptions.withCrossFade())
|
||||||
|
}
|
||||||
|
requestBuilder.into(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun load(
|
||||||
|
imageView: ImageView,
|
||||||
|
@LoadType type: Int,
|
||||||
|
url: String?,
|
||||||
|
skipNetworkCheck: Boolean = false
|
||||||
|
) {
|
||||||
|
load(imageView, type, url, skipNetworkCheck, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取要加载的图片 Url
|
||||||
|
*
|
||||||
|
* @param isSmallPic 加载的是否为缩略图
|
||||||
|
* @param originUrl 原图 Url
|
||||||
|
* @param smallPicUrls 缩略图 Url,按照画质从好到差排序
|
||||||
|
* @return 要加载的图片 Url
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun getUrl(
|
||||||
|
context: Context,
|
||||||
|
isSmallPic: Boolean,
|
||||||
|
originUrl: String,
|
||||||
|
vararg smallPicUrls: String?
|
||||||
|
): String {
|
||||||
|
val urls = mutableListOf(*smallPicUrls)
|
||||||
|
if (isSmallPic) {
|
||||||
|
if (needReverse(context)) {
|
||||||
|
urls.reverse()
|
||||||
|
}
|
||||||
|
for (url in urls) {
|
||||||
|
if (!url.isNullOrEmpty()) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return originUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun needReverse(context: Context): Boolean {
|
||||||
|
return if (imageLoadSettings == SETTINGS_SMART_ORIGIN &&
|
||||||
|
NetworkUtil.isWifiConnected(context)
|
||||||
|
) false
|
||||||
|
else imageLoadSettings != SETTINGS_ALL_ORIGIN
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:ImageLoadSettings
|
||||||
|
private val imageLoadSettings: Int
|
||||||
|
get() = INSTANCE.appPreferences.imageLoadType!!.toInt()
|
||||||
|
|
||||||
|
fun imageToBase64(inputStream: InputStream?): String? {
|
||||||
|
if (inputStream == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return runCatching {
|
||||||
|
inputStream.use {
|
||||||
|
Base64.encodeToString(inputStream.readBytes(), Base64.DEFAULT)
|
||||||
|
}
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun imageToBase64(file: File?): String? {
|
||||||
|
if (file == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
var result: String? = null
|
||||||
|
try {
|
||||||
|
val `is`: InputStream = FileInputStream(file)
|
||||||
|
result = imageToBase64(`is`)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShareTaskCallback {
|
||||||
|
fun onGetUri(uri: Uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IntDef(LOAD_TYPE_SMALL_PIC, LOAD_TYPE_AVATAR, LOAD_TYPE_NO_RADIUS, LOAD_TYPE_ALWAYS_ROUND)
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
annotation class LoadType
|
||||||
|
|
||||||
|
@IntDef(SETTINGS_SMART_ORIGIN, SETTINGS_SMART_LOAD, SETTINGS_ALL_ORIGIN, SETTINGS_ALL_NO)
|
||||||
|
annotation class ImageLoadSettings
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ImageUtil.download(
|
||||||
|
context: Context,
|
||||||
|
url: String?,
|
||||||
|
forShare: Boolean,
|
||||||
|
taskCallback: (Uri) -> Unit
|
||||||
|
) {
|
||||||
|
download(context, url, forShare, object : ImageUtil.ShareTaskCallback {
|
||||||
|
override fun onGetUri(uri: Uri) {
|
||||||
|
taskCallback(uri)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue