I am using android Espresso. I would like to know how I can check if the drawable being used on a view is the same as what should be used as stated in the specs. I am trying to compare the ConstantStates of the drawable use on the view, and the one in the Resources, but I am not getting anywhere.
Is there a way to do this? Or is this check entirely not needed when it comes to automated testing?
For comparing two images I already used this code:
public class ImageComparator {
public static Matcher<View> withImageResource(final int imageResourceId) {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
description.appendText("with drawable from resource id: " + imageResourceId);
}
#Override
public boolean matchesSafely(View view) {
Drawable actualDrawable = ((ImageView) view).getDrawable();
final Drawable correctDrawable = view.getResources().getDrawable(imageResourceId);
if (actualDrawable == null) {
return correctDrawable == null;
}
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return areImagesTheSameAfterSdk21(actualDrawable, correctDrawable);
} else {
return areImagesTheSameBeforeSdk21(actualDrawable, correctDrawable);
}
}
};
}
protected static boolean areImagesTheSameBeforeSdk21(Drawable actualDrawable,
Drawable correctDrawable) {
Drawable.ConstantState actualDrawableConstantState = actualDrawable.getConstantState();
Drawable.ConstantState correctDrawableConstantState = correctDrawable.getConstantState();
return actualDrawableConstantState.equals(correctDrawableConstantState);
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
protected static boolean areImagesTheSameAfterSdk21(Drawable actualDrawable,
Drawable correctDrawable) {
Bitmap correctBitmap = drawableToBitmap(correctDrawable);
Bitmap actualBitmap = drawableToBitmap(actualDrawable);
return correctBitmap.sameAs(actualBitmap);
}
private static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
Bitmap bitmap = Bitmap
.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
Hope it will help
Related
According to this question I used a custom ImageGatter class to display images that I'm getting from a server in TextView using Picasso
public class PicassoImageGetter implements Html.ImageGetter {
private TextView textView = null;
Context mContext;
public PicassoImageGetter() {
}
public PicassoImageGetter(TextView target, Context context) {
textView = target;
mContext = context;
}
#Override
public Drawable getDrawable(String source) {
BitmapDrawablePlaceHolder drawable = new BitmapDrawablePlaceHolder();
Picasso.get().load(source).into(drawable);
return drawable;
}
private class BitmapDrawablePlaceHolder extends BitmapDrawable implements com.squareup.picasso.Target {
protected Drawable drawable;
#Override
public void draw(final Canvas canvas) {
if (drawable != null) {
drawable.draw(canvas);
}
}
public void setDrawable(Drawable drawable) {
this.drawable = drawable;
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
drawable.setBounds(0, 0, width, height);
setBounds(0, 0, width, height);
if (textView != null) {
textView.setText(textView.getText());
}
}
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
setDrawable(new BitmapDrawable(mContext.getResources(), bitmap));
}
#Override
public void onBitmapFailed(Exception e, Drawable errorDrawable) {
setDrawable(errorDrawable);
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
}
}
and used like this
imageGetter = new PicassoImageGetter(contentTextView, this);
Spannable html;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
html = (Spannable) Html.fromHtml(content, Html.FROM_HTML_MODE_LEGACY, imageGetter, null);
} else {
html = (Spannable) Html.fromHtml(content, imageGetter, null);
}
contentTextView.setText(html);
I need to catch this images into internal or external storage to display it if there's no connection, but I don't know the path or file name.
You need to implement picasso cache by your own. This should be like this:
public class PicassoCache {
private static Picasso picassoInstance = null;
private PicassoCache(Context context) {
Downloader downloader = new OkHttp3Downloader(context, Integer.MAX_VALUE);
Picasso.Builder builder = new Picasso.Builder(context);
builder.downloader(downloader);
picassoInstance = builder.build();
}
public static Picasso getPicassoInstance(Context context) {
if (picassoInstance == null) {
new PicassoCache(context);
return picassoInstance;
}
return picassoInstance;
}
}
Then in your code:
#Override
public Drawable getDrawable(String source) {
BitmapDrawablePlaceHolder drawable = new BitmapDrawablePlaceHolder();
PicassoCache.getPicassoInstance(getContext()).load(source).into(drawable);
return drawable;
}
OkHttp3Downloader will install an image cache into your application cache directory. You can also use another constructor with your own provided directory public OkHttp3Downloader(final File cacheDir, final long maxSize)
As an alternative, you can use Glide. This is like picasso but also allows you to show gifs with animation and cache strategy IMHO is a bit easier.
Glide.with(context)
.load(source)
.apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL))
.into(drawable)
I have TransitionDrawable defined in xml like this:
transition.xml
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/ic_disabled" />
<item android:drawable="#drawable/ic_enabled" />
</transition>
I use it to animate state changes of checkbox:
val stateDrawable = ContextCompat.getDrawable(this, R.drawable.transition) as TransitionDrawable
checkbox.buttonDrawable = stateDrawable
checkbox.setOnCheckedChangeListener { icon, checked ->
if (checked) {
stateDrawable.startTransition(300)
} else {
stateDrawable.reverseTransition(300)
}
}
If #drawable/ic_disabled and #drawable/ic_enabled are png files, everything works fine. But if they are vector drawables, transition doesn't work. What am I missing? Does TransitionDrawable not support vector drawables?
I know this is old, but I had the same issue... You gotta convert the Vector to a BitmapDrawable before adding to the TransitionDrawable. Here's an example
TransitionDrawable td = new TransitionDrawable(new Drawable[]{
getBitmapDrawableFromVectorDrawable(this, R.drawable.vector1),
getBitmapDrawableFromVectorDrawable(this, R.drawable.vector2)
});
td.setCrossFadeEnabled(true); // probably want this
// set as checkbox button drawable...
Utility Methods // see https://stackoverflow.com/a/38244327/114549
public static BitmapDrawable getBitmapDrawableFromVectorDrawable(Context context, int drawableId) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return (BitmapDrawable) ContextCompat.getDrawable(context, drawableId);
}
return new BitmapDrawable(context.getResources(), getBitmapFromVectorDrawable(context, drawableId));
}
public static Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) {
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
drawable = (DrawableCompat.wrap(drawable)).mutate();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
Since all I wanted was to change the image with fade, I implemented a simple version of TransitionDrawable that seem to work for fixed size vector drawables.
public class SimpleTransitionDrawable extends Drawable {
private static final int TRANSITION_STARTING = 0;
private static final int TRANSITION_RUNNING = 1;
private static final int TRANSITION_NONE = 2;
private int state = TRANSITION_NONE;
private long startTimeMillis;
private int duration;
private #Nullable Drawable source;
private Drawable target;
public void setTarget(Drawable target) {
this.source = this.target;
this.target = target;
target.setBounds(0, 0, target.getIntrinsicWidth(), target.getIntrinsicHeight());
}
public void startTransition(int durationMillis) {
duration = durationMillis;
state = TRANSITION_STARTING;
invalidateSelf();
}
#Override
public void draw(Canvas canvas) {
int alpha;
switch (state) {
case TRANSITION_STARTING:
startTimeMillis = SystemClock.uptimeMillis();
alpha = 0;
state = TRANSITION_RUNNING;
break;
case TRANSITION_RUNNING:
float normalized = (float) (SystemClock.uptimeMillis() - startTimeMillis) / duration;
alpha = (int) (0xff * Math.min(normalized, 1.0f));
break;
default:
if (target == null) return;
alpha = 0xff;
break;
}
if (source != null && alpha < 0xff) {
source.setAlpha(0xff - alpha);
source.draw(canvas);
}
if (alpha > 0) {
target.setAlpha(alpha);
target.draw(canvas);
}
if (alpha == 0xff) {
state = TRANSITION_NONE;
return;
}
invalidateSelf();
}
#Override public void setAlpha(int alpha) {}
#Override public void setColorFilter(ColorFilter colorFilter) {}
#Override public int getOpacity() {return PixelFormat.TRANSLUCENT;}
}
Initialize it like this:
ImageView view = findViewById(R.id.image_view);
SimpleTransitionDrawable drawable = new SimpleTransitionDrawable();
view.setImageDrawable(drawable);
Then change the drawable with fade:
drawable.setTarget(AppCompatResources.getDrawable(this, R.drawable.my_vector));
drawable.startTransition(350);
Following the same approach made by #aaronvargas https://stackoverflow.com/a/54583929/1508956
I've created some extensions function which encapsulate the implementation and also makes easier the usability
fun Drawable.getBitmapDrawableFromVectorDrawable(context: Context): BitmapDrawable {
return BitmapDrawable(context.resources, getBitmapFromVectorDrawable())
}
fun Drawable.getBitmapFromVectorDrawable(): Bitmap {
val bitmap = Bitmap.createBitmap(
intrinsicWidth,
intrinsicHeight, Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
setBounds(0, 0, canvas.width, canvas.height)
draw(canvas)
return bitmap
}
fun ImageView.setTransitionDrawable(vectorID: Int, firstColor: Int, secondColor: Int): TransitionDrawable? {
val drawables = arrayOfNulls<Drawable>(2)
val firstDrawable = ContextCompat.getDrawable(context, vectorID) ?: return null
DrawableCompat.setTint(firstDrawable, ContextCompat.getColor(context, firstColor))
drawables[0] = firstDrawable.getBitmapDrawableFromVectorDrawable(context)
val secondDrawable = ContextCompat.getDrawable(context, vectorID) ?: return null
DrawableCompat.setTint(secondDrawable, ContextCompat.getColor(context, secondColor))
drawables[1] = secondDrawable.getBitmapDrawableFromVectorDrawable(context)
val transition = TransitionDrawable(drawables)
transition.isCrossFadeEnabled = true
setImageDrawable(transition)
return transition
}
Initialization:
val transition = imageView.setTransitionDrawable(resourceID, R.color.firstColor, R.color.secondColor)
transition?.startTransition(3000)
I would like to show the custom marker to google map and cluster them. The marker contains a ImageView that will shows the avatar that is downloaded from network. Here is my target:
Everythings are OK, however, when I implemented the Google Maps Android Marker Clustering Utility, the ImageView shows the same avatar (sometime two wrong avatars).
Here is my custom MarkerRender:
public class MarkerRender extends DefaultClusterRenderer<Image> {
private static final String TAG = MarkerRender.class.getSimpleName();
private IconGenerator clusterGenerator;
private IconGenerator markerGenerator;
private ImageView mImgMarkerThumbnail;
private ImageView mImgMarkerClusterThumbnail;
private TextView txtSizeCluster;
private Activity activity;
private Bitmap mask, background;
private AtomicInteger imageDownloadCounter;
private int totalItem;
private ImageSize imageSize;
public MarkerRender(FragmentActivity activity, GoogleMap mMap, ClusterManager<Image> mClusterManager) {
super(activity, mMap, mClusterManager);
this.activity = activity;
imageDownloadCounter = new AtomicInteger(0);
mask = BitmapFactory.decodeResource(activity.getResources(),
R.drawable.annotation_behind);
background = BitmapFactory.decodeResource(activity.getResources(),
R.drawable.annotation_behind);
setUpClusterIcon();
setUpMarker();
}
private void setUpClusterIcon() {
clusterGenerator = new IconGenerator(activity);
View clusterView = activity.getLayoutInflater().inflate(R.layout.custom_marker_cluster, null);
txtSizeCluster = (TextView) clusterView.findViewById(R.id.tv_number_marker);
mImgMarkerClusterThumbnail = (ImageView) clusterView.findViewById(R.id.img_load);
clusterGenerator.setContentView(clusterView);
clusterGenerator.setBackground(null);
}
private void setUpMarker() {
markerGenerator = new IconGenerator(activity);
View markerView = activity.getLayoutInflater().inflate(R.layout.custom_marker, null);
mImgMarkerThumbnail = (ImageView) markerView.findViewById(R.id.img_load);
markerGenerator.setContentView(markerView);
markerGenerator.setBackground(null);
}
#Override
protected void onBeforeClusterItemRendered(final Image image, final MarkerOptions markerOptions) {
initImageSizeIfNeed();
Bitmap icon = markerGenerator.makeIcon();
PFLogManager.INSTANCE.logE(TAG, "maken icon: " + icon.hashCode());
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
icon.recycle();
}
#Override
protected void onClusterItemRendered(final Image image, Marker marker) {
super.onClusterItemRendered(image, marker);
ImageLoader.getInstance().loadImage(image.getMapImageLink(), imageSize,
new SimpleImageLoadingListener() {
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
Bitmap croppedBitmap = Helpers.makeCroppedBitmap(loadedImage, background, mask);
mImgMarkerThumbnail.setImageBitmap(croppedBitmap);
}
});
}
#Override
protected void onBeforeClusterRendered(Cluster<Image> cluster, MarkerOptions markerOptions) {
initImageSizeIfNeed();
Bitmap icon = clusterGenerator.makeIcon();
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
icon.recycle();
}
#Override
protected void onClusterRendered(Cluster<Image> cluster, Marker marker) {
super.onClusterRendered(cluster, marker);
ArrayList<Image> list = new ArrayList<>(cluster.getItems());
setTextNumberMarker(cluster);
String urlFirstImage = list.get(0).getMapImageLink();
ImageLoader.getInstance().loadImage(urlFirstImage, imageSize,
new SimpleImageLoadingListener() {
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
final Bitmap croppedBitmap = Helpers.makeCroppedBitmap(loadedImage, background, mask);
mImgMarkerClusterThumbnail.setImageBitmap(croppedBitmap);
}
});
}
private void loadClusterThumbnail(String url) {
}
private void setTextNumberMarker(Cluster<Image> cluster) {
int size = cluster.getSize();
if (size > 99) {
txtSizeCluster.setText("99+");
} else {
txtSizeCluster.setText(String.valueOf(cluster.getSize()));
}
}
#Override
protected boolean shouldRenderAsCluster(Cluster cluster) {
return cluster.getSize() > 1;
}
}
I've guess that the issue is I use only one ImageView to show those avatar, so I try to use unique ImageView for each marker (by inflat new one from xml every time needed), but the result is they are all show the blank marker (just the background and there is no avatar).
I've resolved it myself. My solution is use the Marker.setIcon() method after the image is downloaded from netword or got from cache. I dont use the ImageView anymore.
So, i modified the above MarkerRender class:
The setUpClusterIcon() method:
private void setUpClusterIcon() {
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(markerWidth, markerHeight);
ImageView marker = new ImageView(activity);
marker.setLayoutParams(params);
clusterGenerator = new IconGenerator(activity);
clusterGenerator.setContentView(marker);
clusterGenerator.setBackground(null);
}
And the onClusterItemRendered() method:
protected void onClusterItemRendered(final Image image, final Marker marker) {
super.onClusterItemRendered(image, marker);
ImageLoader.getInstance().loadImage(image.getMapImageLink(), imageSize,
new SimpleImageLoadingListener() {
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
Bitmap croppedBitmap = Helpers.makeClusterItemBitmap(background, loadedImage, mask);
try {
marker.setIcon(BitmapDescriptorFactory.fromBitmap(croppedBitmap));
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
Also the makeClusterItemBitmap helper method:
public static Bitmap makeClusterItemBitmap(Bitmap background, Bitmap original, Bitmap mask) {
Bitmap croppedOriginal = makeCroppedBitmap(original, mask);
Bitmap result = Bitmap.createBitmap(background.getWidth(), background.getHeight(),
Bitmap.Config.ARGB_8888);
Canvas mCanvas = new Canvas(result);
croppedOriginal = Bitmap.createScaledBitmap(croppedOriginal, croppedOriginal.getWidth() - 20, croppedOriginal.getHeight() - 20, true);
mCanvas.drawBitmap(background, 0, 0, null);
mCanvas.drawBitmap(croppedOriginal, 10, 10, null);
return result;
}
public static Bitmap makeCroppedBitmap(Bitmap original, Bitmap mask) {
original = Bitmap.createScaledBitmap(original, mask.getWidth(),
mask.getHeight(), true);
Bitmap result = Bitmap.createBitmap(original.getWidth(), original.getHeight(),
Bitmap.Config.ARGB_8888);
Canvas mCanvas = new Canvas(result);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mCanvas.drawBitmap(original, 0, 0, null);
mCanvas.drawBitmap(mask, 0, 0, paint);
paint.setXfermode(null);
return result;
}
Done, finish three nightmare researchingdays days :P
However, this approach leads new issue: the performance. Cause by drawing new bitmap with many layers, the map is laggy a bit. I'm thinking in improving this :)
Any sugguestion are appriciate :D
first,sorry for my English.
I'm new in Android,I have a activity to show news,the content is from web server and the content contains images.I used a TextView to display it and I want to the images fit the sreen,so I wrote these codes.
public class CustomImageGetter implements Html.ImageGetter {
private Activity context;
private Picasso picasso;
private TextView textView;
public CustomImageGetter(Activity context, Picasso picasso, TextView textView) {
this.context = context;
this.picasso = picasso;
this.textView = textView;
}
#Override
public Drawable getDrawable(final String source) {
final PlaceHolderBitmapDrawable result = new PlaceHolderBitmapDrawable();
new AsyncTask<Void, Void, Bitmap>() {
#Override
protected Bitmap doInBackground(Void... params) {
try {
return picasso
.load(Config.WEB_SERVER + source.substring(1))
.placeholder(R.drawable.placeholder)
.error(R.drawable.placeholder)
.get();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
try {
BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);
DisplayMetrics metrics = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(metrics);
int width = metrics.widthPixels;
int height = width * bitmap.getHeight() / bitmap.getWidth();
drawable.setBounds(0, 0, width, height);
result.setDrawable(drawable);
result.setBounds(0, 0, width, height);
textView.setText(textView.getText());
} catch (Exception e) {
e.printStackTrace();
}
}
}.execute((Void) null);
return result;
}
private static class PlaceHolderBitmapDrawable extends BitmapDrawable {
protected Drawable drawable;
#Override
public void draw(Canvas canvas) {
if (drawable != null)
drawable.draw(canvas);
}
public void setDrawable(Drawable drawable) {
this.drawable = drawable;
}
}
}
The image's width is correct but the height is too short in android 4.4 but in android L is normal.
The left picture runs in android 4.4 and the right runs in android L.
How can I fix the problem?
I ma building a chat application and I am attempting to append an image to an EditText, through use of Picasso to get the image from a URL and the append and ImageGetter to attach the image to the EditText. However, what I have implemented below does not work, as appending messages when using the app displays nothing (but the message does show up in the database).
I have tested without using Picasso, as simply just using the ImageGetter with an image resource within the app works just fine, only it's not from a URL as is required.
What is the proper way to configure the ImageGetter and/or the append method so that this functionality will work with Picasso? Or is there a simpler way?
Append method:
public void appendToMessageHistory(final String username,
final String message) {
if (username != null && message != null) {
Picasso.with(getBaseContext())
.load("http://localhost:3000/uploads/campaign/image/2/2.jpg")
.into(new Target() {
#Override
public void onPrepareLoad(Drawable arg0) {
}
#Override
public void onBitmapLoaded(Bitmap bitmap,
LoadedFrom arg1) {
Drawable drawImage = new BitmapDrawable(
getBaseContext().getResources(), bitmap);
drawImage.setBounds(0, 0,
drawImage.getIntrinsicHeight(),
drawImage.getIntrinsicWidth());
messageHistoryText.append(Html.fromHtml("<b>"
+ username + ":" + "</b>" + "<br>"));
messageHistoryText.append(Html.fromHtml(message
+ "<hr>" + "<br>")
+ System.getProperty("line.separator") + "");
messageHistoryText.append(Html
.fromHtml("<img src = '" + drawImage
+ "'/>",
imageGetter,
null));
}
#Override
public void onBitmapFailed(Drawable arg0) {
}
});
}
}
ImageGetter:
ImageGetter imageGetter = new ImageGetter() {
Drawable imageUsed=null;
#Override
public Drawable getDrawable(String source) {
Picasso.with(getBaseContext())
.load("http://localhost:3000/uploads/campaign/image/2/2.jpg")
.into(new Target() {
#Override
public void onPrepareLoad(Drawable arg0) {
}
#Override
public void onBitmapLoaded(Bitmap bitmap,
LoadedFrom arg1) {
Drawable drawImage = new BitmapDrawable(
getBaseContext().getResources(), bitmap);
drawImage.setBounds(0, 0,
drawImage.getIntrinsicHeight(),
drawImage.getIntrinsicWidth());
imageUsed=drawImage;
}
#Override
public void onBitmapFailed(Drawable arg0) {
}
});
return imageUsed;
}
};
I couldn't get it to work with using Picasso's Target...
My workaround is:
use an AsyncTask for concurrency
use Picasso's get() method to load the image synchronously in the AsyncTask's background method
Like this:
public class PicassoImageGetter implements Html.ImageGetter {
final Resources resources;
final Picasso pablo;
final TextView textView;
public PicassoImageGetter(final TextView textView, final Resources resources, final Picasso pablo) {
this.textView = textView;
this.resources = resources;
this.pablo = pablo;
}
#Override public Drawable getDrawable(final String source) {
final BitmapDrawablePlaceHolder result = new BitmapDrawablePlaceHolder();
new AsyncTask<Void, Void, Bitmap>() {
#Override
protected Bitmap doInBackground(final Void... meh) {
try {
return pablo.load(source).get();
} catch (Exception e) {
return null;
}
}
#Override
protected void onPostExecute(final Bitmap bitmap) {
try {
final BitmapDrawable drawable = new BitmapDrawable(resources, bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
result.setDrawable(drawable);
result.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
textView.setText(textView.getText()); // invalidate() doesn't work correctly...
} catch (Exception e) {
/* nom nom nom*/
}
}
}.execute((Void) null);
return result;
}
static class BitmapDrawablePlaceHolder extends BitmapDrawable {
protected Drawable drawable;
#Override
public void draw(final Canvas canvas) {
if (drawable != null) {
drawable.draw(canvas);
}
}
public void setDrawable(Drawable drawable) {
this.drawable = drawable;
}
}
Hope this is useful.
I built upon Thomas' answer. I used the existing Picasso methods to download any images on a different thread and accounted for the placeholder Drawables as well.
public class PicassoImageGetter implements Html.ImageGetter {
private TextView textView = null;
public PicassoImageGetter() {}
public PicassoImageGetter(TextView target) {
textView = target;
}
#Override
public Drawable getDrawable(String source) {
BitmapDrawablePlaceHolder drawable = new BitmapDrawablePlaceHolder();
Context context = FeedSurferApp.getContext();
FeedSurferApp
.getPicasso()
.load(source)
.error(ResourcesCompat.getDrawable(context.getResources(), R.drawable.connection_error, context.getTheme()))
.into(drawable);
return drawable;
}
private class BitmapDrawablePlaceHolder extends BitmapDrawable implements Target {
protected Drawable drawable;
#Override
public void draw(final Canvas canvas) {
if (drawable != null) {
drawable.draw(canvas);
}
}
public void setDrawable(Drawable drawable) {
this.drawable = drawable;
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
if (textView != null) {
textView.setText(textView.getText());
}
}
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
setDrawable(new BitmapDrawable(FeedSurferApp.getContext().getResources(), bitmap));
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
setDrawable(errorDrawable);
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {}
}
}
I found a way to use ImageGetter with Picasso Target:
public Drawable getDrawable(String source) {
final BitmapDrawablePlaceHolder result = new BitmapDrawablePlaceHolder();
Picasso.with(getContext()).load(source).into(new Target() {
#Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
final BitmapDrawable drawable = new BitmapDrawable(mContext.getResources(), bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
result.setDrawable(drawable);
result.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
content.setText(content.getText());
// cache is now warmed up
}
#Override public void onBitmapFailed(Drawable errorDrawable) { }
#Override public void onPrepareLoad(Drawable placeHolderDrawable) { }
});
return result;
}
This uses the cache and is no longer a synchronous call requiring the AsyncTask.
Credit to: Fetch images with Callback in Picasso?
Another simple way to use Picasso is to create a custom TextView and make it implement Target interface of Picasso.
Then you can do something as simple as this.
Picasso.get().load("Some_img_url").placeholder(R.drawable.ic_launcher_foreground).into(tv)
Below is the class
class TextViewWithImageLoader #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr), Target {
enum class Direction { START, END, TOP, BOTTOM }
private var mWidthInPixels: Int = 0
private var mHeightInPixels: Int = 0
private var mDirection: Direction = Direction.START //default value
// This method initialize the required parameters for the TextView to load the image
fun setupImageLoader(widthInPixels: Int, heightInPixels: Int, direction: Direction) {
mWidthInPixels = widthInPixels
mHeightInPixels = heightInPixels
mDirection = direction
}
// sets the size of the drawable
private fun setDrawableBounds(drawable: Drawable) {
drawable.setBounds(0, 0, mWidthInPixels, mHeightInPixels)
}
// Sets the initial placeholder drawable
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
//checking if height and width are valid
if (placeHolderDrawable != null && mWidthInPixels > 0 && mHeightInPixels > 0) {
setDrawableBounds(placeHolderDrawable)
setDrawable(placeHolderDrawable)
}
}
// set the drawable based on the Direction enum
private fun setDrawable(placeHolderDrawable: Drawable?) {
when (mDirection) {
Direction.START -> setCompoundDrawables(placeHolderDrawable, null, null, null);
Direction.END -> setCompoundDrawables(null, null, placeHolderDrawable, null);
Direction.TOP -> setCompoundDrawables(null, placeHolderDrawable, null, null);
Direction.BOTTOM -> setCompoundDrawables(null, null, null, placeHolderDrawable);
}
}
//In this method we receive the image from the url
override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
//checking if height and width are valid
if (mWidthInPixels > 0 && mHeightInPixels > 0) {
val drawable = BitmapDrawable(resources, bitmap)
setDrawableBounds(drawable)
setDrawable(drawable)
}
}
override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
//Do nothing as we are already setting a default value in onPrepareLoad method
// you can add your logic here if required
}
}
You can use this class directly or if you currently have your own custom view then just extend this class.
You can either go through this medium article for more details
OR
You can see the sample project on github