How can we tile a vector image?

后端 未结 5 718
眼角桃花
眼角桃花 2021-02-12 21:02

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

相关标签:
5条回答
  • 2021-02-12 21:15

    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)!!)
    
    0 讨论(0)
  • 2021-02-12 21:19

    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'.

    0 讨论(0)
  • 2021-02-12 21:20

    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));
    
    0 讨论(0)
  • 2021-02-12 21:22

    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);
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-12 21:36

    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))
    
    0 讨论(0)
提交回复
热议问题