【问题标题】:How can we tile a vector image?我们如何平铺矢量图像?
【发布时间】:2016-03-17 10:19:25
【问题描述】:

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

对于位图图像,可以使用以下 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

还有其他方法可以平铺/重复矢量图像吗?

【问题讨论】:

  • 不是直接的,但是你可以draw()你的VectorDrawable多次
  • 由于矢量图像是可缩放的,您必须在重复之前另外指定宽度/高度。当然也可以手动重复(只需显示几次)。

标签: android vector-graphics


【解决方案1】:

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

}

您可以在带有矢量模式的代码中使用这个可绘制类:

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

【讨论】:

    【解决方案2】:

    感谢@pskink,我制作了一个可以平铺另一个drawable的drawable: 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);
            }
        }
    }
    

    【讨论】:

    • 这段代码有很多警告,你不应该自己调用这些函数,因为你的代码不是支持库的一部分。有没有不使用隐藏 API 的替代方法来编写它?
    【解决方案3】:

    检查 Nick Butcher 解决方案:
    https://gist.github.com/nickbutcher/4179642450db266f0a33837f2622ace3
    将 TileDrawable 类添加到您的项目中,然后将 tiled drawable 设置为您的图像视图:

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

    【讨论】:

    • 答案不应该主要是一个链接 - 可以离线或更改。
    • 虽然 Nick Butcher 的解决方案确实有效,但它存在潜在的内存利用问题,具体取决于您使用 TileDrawable 的方式,因为它需要在每次创建新的 TileDrawable 时将子 Drawable 渲染到位图。
    【解决方案4】:

    我想根据其中一个答案 (here) 中的建议,提出一个不需要支持库隐藏类的完整解决方案,用 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 解决方案 (stackoverflow.com/a/50621285/3032) 有了一个非常大的改进——这个解决方案在某些用例中使用的内存显着减少,因为它不需要将子 Drawable 渲染到位图在绘制到屏幕之前。
    • 我修改了 TilingDrawable 以同时覆盖 getIntrinsicHeight()getIntrinsicWidth() 以返回 -1 这给了我在 ImageView 上设置它时希望得到的结果(例如,作为占位符)。这个drawable并没有真正的固有尺寸,因为它可以缩放到任何尺寸,而docs在这些情况下表示-1
    • 我不明白。在某些情况下,您的意思是此代码不起作用?你能分享一下吗?也许我应该改变答案?
    • 我很挣扎,因为除非 ImageView 的 scaleType 设置为 fitXY,否则在 ImageView 上设置 TilingDrawable(即使用 setImageDrawable())时,此答案中编写的代码未提供所需的结果。通过添加 -1 的内在宽度/高度,我得到了想要的结果。
    【解决方案5】:

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

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

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

    甚至

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

    在任何其他“视图”中(但我不确定它是否适用于所有 API 级别)

    2. 将“.svg”文件导出为“.png”,然后使用“位图”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-27
      • 2021-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-29
      相关资源
      最近更新 更多