With the support library now fully supporting vector images, I\'m trying to switch to vector images as much as I can in my app. An issue I\'m running into is that it seems impos
I'd like to put a full solution that doesn't require the support library hidden class, written in Kotlin, based on what was suggested in one of the answers (here) :
DrawableWrapper.kt
open class DrawableWrapper(drawable: Drawable) : Drawable(), Drawable.Callback {
var wrappedDrawable: Drawable = drawable
set(drawable) {
field.callback = null
field = drawable
drawable.callback = this
}
override fun draw(canvas: Canvas) = wrappedDrawable.draw(canvas)
override fun onBoundsChange(bounds: Rect) {
wrappedDrawable.bounds = bounds
}
override fun setChangingConfigurations(configs: Int) {
wrappedDrawable.changingConfigurations = configs
}
override fun getChangingConfigurations() = wrappedDrawable.changingConfigurations
override fun setDither(dither: Boolean) = wrappedDrawable.setDither(dither)
override fun setFilterBitmap(filter: Boolean) {
wrappedDrawable.isFilterBitmap = filter
}
override fun setAlpha(alpha: Int) {
wrappedDrawable.alpha = alpha
}
override fun setColorFilter(cf: ColorFilter?) {
wrappedDrawable.colorFilter = cf
}
override fun isStateful() = wrappedDrawable.isStateful
override fun setState(stateSet: IntArray) = wrappedDrawable.setState(stateSet)
override fun getState() = wrappedDrawable.state
override fun jumpToCurrentState() = DrawableCompat.jumpToCurrentState(wrappedDrawable)
override fun getCurrent() = wrappedDrawable.current
override fun setVisible(visible: Boolean, restart: Boolean) = super.setVisible(visible, restart) || wrappedDrawable.setVisible(visible, restart)
override fun getOpacity() = wrappedDrawable.opacity
override fun getTransparentRegion() = wrappedDrawable.transparentRegion
override fun getIntrinsicWidth() = wrappedDrawable.intrinsicWidth
override fun getIntrinsicHeight() = wrappedDrawable.intrinsicHeight
override fun getMinimumWidth() = wrappedDrawable.minimumWidth
override fun getMinimumHeight() = wrappedDrawable.minimumHeight
override fun getPadding(padding: Rect) = wrappedDrawable.getPadding(padding)
override fun invalidateDrawable(who: Drawable) = invalidateSelf()
override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) = scheduleSelf(what, `when`)
override fun unscheduleDrawable(who: Drawable, what: Runnable) = unscheduleSelf(what)
override fun onLevelChange(level: Int) = wrappedDrawable.setLevel(level)
override fun setAutoMirrored(mirrored: Boolean) = DrawableCompat.setAutoMirrored(wrappedDrawable, mirrored)
override fun isAutoMirrored() = DrawableCompat.isAutoMirrored(wrappedDrawable)
override fun setTint(tint: Int) = DrawableCompat.setTint(wrappedDrawable, tint)
override fun setTintList(tint: ColorStateList?) = DrawableCompat.setTintList(wrappedDrawable, tint)
override fun setTintMode(tintMode: PorterDuff.Mode) = DrawableCompat.setTintMode(wrappedDrawable, tintMode)
override fun setHotspot(x: Float, y: Float) = DrawableCompat.setHotspot(wrappedDrawable, x, y)
override fun setHotspotBounds(left: Int, top: Int, right: Int, bottom: Int) = DrawableCompat.setHotspotBounds(wrappedDrawable, left, top, right, bottom)
}
TilingDrawable.kt
class TilingDrawable(drawable: Drawable) : DrawableWrapper(drawable) {
private var callbackEnabled = true
override fun draw(canvas: Canvas) {
callbackEnabled = false
val bounds = bounds
val width = wrappedDrawable.intrinsicWidth
val height = wrappedDrawable.intrinsicHeight
var x = bounds.left
while (x < bounds.right + width - 1) {
var y = bounds.top
while (y < bounds.bottom + height - 1) {
wrappedDrawable.setBounds(x, y, x + width, y + height)
wrappedDrawable.draw(canvas)
y += height
}
x += width
}
callbackEnabled = true
}
override fun onBoundsChange(bounds: Rect) {}
override fun invalidateDrawable(who: Drawable) {
if (callbackEnabled)
super.invalidateDrawable(who)
}
override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {
if (callbackEnabled)
super.scheduleDrawable(who, what, `when`)
}
override fun unscheduleDrawable(who: Drawable, what: Runnable) {
if (callbackEnabled)
super.unscheduleDrawable(who, what)
}
}
Sample usage:
yourView.background = TilingDrawable(AppCompatResources.getDrawable(this, R.drawable.your_drawable)!!)
I see 2 easy workarounds to this problem:
1. To create (repeat) pattern you want in SVG manipulative software like 'Inkscape' or 'CorelDraw'. And then use this 'created_manually_pattern_svg' in your 'ImageView' as
...
app:srcCompat="@drawable/created_manually_pattern_svg"
...
or even
...
android:background="@drawable/created_manually_pattern_svg"
...
in any ather 'view' (but i'm not sure if it works in all API levels)
2. Export your '.svg' file into '.png' and then use 'bitmap'.
This is the java version of Nick Butcher solution:
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public class TileDrawable extends Drawable {
private final Paint paint;
public TileDrawable(Drawable drawable, Shader.TileMode tileMode) {
paint = new Paint();
paint.setShader(new BitmapShader(getBitmap(drawable), tileMode, tileMode));
}
@Override
public void draw(@NonNull Canvas canvas) {
canvas.drawPaint(paint);
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
private Bitmap getBitmap(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, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
drawable.draw(canvas);
return bitmap;
}
}
You can use this drawable class in code with vector patterns:
view.setBackground(new TileDrawable(getContext().getDrawable(R.drawable.pattern), Shader.TileMode.REPEAT));
Thanks to @pskink I made a drawable that tiles another drawable: https://gist.github.com/9ffbdf01478e36194f8f
This has to be set in code, it can not be used from XML:
public class TilingDrawable extends android.support.v7.graphics.drawable.DrawableWrapper {
private boolean callbackEnabled = true;
public TilingDrawable(Drawable drawable) {
super(drawable);
}
@Override
public void draw(Canvas canvas) {
callbackEnabled = false;
Rect bounds = getBounds();
Drawable wrappedDrawable = getWrappedDrawable();
int width = wrappedDrawable.getIntrinsicWidth();
int height = wrappedDrawable.getIntrinsicHeight();
for (int x = bounds.left; x < bounds.right + width - 1; x+= width) {
for (int y = bounds.top; y < bounds.bottom + height - 1; y += height) {
wrappedDrawable.setBounds(x, y, x + width, y + height);
wrappedDrawable.draw(canvas);
}
}
callbackEnabled = true;
}
@Override
protected void onBoundsChange(Rect bounds) {
}
/**
* {@inheritDoc}
*/
public void invalidateDrawable(Drawable who) {
if (callbackEnabled) {
super.invalidateDrawable(who);
}
}
/**
* {@inheritDoc}
*/
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (callbackEnabled) {
super.scheduleDrawable(who, what, when);
}
}
/**
* {@inheritDoc}
*/
public void unscheduleDrawable(Drawable who, Runnable what) {
if (callbackEnabled) {
super.unscheduleDrawable(who, what);
}
}
}
Check Nick Butcher solution:
https://gist.github.com/nickbutcher/4179642450db266f0a33837f2622ace3
Add TileDrawable class to your project and then set tiled drawable to your image view:
// after view created
val d = ContextCompat.getDrawable(this, R.drawable.pattern)
imageView.setImageDrawable(TileDrawable(d, Shader.TileMode.REPEAT))