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

This commit is contained in:
Osei Fortune
2021-08-11 10:37:27 -07:00
committed by GitHub
parent 36900d7c05
commit 490f7dce80
23 changed files with 105 additions and 43 deletions

View File

@ -13,6 +13,7 @@
<Button text="css-playground" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo"/> <Button text="css-playground" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo"/>
<Button text="visibility-vs-hidden" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo"/> <Button text="visibility-vs-hidden" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo"/>
<Button text="image-async" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo"/> <Button text="image-async" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo"/>
<Button text="vector-image" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo"/>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</StackLayout> </StackLayout>

View File

@ -0,0 +1,5 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" class="page">
<StackLayout>
<Image src="res://outline_face_24"/>
</StackLayout>
</Page>

View File

@ -4,20 +4,6 @@
<dict> <dict>
<key>AvailableLibraries</key> <key>AvailableLibraries</key>
<array> <array>
<dict>
<key>DebugSymbolsPath</key>
<string>dSYMs</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>TNSWidgets.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
<dict> <dict>
<key>DebugSymbolsPath</key> <key>DebugSymbolsPath</key>
<string>dSYMs</string> <string>dSYMs</string>
@ -35,6 +21,20 @@
<key>SupportedPlatformVariant</key> <key>SupportedPlatformVariant</key>
<string>simulator</string> <string>simulator</string>
</dict> </dict>
<dict>
<key>DebugSymbolsPath</key>
<string>dSYMs</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>TNSWidgets.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array> </array>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>XFWK</string> <string>XFWK</string>

View File

@ -34,7 +34,7 @@
</data> </data>
<key>Info.plist</key> <key>Info.plist</key>
<data> <data>
Xj1cG5BPcNRo2kinT2TzpxSuwlc= WsLLFjQ3sruW6pvEfLRTXorurcg=
</data> </data>
<key>Modules/module.modulemap</key> <key>Modules/module.modulemap</key>
<data> <data>

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip

View File

@ -79,6 +79,7 @@ dependencies {
implementation 'androidx.fragment:fragment:' + androidxVersion implementation 'androidx.fragment:fragment:' + androidxVersion
implementation 'androidx.transition:transition:' + androidxVersion implementation 'androidx.transition:transition:' + androidxVersion
implementation "androidx.exifinterface:exifinterface:1.3.2" implementation "androidx.exifinterface:exifinterface:1.3.2"
implementation "androidx.appcompat:appcompat:1.1.0"
} else { } else {
println 'Using support library' println 'Using support library'
implementation 'com.android.support:support-v4:' + computeSupportVersion() implementation 'com.android.support:support-v4:' + computeSupportVersion()

View File

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

View File

@ -2,6 +2,7 @@ package org.nativescript.widgets;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Color; import android.graphics.Color;
@ -18,6 +19,7 @@ import android.util.Pair;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.exifinterface.media.ExifInterface; import androidx.exifinterface.media.ExifInterface;
import org.json.JSONException; import org.json.JSONException;
@ -31,7 +33,18 @@ import java.io.IOException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
public class Utils { 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) { public static void drawBoxShadow(View view, String value) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) { if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
return; return;

View File

@ -20,16 +20,15 @@ import android.annotation.TargetApi;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import androidx.exifinterface.media.ExifInterface; import android.graphics.Matrix;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import androidx.exifinterface.media.ExifInterface;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
@ -382,7 +381,6 @@ public class Fetcher extends Worker {
try { try {
final TypedValue value = new TypedValue(); final TypedValue value = new TypedValue();
is = res.openRawResource(resId, value); is = res.openRawResource(resId, value);
bitmap = BitmapFactory.decodeResourceStream(res, value, is, null, options); bitmap = BitmapFactory.decodeResourceStream(res, value, is, null, options);
} catch (Exception e) { } catch (Exception e) {
/* do nothing. /* do nothing.
@ -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); 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. * 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 reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height 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 * @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.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.Log; import android.util.Log;
import android.widget.ImageView;
import java.lang.ref.WeakReference; 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 * 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 * 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 boolean mPauseWork = false;
protected Resources mResources; protected Resources mResources;
protected ContentResolver mResolver; protected ContentResolver mResolver;
protected Context mContext;
private static final int MESSAGE_CLEAR = 0; private static final int MESSAGE_CLEAR = 0;
private static final int MESSAGE_INIT_DISK_CACHE = 1; private static final int MESSAGE_INIT_DISK_CACHE = 1;
private static final int MESSAGE_FLUSH = 2; private static final int MESSAGE_FLUSH = 2;
@ -62,6 +64,7 @@ public abstract class Worker {
protected Worker(Context context) { protected Worker(Context context) {
mResources = context.getResources(); mResources = context.getResources();
mResolver = context.getContentResolver(); mResolver = context.getContentResolver();
mContext = context;
// Negative means not initialized. // Negative means not initialized.
if (debuggable < 0) { if (debuggable < 0) {
try { try {
@ -102,7 +105,7 @@ public abstract class Worker {
return; return;
} }
Bitmap value = null; Object value = null;
String cacheUri = uri; String cacheUri = uri;
if (debuggable > 0) { if (debuggable > 0) {
@ -124,9 +127,14 @@ public abstract class Worker {
if (debuggable > 0) { if (debuggable > 0) {
Log.v(TAG, "loadImage.addBitmapToCache: " + owner + ", src: " + cacheUri); 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) { if (value != null) {
@ -134,7 +142,12 @@ public abstract class Worker {
if (debuggable > 0) { if (debuggable > 0) {
Log.v(TAG, "Set ImageBitmap on: " + owner + " to: " + uri); 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 (listener != null) {
if (debuggable > 0) { if (debuggable > 0) {
Log.v(TAG, "OnImageLoadedListener on: " + owner + " to: " + uri); 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 BitmapWorkerTask task = new BitmapWorkerTask(uri, owner, decodeWidth, decodeHeight, keepAspectRatio, useCache, listener);
final AsyncDrawable asyncDrawable = final AsyncDrawable asyncDrawable =
new AsyncDrawable(mResources, mLoadingBitmap, task); new AsyncDrawable(mResources, mLoadingBitmap, task);
owner.setDrawable(asyncDrawable); owner.setDrawable(asyncDrawable);
// NOTE: This uses a custom version of AsyncTask that has been pulled from the // 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. * 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 mDecodeWidth;
private int mDecodeHeight; private int mDecodeHeight;
private boolean mKeepAspectRatio; private boolean mKeepAspectRatio;
@ -306,13 +321,13 @@ public abstract class Worker {
* Background processing. * Background processing.
*/ */
@Override @Override
protected Bitmap doInBackground(Void... params) { protected Object doInBackground(Void... params) {
if (debuggable > 0) { if (debuggable > 0) {
Log.v(TAG, "doInBackground - starting work: " + imageViewReference.get() + ", on: " + mUri); 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 // Wait here if work is paused and the task is not cancelled
synchronized (mPauseWorkLock) { synchronized (mPauseWorkLock) {
@ -327,8 +342,7 @@ public abstract class Worker {
// another thread and the ImageView that was originally bound to this task is still // 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 // bound back to this task and our "exit early" flag is not set, then call the main
// process method (as implemented by a subclass) // process method (as implemented by a subclass)
if (bitmap == null && !isCancelled() && getAttachedOwner() != null if (!isCancelled() && getAttachedOwner() != null && !mExitTasksEarly) {
&& !mExitTasksEarly) {
bitmap = processBitmap(mUri, mDecodeWidth, mDecodeHeight, mKeepAspectRatio, mCacheImage); bitmap = processBitmap(mUri, mDecodeWidth, mDecodeHeight, mKeepAspectRatio, mCacheImage);
} }
@ -341,10 +355,15 @@ public abstract class Worker {
if (debuggable > 0) { if (debuggable > 0) {
Log.v(TAG, "addBitmapToCache: " + imageViewReference.get() + ", src: " + mCacheUri); 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) { if (debuggable > 0) {
Log.v(TAG, "doInBackground - finished work"); Log.v(TAG, "doInBackground - finished work");
} }
@ -356,7 +375,7 @@ public abstract class Worker {
* Once the image is processed, associates it to the imageView * Once the image is processed, associates it to the imageView
*/ */
@Override @Override
protected void onPostExecute(Bitmap value) { protected void onPostExecute(Object value) {
boolean success = false; boolean success = false;
// if cancel was called on this task or the "exit early" flag is set then we're done // if cancel was called on this task or the "exit early" flag is set then we're done
if (isCancelled() || mExitTasksEarly) { if (isCancelled() || mExitTasksEarly) {
@ -377,7 +396,11 @@ public abstract class Worker {
if (debuggable > 0) { if (debuggable > 0) {
Log.v(TAG, "Set ImageDrawable on: " + owner + " to: " + mUri); 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) { if (mOnImageLoadedListener != null) {
@ -389,7 +412,7 @@ public abstract class Worker {
} }
@Override @Override
protected void onCancelled(Bitmap value) { protected void onCancelled(Object value) {
super.onCancelled(value); super.onCancelled(value);
synchronized (mPauseWorkLock) { synchronized (mPauseWorkLock) {
mPauseWorkLock.notifyAll(); mPauseWorkLock.notifyAll();

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FFA500"
android:pathData="M10.25,13c0,0.69 -0.56,1.25 -1.25,1.25S7.75,13.69 7.75,13s0.56,-1.25 1.25,-1.25 1.25,0.56 1.25,1.25zM15,11.75c-0.69,0 -1.25,0.56 -1.25,1.25s0.56,1.25 1.25,1.25 1.25,-0.56 1.25,-1.25 -0.56,-1.25 -1.25,-1.25zM22,12c0,5.52 -4.48,10 -10,10S2,17.52 2,12 6.48,2 12,2s10,4.48 10,10zM10.66,4.12C12.06,6.44 14.6,8 17.5,8c0.46,0 0.91,-0.05 1.34,-0.12C17.44,5.56 14.9,4 12,4c-0.46,0 -0.91,0.05 -1.34,0.12zM4.42,9.47c1.71,-0.97 3.03,-2.55 3.66,-4.44C6.37,6 5.05,7.58 4.42,9.47zM20,12c0,-0.78 -0.12,-1.53 -0.33,-2.24 -0.7,0.15 -1.42,0.24 -2.17,0.24 -3.13,0 -5.92,-1.44 -7.76,-3.69C8.69,8.87 6.6,10.88 4,11.86c0.01,0.04 0,0.09 0,0.14 0,4.41 3.59,8 8,8s8,-3.59 8,-8z"/>
</vector>