Java と Kotlin を使用して

Kanzi Java API は、Kanzi クラスのプラットフォームに依存しないJavaプロキシを実装し、JavaおよびKotlinで Kanzi 機能への豊富なアクセスを提供します。これにより、Android APIと緊密に統合されたアプリケーションを作成することができます。

Androidでは、Kanzi Java API をAndroid UIスレッドから直接使用でき、ディスパッチャーメカニズムは必要ありません。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コードでは、セッターおよびゲッター関数において、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 メソッドが付属しています。

create メソッドのオブジェクトタイプ、KanziObject から派生したクラスは、新しいオブジェクトへの所有参照である 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()
    }
    

カスタムタイプの作成

Kanzi Engine の機能を拡張するために、Java または Kotlin を使用してカスタムタイプを作成できます。拡張可能な 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)
        }
        
      • 次のいずれかのシグネチャを持つ静的な public 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 リソースマネージャーが処理します。

デフォルトで、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)?;

バインディングにコールバックバインディングプロセッサを作成し、2つのノードのプロパティ間で双方向バインディングを設定するには:

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

    ヒント

    既存のタイプを上書きするには、registerClass() の代わりに overrideClass を使用できます。

  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拡張の使用

You can implement custom JNI code to access areas of Kanzi Engine C++ API not yet covered by Kanzi Java API. To ease integration with JNI code, all relevant Kanzi Java API classes come with method long getNative(), which returns a pointer to their C++ counterpart. For the exact C++ type of the pointer, see the API reference for respective Kanzi Java API class.

関連項目

Kanzi Engine の機能を Java で拡張する

テンプレートを使用してJava Kanzi Engine プラグインを作成する

Kanzi Android framework (droidfw) での開発

Kanzi Engine Java API リファレンス

Kanzi Android フレームワーク API リファレンス