使用 Java 和 Kotlin

Kanzi Java API 为 Kanzi 类实现了平台无关的 Java 代理,提供了对 Java 和 Kotlin 中 Kanzi 功能的丰富访问。这使您能够创建与 Android API 紧密集成的应用程序。

在 Android 上,您可以直接从 Android UI 线程使用 Kanzi Java API,而无需调度机制。请参阅 使用 Kanzi Android framework (droidfw) 进行开发时。

本主题概述了 Kanzi Java API 的基础知识。有关完整的 API 参考,请参阅 Kanzi Engine Java API 参考文档。

要求

Kanzi Java API 需要 Java 8 (1.8) 或更高版本。

使用属性

Kanzi Java API 类包含用于内置属性类型(如 metadata.NodeMetadata.OpacityProperty)的静态成员。要读取和写入这些属性:

// Set property.
node.setProperty(Node.OpacityProperty, 0.5f);

// Get property.
float opacity = node.getProperty(Node.OpacityProperty);
// Set property.
node.setProperty(Node.OpacityProperty, 0.5f)

// Get property.
val opacity = node.getProperty(Node.OpacityProperty)

要通过名称访问属性,请创建 DynamicPropertyType 或 AbstractPropertyType 的实例,并使用属性类型的全名:

// Use DynamicPropertyType to access a property type when the type is known.
DynamicPropertyType<Boolean> dynamicProperty =
    new DynamicPropertyType<>("Node.Visible", Boolean.class);
node.setProperty(dynamicProperty, false);

// Use AbstractPropertyType to access a property type when the type is not known.
AbstractPropertyType abstractProperty = new AbstractPropertyType("Node.Visible");
node.setProperty(abstractProperty, false);
// Use DynamicPropertyType to access a property type when the type is known.
val dynamicProperty = DynamicPropertyType("Node.Visible", Boolean::class.java)
node.setProperty(dynamicProperty, false)

// Use AbstractPropertyType to access a property type when the type is not known.
val abstractProperty = AbstractPropertyType("Node.Visible")
node.setProperty(abstractProperty, false)

要定义新的属性类型,请在您的类中创建一个 PropertyType 的公共静态成员实例,并使用 Metadata 进行注释:

@Metadata
@EditorInfo(displayName = "Custom Property")
public static final PropertyType<String> CustomProperty =
    new PropertyType<>("MyNode2D.Custom", String.class);

// Properties can also have an automatically generated name.
// In this case 'MyNode2D.Other'.
@Metadata
public static final PropertyType<String> OtherProperty = new PropertyType<>(String.class);
// You must place this inside the "companion object".

@Metadata
@EditorInfo(displayName = "Custom Property")
val CustomProperty = PropertyType("MyNode2D.Custom", String::class.java)

// Properties can also have an automatically generated name.
// In this case 'MyNode2D.Other'.
@Metadata
val OtherProperty = PropertyType(String::class.java)

要了解有关可以为您的属性类型设置的元数据的更多信息,请参阅 在 Kanzi Studio 中显示 Kanzi Engine 插件自定义类型的参考。。

请参阅 属性系统。

使用枚举

使用 Kanzi Java API 枚举

Kanzi Java API 附带与内置属性相关联的枚举集。

在 Java 代码中,对于 setter 和 getter 函数,像这样访问 Kanzi Java API 枚举值:

textNode.setProperty(TextBlock2D.FontHintingPreferenceProperty, FontHintingPreference.NativeHinting);

Node.HorizontalAlignment value = node.getProperty(Node.HorizontalAlignmentProperty);

由于 Java 和 Kotlin 在接口继承方面的差异,访问 Kanzi Java API 枚举需要使用相应的枚举接口。

例如:使用 metadata.NodeMetadata.HorizontalAlignmentProperty 属性:

  • 这在 Java 和 Kotlin 中都适用:

    NodeEnums.HorizontalAlignment.Center;
    
  • 这仅在 Java 中有效:

    Node.HorizontalAlignment.Center;
    

使用自定义枚举

如果您声明一个自定义枚举,为了能够将此枚举设置为自定义属性的数据类型,该枚举类型必须继承 Kanzi 枚举:

// Define a custom enum and inherit from Kanzi Enum. Make sure to import Kanzi Enum.
enum CustomEnum implements Enum
{
    // Add enum values. Make sure to initialize them.
    Value1(0),
    Value2(1);

    // Add a getter function. Make sure that it is public.
    @Override
    public int getValue()
    {
        return mValue;
    }

    // Add a constructor.
    CustomEnum(int value)
    {
        mValue = value;
    }

    // The actual value.
    int mValue;
}

@Metadata
@EditorInfo(displayName = "Custom Enum Property")
public static final PropertyType<CustomEnum> CustomEnumProperty =
    new PropertyType<>("MyNode2D.CustomEnum", CustomEnum.class);
// Define a custom enum and inherit from Kanzi Enum. Make sure to import Kanzi Enum.
// Declare an integer value in the primary constructor.
enum class CustomEnum(private val mValue: Int) : Enum
{
    // Add enum values. Make sure to initialize them.
    Value1(0),
    Value2(1);

    // Add a getter function.
    override fun getValue(): Int
    {
        return mValue;
    }
}

@Metadata
@EditorInfo(displayName = "Custom Enum Property")
val CustomEnumProperty = PropertyType<CustomEnum>("MyNode2D.CustomEnum", CustomEnum::class.java);

使用消息

Kanzi Java API 类包含内置消息类型的静态成员,例如 metadata.ButtonConceptMetadata.ToggleStateChangedMessageArguments。

要发送消息:

// Construct message arguments.
ToggleButton2D.ToggleStateChangedMessageArguments args =
    new ToggleButton2D.ToggleStateChangedMessageArguments();

// Set message arguments.
args.setToggleStateProperty(1);

// Dispatch message.
node.dispatchMessage(Button2D.ToggleStateMessage, args);
// Construct message arguments.
val args = ButtonConceptMetadata.ToggleStateChangedMessageArguments()

// Set message arguments.
args.toggleStateProperty = 1

// Dispatch message.
node.dispatchMessage(Button2D.ToggleStateMessage, args)

要在节点接收到消息时作出反应,请向该节点添加消息处理程序:

// Add message subscription.
button.addMessageHandler(Button2D.ToggleStateMessage, messageArguments -> {
    // Extract arguments through accessors..
    Integer toggleState = messageArguments.getToggleStateProperty();
    // .. or through generic accessors
    toggleState = messageArguments.getArgument(
        ButtonConceptMetadata.ToggleStateChangedMessageArguments.ToggleStateProperty);
});
// Add message subscription.
button.addMessageHandler(Button2D.ToggleStateMessage)
{ messageArguments ->
    // Extract arguments.
    val toggleState = messageArguments.toggleStateProperty
}

要使用消息名称,请使用消息类型的全名创建 AbstractMessageType 的实例:

// Get reference to a message type.
AbstractMessageType messageType = new AbstractMessageType("Message.MyMessage");
DynamicPropertyType<Boolean> argumentProperty =
    new DynamicPropertyType<>("MyMessage.Argument", Boolean.class);

// Construct message arguments.
MessageArguments args = new MessageArguments();

// Set message arguments.
args.setArgument(argumentProperty, false);

// Send the message.
node.dispatchMessage(messageType, args);
// Get reference to a message type.
val messageType = AbstractMessageType("Message.MyMessage")
val argumentProperty = DynamicPropertyType("MyMessage.Argument", Boolean::class.java)

// Construct message arguments.
val args = MessageArguments()

// Set message arguments.
args.setArgument(argumentProperty, false)

// Send the message.
node.dispatchMessage(messageType, args)

要定义新消息类型:

  1. (可选) 定义一个消息参数类,该类包含您的消息的参数属性类型。

    您定义的消息参数类必须从基类 MessageArguments 或另一个消息参数类派生。

    您不必定义消息参数类。相反,您可以使用现有的消息参数类,包括基类 MessageArguments。

    @Metadata
    public static class ExampleMessageArguments extends MessageArguments
    {
        // Define metaclass.
        @Metadata
        public static Metaclass metaclass =
            new Metaclass("Example.MyNode2D.ExampleMessageArguments");
    
        // Define properties.
        @Metadata
        public static PropertyType<Integer> SomeProperty = new PropertyType<>(Integer.class);
    
        // Optionally, define property accessors. You can instead use the generic argument
        // accessors.
        public Integer getSomeProperty()
        {
            return getArgument(SomeProperty);
        }
        public void setSomeProperty(Integer value)
        {
            setArgument(SomeProperty, value);
        }
    
        // Define constructors.
        // A constructor with a long argument, which Kanzi uses to wrap native arguments.
        public ExampleMessageArguments(long nativeObject)
        {
            super(nativeObject);
        }
        // Any additional constructors for you to use when dispatching messages.
        public ExampleMessageArguments()
        {
            super();
        }
    }
    
    @Metadata
    class ExampleMessageArguments : MessageArguments {
        companion object {
            // Define metaclass.
            @Metadata
            var metaclass = Metaclass("Example.MyNode2D.ExampleMessageArguments")
    
            // Define properties.
            @Metadata
            val SomeProperty = PropertyType(Int::class.java)
        }
    
        // Optionally, define property accessors. You can instead use the generic argument accessors.
    
        fun getSomeProperty(): Int? {
            return getArgument(SomeProperty)?.toInt()
        }
    
        fun setSomeProperty(value: Int) {
            setArgument(SomeProperty, value)
        }
    
        // Define constructors.
    
        // A constructor with a long argument, which Kanzi uses to wrap native arguments.
        constructor(nativeObject: Long) : super(nativeObject)
    
        // Any additional constructors for you to use when dispatching messages.
        constructor() : super()
    }
    
  2. 在您的类中创建一个 MessageType 的公共静态成员实例,并使用 Metadata 进行注释:

    @Metadata
    public static final MessageType<ExampleMessageArguments> ExampleMessage =
        new MessageType<>("Message.MyNode2D.Example", MessageRouting.MessageRoutingBubbling,
            ExampleMessageArguments.class);
    
    // You must place this inside the "companion object".
    
    @Metadata
    val ExampleMessage = MessageType(
        "Message.MyNode2D.Example",
        MessageRouting.MessageRoutingBubbling,
        ExampleMessageArguments::class.java
    )
    

要了解有关您可以为消息和消息参数类型设置的元数据的更多信息,请参阅 在 Kanzi Studio 中显示 Kanzi Engine 插件自定义类型的参考。。

请参阅 使用消息。

创建实例

具体的 Kanzi Java API 类型带有 create 方法,您可以使用这些方法来创建实例。

从 KanziObject 派生的对象类型的 create 方法返回一个 ObjectRef 实例,该实例是对新对象的拥有引用。

管理实例生命周期

ObjectRef 类型在本机后端和 Java ObjectRef 实例之间启用对 KanziObject 派生类型实例的共享所有权。当所有 ObjectRef 实例都关闭且不存在本机引用时,对象将被回收。如果应用程序代码尝试在此状态下访问对象,Kanzi 将抛出一个 StaleObjectException。为防止这种情况,请为您使用的对象维护一个 ObjectRef 实例。

尽管 ObjectRef 实例会自动进行垃圾回收,但为了使内存消耗可预测,您可以手动关闭这些实例。

// Create an EmptyNode2D node.
ObjectRef<EmptyNode2D> nodeRef = EmptyNode2D.create(domain, "My Empty Node");

// Access the EmptyNode2D instance from within the ObjectRef.
EmptyNode2D node = nodeRef.get();

// Use the node.

// Close the ObjectRef.
nodeRef.close();

// If you try to access the node variable after closing its ObjectRef,
// Kanzi throws a StaleObjectException.
node.setName("New Name");
// Create an EmptyNode2D node.
val nodeRef = EmptyNode2D.create(domain, "My Empty Node")

// Access the EmptyNode2D instance from within the ObjectRef.
val node = nodeRef.get()

// Use the node.

// Close the ObjectRef.
nodeRef.close()

// If you try to access the node variable after closing its ObjectRef,
// Kanzi throws a StaleObjectException.
node.name = "New Name"

您还可以使用 Java try-with-resources 语句或 Kotlin use 函数,将 ObjectRef 实例的生命周期与某个范围关联起来:

// Create an EmptyNode2D node.
try (ObjectRef<EmptyNode2D> nodeRef = EmptyNode2D.create(domain, "My Empty Node"))
{
    // Access the EmptyNode2D instance from within the ObjectRef.
    EmptyNode2D node = nodeRef.get();

    // Use the node.

    // Kanzi automatically closes the nodeRef at the end of the try block scope.
}
EmptyNode2D.create(domain, "My Empty Node").use { nodeRef ->
    // Access the EmptyNode2D instance from within the ObjectRef.
    val node = nodeRef.get()

    // Use the node.

    // Kanzi automatically closes the nodeRef at the end of the use block scope.
}

您还可以从非拥有引用创建一个拥有的 ObjectRef 实例。

// Access an EmptyNode2D instance.
EmptyNode2D node = getNode(domain);

// Construct a Reference to the existing object.
// If the node is already stale, throws a StaleObjectException.
ObjectRef<EmptyNode2D> ref1 = new ObjectRef<>(node);

// Try to construct a Reference to the existing object.
// If the node is already stale, returns null.
ObjectRef<EmptyNode2D> ref2 = node.tryCreateObjectRef(EmptyNode2D.class);
// Access an EmptyNode2D instance.
val node = getNode(domain)

// Construct a Reference to the existing object.
// If the node is already stale, throws a StaleObjectException.
val ref1 = ObjectRef(node)

// Try to construct a Reference to the existing object.
// If the node is already stale, returns null.
val ref2 = node.tryCreateObjectRef<EmptyNode2D>(EmptyNode2D::class.java)

多个拥有引用

当 ObjectRef 实例在代码块之间传递时,何时关闭实例可能会变得不清楚。因为 Java 通过引用传递实例,看似独立的实例可能都引用了相同的强引用。为了避免这些问题,Kanzi 对 ObjectRef 实例使用以下指南,并建议用户也这样做:

  • 当从方法返回一个 ObjectRef 实例作为返回值时,所有权与实例一起传递。方法必须返回实例的克隆,或者是未关闭的仅限本地的引用。这也意味着从方法返回接收到的 ObjectRef 实例由调用代码拥有,并且可以在不再需要时关闭或留给垃圾回收。

    // When returning a member ObjectRef, return a clone.
    ObjectRef<EmptyNode2D> getNode1()
    {
        return mNode.clone();
    }
    
    // You can return a local ObjectRef directly, but do not close it explicitly.
    ObjectRef<EmptyNode2D> getNode2()
    {
        ObjectRef<EmptyNode2D> localRef = EmptyNode2D.create(getDomain(), "My Empty Node");
        return localRef;
    
        // Here, do not close the localRef.
    }
    
    // When returning a member ObjectRef, return a clone.
    fun getNode1(): ObjectRef<EmptyNode2D> {
        return mNode.clone()
    }
    
    // You can return a local ObjectRef directly, but do not close it explicitly.
    fun getNode2(): ObjectRef<EmptyNode2D> {
        var localRef = EmptyNode2D.create(domain, "My Empty Node")
    
        return localRef;
    
        // Here, do not close the localRef.
    }
    
  • 当将 ObjectRef 实例作为参数传递给方法时,该实例的所有权由调用者保留。当实现一个以 ObjectRef 作为参数的方法时,其所有权是临时的。如果该方法将实例存储在成员变量中,或希望在方法范围之外延长对象的生命周期,则必须克隆 ObjectRef 实例。

    void myFunction(ObjectRef<Node2D> node)
    {
        // You can use the node as-is, except for closing or storing the node.
        // If you need to store the reference, make a clone.
        mNode = node.clone();
    }
    
    fun myFunction(node: ObjectRef<Node2D?>) {
        // You can use the node as-is, except for closing or storing the node.
        // If you need to store the reference, make a clone.
        mNode = node.clone()
    }
    

创建自定义类型

您可以使用 Java 或 Kotlin 通过创建自定义类型来扩展 Kanzi Engine 的功能。您可以从可扩展的 Kanzi 类型之一派生,或者定义一个抽象类型。

例如,要创建自定义 2D 节点:

  1. 从 Node2D 派生一个类。

    static class MyNode2D extends Node2D
    {
        private MyNode2D(Domain domain, long nativeNode, Metaclass metaclass)
        {
            super(domain, nativeNode, metaclass);
        }
    
    class MyNode2D private constructor(domain: Domain, nativeNode: Long, metaclass: Metaclass) :
        Node2D(domain, nativeNode, metaclass) {
    
        companion object {
    
  2. Kanzi 需要为每个自定义节点提供以下项目:

    • Metaclass。元类必须以这种形式存在:

      @Metadata
      public static final Metaclass metaclass = new Metaclass("Example.MyNode2D");
      
      // You must place this inside the "companion object".
      
      @Metadata
      val metaclass = Metaclass("Example.MyNode2D")
      

      您可以使用 EditorInfo 注释提供其他元数据。请参阅 在 Kanzi Studio 中显示 Kanzi Engine 插件自定义类型的参考。。

    • 工厂。可实例化的派生类型必须提供一个工厂方法,Kanzi 调用该方法来实例化您的类型。您可以:

      • 使用 @Factory 注释您的工厂方法。您可以将方法命名为任何名称,包括 create。方法签名必须是以下之一:

        • @Factory ObjectRef<MyNode2D> myFactoryFunction(Domain domain, String name)

        • @Factory ObjectRef<MyNode2D> myFactoryFunction(Domain domain)

        @Factory
        static public ObjectRef<MyNode2D> myFactoryFunction(Domain domain, String name)
        {
            return Node2D.createDerived(domain, name, metaclass);
        }
        
        // You must place this inside the "companion object".
        
        @JvmStatic
        @Factory
        fun myFactoryFunction(domain: Domain, name: String): ObjectRef<MyNode2D> {
            return Node2D.createDerived(domain, name, metaclass)
        }
        
      • 使用以下签名之一的静态公共 create 方法:

        • ObjectRef<MyNode2D> create(Domain domain, String name)

        • ObjectRef<MyNode2D> create(Domain domain)

        在 Kanzi 中,create 名称是为您用来创建自定义类型的方法保留的。

        static public ObjectRef<MyNode2D> create(Domain domain, String name)
        {
            return Node2D.createDerived(domain, name, metaclass);
        }
        
        // You must place this inside the "companion object".
        
        @JvmStatic
        fun create(domain: Domain, name: String): ObjectRef<MyNode2D> {
            return Node2D.createDerived(domain, name, metaclass)
        }
        

      工厂方法的实现通常是对您正在扩展的类型上的 createDerived 方法的调用。

    • (可选) 自定义初始化。在构造函数期间,Kanzi 不会完全初始化自定义 Kanzi 对象。请将您的自定义初始化代码放在您重写的 initialize() 方法中。

      @Override
      protected void initialize()
      {
          super.initialize();
      
          // Add custom initialization code after calling super.initialize().
      }
      
      override fun initialize() {
          super.initialize()
      
          // Add custom initialization code after calling super.initialize().
      }
      
  3. (可选) 创建自定义属性类型。

  4. (可选) 创建自定义消息参数、类型和处理程序。

  5. (可选) 通过重写相关方法定义自定义节点行为。

    @Override
    protected void arrangeOverride(Vector2 actualSize)
    {
        super.arrangeOverride(actualSize);
    
        // Define custom behavior.
    }
    
    override fun arrangeOverride(actualSize: Vector2?) {
        super.arrangeOverride(actualSize)
        // Define custom behavior.
    }
    

实现自定义资源加载

要在 Java 或 Kotlin 中实现自定义资源加载,定义一个自定义资源管理器协议。

ResourceManager resourceManager = domain.getResourceManager();
resourceManager.registerProtocolHandler(
    "MyProtocol", new ResourceManager.ProtocolHandler() {
        @Override
        public Result handle(ResourceManager resourceManager, String url, String protocol,
            String hostname, String path)
        {
            if (shouldLoadSynchronously)
            {
                // Load a resource synchronously.
                ObjectRef<Resource> resource = loadSynchronousResource();
                return new Result(resource);
            }
            else
            {
                // Use load task for asynchronous loading.
                return new Result(new ResourceManager.LoadTask() {
                    ObjectRef<Resource> mResource = null;
                    @Override
                    public void loadFunction()
                    {
                        // Perform thread-independent loading.
                    }

                    @Override
                    public void finishFunction()
                    {
                        // Create and store the resource.
                        mResource = createResource();
                    }

                    @Override
                    public ObjectRef<? extends Resource> getResult()
                    {
                        // Return the created resource.
                        return mResource.clone();
                    }

                    @Override
                    public void close()
                    {
                        mResource.close();
                    }
                });
            }
        }
    });
domain.resourceManager.registerProtocolHandler("MyProtocol")
{ resourceManager, url, protocol, hostname, path ->
    if (shouldLoadSynchronously) {
        // Load a resource synchronously.
        val resource = loadSynchronousResource()
        ProtocolHandler.Result(resource)
    } else {
        // Use load task for asynchronous loading.
        ProtocolHandler.Result(object : ResourceManager.LoadTask() {
            var mResource: ObjectRef<Resource>? = null
            override fun loadFunction() {
                // Perform thread-independent loading.
            }

            override fun finishFunction() {
                // Create and store the resource.
                mResource = createResource()
            }

            override fun getResult(): ObjectRef<out Resource>? {
                // Return the created resource.
                return mResource!!.clone()
            }

            override fun close() {
                mResource!!.close()
            }
        })
    }
}

如果失败,资源协议 ResourceManager.ProtocolHandler.handle 可能会抛出异常,由 Kanzi Resource Manager 处理。

默认情况下,ResourceManager.LoadTask 会调用 ResourceManager.LoadTask.loadFunction 和 ResourceManager.LoadTask.finishFunction。要设置 ResourceManager.LoadTask 仅调用 ResourceManager.LoadTask.finishFunction,请将 ResourceLoadTaskType.FinishOnly 传递给构造函数。

资源协议可以通过实现 ResourceManager.ReloadProtocolHandler 来支持重新加载操作。Kanzi 调用重新加载处理程序以在 OpenGL 上下文失效时重新初始化现有资源。

ResourceManager resourceManager = domain.getResourceManager();
resourceManager.registerProtocolHandler("MyReloadProtocol",
    new ResourceManager.ProtocolHandler() {
        @Override
        public Result handle(ResourceManager resourceManager, String url, String protocol,
            String hostname, String path)
        {
            TextureBuilder builder = new TextureBuilder()
                                         .setName("Test Empty Texture")
                                         .setWidth(128)
                                         .setHeight(128);

            ObjectRef<Texture> texRef = builder.build(getDomain());
            return new Result(texRef);
        }
    },
    new ResourceManager.ReloadProtocolHandler() {
        @Override
        public void handle(ResourceManager resourceManager, String url, String protocol,
            String hostname, String path, Resource resource)
        {
            if (resource instanceof Texture)
            {
                TextureBuilder builder = new TextureBuilder()
                                             .setName("Reloaded Empty Texture")
                                             .setWidth(128)
                                             .setHeight(128);
                builder.recreate((Texture) resource);
            }
        }
    });
domain.resourceManager.registerProtocolHandler("MyReloadProtocol",
    { resourceManager, url, protocol, hostname, path ->
        val builder = TextureBuilder()
            .setName("Test Empty Texture")
            .setWidth(128)
            .setHeight(128)
        val texRef = builder.build(getDomain())
        ProtocolHandler.Result(texRef)
    },
    { resourceManager, url, protocol, hostname, path, resource ->
        if (resource is Texture) {
            val builder = TextureBuilder()
                .setName("Reloaded Empty Texture")
                .setWidth(128)
                .setHeight(128)
            builder.recreate(resource)
        }
    })

创建数据源

数据源使您能够从您的 Kanzi 应用程序访问第三方数据。

要创建数据源:

  1. 创建 DataSource 类。

    class MyDataSource extends DataSource
    {
        @Metadata
        public static final Metaclass metaclass = new Metaclass("MyPlugin.MyDataSource");
    
        public static ObjectRef<MyDataSource> create(Domain domain, String name)
        {
            return DataSource.createDerived(domain, name, MyDataSource.metaclass);
        }
    
        private MyDataSource(Domain domain, long nativeHandle, Metaclass metaclass)
        {
            super(domain, nativeHandle, metaclass);
        }
    
        @Override
        protected void initialize()
        {
            super.initialize();
    
            createData();
        }
    
        private void createData() {}
    }
    
    class MyKotlinDataSource private constructor(
        domain: Domain,
        nativeHandle: Long,
        metaclass: Metaclass
    ) :
        DataSource(domain, nativeHandle, metaclass) {
        override fun initialize() {
            super.initialize()
            createData()
        }
    
        private fun createData() {}
    
        companion object {
            @Metadata
            val metaclass = Metaclass("MyPlugin.MyDataSource")
    
            @JvmStatic
            fun create(domain: Domain?, name: String?): ObjectRef<MyKotlinDataSource> {
                return createDerived(domain, name, metaclass)
            }
        }
    }
    
  2. 添加所需的成员、访问器和初始化数据的方法。

    private ObjectRef<DataObject> mDataRef;
    private DataObject mData;
    private DataObjectBool mMyBool;
    
    public DataObject getData()
    {
        return mData;
    }
    
    private void createData()
    {
        Domain domain = getDomain();
        mDataRef = DataObject.create(domain, "root");
        mData = mDataRef.get();
    
        // Add the remaining structure of the DataSource.
        try (ObjectRef<DataObjectBool> objRef = DataObjectBool.create(domain, "MyBoolean", true))
        {
            mMyBool = objRef.get();
            mData.addChild(mMyBool);
        }
    }
    
    private var mDataRef: ObjectRef<DataObject>? = null
    private var mData: DataObject? = null
    private var mMyBool: DataObjectBool? = null
    override fun getData(): DataObject {
        return mData!!
    }
    
    private fun createData() {
        mDataRef = DataObject.create(domain, "root")
        mData = mDataRef?.get()
        DataObjectBool.create(domain, "MyBoolean", true).use { objRef ->
            mMyBool = objRef.get()
            mData?.addChild(mMyBool)
        }
    }
    

要详细了解 Kanzi 中的数据源,请参阅 数据源 (Data Sources)。

创建绑定

您可以使用绑定将属性或属性字段的值设置为来自另一个属性、属性字段或数据源的值。

要创建单向绑定:

// Creates a one-way binding to the Opacity property of Node A.
AbstractBinding binding =
    BindingBuilder.createOneWay("../Node A", Node2D.OpacityProperty);
// Creates a one-way binding to the Opacity property of Node A.
val binding = BindingBuilder.createOneWay("../Node A", Node2D.OpacityProperty)
// Creates a one-way binding to the Opacity property of Node A.
let binding =
    kanzi::Binding::create_with_property("../Node A", &kanzi::node_2d::OPACITY_PROPERTY)?;

要将绑定设置为节点的属性:

// Sets the binding to Node B. Creates a binding runtime that you can use to remove the
// binding.
AbstractBindingRuntime runtime = nodeB.setBinding(binding, Node2D.OpacityProperty);
// Sets the binding to Node B. Creates a binding runtime that you can use to remove the
// binding.
val runtime = nodeB.setBinding(binding, Node2D.OpacityProperty)
// Sets the binding to Node B. Creates a binding runtime that you can use to remove the
// binding.
let runtime = node_b.set_binding1(&binding, &*kanzi::node_2d::OPACITY_PROPERTY)?;

要从节点中移除绑定:

// Removes from Node B the binding to the Opacity property of Node A using the binding
// runtime.
nodeB.removeBinding(runtime);
// Removes from Node B the binding to the Opacity property of Node A using the binding runtime.
nodeB.removeBinding(runtime)
// Removes from Node B the binding to the Opacity property of Node A using the binding
// runtime.
node_b.remove_binding(&runtime)?;

要创建一个回调绑定处理器到一个绑定,并在两个节点的属性之间设置双向绑定:

// Create a two-way binding to the Opacity property of Node A.
AbstractBinding binding =
    BindingBuilder.createTwoWay("../Node A", Node2D.OpacityProperty);

// Create a callback processor that accepts only float values greater than or equal to
// 0.5.
ObjectRef<CallbackBindingProcessor> processorRef =
    CallbackBindingProcessor.create(getDomain(), value -> {
        if (value instanceof Float)
        {
            return ((float) value >= 0.5f);
        }
        return false;
    });

// Add a callback processor to the binding. You must do this before adding the binding
// to a node.
binding.addProcessor(processorRef.get());

// Bind from the Opacity property of Node A to the Opacity property of Node B.
AbstractBindingRuntime runtime = nodeB.setBinding(binding, Node2D.OpacityProperty);

final float opacityAa = 0.333333f;
// Setting the opacity of Node A to less than 0.5 does not change the opacity of
// Node B.
nodeA.setOpacity(opacityAa);
final float opacityBa = nodeB.getOpacity();

final float opacityAb = 0.55555f;
// Setting the opacity of Node A to a value greater than or equal to 0.5 changes the
// opacity of Node B.
nodeA.setOpacity(opacityAb);
// Create a two-way binding to the Opacity property of Node A.
val binding = BindingBuilder.createTwoWay("../Node A", Node2D.OpacityProperty)

// Create a callback processor that accepts only float values greater than or equal to 0.5.
val processorRef = CallbackBindingProcessor.create(domain) { value: Any? ->
    if (value is Float) {
        return@create value >= 0.5f
    }
    false
}

// Add a callback processor to the binding. You must do this before adding the binding to a
// node.
binding.addProcessor(processorRef.get())

// Bind from the Opacity property of Node A to the Opacity property of Node B.
val runtime = nodeB.setBinding(binding, Node2D.OpacityProperty)
val opacityAa = 0.333333f
// Setting the opacity of Node A to less than 0.5 does not change the opacity of
// Node B.
nodeA.opacity = opacityAa
val opacityBa = nodeB.opacity
val opacityAb = 0.55555f
// Setting the opacity of Node A to a value greater than or equal to 0.5 changes the opacity of
// Node B.
nodeA.opacity = opacityAb
// Create a two-way binding to the Opacity property of Node A.
let binding = kanzi::TwoWayBinding::create_with_property(
    "../Node A",
    &*kanzi::node_2d::OPACITY_PROPERTY,
)?;

// Create a callback processor that accepts only float values greater than or equal to 0.5.
let processor = kanzi::CallbackBindingProcessor::create(domain, move |value| {
    if let Some(value) = value.cast_ref::<f32>()? {
        return Ok(value.get()? >= 0.5);
    }
    Ok(false)
})?;

// Add a callback processor to the binding. You must do this before adding the binding to a node.
binding.add_processor(&processor)?;

// Bind from the Opacity property of Node A to the Opacity property of Node B.
let runtime = node_b.set_binding1(&binding, &kanzi::node_2d::OPACITY_PROPERTY)?;

// Setting the opacity of Node A to less than 0.5 does not change the
// opacity of Node B.
{
    let opacity = 0.333333;
    node_a.set_opacity(opacity)?;
    assert_ne!(opacity, node_b.get_opacity()?);
}

// Setting the opacity of Node A to a value greater than or equal to 0.5 changes the
// opacity of Node B.
{
    let opacity = 0.555555;

    node_a.set_opacity(opacity)?;
    assert_eq!(opacity, node_b.get_opacity()?);
}

node_b.remove_binding(&runtime)?;

使用回调处理器监听属性值更改:

// Create a one-way binding to an object's own Opacity property.
AbstractBinding binding = BindingBuilder.createOneWay(".", Node2D.OpacityProperty);

// Implement the action to be performed on change in the property as a callback
// processor.
ObjectRef<CallbackBindingProcessor> processorRef =
    CallbackBindingProcessor.create(getDomain(), value -> {
        Log.debug("Opacity value changed to: " + value);

        // The return does not matter here.
        return true;
    });

// Add a callback processor to the binding. You must do this before adding the binding
// to a node.
binding.addProcessor(processorRef.get());

// Add binding to the node you want to observe.
// Note the lack of target property and field.
node.setBinding(binding);
// Create a one-way binding to an object's own Opacity property.
val binding = BindingBuilder.createOneWay(".", Node2D.OpacityProperty)

// Implement the action to be performed on change in the property as a callback
// processor.
val processorRef = CallbackBindingProcessor.create(domain) { value: Any ->
    Log.debug("Opacity value changed to: $value")

    // The return does not matter here.
    true
}

// Add a callback processor to the binding. You must do this before adding the binding
// to a node.
binding.addProcessor(processorRef.get())

// Add binding to the node you want to observe.
// Note the lack of target property and field.
node.setBinding(binding)

要创建、设置和删除具有所有者的绑定:

// Create two bindings.
AbstractBinding binding1 =
    BindingBuilder.createOneWay("../Node A", Node2D.OpacityProperty);
AbstractBinding binding2 =
    BindingBuilder.createOneWay("../Node A", Node2D.BackgroundBrushProperty);
// Bind to opacity and background brush properties, use Node A as owner.
AbstractBindingRuntime runtime1 =
    nodeB.setBindingWithOwner(binding1, nodeA, Node2D.OpacityProperty);
AbstractBindingRuntime runtime2 =
    nodeB.setBindingWithOwner(binding2, nodeA, Node2D.BackgroundBrushProperty);
// Remove bindings at the same time using Node A as owner.
nodeB.removeBindingsWithOwner(nodeA);
// Create two bindings.
val binding1 = BindingBuilder.createOneWay("../Node A", Node2D.OpacityProperty)
val binding2 = BindingBuilder.createOneWay("../Node A", Node2D.BackgroundBrushProperty)
// Bind to opacity and background brush properties, use Node A as owner.
val runtime1 = nodeB.setBindingWithOwner(binding1, nodeA, Node2D.OpacityProperty)
val runtime2 =
    nodeB.setBindingWithOwner(binding2, nodeA, Node2D.BackgroundBrushProperty)
// Remove bindings at the same time using Node A as owner.
nodeB.removeBindingsWithOwner(nodeA)

要创建并设置修饰符绑定:

// Create a binding to Render Transformation property Translation X property field of
// Node A.
AbstractBinding binding = BindingBuilder.createOneWay("../Node A",
    Node2D.RenderTransformationProperty, PropertyField.PropertyFieldTranslationX);
// Bind it to Translation Y property field of the same node.
AbstractBindingRuntime runtime = nodeA.setModifierBinding(binding,
    Node2D.RenderTransformationProperty, PropertyField.PropertyFieldTranslationY);
// Create a binding to Render Transformation property Translation X property field of Node A.
val binding = BindingBuilder.createOneWay(
    "../Node A",
    Node2D.RenderTransformationProperty,
    PropertyField.PropertyFieldTranslationX
)
// Bind it to Translation Y property field of the same node.
val runtime = nodeA.setModifierBinding(
    binding,
    Node2D.RenderTransformationProperty,
    PropertyField.PropertyFieldTranslationY
)

请参阅 绑定 (Bindings)。

创建 Kanzi Engine Java 插件

您可以将 Java 或 Kotlin 类型分离到 Kanzi Engine Java 插件中。这使您可以从 Kanzi Studio 使用这些类型,并在多个应用程序之间重用它们。

要在 Java 或 Kotlin 中创建 Kanzi Engine 插件:

  1. 为插件创建一个类。

    class TestPlugin extends Plugin
    {
        @Override
        public String getName()
        {
            return "testplugin";
        }
    }
    
    class TestPlugin : Plugin() {
        override fun getName(): String {
            return "testplugin"
        }
    }
    

    小技巧

    从 getName() 返回的字符串必须与 Jar 文件的名称匹配。

  2. 使用 Kanzi 注册您的自定义类型。

    @Override
    public void registerClasses(MetaclassRegistry metaclassRegistry)
    {
        metaclassRegistry.registerClass(MyDataSource.class);
    }
    
    override fun registerClasses(metaclassRegistry: MetaclassRegistry) {
        metaclassRegistry.registerClass(MyKotlinDataSource::class.java)
    }
    

    小技巧

    您可以使用 overrideClass 而不是 registerClass() 来覆盖现有类型。

  3. 将插件加载到您的 Android 应用程序中。

    请参阅 使用 Kanzi Engine Java 插件。

要了解有关使用 Java 或 Kotlin 创建 Kanzi Engine 插件的更多信息,请参阅 使用模板创建一个 Java Kanzi Engine 插件。 和 手动创建一个 Java Kanzi Engine 插件。

特定平台代码

Java 插件可以在多个平台上运行,例如 Kanzi Android framework (droidfw) 和在 Kanzi Studio Preview。确保将插件仅在兼容的平台上运行平台特定的代码。例如,仅当插件在 Android 环境中运行时调用 Android API。

要检测环境,请使用 Platform 方法:

if (Platform.isAndroid())
{
    // Initialize the code path for when the plugin runs on Android.
    Object context = domain.getPlatformContext();
}
else
{
    // Initialize the code path for when the plugin runs in the Kanzi Studio Preview.
}
if (Platform.isAndroid()) {
    // Initialize the code path for when the plugin runs on Android.
    val context = domain.platformContext
} else {
    // Initialize the code path for when the plugin runs in the Kanzi Studio Preview.
}

异常

Kanzi 使用已检查异常来指示可恢复的错误。例如,如果在调用 Node.acquireResource 方法时未找到资源,Kanzi 将抛出 ObjectNotFoundException 异常。

由本机代码抛出的 Kanzi Engine 异常被打包为 NativeException 的实例。Java 代码不需要处理这些异常。相反,应允许异常向上传播,以便 Kanzi 将其重新转换为 Kanzi Engine 可以处理的本机异常。

使用自定义 JNI 扩展

您可以实现自定义 JNI 代码来访问 Kanzi Engine C++ API 中尚未被 Kanzi Java API 覆盖的部分。为了简化与 JNI 代码的集成,所有相关的 Kanzi Java API 类都带有方法 long getNative(),该方法返回指向其 C++ 对应部分的指针。有关指针的确切 C++ 类型,请参阅相应 Kanzi Java API 类的 API 参考。

另请参阅

使用 Java 扩展 Kanzi Engine 的功能。

使用模板创建一个 Java Kanzi Engine 插件。

使用 Kanzi Android framework (droidfw) 进行开发时

Kanzi Engine Java API 参考文档

Kanzi Android 框架 API 参考