65.9K
CodeProject 正在变化。 阅读更多。
Home

Android. ImageView 支持 SVG

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (19投票s)

2010年11月25日

CPOL

7分钟阅读

viewsIcon

176590

downloadIcon

4778

在 ImageView 控件中使用 SVG 的技术。

引言

如您所知,Android 不支持 SVG 格式。但 SVG 的好处是显而易见的。首先,它是可伸缩的。您无需拥有不同分辨率的图片,也无需对位图图像进行拉伸(例如,会损失质量)。SVG 图像可以缩放到任何分辨率,并且质量保持不变。其次,SVG 是一个普通的 XML 文件,因此其大小可能比同等图片的栅格格式文件要小得多。此外,由于这个特性,您还可以动态更改图片。您可以在普通的文本编辑器中打开 SVG 文件,查看其构成。等等……但由于 Android 不处理 SVG,所以需要一些原生编码。好消息是,原生编码的工作量不会很大。有一些开源库可以用于解析和栅格化 SVG 格式。

必备组件

有很多关于如何在 Android 平台进行原生开发的教程,所以这里我就不再赘述了。我只会提供一些有用的技巧。

  • 首先,您需要 Eclipse IDE。您可以在 [ 1 ] 下载。
  • 或者,您也可以选择使用 Motodev Studio [ 2 ]。我更喜欢后者,因为它有一些非常棒的功能。顺便说一句,您可以将 Motodev Studio 作为插件安装到 Eclipse IDE 中。我没能在 OpenSUSE 上成功配置它,但作为插件,它运行得很好。
  • 安装好 Eclipse 后,添加 CDT 插件。
  • 添加 Android 插件 [ 3 ]。
  • 之后,添加 Eclipse Sequoyah [ 4 ] 插件,这是原生调试所必需的。但请确保您在安装 Sequoyah 之前安装了 CDT。Sequoyah 项目声称会安装所有依赖项,但实际上并没有安装 CDT。在安装 Sequoyah 时,请确保取消勾选“按类别分组项”并勾选“Sequoyah Android 原生支持”。
  • Windows 用户还需要 cygwin [ 5 ](将 *Cygwin/bin* 路径添加到您的系统中)。安装时,请设置开发工具。
  • 下载 Android SDK [ 6 ]。
  • 最后,您还需要 Android NDK。下载 CrystaX NDK [ 7 ]。它支持 C++ 异常、RTTI 和标准 C++ 库。
  • 在 Eclipse 偏好设置中,设置 Android SDK 和 NDK 的位置。现在就这些了。

第一种方法

对于第一个方法,我将使用 android-libsvg [ 8 ] 库。它实际上依赖于 libsvg、libpng、libjpeg、libexpat 和 zlib。但目前,它支持 SVG 格式的几乎所有功能。要获取其源代码,请在文件系统中创建一个名为 *android-libsvg* 的文件夹,然后在控制台(Windows 用户请在 cygwin 中)进入该文件夹,并运行“bzr branch lp:libsvg-android”命令。Bazaar 会将源代码下载到该文件夹。

开始

好的。创建一个新的 Android 项目“ImageViewSvg”。现在右键单击项目,然后选择 AndroidTools/Add Native support。它会在项目中创建一个“jni”文件夹。删除其中的所有内容,并将 *libsvg-android* 项目的“jni”文件夹中的内容复制过来。刷新项目中的 *jni* 文件夹。让我们来看看“jni”文件夹中的 *Android.mk* 文件。我将解释一些变量:

  • LOCAL_PATH := $(call my-dir) – my-dir 宏设置 LOCAL_PATH 变量,用于将源文件定位到当前目录
  • include $(CLEAR_VARS) – 清除所有局部变量
  • LOCAL_MODULE – 库的名称
  • LOCAL_CFLAGS – 设置编译器标志和一些包含目录
  • LIBJPEG_SOURCES, … – 将要使用的每个库的源文件列表
  • LOCAL_LDLIBS – 链接到附加库
  • LOCAL_SRC_FILES – 要编译的所有源文件列表,这里包含所有库的所有源文件
  • BUILD_SHARED_LIBRARY – 链接到 mk 文件以构建共享库

更多信息,请参阅 NDK 中的 *ANDROID-MK.TXT*。

在 Windows 上,有时在重启 IDE 后,它可能无法运行 ndk-build。然后右键单击项目,选择“Build Path/Configure Build Path”,并将“Build command”更改为类似“bash /cygdrive/c/ndk/ndk-build”的内容。

接下来,创建 *com.toolkits.libsvgandroid* 包,并将 *libsvg-android* 项目中的 *SvgRaster.java* 复制到其中。准备工作就绪。

ImageViewSvg 类

要使 `ImageView` 类支持 SVG 格式,只需继承它并重写一些方法即可。但我想让它能够将标准的 `android:src` 属性设置为 SVG 文件,并能够从标准的“drawable”文件夹或“raw”文件夹中选择文件。起初,我们将所有逻辑都放在构造函数中。要访问 `android:src` 属性,请将 *attrs.xml* 文件添加到 *res/values* 文件夹中。

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<declare-styleable name="ImageViewSvg">
		<attr name="android:src"/>
	</declare-styleable>
</resources>

让我们看看 `ImageView` 类的构造函数源代码。它包含以下代码:

Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
if (d != null) {
    setImageDrawable(d);
}

再看看 `setImageBitmap` 方法。它只是调用 `setImageDrawable`。因此,如果我们有相应的位图,就可以在构造函数中使用它。但是,如何从“drawable”文件夹获取文件呢?没什么特别的——从 `android:src` 属性获取资源 ID,然后将原始文件读入输入流。接下来,*libandroidsvg* 为我们提供了解析 SVG 文件的方法:

因此,构造函数将如下所示:

public ImageViewSvg(Context context, AttributeSet attrs, int defStyle) {

		// Let's try load supported by ImageView formats
		super(context, attrs, defStyle);
        
        if(this.getDrawable() == null)
        {
        	//  Get defined attributes
            TypedArray a = context.obtainStyledAttributes(attrs,
                    R.styleable.ImageViewSvg, defStyle, 0);
                        
            // Getting a file name
            CharSequence cs = a.getText(R.styleable.ImageViewSvg_android_src);
            String file = cs.toString();
            
            // Is it SVG file?
            if (file.endsWith(".svg")) {
            	
            	// Retrieve ID of the resource
                int id = a.getResourceId(R.styleable.ImageViewSvg_android_src, -1);
                if(id != -1){
               	try {
                		// Get the input stream for the raw resource
                		InputStream inStream = getResources().openRawResource(id);
                		int size = inStream.available();
                		
                		// Read into the buffer
                		byte[] buffer = new byte[size];
                		inStream.read(buffer);
						inStream.close();
						
						// And make a string
						mSvgContent = 
						EncodingUtils.getString
							(buffer, "UTF-8");
						
						// Parse it
            			mSvgId = SvgRaster.svgAndroidCreate();
            			SvgRaster.svgAndroidParseBuffer
					(mSvgId, mSvgContent.toString());
            			SvgRaster.svgAndroidSetAntialiasing(mSvgId, true);
                    	            			           	
            			mIsSvg = true;	

						
					} catch (IOException e) {
						mIsSvg = false;
						e.printStackTrace();
					}                	
                }
            }
        }
}

另一个问题是 SVG 没有大小。它是可伸缩的,仅此而已。此外,`ImageView` 的布局参数可以设置为 `wrap_content`、`fill_parent`,或者我们可以设置图像的预定义大小。但是,当需要布局时,Android 会设置大小,我们可以重写 `onSizeChanged` 方法。唯一的问题是 `wrap_content` 属性。在这种情况下,大小将是 `0`。想法是在运行时将 `wrap_content` 替换为 `fill_parent`。但在构造函数中这样做不会有任何效果。如果您通过源代码进行调试,就会发现父布局直接从属性中获取布局参数并调用 `setLayoutParams` 方法。让我们重写它:

@Override 
public void setLayoutParams(ViewGroup.LayoutParams params){
	if(mIsSvg)
	{
		// replace WRAP_CONTENT if needed
		if(params.width == ViewGroup.LayoutParams.WRAP_CONTENT
				&& getSuggestedMinimumWidth() == 0)
			params.width = ViewGroup.LayoutParams.FILL_PARENT;
		if(params.height == ViewGroup.LayoutParams.WRAP_CONTENT
				&& getSuggestedMinimumHeight() == 0)
			params.height = ViewGroup.LayoutParams.FILL_PARENT;
	}
	super.setLayoutParams(params);
}

还有 `onSizeChanged`:

@Override 
public void onSizeChanged(int w, int h, int ow, int oh){
	if(mIsSvg){
		//Create the bitmap to raster svg to
			Canvas canvas = new Canvas();
		mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
		canvas.setBitmap(mBitmap);
		// Render SVG with use of libandroidsvg
		SvgRaster.svgAndroidRenderToArea(
			mSvgId, canvas,
			0, 0, canvas.getWidth(), canvas.getHeight());	
		this.setImageBitmap(mBitmap);
	}
	else
		super.onSizeChanged(w, h, ow, oh);
}

最后,是时候尝试一下了。创建以下布局:

<?xml version="1.0" encoding="utf-8"?>
	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
		android:background="#AA0000"
		android:layout_height="fill_parent" 
		android:layout_width="fill_parent"
		android:layout_weight="1.0" 
		android:gravity="center"
		>
		<com.imageviewsvg.controls.ImageViewSvg
			android:src="@drawable/lion" 
			android:layout_width="100dip"
			android:layout_height="100dip" 
			android:id="@+id/svgview"
			android:layout_gravity="center" 
			/>
	</LinearLayout>

然后运行。

pic1.JPG

调试原生代码

要调试原生代码,您可以按照 Carlos Souto 在 Sequoyah 项目 [ 9 ] 中提供的说明进行操作。但初次使用时,有些地方可能不清楚。因此,有一些技巧:

  • 配置 C++ 调试配置时,应用程序实际上必须是 `app_process`,不要在意 Eclipse 说程序不存在,它稍后会创建。
  • 每次运行调试时都应该运行 ndk-gdb。有时命令应该是 ndk-gdb –adb=/tools/adb –force。
  • 不要忘记在清单文件中设置“debuggable”。

第二种方法。Anti Grain Geometry。

还有一个库可以用于栅格化 SVG 文件。那就是 Anti Grain Geometry [ 10 ]。唯一需要的额外库是 `libexpat`。我们在项目中已经有了。在 *jni* 文件夹中,创建如下文件夹:

pic2.JPG

将相应的 *agg* 源文件夹中的文件复制到 *gpc/include/src* 文件夹中。那里有一个 *examples* 文件夹,您可以在其中找到 *svg_viewer* 文件夹。复制除 *svg_test* 之外的所有文件到 *aggsvg jni* 文件夹。它将使用 AGG 来解析和栅格化 SVG。但它只支持基本的 SVG 功能,无法解析复杂的内容。您需要自己扩展解析器。在 *aggsvg-android* 文件夹中,创建一个 *aggsvgandroid.cpp* 文件。示例从文件系统中解析 SVG。要解析 *string*,请向 *parser* 类添加以下方法:

void parser::parse(const char *chars, int length){

    	char msg[1024];

	    XML_Parser p = XML_ParserCreate(NULL);
	    if(p == 0)
	    {
		    throw exception("Couldn't allocate memory for parser");
	    }

	    XML_SetParamEntityParsing(p, XML_PARAM_ENTITY_PARSING_ALWAYS);
	    XML_UseForeignDTD(p, true);

	    XML_SetUserData(p, this);
	    XML_SetElementHandler(p, start_element, end_element);
	    XML_SetCharacterDataHandler(p, content);

	    int done = 0;
	    std::string str = std::string(chars);
	    std::istringstream inputString(str);

	    while(true){
	    	if(done)
	    		break;
            size_t len = inputString.readsome(m_buf, buf_size);
            done = len < buf_size;
            if(!XML_Parse(p, m_buf, len, done))
            {
                sprintf(msg,
                    "%s at line %d\n",
                    XML_ErrorString(XML_GetErrorCode(p)),
                    (int)XML_GetCurrentLineNumber(p));
                throw exception(msg);
            }
	    }
        XML_ParserFree(p);

        char* ts = m_title;
        while(*ts)
        {
            if(*ts < ' ') *ts = ' ';
            ++ts;
        }
    }

在 *Android.mk* 文件的末尾,添加一个部分来构建另一个库。这很简单。只需在构建第一个库后清除变量,然后设置它们来构建另一个库。这是使用 AGG 进行栅格化的类:

class SvgRasterizer{
	agg::svg::path_renderer m_path;
    double m_min_x;
    double m_min_y;
    double m_max_x;
    double m_max_y;
    double m_x;
    double m_y;
    pix_format_e pixformat;
	agg::rendering_buffer m_rbuf_window;

public:
	SvgRasterizer(pix_format_e format, uint32_t width, 
			uint32_t height, void *pixels) : \
		m_path(), \
		m_min_x(0.0), \
		m_min_y(0.0), \
		m_max_x(0.0), \
		m_max_y(0.0), \
		pixformat(format)
	{
		m_rbuf_window.attach((unsigned char*)pixels, width, height, 4*width);
	}

	void parse_svg(const char* svg, int length){
		// Create parser
		agg::svg::parser p(m_path);
		// Parse SVG
		p.parse(svg, length);
		// Make all polygons CCW-oriented
		m_path.arrange_orientations();
		// Get bounds of the image defined in SVG
        m_path.bounding_rect(&m_min_x, &m_min_y, &m_max_x, &m_max_y);
	}

	void rasterize_svg()
	{
		typedef agg::pixfmt_rgba32 pixfmt;
		typedef agg::renderer_base<pixfmt> renderer_base;
		typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_solid;

        pixfmt pixf(m_rbuf_window);
        renderer_base rb(pixf);
        renderer_solid ren(rb);

        agg::rasterizer_scanline_aa<> ras;
        agg::scanline_p8 sl;
        agg::trans_affine mtx;

        double scl;
        // Calculate the scale the image to fit given bitmap
        if(m_max_y > m_max_x)
        	scl = pixf.height()/m_max_y;
        else
        	scl = pixf.width()/m_max_x;

        // Default gamma as is
        ras.gamma(agg::gamma_power(1.0));
        mtx *= agg::trans_affine_scaling(scl);

        m_path.expand(0.0);

        // Render image
        m_path.render(ras, sl, ren, mtx, rb.clip_box(), 1.0);

        ras.gamma(agg::gamma_none());
	}
};

在源代码中,我添加了测试这两种方法的功能:

pic3.JPG

结论

所以,至少有两种方法可以在 Android 中显示 SVG 文件。 `libsvg-android` 的主要优点是它开箱即用,但它比 AGG 慢三倍多。而使用 AGG,您需要自己扩展 SVG 解析器。另外,使用 AGG,您还可以获得图像处理的额外功能。我只是在布局中使用了 `ImageView`,但要以编程方式使用它,您当然应该重写更多方法,例如 `setImageResource`。

就是这些了。谢谢!

资源

  1. http://www.eclipse.org
  2. http://developer.motorola.com/docstools/motodevstudio
  3. https://developer.android.com.cn/guide/developing/tools/adt.html
  4. http://www.eclipse.org/sequoyah/
  5. http://www.cygwin.com/
  6. https://developer.android.com.cn/sdk/index.html
  7. http://www.crystax.net/android/ndk-r4.php?lang=en
  8. https://launchpad.net/libsvg-android
  9. http://www.eclipse.org/sequoyah/documentation/native_debug.php
  10. http://www.antigrain.com/

历史

  • 2010 年 11 月 25 日:首次发布
© . All rights reserved.