mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-19 23:13:04 +08:00
Merge branch 'master' of github.com:NativeScript/NativeScript
This commit is contained in:
@ -78,6 +78,7 @@ dependencies {
|
||||
implementation 'androidx.viewpager:viewpager:' + androidxVersion
|
||||
implementation 'androidx.fragment:fragment:' + androidxVersion
|
||||
implementation 'androidx.transition:transition:' + androidxVersion
|
||||
implementation "androidx.exifinterface:exifinterface:1.3.2"
|
||||
} else {
|
||||
println 'Using support library'
|
||||
implementation 'com.android.support:support-v4:' + computeSupportVersion()
|
||||
@ -99,4 +100,4 @@ task copyAar {
|
||||
|
||||
assemble.dependsOn(cleanBuildDir)
|
||||
copyAar.dependsOn(assemble)
|
||||
build.dependsOn(copyAar)
|
||||
build.dependsOn(copyAar)
|
||||
|
@ -17,13 +17,17 @@
|
||||
package org.nativescript.widgets.image;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.ExifInterface;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
|
||||
@ -34,6 +38,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@ -257,8 +262,9 @@ public class Fetcher extends Worker {
|
||||
if (debuggable > 0) {
|
||||
Log.v(TAG, "process: " + uri);
|
||||
}
|
||||
|
||||
if (uri.startsWith(FILE_PREFIX)) {
|
||||
if(uri.startsWith(CONTENT_PREFIX)){
|
||||
return decodeSampledBitmapFromContent(uri,mResolver , decodeWidth, decodeHeight, keepAspectRatio, getCache());
|
||||
}else if (uri.startsWith(FILE_PREFIX)) {
|
||||
String filename = uri.substring(FILE_PREFIX.length());
|
||||
return decodeSampledBitmapFromFile(filename, decodeWidth, decodeHeight, keepAspectRatio, getCache());
|
||||
} else if (uri.startsWith(RESOURCE_PREFIX)) {
|
||||
@ -474,6 +480,62 @@ public class Fetcher extends Worker {
|
||||
return scaleAndRotateBitmap(bitmap, ei, reqWidth, reqHeight, keepAspectRatio);
|
||||
}
|
||||
|
||||
private static void closePfd(ParcelFileDescriptor pfd){
|
||||
if(pfd != null){
|
||||
try {
|
||||
pfd.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode and sample down a bitmap from a file to the requested width and height.
|
||||
*
|
||||
* @param content The content uri of the file to decode
|
||||
* @param reqWidth The requested width of the resulting bitmap
|
||||
* @param reqHeight The requested height of the resulting bitmap
|
||||
* @param cache The Cache used to find candidate bitmaps for use with inBitmap
|
||||
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
|
||||
* that are equal to or greater than the requested width and height
|
||||
*/
|
||||
public static Bitmap decodeSampledBitmapFromContent(String content, ContentResolver resolver, int reqWidth, int reqHeight,
|
||||
boolean keepAspectRatio, Cache cache) {
|
||||
|
||||
// First decode with inJustDecodeBounds=true to check dimensions
|
||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
|
||||
|
||||
Uri uri = android.net.Uri.parse(content);
|
||||
ParcelFileDescriptor pfd = null;
|
||||
try {
|
||||
pfd = resolver.openFileDescriptor(uri, "r");
|
||||
BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(), null, options);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.v(TAG, "File not found " + content);
|
||||
closePfd(pfd);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight);
|
||||
|
||||
// If we're running on Honeycomb or newer, try to use inBitmap
|
||||
if (Utils.hasHoneycomb()) {
|
||||
addInBitmapOptions(options, cache);
|
||||
}
|
||||
|
||||
// Decode bitmap with inSampleSize set
|
||||
options.inJustDecodeBounds = false;
|
||||
|
||||
final Bitmap bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(), null, options);
|
||||
|
||||
ExifInterface ei = getExifInterface(pfd.getFileDescriptor());
|
||||
closePfd(pfd);
|
||||
|
||||
return scaleAndRotateBitmap(bitmap, ei, reqWidth, reqHeight, keepAspectRatio);
|
||||
}
|
||||
|
||||
private static Bitmap scaleAndRotateBitmap(Bitmap bitmap, ExifInterface ei, int reqWidth, int reqHeight,
|
||||
boolean keepAspectRatio) {
|
||||
if (bitmap == null) {
|
||||
@ -669,4 +731,4 @@ public class Fetcher extends Worker {
|
||||
}
|
||||
//END_INCLUDE(add_bitmap_options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package org.nativescript.widgets.image;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
@ -37,6 +38,7 @@ public abstract class Worker {
|
||||
|
||||
protected static final String RESOURCE_PREFIX = "res://";
|
||||
protected static final String FILE_PREFIX = "file:///";
|
||||
protected static final String CONTENT_PREFIX = "content://";
|
||||
|
||||
static final String TAG = "JS";
|
||||
private static final int FADE_IN_TIME = 200;
|
||||
@ -49,7 +51,7 @@ public abstract class Worker {
|
||||
|
||||
protected boolean mPauseWork = false;
|
||||
protected Resources mResources;
|
||||
|
||||
protected ContentResolver mResolver;
|
||||
private static final int MESSAGE_CLEAR = 0;
|
||||
private static final int MESSAGE_INIT_DISK_CACHE = 1;
|
||||
private static final int MESSAGE_FLUSH = 2;
|
||||
@ -59,7 +61,7 @@ public abstract class Worker {
|
||||
|
||||
protected Worker(Context context) {
|
||||
mResources = context.getResources();
|
||||
|
||||
mResolver = context.getContentResolver();
|
||||
// Negative means not initialized.
|
||||
if (debuggable < 0) {
|
||||
try {
|
||||
@ -539,4 +541,4 @@ public abstract class Worker {
|
||||
public void closeCache() {
|
||||
new CacheAsyncTask().execute(MESSAGE_CLOSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,33 @@
|
||||
package org.nativescript.widgets;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class Utils {
|
||||
public static void drawBoxShadow(View view, String value) {
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
|
||||
@ -16,14 +37,14 @@ public class Utils {
|
||||
|
||||
Drawable currentBg = view.getBackground();
|
||||
|
||||
if(currentBg != null) {
|
||||
if (currentBg != null) {
|
||||
Log.d("BoxShadowDrawable", "current BG is: " + currentBg.getClass().getName());
|
||||
}
|
||||
|
||||
if(currentBg == null) {
|
||||
if (currentBg == null) {
|
||||
Log.d("BoxShadowDrawable", "view had no background!");
|
||||
currentBg = new ColorDrawable(Color.TRANSPARENT);
|
||||
} else if(currentBg instanceof BoxShadowDrawable) {
|
||||
} else if (currentBg instanceof BoxShadowDrawable) {
|
||||
currentBg = ((BoxShadowDrawable) view.getBackground()).getWrappedDrawable();
|
||||
Log.d("BoxShadowDrawable", "already a BoxShadowDrawable, getting wrapped drawable:" + currentBg.getClass().getName());
|
||||
}
|
||||
@ -33,21 +54,237 @@ public class Utils {
|
||||
view.setBackground(new BoxShadowDrawable(currentBg, value));
|
||||
|
||||
Drawable bg = view.getBackground();
|
||||
if(bg != null) {
|
||||
if (bg != null) {
|
||||
Log.d("BoxShadowDrawable", "new current bg: " + bg.getClass().getName());
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
while (view.getParent() != null && view.getParent() instanceof ViewGroup) {
|
||||
count++;
|
||||
ViewGroup parent = (ViewGroup) view.getParent();
|
||||
parent.setClipChildren(false);
|
||||
parent.setClipToPadding(false);
|
||||
// removing clipping from all breaks the ui
|
||||
if (count == 1) {
|
||||
count++;
|
||||
ViewGroup parent = (ViewGroup) view.getParent();
|
||||
parent.setClipChildren(false);
|
||||
parent.setClipToPadding(false);
|
||||
// removing clipping from all breaks the ui
|
||||
if (count == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface AsyncImageCallback {
|
||||
void onSuccess(Bitmap bitmap);
|
||||
|
||||
void onError(Exception exception);
|
||||
}
|
||||
|
||||
static class ImageAssetOptions {
|
||||
int width;
|
||||
int height;
|
||||
boolean keepAspectRatio;
|
||||
boolean autoScaleFactor;
|
||||
}
|
||||
|
||||
private static final Executor executors = Executors.newCachedThreadPool();
|
||||
|
||||
|
||||
private static Pair<Integer, Integer> getAspectSafeDimensions(float sourceWidth, float sourceHeight, float reqWidth, float reqHeight) {
|
||||
float widthCoef = sourceWidth / reqWidth;
|
||||
float heightCoef = sourceHeight / reqHeight;
|
||||
float aspectCoef = Math.min(widthCoef, heightCoef);
|
||||
|
||||
return new Pair<>((int) Math.floor(sourceWidth / aspectCoef), (int) Math.floor(sourceHeight / aspectCoef));
|
||||
}
|
||||
|
||||
|
||||
private static Pair<Integer, Integer> getRequestedImageSize(Pair<Integer, Integer> src, Pair<Integer, Integer> maxSize, ImageAssetOptions options) {
|
||||
int reqWidth = options.width;
|
||||
if (reqWidth <= 0) {
|
||||
reqWidth = Math.min(src.first, maxSize.first);
|
||||
}
|
||||
int reqHeight = options.height;
|
||||
if (reqHeight <= 0) {
|
||||
reqHeight = Math.min(src.second, maxSize.second);
|
||||
}
|
||||
|
||||
if (options.keepAspectRatio) {
|
||||
Pair<Integer, Integer> safeAspectSize = getAspectSafeDimensions(src.first, src.second, reqWidth, reqHeight);
|
||||
reqWidth = safeAspectSize.first;
|
||||
reqHeight = safeAspectSize.second;
|
||||
}
|
||||
|
||||
return new Pair<>(reqWidth, reqHeight);
|
||||
}
|
||||
|
||||
|
||||
private static void closePfd(ParcelFileDescriptor pfd) {
|
||||
if (pfd != null) {
|
||||
try {
|
||||
pfd.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int calculateAngleFromFile(String filename) {
|
||||
int rotationAngle = 0;
|
||||
ExifInterface ei;
|
||||
try {
|
||||
ei = new ExifInterface(filename);
|
||||
int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
|
||||
|
||||
switch (orientation) {
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
rotationAngle = 90;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
rotationAngle = 180;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
rotationAngle = 270;
|
||||
break;
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
return rotationAngle;
|
||||
}
|
||||
|
||||
|
||||
private static int calculateAngleFromFileDescriptor(FileDescriptor fd) {
|
||||
int rotationAngle = 0;
|
||||
ExifInterface ei;
|
||||
try {
|
||||
ei = new ExifInterface(fd);
|
||||
int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
|
||||
|
||||
switch (orientation) {
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
rotationAngle = 90;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
rotationAngle = 180;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
rotationAngle = 270;
|
||||
break;
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
return rotationAngle;
|
||||
}
|
||||
|
||||
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
public static void loadImageAsync(final Context context, final String src, final String options, final int maxWidth, final int maxHeight, final AsyncImageCallback callback) {
|
||||
executors.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
|
||||
bitmapOptions.inJustDecodeBounds = true;
|
||||
|
||||
try {
|
||||
Bitmap bitmap;
|
||||
ParcelFileDescriptor pfd = null;
|
||||
if (src.startsWith("content://")) {
|
||||
Uri uri = android.net.Uri.parse(src);
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
try {
|
||||
pfd = resolver.openFileDescriptor(uri, "r");
|
||||
} catch (final FileNotFoundException e) {
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onError(e);
|
||||
}
|
||||
});
|
||||
closePfd(pfd);
|
||||
return;
|
||||
}
|
||||
android.graphics.BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(), null, bitmapOptions);
|
||||
} else {
|
||||
android.graphics.BitmapFactory.decodeFile(src, bitmapOptions);
|
||||
}
|
||||
|
||||
ImageAssetOptions opts = new ImageAssetOptions();
|
||||
opts.keepAspectRatio = true;
|
||||
opts.autoScaleFactor = true;
|
||||
|
||||
try {
|
||||
JSONObject object = new JSONObject(options);
|
||||
opts.width = object.optInt("width", 0);
|
||||
opts.height = object.optInt("height", 0);
|
||||
opts.keepAspectRatio = object.optBoolean("keepAspectRatio", true);
|
||||
opts.autoScaleFactor = object.optBoolean("autoScaleFactor", true);
|
||||
} catch (JSONException ignored) {
|
||||
}
|
||||
|
||||
|
||||
Pair<Integer, Integer> sourceSize = new Pair<>(bitmapOptions.outWidth, bitmapOptions.outHeight);
|
||||
Pair<Integer, Integer> maxSize = new Pair<>(maxWidth, maxHeight);
|
||||
Pair<Integer, Integer> requestedSize = getRequestedImageSize(sourceSize, maxSize, opts);
|
||||
int sampleSize = org.nativescript.widgets.image.Fetcher.calculateInSampleSize(bitmapOptions.outWidth, bitmapOptions.outHeight, requestedSize.first, requestedSize.second);
|
||||
BitmapFactory.Options finalBitmapOptions = new BitmapFactory.Options();
|
||||
finalBitmapOptions.inSampleSize = sampleSize;
|
||||
|
||||
|
||||
String error = null;
|
||||
// read as minimum bitmap as possible (slightly bigger than the requested size)
|
||||
|
||||
|
||||
if (pfd != null) {
|
||||
bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(), null, finalBitmapOptions);
|
||||
} else {
|
||||
bitmap = android.graphics.BitmapFactory.decodeFile(src, finalBitmapOptions);
|
||||
}
|
||||
|
||||
|
||||
if (bitmap != null) {
|
||||
if (requestedSize.first != bitmap.getWidth() || requestedSize.second != bitmap.getHeight()) {
|
||||
// scale to exact size
|
||||
bitmap = android.graphics.Bitmap.createScaledBitmap(bitmap, requestedSize.first, requestedSize.second, true);
|
||||
}
|
||||
int rotationAngle;
|
||||
|
||||
if (pfd != null) {
|
||||
rotationAngle = calculateAngleFromFileDescriptor(pfd.getFileDescriptor());
|
||||
closePfd(pfd);
|
||||
} else {
|
||||
rotationAngle = calculateAngleFromFile(src);
|
||||
}
|
||||
|
||||
if (rotationAngle != 0) {
|
||||
Matrix matrix = new android.graphics.Matrix();
|
||||
matrix.postRotate(rotationAngle);
|
||||
bitmap = android.graphics.Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
}
|
||||
}
|
||||
if (bitmap == null) {
|
||||
error = "Asset '" + src + "' cannot be found.";
|
||||
}
|
||||
|
||||
final String finalError = error;
|
||||
final Bitmap finalBitmap = bitmap;
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (finalError != null) {
|
||||
callback.onError(new Exception(finalError));
|
||||
} else {
|
||||
callback.onSuccess(finalBitmap);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (final Exception ex) {
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onError(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// public static void clearBoxShadow(View view) {
|
||||
|
Reference in New Issue
Block a user