Статьи

Создание интерфейса с Котлиным и Анко

С самого начала разработки Android работа с пользовательским интерфейсом была XML-вещью. Хотя теоретически пользовательский интерфейс можно программировать с использованием Java, он не очень полезен. Недавно JetBrains представила Kotlin, современный язык для JVM, который может служить этой цели для Android.

Jetbrains объявил Anko более быстрым и простым стилем разработки для Android. Kotlin предлагает библиотеку Anko в качестве DSL (предметно-ориентированного языка) для разработки экрана Android. Быстрый пример:

Ниже приведен простой пользовательский интерфейс Android, состоящий из imageView и Button .

Вот его код Анко:

 verticalLayout{ imageView(R.drawable.anko_logo). lparams(width= matchParent) { padding = dip(20) margin = dip(15) } button("Tap to Like") { onClick { toast("Thanks for the love!") } } } 

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

Преимущества использования Anko

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

Теперь давайте создадим приложение, которое перечисляет задачи с использованием Anko Layout и Kotlin.

Вы можете найти репозиторий для этого приложения на GitHub.

Добавление библиотеки Anko в Android Studio

Взгляните на Streamline Android Java Code с Kotlin, чтобы узнать, как добавить Kotlin в ваш проект Android. Наряду с Kotlin нам нужно добавить зависимости Anko в app/build.gradle чтобы мы могли скомпилировать проект:

 compile 'org.jetbrains.anko:anko-sdk15:0.8.3' //sdk19, 21 and 23 are also available 

Эта зависимость может быть добавлена ​​в зависимости от того, для какого minSdkVersion вы нацеливаете свое приложение. Приведенный выше пример описывает, что он нацелен на 15 <= minSdkVersion <19. Вы можете проверить, какие другие библиотеки Anko доступны, которые могут вам понадобиться в репозитории Anko GitHub .
Мы также собираемся использовать следующие библиотеки:

 compile 'org.jetbrains.anko:anko-design:0.8.3' compile 'org.jetbrains.anko:anko-appcompat-v7:0.8.3' 

Вызов Anko Layout в действии

Мы больше не пишем макеты XML, поэтому нам не нужно ни вызывать представления XML, ни использовать метод findViewById() . Предположим, что нашим классом пользовательского интерфейса Anko является MainUI , тогда мы можем установить содержимое нашей деятельности с MainUI следующим образом:

 var ui = MainUI() //MainUI class replaces the XML layout ui.setContentView(this) //this refers to the Activity class 

Теперь создайте новый файл Kotlin MainActivity.kt и добавьте в него следующий код:

 import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import org.jetbrains.anko.*; import java.util.* class MainActivity : AppCompatActivity() { val task_list = ArrayList<String>() //list consisting of tasks override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) savedInstanceState?.let { val arrayList = savedInstanceState.get("ToDoList") task_list.addAll(arrayList as List<String>) } var adapter=TodoAdapter(task_list) //define adapter var ui = MainUI(adapter) //define Anko UI Layout to be used ui.setContentView(this) //Set Anko UI to this Activity } override fun onSaveInstanceState(outState: Bundle?) { outState?.putStringArrayList("ToDoList", task_list) super.onSaveInstanceState(outState) } } 

task_list — это ArrayList который будет заполнять TodoAdapter of Listview в нашем приложении. MainUI(adapter) — это наш файл пользовательского интерфейса Anko, в качестве аргумента которого используется адаптер класса TodoAdapter . Итак, давайте создадим класс TodoAdapter следующим.

Строительный адаптер для ListView

Класс TodoAdapter имеет list полей члена типа ArrayList<String> и расширяет BaseAdapter . Итак, нам нужно переопределить следующие 4 функции-члена:

 public int getCount() public Object getItem(int i) public long getItemId(int i) public View getView(int i, View view, ViewGroup viewGroup) 

В getView() мы getView() макет элемента списка, используя Anko.

 override fun getView(i : Int, v : View?, parent : ViewGroup?) : View { return with(parent!!.context) { //taskNum will serve as the S.No. of the list starting from 1 var taskNum: Int = i +1 //Layout for a list view item linearLayout { lparams(width = matchParent, height = wrapContent) padding = dip(10) orientation = HORIZONTAL //Task Number textView { id = R.id.taskNum text=""+taskNum textSize = 16f typeface = Typeface.MONOSPACE padding =dip(5) } //Task Name textView { id = R.id.taskName text=list.get(i) textSize = 16f typeface = DEFAULT_BOLD padding =dip(5) } } } } 
  • В этой функции мы возвращаем представление, содержащее элемент List, который представляет собой макет horizontalListView . Это достигается с with синтаксиса Kotlin’s, который позволяет нам вызывать одновременно много методов для экземпляра объекта.
  • Каждый элемент списка содержит два textview для отображения номера и имени задачи.
  • linearLayout , textView — это функции расширения. Расширения дают нам возможность включить любой класс с новой функциональностью.
  • text , textSize , typeface имеют свои методы получения и установки, определенные в классе android.widget.TextView . padding — это свойство расширения, определенное в Anko.

Продвигаясь вперед, нам нужно определить функции манипуляции для списка. Итак, у нас есть функции add(String) и delete(Int) в классе TodoAdapter . add(String) принимает имя задачи для добавления в качестве аргумента. Позиция элемента служит аргументом в функции delete(Int) как показано ниже:

 //function to add an item to the list fun add(text: String) { list.add(list.size, text) notifyDataSetChanged() //refreshes the underlying dataset } //function to delete an item from list fun delete(i:Int) { list.removeAt(i) notifyDataSetChanged() //refreshes the underlying dataset } 

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

 import android.graphics.Typeface import android.graphics.Typeface.DEFAULT_BOLD import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.LinearLayout.HORIZONTAL import org.jetbrains.anko.* import java.util.* class TodoAdapter(val list: ArrayList<String> = ArrayList<String>()) : BaseAdapter() { override fun getView(i : Int, v : View?, parent : ViewGroup?) : View { return with(parent!!.context) { //taskNum will serve as the S.No. of the list starting from 1 var taskNum: Int = i +1 //Layout for a list view item linearLayout { id = R.id.listItemContainer lparams(width = matchParent, height = wrapContent) padding = dip(10) orientation = HORIZONTAL textView { id = R.id.taskNum text=""+taskNum textSize = 16f typeface = Typeface.MONOSPACE padding =dip(5) } textView { id = R.id.taskName text=list.get(i) textSize = 16f typeface = DEFAULT_BOLD padding =dip(5) } } } } override fun getItem(position : Int) : String { return list[position] } override fun getCount() : Int { return list.size } override fun getItemId(position : Int) : Long { //can be used to return the item's ID column of table return 0L } //function to add an item to the list fun add(text: String) { list.add(list.size, text) notifyDataSetChanged() } //function to delete an item from list fun delete(i:Int) { list.removeAt(i) notifyDataSetChanged() } } 

Обратите внимание, что мы должны импортировать org.jetbrains.anko.* Чтобы использовать Anko DSL в наших файлах классов.

Проектирование экрана дел

Anko предоставляет нам удобство использования пользовательского интерфейса для Activity в отдельном классе Kotlin. Таким образом, каждый экран можно рассматривать как пару UI-Activity классов Kotlin. Этот класс пользовательского интерфейса разработан путем расширения возможностей интерфейса AnkoComponent<T> определенного в пакете org.jetbrains.anko . Наряду с этим интерфейсом JetBrains предлагает функцию предварительного просмотра макета DSL бесплатно. Вот как Anko DSL Layout Preview выглядит в Android Studio:

(Источник: blog.jetbrains.com)
DSL Layout Preview

Соответствующий плагин для Anko Preview можно скачать здесь . Обратите внимание, что на момент написания этой статьи Anko DSL Preview для Android Studio 2.2 был указан как открытый выпуск .
Возвращаясь к To-do-App, теперь мы спроектируем класс MainUI который содержит список всех задач. Класс MainUI расширяет интерфейс AnkoComponent<T> , где T относится к владельцу пользовательского интерфейса, то есть к деятельности, содержимым которой будет этот пользовательский интерфейс. В нашем случае владельцем является MainActivity который мы уже определили выше. Затем, во время инициализации, мы должны передать объект TodAadapter этому классу, так как этот адаптер будет использоваться для заполнения списка. Итак, MainUI класса MainUI становится:

 class MainUI(val todoAdapter : TodoAdapter) : AnkoComponent<MainActivity> 

Теперь нам нужно переопределить функцию createView() которая будет принимать объект AnkoContext в качестве аргумента и возвращать тип View :

 override fun createView(ui: AnkoContext<MainActivity>): View = with(ui) {} 

Определение пользовательского интерфейса, которое мы предоставляем внутри функции createView() возвращается в действие владельца, которое в данном случае является MainActivity . Итак, давайте приступим к кодированию createView() .

Шаг 1 — Проектирование главного экрана

Домой дел
Изначально на домашнем экране есть пустой список задач. Итак, у нас есть textView который просит пользователя создать список Todo для дня:

  return relativeLayout { //declaring the ListView var todoList : ListView? =null //textView displayed when there is no task val hintListView = textView("What's your Todo List for today?") { textSize = 20f }.lparams { centerInParent() } } 

centerInParent() — это вспомогательный метод для определения макета представления относительно центра по вертикали и по горизонтали.
Поскольку это приложение todo, его суть заключается в списке задач. Итак, здесь мы определяем наш listView :

 //listView verticalLayout { todoList=listView { //assign adapter adapter = todoAdapter } }.lparams { margin = dip(5) } 

todoAdapter является переменной- MainUI класса MainUI который мы определили в объявлении класса. Мы инициируем adapter listView со значением todoAdapter которое является TodoAdpater класса TodoAdpater и будет заполнять список.
Чтобы помочь пользователю добавить задачу, мы предоставили floatingActionButton кнопку floatingActionButton в правом нижнем углу главного экрана, следуя принципам Material design . Итак, в Anko мы программируем floatingActionButton кнопку floatingActionButton как:

 floatingActionButton { imageResource = android.R.drawable.ic_input_add }.lparams { //setting button to bottom right of the screen margin = dip(10) alignParentBottom() alignParentEnd() alignParentRight() gravity = Gravity.BOTTOM or Gravity.END } 
Шаг 2 — Отображение диалогового окна предупреждения AddTask

Anko предоставляет простой способ установить onClickListener для View . Итак, мы можем добавить onClickListener к floatingActionButton , добавив в него метод onClick() . Давайте создадим настраиваемое диалоговое окно, появляющееся при щелчке floatingActionButton кнопки floatingActionButton , которое попросит пользователя ввести задачу и добавить ее в список:

 floatingActionButton { imageResource = android.R.drawable.ic_input_add onClick { val adapter = todoList?.adapter as TodoAdapter alert { customView { verticalLayout { //Dialog Title toolbar { id = R.id.dialog_toolbar lparams(width = matchParent, height = wrapContent) backgroundColor = ContextCompat.getColor(ctx, R.color.colorAccent) title = "What's your next milestone?" setTitleTextColor(ContextCompat.getColor(ctx, android.R.color.white)) } val task = editText { hint = "To do task " padding = dip(20) } positiveButton("Add") { if(task.text.toString().isEmpty()) { toast("Oops!! Your task says nothing!") } else { adapter.add(task.text.toString()) showHideHintListView(todoList!!) } } } } }.show() } }.lparams { //setting button to bottom right of the screen margin = dip(10) alignParentBottom() alignParentEnd() alignParentRight() gravity = Gravity.BOTTOM or Gravity.END } 
  • alert{} — встроенная функция для создания диалогового окна Anko. По умолчанию в диалоговом окне Anko мы можем установить текстовое сообщение и предоставить postiveButton и postiveButton . Мы можем настроить диалог оповещения, используя customView .
  • verticalLayout — это linearLayout с ориентацией по вертикали.
  • Мы добавили заголовок в диалог, используя toolbar , тем самым настроив его. Обратите внимание, как мы назначаем цвет для представления в диалоговом окне: backgroundColor = ContextCompat.getColor(ctx, R.color.colorAccent)
    Здесь ctx ссылается на Context определенный в классе AlertDialogBuilder в пакете org.jetbrains.anko , который нам нужно передать в качестве аргумента, чтобы мы дали Android знать контекст, на который мы org.jetbrains.anko .
  • postiveButton() — это метод Anko Helper, который позволяет нам определить, что происходит, когда пользователь отправляет диалог. Здесь мы проверяем, не является ли task пустой, мы добавляем задачу в адаптер списка, используя метод add который мы определили в классе TodoAdapter .
  • Что такое showHideHintListView(todoList!!) ? Ну, это метод, который мы определили, чтобы скрыть textView hintListView который появляется на домашнем экране, чтобы освободить место для нашего списка. Когда listView пуст, мы показываем hintListView иначе мы его скрываем.

    // функция для отображения или скрытия над textView
    забавный showHideHintListView (listView: ListView) {
    if (getTotalListItems (listView)> 0) {
    hintListView.visibility = View.GONE
    } еще {
    hintListView.visibility = View.VISIBLE
    }
    }

Здесь getTotalListItems(listView) является методом- MainUI класса MainUI который возвращает количество переданных элементов в listView . Это нормальная функция Котлина:

 //function to get total number of items in list fun getTotalListItems(list: ListView?) = list?.adapter?.count ?: 0 

Наконец, нажав на floatingActionButton мы видим диалог:

Добавить задачу

И как только мы добавим несколько задач, мы увидим список задач:

Список заданий

Шаг 3 — Удаление задачи

Помните, что мы определили метод delete(Int) в классе TodoAdapter который удаляет элемент из списка. Теперь пришло время разработать интерфейс, который в свою очередь будет называть это
метод. Следуя шаблонам проектирования Android, мы можем представить параметры задачи, нажав и удерживая задачу. Итак, давайте определим, что происходит при onLongClick элемента списка. Вернитесь к определению listView и добавьте следующее:

 onItemLongClick { adapterView, view, i, l -> val options = listOf("Delete") selector("Task Options", options) { j -> var task=adapter.getItem(i) todoAdapter?.delete(i) //check if list is empty then show hint showHideHintListView(this@listView) longToast("Task ${task} has been deleted") } true } 
  • Здесь todoAdapter является объектом класса TodoAdapter . При вызове метода delete на adapter выдается сообщение о том, что оно могло измениться ко времени Итак, мы должны вызвать метод delete в todoAdapter . Другим вариантом является TodoAdapter adapter к TodoAdapter . И kotlin способ сделать это:
    (адаптер как TodoAdapter) ?. удалить (я)
    i ссылаюсь на позицию элемента, по которому щелкают.
  • selector — это своего рода dialog Anko, который дает нам возможность определить список интерактивных элементов. Здесь мы выбрали только один вариант, т.е. Удалить. Мы можем предоставить пользователю другие варианты на выбор. Ниже приведен пример:
 verticalLayout { todoList=listView { adapter = todoAdapter onItemLongClick { adapterView, view, i, l -> val options = listOf("Completed","In Progress","Not Started","Delete") selector("Task Options", options) { j -> if (j == 3) { var task=adapter.getItem(i) todoAdapter?.delete(i) showHideHintListView(this@listView) longToast("Task ${task} has been deleted") }else{ longToast("Task ${adapter.getItem(i).toString()} has been marked as \"${options[j]}\"") } } true } } }.lparams { margin = dip(5) } 

Обновление базы данных, уведомление пользователя или любой другой код может выполняться вместо тоста для расширения функциональности приложения To-do. Вот как selecter диалог selecter на экране:

Параметры задачи

Таким образом, полный код для класса MainUI :

 import android.support.v4.content.ContextCompat import android.view.Gravity import android.view.View import android.widget.FrameLayout import android.widget.ListView import org.jetbrains.anko.* import org.jetbrains.anko.appcompat.v7.toolbar import org.jetbrains.anko.design.floatingActionButton class MainUI(val todoAdapter: TodoAdapter) : AnkoComponent<MainActivity> { override fun createView(ui: AnkoContext<MainActivity>): View = with(ui) { return relativeLayout { var todoList : ListView? =null //textView displayed when there is no task val hintListView = textView("What's your Todo List for today?") { textSize = 20f }.lparams { centerInParent() } //function to show or hide above textView fun showHideHintListView(listView: ListView) { if (getTotalListItems(listView)>0) { hintListView.visibility = View.GONE } else { hintListView.visibility = View.VISIBLE } } //layout to display ListView verticalLayout { todoList=listView { adapter = todoAdapter onItemLongClick { adapterView, view, i, l -> val options = listOf("Completed","In Progress","Not Started","Delete") selector("Task Options", options) { j -> if (j == 3) { var task=adapter.getItem(i) todoAdapter?.delete(i) showHideHintListView(this@listView) longToast("Task ${task} has been deleted") }else{ longToast("Task ${adapter.getItem(i).toString()} has been marked as \"${options[j]}\"") } } true } } }.lparams { margin = dip(5) } //Add task FloatingActionButton at bottom right floatingActionButton { imageResource = android.R.drawable.ic_input_add onClick { val adapter = todoList?.adapter as TodoAdapter alert { customView { verticalLayout { toolbar { id = R.id.dialog_toolbar lparams(width = matchParent, height = wrapContent) backgroundColor = ContextCompat.getColor(ctx, R.color.colorAccent) title = "What's your next milestone?" setTitleTextColor(ContextCompat.getColor(ctx, android.R.color.white)) } val task = editText { hint = "To do task " padding = dip(20) } positiveButton("Add") { if(task.text.toString().isEmpty()) { toast("Oops!! Your task says nothing!") } else { adapter.add(task.text.toString()) showHideHintListView(todoList!!) } } } } }.show() } }.lparams { //setting button to bottom right of the screen margin = dip(10) alignParentBottom() alignParentEnd() alignParentRight() gravity = Gravity.BOTTOM or Gravity.END } }.apply { layoutParams = FrameLayout.LayoutParams(matchParent, matchParent) .apply { leftMargin = dip(5) rightMargin = dip(5) } } } //function to get total number of items in list fun getTotalListItems(list: ListView?) = list?.adapter?.count ?: 0 } 

Последние мысли

Мы не использовали какой-либо ресурс XML-макета при разработке этого приложения To-Do, но мы можем разработать приложение в похожем стиле. Anko снимает с себя бремя представления данных, реакции на взаимодействие с пользователем, подключения к базам данных и многого другого из действий или фрагментов в приложении. Кроме того, изоляция UI и классов Activity приближает приложение к архитектуре MVP (Model-View-Presenter) . Вы можете узнать о расширенных возможностях Anko здесь .
Несмотря на то, что у него есть несколько недостатков, таких как медленная компиляция и большой размер приложения, он обладает большим преимуществом, когда дело доходит до повторного использования, обслуживания и тестирования кода. Таким образом, Kotlin-Anko полностью готов для приложений Android.

Дайте мне знать ваше мнение об Анко в разделе комментариев.