Kotlin 関数にスコープ制限を追加する (Compose を例にします)

序文

Jetpack Compose についてはもう理解し始めたでしょうか?

すでに理解し始めていて、手書きで書いている場合。Compose にはスコープのアプリケーションが多数あることに気づいたかどうかはわかりません。たとえば、修飾子はまたはスコープweight内でのみ使用できます別の例として、コンポーネントはスコープ内でのみ使用できます。RowScopeColumnScopeitemLazyListScope

Compose についてまだ学習していない場合は、kotlin 標準ライブラリに 5 つのスコープ関数があることも知っておく必要があります。let() apply() also() with() run()これらの 5 つの関数は、さまざまな方法でコンテキスト オブジェクトを保持して返します。つまり、これらの関数が呼び出されたときに、ラムダパラメータに記述されたコードは特定のスコープ内にあります。

これらの範囲制限がどのように実装されるかについて考えたことがあるかどうかはわかりませんが、特定のスコープでの使用のみをサポートするコンポーザブル関数をカスタマイズしたい場合、どのように記述すればよいでしょうか?

この記事ではそんな疑問を解決します。

範囲

ただし、正式に始める前に、kotlin のスコープに関する基本的な知識を追加しましょう。

範囲とは何ですか

実際、私たちプログラマーにとって、どの言語を学ぶとしても、スコープを理解する必要があります。

簡単な例を挙げると、次のようになります。

val valueFile = "file"

fun a() {
    
    
    val valueA = "a"
    println(valueFile)
    println(valueA)
    println(valueB)
}

fun b() {
    
    
    val valueB = "b"
    println(valueFile)
    println(valueA)
    println(valueB)
}

valueBこのコードは関数 a ではアクセスできず、関数 b でもアクセスできないため、確実にエラーが報告されることを知るために実行する必要はありませんvalueAただし、どちらの機能にも正常にアクセスできますvalueFile

これは、valueFileのスコープが .kt ファイル全体であるため、つまり、このファイル内のコード内にある限り、アクセスできるためです。

valueAと のスコープはそれぞれ関数 a と b 内にありvalueB、明らかに、それらはそれぞれのスコープ内でのみ使用できます。

同様に、クラスのメソッドまたは関数を呼び出したい場合は、スコープも考慮する必要があります。

class Test {
    
    
    val valueTest = "test"

    fun a(): String {
    
    
        val valueA = "a"
        println(valueTest)
        println(valueA)

        return "returnA"
    }
    
    fun b() {
    
    
       println(valueA)
       println(valueTest)
       println(a())
    }
}

fun main() {
    
    
    println(valueTest)
    println(valueA)
    println(a())
}

ここで挙げた例は適切ではないかもしれませんが、これはこの状況を説明するためのものです。あまり絡まりすぎないでください~

明らかに、上記のコードはfunction 内のmain変数valueTestおよびにアクセスvalueA できず、 function を呼び出すこともできませんa()。一方、Testクラス内の関数は明らかにおよび にa()アクセスでき、関数はfunction を呼び出すこともできます。関数は、変数にはアクセスできますが、変数にはアクセスできません。valueTestvalueA b()a()valueTestvalueA

これは、関数a()b()変数がvalueTest同じスコープ、つまりTestクラスのスコープ内にあるためです。

変数は関数のスコープ内にvalueAあり、 はのスコープ内にあります。実際、ここでの のスコープはネストされたスコープと呼ばれます。つまり、と のスコープ内に同時に配置されます。a()a()TestvalueAa()Test

このセクションでは、今日紹介する内容を紹介するだけなので、スコープに関する知識は簡単に紹介されていますが、スコープに関する知識について詳しく知りたい場合は、参考 1 を参照してください。

kotlin 標準ライブラリのスコープ付き関数

序文で、kotlin 標準ライブラリにはスコープ関数と呼ばれるものが 、 、 、 、withrun5letあると述べましたalsoapply

彼らは何をしますか?

まず、よく見かけるコード形式を見てみましょう。

val person = Person()
person.fullName = "equationl"
person.lastName = "l"
person.firstName = "equation"
person.age = 24
person.gender = "man"

場合によっては、大量の繰り返しを何度も記述する必要があるかもしれませんがperson、これは非常に読みやすく、書くのが面倒です。

この時点で、scope 関数を使用できます。たとえば、with次のように書き換えます。

with(person) {
    
    
    fullName = "equationl"
    lastName = "l"
    firstName = "equation"
    age = 24
    gender = "man"
}

この時点では、それを省略してperson、その属性値に直接アクセスまたは変更できます。これは、with最初のパラメーターが 2 番目のパラメーターとして使用する必要があるラムダ コンテキスト オブジェクトを受け取るためです。つまり、2 番目のパラメーターのラムダは匿名であるためです。この関数は、最初のパラメータで渡されるオブジェクトです。このとき、IDE のプロンプトでは、 with の匿名関数のスコープが次であることも示されていますPerson

1.png

したがって、この匿名関数では、人の属性に直接アクセスしたり、属性を変更したりできます。

同様に、run関数を使用して次のように書き換えることもできます。

person.run {
    
    
    fullName = "equationl"
    lastName = "l"
    firstName = "equation"
    age = 24
    gender = "man"
}

runこれは、拡張関数の形式でコンテキスト オブジェクトを受け取り、そのパラメータがラムダ匿名関数のみであることwithを除いて、 と非常に似ていることがわかります。run

もありますlet:

person.let {
    
    
    it.fullName = "equationl"
    it.lastName = "l"
    it.firstName = "equation"
    it.age = 24
    it.gender = "man"
}

it との違いrunは、匿名関数のコンテキスト オブジェクトが暗黙的なレシーバー (this) ではなく、パラメーター (it) として存在することです。

also()次に使用します:

person.also {
    
    
    it.fullName = "equationl"
    it.lastName = "l"
    it.firstName = "equation"
    it.age = 24
    it.gender = "man"
}

と同様let、これも拡張関数であり、コンテキストもパラメータとして匿名関数に渡されますが、それとは異なり、letコンテキスト オブジェクトを返します。これにより、次のようなチェーン呼び出しが容易になります。

val personString = person
    .also {
    
    
        it.age = 25
    }
    .toString()

そして最後にapply:

person.apply {
    
    
    fullName = "equationl"
    lastName = "l"
    firstName = "equation"
    age = 24
    gender = "man"
}

と同様also、これは拡張関数であり、コンテキスト オブジェクトも返しますが、そのコンテキストは匿名関数のパラメーターではなく、暗黙的なレシーバーになります。

以下は、5 つの機能の比較表と表です。

2.png

関数 コンテキストフォーム 戻り値 拡張機能ですか
暗黙的な受信者 (これ) ラムダ関数 (単位) いいえ
走る 暗黙的な受信者 (これ) ラムダ関数 (単位) はい
させて 無名関数のパラメータ (it) ラムダ関数 (単位) はい
また 無名関数のパラメータ (it) コンテキストオブジェクト はい
申し込み 暗黙的な受信者 (これ) コンテキストオブジェクト はい

Compose のスコープ制限

序文で述べたように、Compose にはスコープ制限が数多く適用されています。

たとえば、 Modifier モディファイアは、このCompose モディファイアのリストから、多くのモディファイアの範囲が制限されていることがわかります。

3.png

ここで修飾子を制限する理由は非常に簡単です。

Android View システムにはタイプ セーフティがありません。開発者は通常、さまざまなレイアウト パラメータを試して、特定の親のコンテキストで考慮されるパラメータとその意味を発見します。

従来の XML ビュー システムでは、レイアウトのパラメーターに制限がないため、すべてのパラメーターを任意のレイアウトで使用できるため、いくつかの問題が発生します。軽く言えば、パラメータが無効で、無駄なパラメータが大量に書き込まれているだけですが、真剣に考えると、レイアウトの通常の使用に支障をきたす可能性があります。

もちろん、Modifier 修飾子の制限は Compose のアプリケーションの 1 つにすぎず、Compose にはスコープ制限の例が多数あります。次に例を示します。

4.png

上図ではスコープitem内でのみ使用でき、スコープ内でのみ使用できます。LazyListScopedrawRectDrawScope

もちろん、前に述べたように、スコープには関数やメソッドだけでなく、クラスのプロパティもあります。たとえば、DrawScopeという名前のプロパティがスコープに提供されておりsize、現在のキャンバス サイズはそれを通じて取得できます。

5.png

では、これらはどのように達成されるのでしょうか?

スコープ制限機能をカスタマイズする

原理

独自のスコープ関数の実装を開始する前に、まず原理を理解する必要があります。

ここではCompose を例にCanvas挙げます。

まず、Canvasの定義:

6.png

ここでは、onDraw の修飾子とラムダのCanvas2 つのパラメーターが受信されていることがわかります。このラムダのレシーバー (受信者)DrawScopeは、つまり、匿名関数 onDraw のスコープが制限されているDrawScopeということも意味します。DrawScopeスコープ内のプロパティ、メソッドなどを内部的に使用する匿名関数で使用できます。

これがどれほど神聖なものであるかを見てみましょうDrawScope:

7.png

これはインターフェースであり、いくつかの属性変数 (上で述べたようにsize) といくつかのメソッド (上で述べたようにdrawRect) を定義していることがわかります。

次に、このインターフェイスを実装し、特定の実装コードを記述します。

8.png

達成

要約すると、独自のスコープ制限を実装する場合は、大まかに 3 つのステップに分けることができます。

  1. インターフェイスをスコープとして記述する
  2. このインターフェースを実装する
  3. 公開されたメソッドでは、ラムダ パラメーター レシーバーは上記で定義されたインターフェイスを使用します。

例を挙げてみましょう。

次のような、新しいユーザーの操作をガイドするマスク ガイド レイヤーを Compose に実装するとします。

main_intro.gif

画像ソースイントロ-ショーケース-ビュー

ただし、ガイド レイヤー上のプロンプトが多様化できることを望んでいます。たとえば、テキスト プロンプト、画像プロンプト、さらにはビデオやアニメーションのプロンプトの再生もサポートできますが、これらのプロンプト項目がマスク レイヤーの外部で呼び出されるのは望ましくありません。 、マスク レイヤーの一部のパラメーターに依存しているため、外部から呼び出すとエラーが発生します。

現時点では、範囲制限の使用は非常に適切です。

まず、インターフェイスを作成します。

interface ShowcaseScreenScope {
    
    
    val isShowOnce: Boolean

    @Composable
    fun ShowcaseTextItem()
}

このインターフェースでは、ガイドレイヤーを一度だけ表示するかどうかを示す属性変数を定義し、ガイドレイヤーに文字列を表示するisShowOnceメソッドを定義し、同様に表示画像を示す定義も行うことができます。ShowcaseTextItemShowcaseImageItem

次に、このインターフェイスを実装します。

private class ShowcaseScopeImpl: ShowcaseScreenScope {
    
    

    override val isShowOnce: Boolean
        get() = TODO("在这里编写是否只显示一次的逻辑")

    @Composable
    override fun ShowcaseTextItem() {
    
    
        // 在这里写你的实现代码
        Text(text = "我是说明文字")
    }
}

インターフェイスの実装では、必要に応じて対応する実装ロジック コードを記述します。

最後に、外部呼び出しで使用できるコンポーザブルを作成します。

@Composable
fun ShowcaseScreen(content: @Composable ShowcaseScreenScope.() -> Unit) {
    
    
    // 在这里实现其他逻辑(例如显示遮罩)后调用 content
    // ……
    ShowcaseScopeImpl().content()
}

このコンポーザブルでは、マスク レイヤー UI の表示やアニメーションの表示など、最初に他のロジックを処理してから、ShowcaseScopeImpl().content()渡したサブアイテムを結合するために呼び出します。

最後に、使用する場合は次のように呼び出します。

ShowcaseScreen {
    
    
    if (!isShowOnce) {
    
    
        ShowcaseTextItem()
    }
}

もちろん、これShowcaseTextItem()と はスコープisShowOnce内にあり、外部から呼び出すことはできません。ShowcaseScreenScope

9.png

要約する

この記事では、Kotlin のスコープの概念と標準ライブラリのスコープ関数を簡単に紹介し、Compose でのスコープの適用まで拡張し、最後に実装原理を分析し、独自の Compose スコープ関数をカスタマイズする方法を説明します。

この記事の内容は比較的単純で、多くの知識ポイントは要点を押さえたものであり、多くの説明は必要ありません。読者には、記事の最後にある参考リンクにある他の著名人によって書かれた記事を読むことをお勧めします。

参考文献

  1. スコープとスコープ関数
  2. Kotlin DSL の動作: Compose のようなコードを作成する
  3. コンポーザブルのスコープを親コンポーザブルに設定する
  4. Compose 修飾子 - Compose での型の安全性

おすすめ

転載: blog.csdn.net/sinat_17133389/article/details/130894365