feat(android): vector drawable support (#9464)

This commit is contained in:
Osei Fortune
2021-08-11 10:37:27 -07:00
committed by Nathan Walker
parent b2f792324d
commit 76499a5367
23 changed files with 105 additions and 43 deletions

View File

@@ -7,6 +7,9 @@ import android.content.Context;
import android.graphics.*;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.util.Log;
import androidx.appcompat.app.AppCompatDelegate;
import org.nativescript.widgets.image.BitmapOwner;
import org.nativescript.widgets.image.Fetcher;
@@ -14,7 +17,7 @@ import org.nativescript.widgets.image.Worker;
/**
* @author hhristov
*/
public class ImageView extends android.widget.ImageView implements BitmapOwner {
public class ImageView extends androidx.appcompat.widget.AppCompatImageView implements BitmapOwner {
private static final double EPSILON = 1E-05;
private Path path = new Path();
@@ -36,6 +39,10 @@ public class ImageView extends android.widget.ImageView implements BitmapOwner {
private Worker.OnImageLoadedListener mListener;
private boolean mAttachedToWindow = false;
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
public float getRotationAngle() {
return rotationAngle;
}
@@ -214,7 +221,9 @@ public class ImageView extends android.widget.ImageView implements BitmapOwner {
@Override
protected void onDraw(Canvas canvas) {
BorderDrawable background = this.getBackground() instanceof BorderDrawable ? (BorderDrawable) this.getBackground() : null;
if(this.mBitmap == null && this.getDrawable() != null) {
super.onDraw(canvas);
}
if (this.mBitmap != null) {
float borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius;
@@ -332,4 +341,4 @@ public class ImageView extends android.widget.ImageView implements BitmapOwner {
public void setDrawable(Drawable asyncDrawable) {
this.setImageDrawable(asyncDrawable);
}
}
}

View File

@@ -2,6 +2,7 @@ package org.nativescript.widgets;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
@@ -18,6 +19,7 @@ import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.exifinterface.media.ExifInterface;
import org.json.JSONException;
@@ -31,7 +33,18 @@ import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class Utils {
public static Drawable getDrawable(String uri, Context context){
String resPath = uri.substring("res://".length());
int resId = context.getResources().getIdentifier(resPath, "drawable", context.getPackageName());
if (resId > 0) {
return AppCompatResources.getDrawable(context, resId);
} else {
Log.v("JS", "Missing Image with resourceID: " + uri);
return null;
}
}
public static void drawBoxShadow(View view, String value) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
return;

View File

@@ -20,16 +20,15 @@ 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 androidx.exifinterface.media.ExifInterface;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import android.util.TypedValue;
import androidx.exifinterface.media.ExifInterface;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -377,13 +376,12 @@ public class Fetcher extends Worker {
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap bitmap = null;
InputStream is = null;
InputStream is = null;
try {
final TypedValue value = new TypedValue();
is = res.openRawResource(resId, value);
bitmap = BitmapFactory.decodeResourceStream(res, value, is, null, options);
final TypedValue value = new TypedValue();
is = res.openRawResource(resId, value);
bitmap = BitmapFactory.decodeResourceStream(res, value, is, null, options);
} catch (Exception e) {
/* do nothing.
If the exception happened on open, bm will be null.
@@ -391,8 +389,10 @@ public class Fetcher extends Worker {
*/
}
if (bitmap == null && options != null && options.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
if (bitmap == null) {
// throw new IllegalArgumentException("Problem decoding into existing bitmap");
return null;
}
ExifInterface ei = getExifInterface(is);
@@ -449,7 +449,7 @@ public class Fetcher extends Worker {
/**
* Decode and sample down a bitmap from a file to the requested width and height.
*
* @param filename The full path of the file to decode
* @param fileName The full path 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

View File

@@ -26,9 +26,10 @@ import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.widget.ImageView;
import java.lang.ref.WeakReference;
import org.nativescript.widgets.Utils;
/**
* This class wraps up completing some arbitrary long running work when loading a bitmap to an
* ImageView. It handles things like using a memory and disk cache, running the work in a background
@@ -52,6 +53,7 @@ public abstract class Worker {
protected boolean mPauseWork = false;
protected Resources mResources;
protected ContentResolver mResolver;
protected Context mContext;
private static final int MESSAGE_CLEAR = 0;
private static final int MESSAGE_INIT_DISK_CACHE = 1;
private static final int MESSAGE_FLUSH = 2;
@@ -62,6 +64,7 @@ public abstract class Worker {
protected Worker(Context context) {
mResources = context.getResources();
mResolver = context.getContentResolver();
mContext = context;
// Negative means not initialized.
if (debuggable < 0) {
try {
@@ -102,7 +105,7 @@ public abstract class Worker {
return;
}
Bitmap value = null;
Object value = null;
String cacheUri = uri;
if (debuggable > 0) {
@@ -124,9 +127,14 @@ public abstract class Worker {
if (debuggable > 0) {
Log.v(TAG, "loadImage.addBitmapToCache: " + owner + ", src: " + cacheUri);
}
mCache.addBitmapToCache(cacheUri, value);
mCache.addBitmapToCache(cacheUri, (Bitmap) value);
}
}
/* Try loading as drawable */
if(value == null){
value = Utils.getDrawable(uri, mContext);
}
}
if (value != null) {
@@ -134,7 +142,12 @@ public abstract class Worker {
if (debuggable > 0) {
Log.v(TAG, "Set ImageBitmap on: " + owner + " to: " + uri);
}
owner.setBitmap(value);
if(value instanceof Drawable){
owner.setDrawable((Drawable) value);
}else {
owner.setBitmap((Bitmap) value);
}
if (listener != null) {
if (debuggable > 0) {
Log.v(TAG, "OnImageLoadedListener on: " + owner + " to: " + uri);
@@ -145,6 +158,8 @@ public abstract class Worker {
final BitmapWorkerTask task = new BitmapWorkerTask(uri, owner, decodeWidth, decodeHeight, keepAspectRatio, useCache, listener);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(mResources, mLoadingBitmap, task);
owner.setDrawable(asyncDrawable);
// NOTE: This uses a custom version of AsyncTask that has been pulled from the
@@ -277,7 +292,7 @@ public abstract class Worker {
/**
* The actual AsyncTask that will asynchronously process the image.
*/
private class BitmapWorkerTask extends AsyncTask<Void, Void, Bitmap> {
private class BitmapWorkerTask extends AsyncTask<Void, Void, Object> {
private int mDecodeWidth;
private int mDecodeHeight;
private boolean mKeepAspectRatio;
@@ -306,13 +321,13 @@ public abstract class Worker {
* Background processing.
*/
@Override
protected Bitmap doInBackground(Void... params) {
protected Object doInBackground(Void... params) {
if (debuggable > 0) {
Log.v(TAG, "doInBackground - starting work: " + imageViewReference.get() + ", on: " + mUri);
}
Bitmap bitmap = null;
Object bitmap = null;
// Wait here if work is paused and the task is not cancelled
synchronized (mPauseWorkLock) {
@@ -327,8 +342,7 @@ public abstract class Worker {
// another thread and the ImageView that was originally bound to this task is still
// bound back to this task and our "exit early" flag is not set, then call the main
// process method (as implemented by a subclass)
if (bitmap == null && !isCancelled() && getAttachedOwner() != null
&& !mExitTasksEarly) {
if (!isCancelled() && getAttachedOwner() != null && !mExitTasksEarly) {
bitmap = processBitmap(mUri, mDecodeWidth, mDecodeHeight, mKeepAspectRatio, mCacheImage);
}
@@ -341,10 +355,15 @@ public abstract class Worker {
if (debuggable > 0) {
Log.v(TAG, "addBitmapToCache: " + imageViewReference.get() + ", src: " + mCacheUri);
}
mCache.addBitmapToCache(mCacheUri, bitmap);
mCache.addBitmapToCache(mCacheUri, (Bitmap) bitmap);
}
}
/* Try loading as Drawable */
if (bitmap == null){
bitmap = Utils.getDrawable(mUri, mContext);
}
if (debuggable > 0) {
Log.v(TAG, "doInBackground - finished work");
}
@@ -356,7 +375,7 @@ public abstract class Worker {
* Once the image is processed, associates it to the imageView
*/
@Override
protected void onPostExecute(Bitmap value) {
protected void onPostExecute(Object value) {
boolean success = false;
// if cancel was called on this task or the "exit early" flag is set then we're done
if (isCancelled() || mExitTasksEarly) {
@@ -377,7 +396,11 @@ public abstract class Worker {
if (debuggable > 0) {
Log.v(TAG, "Set ImageDrawable on: " + owner + " to: " + mUri);
}
owner.setBitmap(value);
if(value instanceof Drawable){
owner.setDrawable((Drawable) value);
} else if(value instanceof Bitmap){
owner.setBitmap((Bitmap) value);
}
}
if (mOnImageLoadedListener != null) {
@@ -389,7 +412,7 @@ public abstract class Worker {
}
@Override
protected void onCancelled(Bitmap value) {
protected void onCancelled(Object value) {
super.onCancelled(value);
synchronized (mPauseWorkLock) {
mPauseWorkLock.notifyAll();