我们如何平铺矢量图像?

How can we tile a vector image?

随着支持库现在完全支持矢量图像,我正尝试在我的应用程序中尽可能多地切换到矢量图像。 我 运行 遇到的一个问题是似乎不可能重复它们。

对于位图图像,可以使用以下 xml:

<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/repeat_me"
    android:tileMode="repeat"
    />

这不起作用,因为矢量图像不能用于位图中: https://code.google.com/p/android/issues/detail?id=187566

还有其他方法可以 tile/repeat 矢量图像吗?

感谢@pskink,我制作了一个平铺另一个可绘制对象的可绘制对象: https://gist.github.com/9ffbdf01478e36194f8f

这必须在代码中设置,不能从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);
        }
    }
}

我想提出一个不需要隐藏支持库 class 的完整解决方案,它是根据其中一个答案 () 中的建议用 Kotlin 编写的:

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)
    }
}

示例用法:

yourView.background = TilingDrawable(AppCompatResources.getDrawable(this, R.drawable.your_drawable)!!)

查看 Nick Butcher 解决方案:
https://gist.github.com/nickbutcher/4179642450db266f0a33837f2622ace3
将 TileDrawable class 添加到您的项目,然后将平铺可绘制对象设置为您的图像视图:

// after view created
val d = ContextCompat.getDrawable(this, R.drawable.pattern)
imageView.setImageDrawable(TileDrawable(d, Shader.TileMode.REPEAT))

这是 Nick Butcher 解决方案的 java 版本:

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;
    }

}

您可以在带有矢量图案的代码中使用此可绘制对象class:

view.setBackground(new TileDrawable(getContext().getDrawable(R.drawable.pattern), Shader.TileMode.REPEAT));

我看到了 2 个解决这个问题的简单方法:

1. 在 'Inkscape' 或 'CorelDraw' 等 SVG 操作软件中创建(重复)您想要的图案。然后在你的 'ImageView' 中使用这个 'created_manually_pattern_svg' as

... 
app:srcCompat="@drawable/created_manually_pattern_svg"
...

甚至

...
android:background="@drawable/created_manually_pattern_svg"
...

在任何其他 'view'(但我不确定它是否适用于所有 API 级别)

2. 将“.svg”文件导出为“.png”,然后使用 'bitmap'.