Kotlin как вынести функции в отдельный файл
Перейти к содержимому

Kotlin как вынести функции в отдельный файл

  • автор:

Многофайловые программы на языке C. Объектный код и заголовочные файлы

Запуск gcc позволяет обработать файл с исходным кодом препроцессором и далее скомпилировать его. Однако при этом сам инструмент gcc не компилирует файл исходного кода в конечный исполняемый файл. Он компилирует его в объектный файл, после чего вызывает так называемый линковщик, или компоновщик. Но зачем надо сначала получать объектный файл, а потом из него уже исполняемый? Для программ, состоящих из одного файла, такой необходимости нет. Хотя при желании здесь также можно отказаться от компоновки, если выполнить команду gcc с ключом -c :

gcc -c hello.c

В результате получится файл с расширением *.o . Чтобы получить из объектного файла исполняемый, надо использовать ключ -o :

gcc -o hello hello.o

Для программ, состоящих из нескольких файлов исходного кода, получение объектных файлов является необходимым. Именно из них потом компонуется единственный исполняемый файл.

Компиляция программы, состоящей из нескольких файлов исходного кода

Рассмотрим пример. Пусть в одном файле определена пара функций, а в другом, содержащем функцию main , осуществляется их вызов.

#include void l2r(char **c, int n) { int i, j; for(i = 0 ; i  n; i++, c++) { for (j = 0; j  i; j++) printf("\t"); printf ("%s\n", *c); } } void r2l(char **c, int n) { int j; for(; n > 0; n--, c++) { for (j = 1; j  n; j++) printf("\t"); printf ("%s\n", *c); } }
#include #define N 5 int main() { char strs[N][10]; char *p[N]; int i; for(i = 0; i  N; i++) { scanf("%s", strs[i]); p[i] = &strs[i][0]; } l2r(p, N); r2l(p, N); }

В теле функции main заполняется массив, состоящий из строк, а также массив указателей на эти строки. Далее в функции l2r() и r2l() передаются ссылки на первый элемент массива указателей и значение символической константы N. Эти функции осуществляют вывод элементов массива строк с отступами.

Чтобы получить исполняемый файл этой программы, надо сначала получить объектные файлы из исходных:

gcc -c superprint.c gcc -c main.c

Тоже самое можно сделать за один вызов gcc :

gcc -c superprint.c main.c

Или даже вот так, если в каталоге находятся только файлы текущего проекта:

gcc -c *.c

В любом случае в каталоге появятся два объектных файла: superprint.o и main.o . Далее их можно скомпилировать в один исполняемый файл так:

gcc -o myprint main.o superprint.o
gcc -o myprint *.o

Обратите внимание на то, что в данном случае обязательно требуется указывать имя исполняемого файла. Такой вариант недопустим:

gcc -o main.o superprint.o

Если теперь запустить файл myprint , то программа будет ожидать ввода пяти слов, после чего выведет их на экран два раза по-разному: с помощью функций l2r() , потом r2l() :

Результат работы программы, скомпилированной из двух файлов

Задумаемся, каким образом в представленной выше программе код в теле main «узнает» о существовании функций l2r и r2l . Ведь в исходном коде файла main.c нигде не указано, что мы подключаем файл superprint.c , содержащий эти функции. Действительно, если попытаться получить из main.c отдельный исполняемый файл, т. е. скомпилировать программу без superprint.c :

gcc main.c

, то ничего не получится. Компилятор сообщит об ошибке вызова неопределенных идентификаторов. Получить из файла superprint.c отдельный исполняемый файл вообще невозможно, т.к. там отсутствует функция main() . А вот получить из этих файлов отдельные объектные файлы можно. Представим, что одни объектные файлы как бы «выставляют наружу» имена определенных в них функций и глобальных переменных, а другие — вызовы этих имен из тел других функций. Дальше объектные файлы «ожидают», что имена будут связаны с их вызовами. Связывание происходит при компиляции исполняемого файла из объектных.

Создание заголовочных файлов

Продолжим разбирать приведенную выше программу. Что будет, если в функции main осуществить неправильный вызов функций l2r() и r2l() ? Например, указать неверное количество параметров. В таком случае создание объектных файлов пройдет без ошибок, и скорее всего удастся получить исполняемый файл; но вот работать программа будет неправильно. Такое возможно потому, что ничего не контролирует соответствие вызовов прототипам (объявлениям) функций.

Куда правильней сообщать о неверном вызове функций уже при получении объектного файла. Поэтому хотя можно обойтись и без этого, но очень желательно сообщать функции main прототипы функций, которые из нее вызываются. Это можно сделать, прописав объявления функций в файле main.c :

void l2r (char **c, int n); void r2l (char **c, int n); main () 

Теперь, если мы передадим неправильные параметры, ошибка возникнет уже на этапе получения объектных файлов.

А теперь представим, что программа у нас несколько больше и содержит десяток файлов исходного кода. Файл aa.c требует функций из файла bb.c , dd.c , ee.c . В свою очередь dd.c вызывает функции из ee.c и ff.c , а эти два последних файла активно пользуются неким файлом stars.c и одной из функций в bb.c . Программист замучится сверять, что чего вызывает откуда и куда, где и какие объявления надо прописывать. Поэтому все прототипы (объявления) функций проекта, а также совместно используемые символические константы и макросы выносят в отдельный файл, который подключают к каждому файлу исходного кода. Такие файлы называются заголовочными; с ними мы уже не раз встречались. В отличие от заголовочных файлов стандартной библиотеки, заголовочные файлы, которые относятся только к вашему проекту, при подключении к файлу исходного кода заключаются в кавычки, а не скобки. Об этом упоминалось в предыдущем уроке.

Итак, более грамотно будет не добавлять объявления функций в файл main.c , а создать заголовочный файл, например, myprint.h и поместить туда прототипы функций l2r и r2l . При этом в файле main.c следует прописать директиву препроцессора:

#include "myprint.h"

В принципе смысла подключать myprint.h к файлу superprint.c в данном случае нет, т.к. последний не использует никаких сторонних функций, кроме стандартной библиотеки. Но если планируется расширять программу и есть вероятность, что в файле superprint.c будут вызываться сторонние для него функции, то будет надежней сразу подключить заголовочный файл.

Обратим внимание еще на один момент. Стоит ли в описанном в этом уроке примере выносить константу N в заголовочный файл? Здесь нельзя дать однозначный ответ. Если ее туда вынести, то она станет доступна в обоих файлах, и поэтому можно изменить прототипы функций так, чтобы они принимали только один параметр (указатель), а значение N будет известно функциям их заголовочного файла. Однако стоит ли так делать? В функции r2l второй параметр изменяется в процессе ее выполнения, делать это с константой будет невозможно. Придется переписывать тело функции. Кроме того, вдруг в последствии нам захочется использовать файл superprint.c в другом проекте, где будут свои порядки, и константы N в заголовочном файле не найдется.

В таком случае лучше N не выносить в заголовочный файл. Хотя имейте в виду, в каких-то проектах символическая константа может использоваться так часто и во множестве функций, что ее будет лучше поместить в заголовочный файл.

Особенности использования глобальных переменных

Если можно отказаться от использования глобальных переменных, то лучше это сделать. Желательно стремиться к тому, чтобы любой файл проекта, скажем так, «не лез к соседу за данными, а сосед не разбрасывал эти данные в виде глобальных переменных». Обмен данными между функциями следует осуществлять путем передачи аргументов и возврата значений с помощью оператора return . (Массивов это не касается.)

Однако в языке программирования C есть проблема. С помощью return можно вернуть только одно значение. Но могут быть случаи, когда функция должна изменить несколько переменных (здесь не имеются ввиду элементы массива). В таком случае без глобальных переменных обойтись сложно.

  • Если в файле aa.c объявлена переменная за пределами любой функции (например, так: int count ), то она является глобальной для всех файлов проекта. Чтобы получить значение этой переменной в файле aa.c достаточно просто указать ее имя (если в функции нет локальной переменной с тем же именем). Чтобы получить значение из других файлов, надо указать, что имеется в виду глобальная переменная, а не локальная. Делается это с помощью ключевого слова extern (например, extern count ).
  • Бывают ситуации, когда в одном файле для нескольких содержащихся в нем функций нужна глобальная переменная. Но эта переменная не должна быть доступна функциям, содержащимся в других файлах. В таком случае глобальная переменная объявляется с ключевым словом static (например, static int count ). Тем самым мы как бы скрываем глобальную переменную.

Создайте две глобальные переменные в одном файле. В другом файле напишите функцию, которая меняет их значение.

Курс с решением задач:
pdf-версия

Функции

В Kotlin функции объявляются с помощью ключевого слова fun .

fun double(x: Int): Int

Использование функций

При вызове функции используется традиционный подход:

val result = double(2) 

Для вызова вложенной функции используется знак точки.

Stream().read() //создаёт экземпляр класса Stream и вызывает read() 

Параметры

Параметры функции записываются аналогично системе обозначений в языке Pascal — имя: тип. Параметры разделены запятыми. Каждый параметр должен быть явно указан.

fun powerOf(number: Int, exponent: Int): Int < /*. */ >

Вы можете использовать завершающую запятую при объявлении параметров функции.

fun powerOf( number: Int, exponent: Int, // завершающая запятая ) < /*. */ >

Аргументы по умолчанию

Параметры функции могут иметь значения по умолчанию, которые используются в случае, если аргумент функции не указан при её вызове. Это позволяет снизить уровень перегруженности кода.

fun read( b: ByteArray, off: Int = 0, len: Int = b.size, ) < /*. */ >

Значения по умолчанию указываются после типа знаком = .

Переопределённые методы всегда используют те же самые значения по умолчанию, что и их базовые методы. При переопределении методов со значениями по умолчанию в сигнатуре эти параметры должны быть опущены.

open class A < open fun foo(i: Int = 10) < /*. */ >> class B : A() < override fun foo(i: Int) < /*. */ >// значение по умолчанию указать нельзя > 

Если параметр по умолчанию предшествует параметру без значения по умолчанию, значение по умолчанию можно использовать только при вызове функции с именованными аргументами.

fun foo( bar: Int = 0, baz: Int, ) < /*. */ >foo(baz = 1) // Используется значение по умолчанию bar = 0 

Но если последний аргумент после параметров по умолчанию — лямбда, вы можете передать её либо как именованный аргумент, либо за скобками.

fun foo( bar: Int = 0, baz: Int = 1, qux: () -> Unit, ) < /*. */ >foo(1) < println("hello") >// Используется значение по умолчанию baz = 1 foo(qux = < println("hello") >) // Используется оба значения по умолчанию: bar = 0 и baz = 1 foo < println("hello") >// Используется оба значения по умолчанию: bar = 0 и baz = 1 

Именованные аргументы

При вызове функции вы можете явно указать имена одного или нескольких аргументов. Это может быть полезно, когда у функции большой список аргументов, и сложно связать значение с аргументом, особенно если это логическое или null значение.

При явном указывании имен аргументов в вызове функции, вы можете свободно изменять порядок их перечисления, и, если вы хотите использовать их значения по умолчанию, вы можете просто пропустить эти аргументы.

Рассмотрим следующую функцию reformat() , которая имеет 4 аргумента со значениями по умолчанию:

fun reformat( str: String, normalizeCase: Boolean = true, upperCaseFirstLetter: Boolean = true, divideByCamelHumps: Boolean = false, wordSeparator: Char = ' ', ) < /*. */ >

При её вызове, вам не нужно явно указывать все имена аргументов.

reformat( "String!", false, upperCaseFirstLetter = false, divideByCamelHumps = true, '_' ) 

Вы можете пропустить все аргументы со значением по умолчанию.

reformat("This is a long String!") 

Вы также можете пропустить не только все аргументы со значениями по умолчанию, но и лишь некоторые из них. Однако после первого пропущенного аргумента вы должны указывать имена всех последующих аргументов.

reformat("This is a short String!", upperCaseFirstLetter = false, wordSeparator = '_') 

Вы можете передать переменное количество аргументов ( vararg ) с именами, используя оператор spread .

fun foo(vararg strings: String) < /*. */ >foo(strings = *arrayOf("a", "b", "c")) 

On the JVM: You can’t use the named argument syntax when calling Java functions because Java bytecode does not > always preserve the names of function parameters. —>

В JVM: синтаксис именованных аргументов не может быть использован при вызове Java функций, потому как байт-код Java не всегда сохраняет имена параметров функции.

Функции с возвращаемым типом Unit

Если функция не возвращает никакого полезного значения, её возвращаемый тип — Unit . Unit — тип только с одним значением — Unit . Это значение не нуждается в явном указании возвращения функции.

fun printHello(name: String?): Unit < if (name != null) println("Hello $name") else println("Hi there!") // `return Unit` или `return` необязательны >

Указание типа Unit в качестве возвращаемого значения тоже не является обязательным. Код, написанный выше, и следующий код совершенно идентичны:

fun printHello(name: String?) < /*. */ >

Функции с одним выражением

Когда функция возвращает одно единственное выражение, фигурные скобки < >могут быть опущены, и тело функции может быть описано после знака = .

fun double(x: Int): Int = x * 2 

Явное объявление возвращаемого типа является необязательным, когда он может быть определен компилятором.

fun double(x: Int) = x * 2 

Явные типы возвращаемых значений

Функции с блочным телом всегда должны иметь явно указанный возвращаемый ими тип данных, если только они не предназначены для возврата Unit , тогда указание типа возвращаемого значения необязательно.

Kotlin самостоятельно не вычисляет тип возвращаемого значения для функций с блочным телом, потому что подобные функции могут иметь сложную структуру, и возвращаемый тип будет неочевидным для читающего этот код человека (иногда даже для компилятора).

Нефиксированное число аргументов (varargs)

Параметр функции (обычно для этого используется последний) может быть помечен модификатором vararg .

fun asList(vararg ts: T): List < val result = ArrayList() for (t in ts) // ts - это массив (Array) result.add(t) return result > 

Это позволит указать несколько значений в качестве аргументов функции.

val list = asList(1, 2, 3) 

Внутри функции параметр с меткой vararg и типом T виден как массив элементов T , таким образом переменная ts в вышеуказанном примере имеет тип Array .

Только один параметр может быть помечен как vararg . Если параметр с именем vararg не стоит на последнем месте в списке аргументов, значения для последующих параметров могут быть переданы только с использованием синтаксиса именованных аргументов. В случае, если параметр является функцией, для этих целей можно вынести лямбду за фигурные скобки.

При вызове vararg -функции вы можете передать аргументы один за другим, например asList(1, 2, 3) , или, если у нас уже есть необходимый массив элементов и вы хотите передать его содержимое в функцию, используйте оператор spread (необходимо пометить массив знаком * ).

val a = arrayOf(1, 2, 3) val list = asList(-1, 0, *a, 4) 

Если вы хотите передать массив примитивного типа в vararg , вам необходимо преобразовать его в обычный (типизированный) массив с помощью функции toTypedArray() .

val a = intArrayOf(1, 2, 3) // IntArray - массив примитивного типа val list = asList(-1, 0, *a.toTypedArray(), 4) 

Инфиксная запись

Функции, помеченные ключевым словом infix , могут вызываться с использованием инфиксной записи (без точки и скобок для вызова). Инфиксные функции должны соответствовать следующим требованиям:

  • Они должны являться членом другой функции или расширения;
  • В них должен использоваться только один параметр;
  • Параметр не должен принимать переменное количество аргументов и не должен иметь значения по умолчанию.
infix fun Int.shl(x: Int): Int < /*. */ >// вызов функции, с использованием инфиксной записи 1 shl 2 // то же самое, что 1.shl(2) 

Infix function calls have lower precedence than arithmetic operators, type casts, and the `rangeTo` operator. > The following expressions are equivalent: > * `1 shl 2 + 3` is equivalent to `1 shl (2 + 3)` > * `0 until n * 2` is equivalent to `0 until (n * 2)` > * `xs union ys as Set ` is equivalent to `xs union (ys as Set )` > > On the other hand, an infix function call’s precedence is higher than that of the boolean operators `&&` and `||`, `is`- > and `in`-checks, and some other operators. These expressions are equivalent as well: > * `a && b xor c` is equivalent to `a && (b xor c)` > * `a xor b in c` is equivalent to `(a xor b) in c` —>

  • 1 shl 2 + 3 эквивалентно 1 shl (2 + 3) ,
  • 0 until n * 2 эквивалентно 0 until (n * 2) ,
  • xs union ys as Set эквивалентно xs union (ys as Set) .
  • a && b xor c эквивалентно a && (b xor c) ,
  • a xor b in c эквивалентно (a xor b) in c .

Обратите внимание, что инфиксные функции всегда требуют указания как получателя, так и параметра. Когда вы вызываете метод на текущем приемнике, используя инфиксную запись, явно используйте this . Это необходимо для обеспечения однозначного синтаксического анализа.

class MyStringCollection < infix fun add(s: String) < /*. */ >fun build() < this add "abc" // Верно add("abc") // Верно //add "abc" // Не верно: получатель должен быть указан >> 

Область видимости функций

В Kotlin функции могут быть объявлены в самом начале файла, что значит, что вам необязательно создавать класс, чтобы воспользоваться его функцией (как в Java, C# или Scala). В дополнение к этому, функции в Kotlin могут быть объявлены локально, как функции-члены и функции-расширения.

Локальные функции

Kotlin поддерживает локальные функции, т.е. функции, вложенные в другие функции.

fun dfs(graph: Graph) < fun dfs(current: Vertex, visited: MutableSet) < if (!visited.add(current)) return for (v in current.neighbors) dfs(v, visited) >dfs(graph.vertices[0], HashSet()) > 

Локальная функция может иметь доступ к локальным переменным внешних по отношению к ним функций (типа closure). Таким образом, в примере, приведённом выше, visited может быть локальной переменной.

fun dfs(graph: Graph) < val visited = HashSet() fun dfs(current: Vertex) < if (!visited.add(current)) return for (v in current.neighbors) dfs(v) >dfs(graph.vertices[0]) > 

Функции-члены

Функции-члены — это функции, объявленные внутри классов или объектов.

class Sample < fun foo() < print("Foo") >> 

Функции-члены вызываются с использованием точки.

Sample().foo() // создаёт инстанс класса Sample и вызывает его функцию foo 

Для более подробной информации о классах и их элементах см. Классы и Наследование.

Функции-обобщения

Функции могут иметь обобщённые параметры, которые задаются треугольными скобками и помещаются перед именем функции.

fun singletonList(item: T): List  < /*. */ >

Для более подробной информации см. Обобщения.

Функции с хвостовой рекурсией

Kotlin поддерживает стиль функционального программирования, известный как «хвостовая рекурсия». Это позволяет использовать циклические алгоритмы вместо рекурсивных функции, но без риска переполнения стэка. Когда функция помечена модификатором tailrec и её форма отвечает требованиям компилятора, он оптимизирует рекурсию, оставляя вместо неё быстрое и эффективное решение этой задачи, основанное на циклах.

val eps = 1E-10 // этого достаточно, может быть 10^-15 tailrec fun findFixPoint(x: Double = 1.0): Double = if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x)) 

Этот код высчитывает fixpoint косинуса, который является математической константой. Он просто-напросто постоянно вызывает Math.cos , начиная с 1.0 до тех пор, пока результат не изменится, приняв значение 0.7390851332151611 для заданной точности eps . Получившийся код эквивалентен вот этому более традиционному стилю:

val eps = 1E-10 // этого достаточно, может быть 10^-15 private fun findFixPoint(): Double < var x = 1.0 while (true) < val y = Math.cos(x) if (Math.abs(x - y) < eps) return x x = Math.cos(x) >> 

Для соответствия требованиям модификатора tailrec , функция должна вызывать сама себя в качестве последней операции, которую она предпринимает. Вы не можете использовать хвостовую рекурсию, когда существует ещё какой-то код после вызова этой самой рекурсии. Также нельзя использовать её внутри блоков try / catch / finally или в open функциях. На данный момент хвостовая рекурсия поддерживается только в backend виртуальной машины Java (JVM) и в Kotlin/Native.

См. также:

  • Встроенные функции,
  • Функции-расширения,
  • Высокоуровневые функции и лямбды.

© 2015—2024 Open Source Community

Кotlin и Android - видимость функции из другого файла, а не MainActivity

некоторые функции хочу поместить в отдельный файл MyFunc.kt, а не в MainActivity.kt. Как сделать так, чтобы функции из MyFunc.kt можно было выбирать в конструкторе кнопок Design и вешать на свойство OnClick?

Отслеживать
17.9k 11 11 золотых знаков 25 25 серебряных знаков 57 57 бронзовых знаков
задан 31 июл 2021 в 16:46
117 1 1 серебряный знак 9 9 бронзовых знаков
Импортировать..
31 июл 2021 в 18:13

Добавьте подробностей в ваш вопрос, объясните, что такое "конструктор кнопок Design", как это выглядит? Приведите пример кода, который вы пытались написать, и что конкретно у вас не получилось сделать?

1 авг 2021 в 17:25

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Пусть файл MyFunc.kt с функцией hello() лежит в пакете com.example.utils :

package com.example.utils fun hello()

Чтобы использовать эту функцию в MainActivity.kt , сделаем импорт этой функции:

package com.example import com.example.utils.hello class MainActivity : AppCompatActivity() < override fun onCreate(savedInstanceState: Bundle?) < . val button = findViewById(. ) button.setOnClickListener < hello() >> > 

В одном файле может находится сразу несколько функций, каждую нужно импортировать отдельно. Если имя пакета совпадает с текущим, то функцию можно не импортировать.

Ресурсы в Jetpack Compose

Кроме файлов кода на языке Kotlin проект может включать дополнительные файлы, которые используются в приложении, например, файлы изображений, определения иконок, файлы xml и так далее. Подобные файлы называются ресурсами . Все ресурсы находятся в проекте в каталоге res .

Одним из наиболее применяемых типов ресурсов являются ресурсы строк. Они используются при выведении названия приложения, различного текста, например, текста кнопок и т.д. Например, нам нужно вывести в различных местах приложения один и тот же текст. Вместо того, чтобы по несколько раз определять его в коде Kotlin, мы можем определить его один раз в виде строкового ресурса и использовать в любом месте приложения.

По умолчанию строковые ресурсы хранятся в проекте в каталоге res/values . При создании проекта в этот каталог по умолчанию добавляется файл строковых ресурсов strings.xml .

Ресурсы строк в Jetpack Compose и Android

Если мы откроем файл, то мы найдем в нем строки наподобие следующих:

 helloapp  

Каждый отдельный строковый ресурс определяется с помощью элемента string , а его атрибут name содержит название ресурса. Так, в самом простом виде этот файл определяет один ресурс "app_name", который устанавливает название приложения и который в моем случае имеет значение "helloapp" (по умолчанию значение этого ресурса соответствует названию проекта). Но естественно мы можем определить любые строковые ресурсы.

Затем в приложении в файлах кода мы можем ссылаться на эти ресурсы через их идентификатор, который имеет следующий вид:

R.string.название_ресурса

Например, для обращения к определенному по умолчанию строковому ресурсу app_name применяется идентификатор

R.string.app_name

Чтобы получить строковый ресурс в коде Kotlin, применяется встроенная функция androidx.compose.ui.res.stringResource() , в которую передается идентификатор ресурса и которая возвращает строку в виде объекта String. Например, получим в коде ресурс app_name:

package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material.Text import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContent < Text( text = stringResource(R.string.app_name), fontSize = 28.sp ) >> >

В данном случае параметру text компонента Text передается строка из строкового ресурса app_name:

Функция stringResource в Kotlin и Jetpack Compose и Android

Подобным образом при необходимоси мы можем определять и свои ресурсы. Например, изменим файл res/values/strings.xml следующим образом:

 helloapp Hello METANIT.COM  

Здесь добавлен строковый ресурс "message", который имеет значение "Hello METANIT.COM"

Используем этот строковый ресурс в коде:

package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material.Text import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContent < Text( text = stringResource(R.string.message), fontSize = 28.sp ) >> >

Получение ресурса строк в stringResource в Kotlin и Jetpack Compose и Android

Добавление файла ресурсов строк

Хотя по умолчанию для ресурсов строк применяется файл strings.xml , но можно добавлять дополнительные файлы ресурсов в каталог проекта res/values . При этом достаточно соблюдать структуру файла: он должен иметь корневой узел и иметь один или несколько элементов .

Так, нажмем на папку res/values правой кнопкой мыши и в появившемся списке выберем пункт New -> Value Resource File :

Добавление ресурса строк в Jetpack Compose и Android Studio

После этого нам будет предложено определить для файла имя:

Add string resource in Jetpack Compose and Android Studio

Назовем, к примеру, headers.xml (название файла произвольное), а для всех остальных полей оставим значения по умолчанию. И в папку res/values будет добавлен новый файл headers.xml . Определим в нем пару ресурсов:

  Добро пожаловать Отправить  

И после этого мы также сможем использовать эти ресурсы в коде Kotlin, например:

package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContent < Column< Text( text = stringResource(R.string.welcome), fontSize = 28.sp ) Button(onClick=<>) < Text( text = stringResource(R.string.click_button), fontSize = 22.sp ) >> > > >

Форматирование строк

Функция stringResource() позволяет применять к ресурсам строк форматирование. Преимуществом форматирования является то, что мы можем определить общий щаблон и затем подставлять в него необходимые значения. Например, изменим файл strings.xml :

 helloapp Имя: %1$s. Возраст: %2$d. Компания: %3$s  

Здесь ресурс user_data представляет строку с форматированием. Так, она содержит такие символы как %1$s, %2$d и %3$ds. Что они означают? %1$s указывает, что это первый аргумент, а символ "s" говорит, что этот аргумент представляет строку. %2$d представляет второй аргумент, а символ "d" в конце указывает, что это будет целое число. Аналогично %3$s указывает, что это третий аргумент, который представляет строку.

Получим ресурс в коде Kotlin:

package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.material.Text import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContent < Column< Text( text = stringResource(R.string.user_data, "Tom", 37, "JetBrains"), fontSize = 28.sp ) Text( text = stringResource(R.string.user_data, "Bob", 41, "Google"), fontSize = 28.sp ) >> > >

Форматирование строк в функции stringResource в Kotlin в Jetpack Compose

Вызов функции stringResource(R.string.user_data, "Tom", 37, "JetBrains") получает ресурс "user_data" и в качестве последующих параметров передает в строку вставляемые значения. Так, вместо первого аргумента-строки в user_data вставляется второй аргумент функции - строка "Tom", вместо второго аргумента-числа в user_data вставляется число 37, а вместо третьего аргумента в user_data передается строка "JetBrains".

Ресурсы Plurals

Ресурсы Plurals представляют еще один вид набора строк, в которым применяется форматирование. Он предназначен для описания количества элементов. Для чего это надо? К примеру, возьмем существительное: нередко оно изменяет окончание в зависимости от числительного, которое с ним употребляется: 1 цветок, 2 цветка, 7 цветков. Для подобных случаев и используется ресурс plurals .

Посмотрим на примере. Добавим в папку res/values новый ресурс flowers.xml :

   %d цветок %d цветка %d цветков   

Для задания ресурса используется элемент . Его атрибут name хранит название ресурса.

Сами наборы строк вводятся дочерними элементами . Этот элемент имеет атрибут quantity , который имеет значение, указывающее, когда эта строка используется. Данный атрибут может принимать следующие значения:

  • zero : строка для количества в размере 0
  • one : строка для количества в размере 1 (для русского языка - для задания всех количеств, оканчивающихся на 1, кроме 11)
  • two : строка для количества в размере 2
  • few : строка для небольшого количества
  • many : строка для больших количеств
  • other : все остальные случаи

Причем в данном случае многое зависит от конкретного языка. А система сама позволяет определить, какое значение брать для того или иного числа.

Получим и используем этот ресурс в коде Kotlin:

package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.material.Text import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContent < val resources = LocalContext.current.resources Column< Text( text = resources.getQuantityString(R.plurals.flowers, 21, 21), fontSize = 22.sp ) Text( text = resources.getQuantityString(R.plurals.flowers, 7, 7), fontSize = 22.sp ) >> > >

На данный момент Jetpack Compose не поддерживает напрямую получение подобных ресурсов. Поэтому для их извлечения вначале необходимо получить текущий контекст через свойство LocalContext.current и затем обратиться к его свойству resource , которое представляет класс Resources и ассоциирован со всеми ресурсами приложения.

Далее с помощью метода getQuantityString класса Resources получаем значение ресурса. Первым параметром передаем идентификатор ресурса. Вторым параметром идет значение. для которого нужно найти нужную строку. Третий параметр представляет собой значение, которое будет вставляться на место плейсхолдера %d . То есть мы получаем строку для числа 21.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *