在运行时创建和组合NinePatchDrawables

时间:2013-09-18 08:16:45

标签: android nine-patch

我正在创建一个复合自定义控件。该控件由底部的单个元素和顶部的未知数量的元素组成。每个元素的背景是StateListDrawable,它定义了用于该元素的每个状态的NinePatchDrawable。

由于单个顶部元素会改变其宽度以适合其内容,因此底部元素的正下方区域也需要更改宽度以适应。顶部元素的位置也可能向左或向右移动。

control layout mock-up

上面和下面的图片只是快速模型,以传达我需要实现的布局和行为。我不负责设计要使用的实际背景图像,但它们不是均匀的扁平灰色。

desired stretch behavior

我设法通过以下方式解决了这个重新调整大小问题的第一部分:

  • 创建一个扩展Drawable的自定义类。
  • 传递一个NinePatchDrawables数组,定义底部视图的各种潜在区域(例如left_end,right_end,middle_below_control,middle_below_gap)。
  • 在自定义课程“onBoundChange()”中更改每个界限以匹配上述相关视图。
  • 将我的重新调整大小的NinePatches数组绘制到onDraw()中的底部视图画布。
  • 根据数组中NinePatches的填充为Bottom View设置组合填充。

这个解决方案虽然引发了许多问题:

  • 底部视图的每个潜在区域都需要StateListDrawables。
  • 您需要为每个StateListDrawable中的每个状态添加单独的NinePatch图像。
  • 您必须为底视图设计一个图像,该图像的宽度非常均匀。
  • 你需要耳朵防护者和厚厚的皮肤来处理来自平面设计师的所有喊叫。

我真正需要实现的目标是:

  • 为底部视图设置一个StateListDrawable。
  • 为每个状态提取单个图像,并将其切片到所需的部分。
  • 为每个切片创建一个新的NinePatchDrawable,将上面视图中的数据块仅应用于宽度。不高。
  • 然后根据我之前的解决方案重新组合它们。

不幸的是,正如我们所知,你无法真正使用数据块。它是在编译时从源图像生成的,用于创建NinePatch二进制文件。

可以找到关于此类主题的更好答案之一here。基于此,可能的折衷解决方案可能是:

  • 对于每种顶级元素类型,让图形设计者提供额外的空白虚拟NinePatch源,其具有相同的高度和宽度尺寸,相同的宽度填充和补丁定义,但具有高度填充和补丁定义,因为他们需要底视图。
  • 在相关虚拟NinePatch编译资源上使用getNinePatchChunk()
  • 将结果数据块与相关的底部视图位图切片组合,以使用NinePatch(Bitmap bitmap, byte[] chunk, String srcName)创建新的NinePatch。
  • 忍受来自平面设计师的肮脏外表,但至少他们已经停止了喊叫。

这似乎是一种笨拙的做事方式。是否有更好,更有效的方法来实现所有目标?

注意:在应用中使用时,滚动视图中可能会有许多此自定义控件的实例,因此需要将嵌套布局保持在最低限度。

1 个答案:

答案 0 :(得分:0)

我终于想出了一种避免使用空白虚拟NinePatch源的方法,这是我上面的妥协解决方案。

基于这个excellent solution类似的问题,我编写了一个实用工具方法来从NinePatch资源中提取填充信息。使用它,我可以从相邻的NinePatch区域中提取必要的填充,并将它们应用到我的新切片。希望这对其他人有所帮助。

public static Rect getNinePatchPadding(Context context, int drawableId) {
    Rect mPadding = new Rect();
    Bitmap bm = BitmapFactory.decodeResource(context.getResources(), drawableId);
    final byte[] chunk = bm.getNinePatchChunk();
    if (!NinePatch.isNinePatchChunk(chunk)) return null;
    ByteBuffer byteBuffer = ByteBuffer.wrap(chunk).order(ByteOrder.nativeOrder());

    // Skip to padding
    for (int i = 0; i < 12; i++) {
        byteBuffer.get();
    }

    mPadding.left = byteBuffer.getInt();
    mPadding.top = byteBuffer.getInt();
    mPadding.right = byteBuffer.getInt();
    mPadding.bottom = byteBuffer.getInt();

    return mPadding;
}