1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/scalalibs-scaloid

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Проще Android

Scaloid — это библиотека, которая упрощает ваш код для Android. Она делает ваш код легким для понимания и поддержки за счет использования языка Scala.

Например, следующий блок кода:

val button = new Button(context)
button.setText("Привет")
button.setOnClickListener(new OnClickListener() {
  def onClick(v: View) {
    Toast.makeText(context, "Привет!", Toast.LENGTH_SHORT).show()
  }
})
layout.addView(button)

упростится до:

SButton("Привет", toast("Привет!"))
```### Преимущества
 * **Пишите элегантные программы для Android**<br/>
   Простота  основной принцип, который обеспечивает программируемость и типизацию.
 * **Простота использования**<br/>
   Ознакомьтесь с [кратким руководством](https://github.com/pocorall/scaloid/wiki/Installation#wiki-quick-start)
 * **Совместимость с вашими старыми кодами**<br/>
   Вы можете [использовать Scaloid вместе со стандартными API Android на Java](https://github.com/pocorall/scaloid/wiki/Appendix#wiki-i-cant-use-scaloid-because-it-does-not-provide-a-functionality-x). Вы можете постепенно улучшать свои старые коды.
 * **Профессиональное качество**<br/>
   Это не игрушечный проект. Создатель Scaloid использует его для создания [приложения, скачанного миллионами раз](https://play.google.com/store/apps/details?id=com.soundcorset.client.android).### Демонстрационные примеры

Откройте один из этих проектов, чтобы начать новый проект:
 * [<b>Пример Hello World для Scaloid с использованием sbt</b>](https://github.com/pocorall/hello-scaloid-sbt) (рекомендован, он компилируется быстрее всего)
 * [<b>Пример Hello World для Scaloid с использованием Maven</b>](https://github.com/pocorall/hello-scaloid-maven)
 * [<b>Пример Hello World для Scaloid с использованием Gradle</b>](https://github.com/pocorall/hello-scaloid-gradle)

Узнайте, как можно использовать Scaloid в действии:

* [**Перенос приложения apidemos с использованием Scaloid**](https://github.com/pocorall/scaloid-apidemos)
* [**Список проектов, использующих Scaloid**](https://github.com/pocorall/scaloid/wiki/Appendix#wiki-list-of-projects-using-scaloid)
* [**Руководство Гастона Хилара**](http://www.drdobbs.com/mobile/developing-android-apps-with-scala-and-s/240161584) - [часть 1](http://www.drdobbs.com/mobile/developing-android-apps-with-scala-and-s/240161584) и [часть 2](http://www.drdobbs.com/mobile/developing-android-apps-with-scala-and-s/240162204)## Содержание
* [Основной принцип проектирования](#основной-принцип-проектирования)
* [Разметка UI без XML](#разметка-ui-без-xml)
  * [Контекст разметки](#контекст-разметки)
  * [Стили для программистов](#стили-для-программистов)
  * [Автоматический конвертер разметки](#автоматический-конвертер-разметки)
* [Управление жизненным циклом](#управление-жизненным-циклом)
* [Обработка асинхронных задач](https://github.com/pocorall/scaloid/wiki/Basics#wiki-asynchronous-task-processing)
* [Неявные преобразования](https://github.com/pocorall/scaloid/wiki/Basics#wiki-implicit-conversions)
  * [Конкретные слушатели](https://github.com/pocorall/scaloid/wiki/Basics#wiki-enriched-implicit-classes)
  * [База данных курсора](http://blog.scaloid.org/2014/02/simple-enhancements-on-accessing.html)
* [Характеристики](https://github.com/pocorall/scaloid/wiki/Basics#wiki-traits)
* [Умное логирование](https://github.com/pocorall/scaloid/wiki/Basics#wiki-logging)
* [Улучшенные гетеры/сетеры](#scala-getters-and-setters)
* [Классы](#классы)
  * [Концентрированный билдер диалога](#класс-alertdialogbuilder)
  * [Привлекательный ArrayAdapter](#класс-sarrayadapter)
  * [Динамическое управление настройками](#класс-preferences)  [<sub>`Читать в блоге`</sub>](http://blog.scaloid.org/2013/03/dynamicly-accessing-sharedpreferences.html)
  * [Конкретное привязывание служб](#класс-localservice)  [<sub>`Читать в блоге`</sub>](http://blog.scaloid.org/2013/03/introducing-localservice.html)## Другие ссылки  
 * [<b>Краткое руководство по началу работы</b>](https://github.com/pocorall/scaloid/wiki/Installation#wiki-quick-start)
 * [<b>Документация API</b>](http://docs.scaloid.org/)
 * [<b>Блог</b>](http://blog.scaloid.org/)
 * [<b>Twitter</b>](https://twitter.com/scaloid/)
 * [<b>Часто задаваемые вопросы</b>](https://github.com/pocorall/scaloid/wiki/Appendix#wiki-faqs-about-scaloid)
     * [Часто задаваемые вопросы о Scala на Android](https://github.com/pocorall/scaloid/wiki/Appendix#wiki-faqs-about-scala-on-android)
 * [<b>Интерьер Scaloid</b>](https://github.com/pocorall/scaloid/wiki/Inside-Scaloid)
 * [<b>Мы нанимаем!</b>](#мы-нанимаем!)

## Основной принцип дизайна

"Быть практически простым" является основным принципом Scaloid. Наиболее часто используемые вещи должны быть записаны короче, как это делается в [коде Хаффмана](https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D0%B3%D0%BB%D0%B8%D1%88%D0%B5%D0%BD%D0%B8%D0%B5_%D0%A5%D0%B0%D1%84%D1%84%D0%BC%D0%B0%D0%BD%D0%B0). Для этого я сначала наблюдал за программами на Android, которые я писал, и думал, какие части кода являются более фундаментальными, чем другие. Например, что самое важное в кнопках? Кнопкам должно быть видно что-то на них, такое как заголовок или изображение, поэтому кнопки создаются так: `SButton("Привет")`. Второе наиболее важное  это выполнение действия при нажатии: `SImageButton(R.drawable.hello, toast("Мир!"))`. А третье? Ответ может быть разным для разных людей, но я считаю, что частота повторения действий типа "нажать и удерживать" хороша: `SButton("Добавить", n += 1, 500)` увеличивает значение `n` каждые 500 миллисекунд, когда пользователь удерживает кнопку.## Создание макета интерфейса без использования XML
<p align="center"><img src="http://o-n2.com/verboseSimple.png"></p>

SDK Android использует XML для создания макетов интерфейсов пользователя. Однако XML считается всё ещё несколько громоздким и недостаточно программируемым. Scaloid собирает макеты интерфейса пользователя в стиле Scala DSL, таким образом достигая ясности и программируемости. Например, предположим старый XML-макет, показанный ниже:

```xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="wrap_content" android:padding="20dip">
    <TextView android:layout_width="match_parent"
            android:layout_height="wrap_content" android:text="Войти"
            android:layout_marginBottom="25dip" android:textSize="24.5sp"/>
    <TextView android:layout_width="match_parent"
            android:layout_height="wrap_content" android:text="ИД"/>
    <EditText android:layout_width="match_parent"
            android:layout_height="wrap_content" android:id="@+id/userId"/>
    <TextView android:layout_width="match_parent"
            android:layout_height="wrap_content" android:text="Пароль"/>
    <EditText android:layout_width="match_parent"
            android:layout_height="wrap_content" android:id="@+id/password"
            android:inputType="textPassword"/>
    <Button android:layout_width="match_parent"
            android:layout_height="wrap_content" android:id="@+id/signin"
            android:text="Войти"/>
    <LinearLayout android:orientation="horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
        <Button android:text="Помощь" android:id="@+id/help"
                android:layout_width="match_parent" android:layout_height="wrap_content"/>
        <Button android:text="Зарегистрироваться" android:id="@+id/signup"
                android:layout_width="match_parent" android:layout_height="wrap_content"/>
    </LinearLayout>
</LinearLayout>

сужается до:```scala new SVerticalLayout { STextView("Войти").textSize(24.5.sp).<<.marginBottom(25.dip).>> STextView("ID") SEditText() STextView("Пароль") SEditText() inputType TEXT_PASSWORD SButton("Войти") new SLinearLayout { SButton("Помощь") SButton("Регистрация") }.wrap.here }.padding(20.dip)


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

```scala
new SVerticalLayout {
  STextView("Войти").textSize(24.5.sp).<<.marginBottom(25.dip).>>
  STextView("ID")
  val userId = SEditText()
  STextView("Пароль")
  val pass = SEditText() inputType TEXT_PASSWORD
  SButton("Войти", signin(userId.text, pass.text))
  new SLinearLayout {
    SButton("Помощь", openUri("http://help.url"))
    SButton("Регистрация", openUri("http://signup.uri"))
  }.wrap.here
}.padding(20.dip)

И поскольку описание макета Scaloid — это просто Scala-код, он безопасен по типам.

Автоматический конвертер макета

Этот конвертер преобразует XML-макет Android в макет Scaloid:

http://layout.scaloid.org

Совет по миграции

Scaloid полностью совместим с устаревшими XML-макетами. Вы можете получить доступ к виджету, описанному в XML-макете, следующим образом:

onCreate {
  setContentView(R.layout.main)
  val name = find[EditText](R.id.name)
  // делайте что-то с `name`
}
```Описание макета, показанное выше, является высоко программируемым. Вы можете легко связать вашу логику с макетом:

```scala
new SVerticalLayout {
  STextView("Войти").textSize(24.5.sp).<<.marginBottom(25.dip).>>
  STextView("ID")
  val userId = SEditText()
  STextView("Пароль")
  val pass = SEditText() inputType TEXT_PASSWORD
  SButton("Войти", signin(userId.text, pass.text))
  new SLinearLayout {
    SButton("Помощь", openUri("http://help.url"))
    SButton("Регистрация", openUri("http://signup.uri"))
  }.wrap.here
}.padding(20.dip)

И поскольку описание макета Scaloid — это просто Scala-код, он безопасен по типам.

Автоматический конвертер макета

Этот конвертер преобразует XML-макет Android в макет Scaloid:

http://layout.scaloid.org

Совет по миграции

Scaloid полностью совместим с устаревшими XML-макетами. Вы можете получить доступ к виджету, описанному в XML-макете, следующим образом:

onCreate {
  setContentView(R.layout.main)
  val name = find[EditText](R.id.name)
  // делайте что-то с `name`
}

Адаптивный макет

Основательно, макет, написанный в Scaloid, это просто обычный Scala-код, поэтому вы можете свободно составлять макет согласно конфигурации устройства:

import org.scaloid.util.Configuration._

if(long) SButton("Эта кнопка отображается только при длинном экране "
  + "(ширина: "+ width + ", высота: " + height + ")")
if(landscape) new SLinearLayout {
  SButton("Кнопки для")
  SButton("горизонтального макета")
  if(dpi <= HDPI) SButton("У вас высокое разрешение экрана!")
}.here

Дополнительные детали см. в этом блог-посте:

Управление жизненным циклом

С использованием Android API регистрация и отмена регистрации BroadcastReceiver могут выполняться следующим образом:

var connectivityListener: BroadcastReceiver = null

def onResume() {
  super.onResume()
  // ...
  connectivityListener = new BroadcastReceiver {
    def onReceive(context: Context, intent: Intent) {
      doSomething()
    }
  } 
  registerReceiver(connectivityListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
}

def onPause() {
  unregisterReceiver(connectivityListener)
  // ...
  super.onPause()
}

В Scaloid эквивалентный код выглядит так:

broadcastReceiver(ConnectivityManager.CONNECTIVITY_ACTION) {
  doSomething()
}

Scaloid имеет гибкую архитектуру управления регистрацией и отменой регистрации ресурсов. Если этот код написан в сервисах, регистрация и отмена регистрации выполняются соответственно в методах onCreate и onDestroy. Если тот же код находится в активностях, регистрация и отмена регистрации выполняются соответственно в методах onResume и onPause. Это просто базовое поведение. Вы можете переопределить предпочтения, определяющие момент выполнения регистрации и отмены регистрации. Переопределение этого просто:

broadcastReceiver(ConnectivityManager.CONNECTIVITY_ACTION) {
  doSomething()
}(this, onStartStop)
```Затем приемник регистрируется при вызове `onStart` и отменяет регистрацию при вызове `onStop`.

#### `onDestroy` может быть вызван несколько раз!

Вы можете объявить поведение `onDestroy` в нескольких местах. Это значительно упрощает управление ресурсами. Предположим, что вам нужно открыть поток из файла:

```scala
def openFileStream(file: File): InputStream = {
  val stream = new FileInputStream(file)
  onDestroy(stream.close()) // автоматически закрывается при уничтожении активности!!
  stream
}

Метод onDestroy добавляет функцию в список задач, запускаемых при уничтожении активности. Поэтому мы просто получаем поток из openFileStream() и забываем о его освобождении. Другие состояния жизненного цикла (onCreate, onResume, onStop и т.д.) также можно обрабатывать аналогичным образом.

Дополнительная информация: Подробнее читайте в [этой статье блога](http://blog.scaloid.org/2 Yöntemler/2013/02/better-resource-releasing-in-android.html).

Асинхронная обработка задач

Android API предоставляет runOnUiThread() только для класса Activity. Scaloid предоставляет версию Scala для runOnUiThread(), которая работает везде кроме Activity. Вместо:

activity.runOnUiThread {
  new Runnable() {
    def run() {
      debug("Запуск только в классе Activity")
    }
  }
}

в Scaloid используется следующий синтаксис:

runOnUiThread(debug("Запуск в любом контексте"))
```Выполнение задачи асинхронно и уведомление потока пользовательского интерфейса  это очень часто используемый шаблон. Хотя Android API предоставляет помощный класс `AsyncTask`, реализация такой простой идеи всё ещё затруднена даже при использовании Scala:```scala
new AsyncTask[String, Void, String] {
  def doInBackground(params: Array[String]): String = {
    doAJobTakeSomeTime(params)
  }

  override def onPostExecute(result: String): Unit = {
    alert("Готово!", result)
  }
}.execute("параметр")

Используя scala.concurrent.Future, асинхронная задача выше может быть переписана следующим образом:

Future {
  val result = doAJobTakeSomeTime(params)
  runOnUiThread(alert("Готово!", result))
}

Когда вы не хотите создавать сложные взаимодействия с пользовательским интерфейсом, но просто хотите отобразить что-то, вызывая одну методу Scaloid (например, alert, toast, и spinnerDialog), Scaloid автоматически выполняет runOnUiThread за вас. Поэтому, блок кода выше можно свести к следующему:

Future {
  alert("Готово!", doAJobTakeSomeTime(params))
}

Это большой плюс, так как он четко демонстрирует вашу идею.

Как мы избавились от AsyncTask, мы также можем устранить все остальные помощники Java для асинхронной работы, такие как AsyncQueryHandler и AsyncTaskLoader. Сравните с оригинальным кодом на Java и Scala версией примерного приложения ApiDemos.

Использование Future является лишь одним примером обработки асинхронных задач в Scaloid. Вы можете свободно использовать любые современные средства управления задачами. ```Дополнительная информация: Обратитесь к этому блогу для важных соображений при использовании Future в Android.

Неявные преобразования

Scaloid использует несколько неявных преобразований. Некоторые доступные неявные преобразования показаны ниже:

Преобразование URI

String => Uri

Функции, такие как воспроизведение звука play() или открытие URI openUri(), принимают экземпляр типа Uri в качестве параметра. Однако мы часто имеем URI в виде строки типа String. Scaloid выполняет неявное преобразование String в Uri. Поэтому вы можете свободно использовать String, когда воспроизводите звуковой сигнал:

play("content://media/internal/audio/media/50")

или открываете URI:

openUri("http://scaloid.org")

либо где угодно еще.

Альтернативно, можно явно указать преобразование следующим образом:

val uri:Uri = "http://scaloid.org".toUri

Преобразование единиц

Единицы dip и sp могут быть преобразованы в пиксели.

val inPixel:Int = 32.dip
val inPixel2:Int = 22.sp

Обратное преобразование также возможно — из пикселей в dip и sp.

val inDip:Double = 35.px2dip
val inSp:Double = 27.px2sp

Идентификаторы ресурсов

Scaloid предоставляет несколько неявных преобразований, которые конвертируют идентификатор ресурса типа Int в CharSequence, Array[CharSequence], Array[String], Drawable и Movie. Например:```scala def toast(msg:CharSequence) = ...

toast(R.string.my_message) // работает благодаря неявному преобразованию!


Хотя Scaloid предоставляет эти преобразования неявно, во многих случаях требуется явное преобразование. В этом случае методы `r2...` доступны для типов `Int`:

```scala
warn("Будет отображено содержимое ресурса: " + R.string.my_message.r2String)

Сейчас поддерживаются r2Text, r2TextArray, r2String, r2StringArray, r2Drawable и r2Movie.

Дополнительная информация:

Контекст как неявный параметр

Многие методы в API Android требуют экземпляр класса Context. Предоставление этого контекста для каждого вызова метода приводит к громоздкому коду. Мы используем неявный параметр для решения этой проблемы. Просто объявите неявное значение, представляющее текущий контекст:

implicit val ctx = ...

или просто расширяйте трейт SContext, который это делает за вас. Тогда код, требующий Context, становится намного проще, например:

Намерение
new Intent(context, classOf[MyActivity])

сужается до:

SIntent[MyActivity]

Когда метод принимает Intent в качестве первого параметра, в котором мы хотим передать новый объект Intent, этот параметр может быть опущен. Например:

startService(new Intent(context, classOf[MyService]))
stopService(new Intent(context, classOf[MyService]))
```сужается до:

```scala
startService[MyService]
stopService[MyService]

или

val intent = // инициализируйте Intent и установите некоторые атрибуты
intent.start[MyActivity]

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

new Intent().putExtra("valueA", valueA).putExtra("valueB", valueB).putExtra("valueC", valueC)

сужается до:

new Intent().put(valueA, valueB, valueC)

Toast

toast("Привет, там!")

Если вы хотите более длительный Toast:

longToast("длинный Toast")

Диалог

ProgressDialog.show(context, "Диалог", "работаем...", true)

сужается до:

spinnerDialog("Диалог", "работаем...")

Когда вы вызываете toast, longToast или spinnerDialog из нити, не связанной с UI, вам не нужно беспокоиться о многониточности. Пример Toast, показанный выше, эквивалентен следующему коду на Java:

activity.runOnUiThread(new Runnable() {
    public void run() {
        Toast.makeText(activity, "Привет, там!", Toast.LENGTH_SHORT).show();
    }
});

Ожидаемый Intent

PendingIntent.getActivity(context, 0, new Intent(context, classOf[MyActivity]), 0)
PendingIntent.getService(context, 0, new Intent(context, classOf[MyService]), 0)

сужается до:

pendingActivity[MyActivity]
pendingService[MyService]

Открытие URI

Это открывает веб-браузер (или другое представление, назначенное протоколу http).

openUri("http://scaloid.org")

Системные службы

Получение объектов системных служб становится намного проще. В следующем примере старого кода:

val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE).asInstanceOf[Vibrator]
vibrator.vibrate(500)
```сужается до:

```scala
vibrator.vibrate(500)

Под капотом Scaloid определяет функцию vibrator следующим образом:

def vibrator(implicit ctx: Context) = ctx.getSystemService(Context.VIBRATOR_SERVICE).asInstanceOf[Vibrator]

Все доступные аксессуары системных служб в Android API уровня 8 определены (например, audioManager, alarmManager, notificationManager и т.д.). Название аксессуара системной службы совпадает с названием его класса, за исключением того, что первая буква приведена в нижний регистр.

Улучшенные неявные классы

Предположим, что существует Android-класс Foo, например, Scaloid определяет неявное преобразование Foo => RichFoo. Класс RichFoo определяет дополнительные методы для более удобного доступа к Foo. Это распространенный шаблон в Scala для расширения существующего API (см. шаблон pimp-my-library). В этом разделе описываются различные возможности, добавленные к существующим Android API-классам.

Слушатели

Android API определяет множество интерфейсов слушателей для уведомлений обратного вызова. Например, View.OnClickListener используется для получения уведомлений при клике на представление:

find[Button](R.id.search).setOnClickListener(new View.OnClickListener {
  def onClick(v: View) {
    openUri("http://scaloid.org")
  }
})

Scaloid предоставляет сокращение, которое значительно уменьшает длину кода:

find[Button](R.id.search).onClick(openUri("http://scaloid.org"))
```Все остальные методы, добавляющие слушателей, такие как `.onKey()`, `.onLongClick()` и `.onTouch()`, также определены.

Некоторые соглашения, которые мы используем для названия методов:

* Мы опускаем `set...`, `add...` и `...Listener` из названия метода, так как они менее значимы.<br/> 
  Например, `.setOnKeyListener()` становится `.onKey()`.
* Каждый метод имеет две версии параметров, одна ленивая, а другая  функция, которая имеет все параметры, определенные в оригинальном Android API. Например, эти два использования допустимы:

```scala
button.onClick(info("pressed"))
button.onClick((v: View) => info("pressed a button " + v))
  • Методы add... сокращаются до метода +=, если это не слушатель-добавитель.
    Например, layout.addView(button) становится layout += button.

Множество методов-слушателей

Методы beforeTextChanged(), onTextChanged() и afterTextChanged() определены в RichTextView, который может быть неявно преобразован из TextView. Это более удобно, чем использование TextWatcher напрямую. Например:

inputField.beforeTextChanged(saveTextStatus())

эквивалентно:

inputField.addTextChangedListener(new TextWatcher {
  override def beforeTextChanged(s: CharSequence, start: Int, before: Int, count: Int): Unit = {
    saveTextStatus()
  }
  
  override def onTextChanged(s: CharSequence, start: Int, before: Int, count: Int): Unit = {}
  
  override def afterTextChanged(s: Editable): Unit = {}
})

Кроме того, мы переопределяем beforeTextChanged() с полными параметрами, определёнными в оригинальном слушателе:

inputField.beforeTextChanged((s: CharSequence, _, _) => saveText(s))

Другие слушатели в Android API также могут быть доступны таким образом.## Контекст макета

В Android API информация о макете хранится в объекте View через метод View.setLayoutParams(ViewGroup.LayoutParams). Конкретный тип параметров передачи в этот метод определяется типом объекта ...Layout, содержащего объект View. Например, рассмотрим следующий пример кода на Java:

LinearLayout layout = new LinearLayout(context);
Button button = new Button(context);
button.setText("Нажми");
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams();
params.weight = 1.0f;  // устанавливает некоторое значение
button.setLayoutParams(params);
layout.addView(button);

Поскольку кнопка добавлена в LinearLayout, параметры макета должны быть типа LinearLayout.LayoutParams, в противном случае может возникнуть ошибка выполнения . Тем временем, Scaloid устраняет эту нагрузку, при этом сохраняя строгую типизацию LayoutParams. Код ниже эквивалентен предыдущему коду на Java:

val layout = new SLinearLayout {
  SButton("Нажми").<<.Weight(1.0f).>>
}

В анонимном конструкторе SLinearLayout Scaloid предоставляет неявную функцию, называемую "контекстом макета". Это влияет на тип возвращаемого значения метода << определенного в классе SButton. Если мы используем SFrameLayout как контекст макета, метод << возвращает FrameLayout.LayoutParams, который не имеет метода Weight. Поэтому следующий код приведет к синтаксической ошибке :

val layout = new SFrameLayout {
  SButton("Нажми").<<.Weight(1.0f).>>   // Синтаксическая ошибка на Weight()
}

Сравнительно с описанием макета на XML, макет Scaloid прост и безопасен по типам.Метод << перегружается с параметрами <<(ширина: Int, высота: Int), что позволяет задать размер компонента представления. Например:

SButton("Клик").<<(40.dip, WRAP_CONTENT)

Оператор new и метод apply

Обычно компоненты типа View ссылаются несколько раз в одном Activity. Например:

lazy val button = new SButton() text "Клик"
onCreate {
  contentView = new SLinearLayout {
    button.here
  }
}
// ... использование кнопки где-то в других методах (например, изменение текста или добавление слушателей)

Префиксированные классы в Scaloid (например, SButton) имеют спутниковый объект, который реализует методы apply, создающие новый компонент. Эти методы также добавляют компонент в контекст макета, окружающий его. Поэтому блок кода из примера выше:

button = new SButton() text "Клик"
button.here

эквивалентен следующему:

button = SButton("Клик")

Поскольку методы apply имеют доступ к контексту макета, они не могут вызываться вне этого контекста. В этом случае используйте оператор new.

Метод >>

Как мы уже отмечали, метод << возвращает объект, являющийся типом ViewGroup.LayoutParams:

val params = SButton("Клик").<<   // тип LayoutParams

Этот класс предоставляет некоторые сеттеры для цепочки вызовов:

val params = SButton("Клик").<<.marginBottom(100).marginLeft(10)   // тип LayoutParams

Если вы хотите использовать объект SButton снова, Scaloid предоставляет метод >>, возвращающий обратно к объекту:```scala val button = SButton("Клик").<<.marginBottom(100).marginLeft(10).>> // тип SButton


### Вложенный контекст макета

Когда контекст макета является вложенным, применяется внутренний контекст:

```scala
val layout = new SFrameLayout {
  new SLinearLayout {
    SButton("Клик").<<.Weight(1.0f).>>   // в контексте SLinearLayout
  }.here
}

Методы fill, wrap, wf и fw

Когда вы получаете LayoutParams из <<, значения свойств width и height по умолчанию равняются width = FILL_PARENT и height = WRAP_CONTENT. Вы можете переопределить эти значения при необходимости:

SButton("Клик").<<(FILL_PARENT, FILL_PARENT)

Это очень часто используемый шаблон. Поэтому Scaloid предоставляет более короткие варианты:

SButton("Клик").<<.fill

Если вы хотите, чтобы элемент View был обёрнут,

SButton("Кликнуть").<<(WRAP_CONTENT, WRAP_CONTENT)

Это также может быть сокращено следующим образом:

SButton("Кликнуть").<<.wrap

Аналогично, <<(WRAP_CONTENT, FILL_PARENT) и <<(FILL_PARENT, WRAP_CONTENT) могут быть сокращены как <<.wf и <<.fw соответственно.

Поскольку паттерн <<.wrap.>> встречается так часто в реальном коде Android, допускается удаление .<< и .>> в этом случае:

SButton("Кликнуть").wrap    // возвращает тип SButton

Этот паттерн также применим для методов .fill, .fw и .wf.

Стили для программистов

Названия атрибутов

Scaloid следует названиям атрибутов XML в API Android с некоторыми улучшениями.Для атрибутов XML свойства, связанные с макетом, имеют префикс layout_, но Scaloid позволяет использовать их без этого префикса. Для булевых атрибутов значение по умолчанию — false. Однако, если атрибут объявлен явно без параметров, Scaloid считает его значением true. Например:```scala new SRelativeLayout { STextView("привет").<<.centerHorizontal.alignParentBottom.>> }


Scaloid игнорирует лишнее `"true"` для атрибута `centerHorizontal`. Эквивалентное описание макета XML для `TextView` выглядит так:

```xml
<TextView
    android:id="@+id/textViewId"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_alignParentBottom="true"
    android:text="привет"/>

Для методов макета, названных четырьмя направлениями (например, ...Top, ...Right, ...Bottom и ...Left), Scaloid предоставляет дополнительные методы, которые указывают все свойства сразу. Например, поскольку Android XML макет определяет свойства margin... (marginTop(v:Int), marginRight(v:Int), marginBottom(v:Int) и marginLeft(v:Int)), Scaloid предоставляет дополнительные методы margin(top:Int, right:Int, bottom:Int, left:Int) и margin(amount:Int), которые можно использовать следующим образом:

STextView("привет").<<.margin(5.dip, 10.dip, 5.dip, 10.dip)

или

STextView("привет").<<.margin(10.sp)  // присваивает одинаковое значение во всех направлениях

SDK Android ввел стили для повторного использования общих свойств в макете XML. Мы уже отмечали, что XML является громоздким. Чтобы применять стили в Scaloid, вам не нужно учиться новому синтаксису или библиотеке API, потому что макет Scaloid это обычный код Scala. Просто напишите код, который будет работать как стили.

Базовый: Индивидуальное присваивание

Предположим следующий код, который повторяет некоторые свойства:```scala SButton("первый").textSize(20.dip).<<.margin(5.dip).>> SButton("предыдущий").textSize(20.dip).<<.margin(5.dip).>> SButton("следующий").textSize(20.dip).<<.margin(5.dip).>> SButton("последний").textSize(20.dip).<<.margin(5.dip).>>


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

```scala
def myStyle = (_: SButton).textSize(20.dip).<<.margin(5.dip).>>
myStyle(SButton("первый"))
myStyle(SButton("предыдущий"))
myStyle(SButton("следующий"))
myStyle(SButton("последний"))

Еще недостаточно? Вот более короткий вариант:

def myStyle = (_: SButton).textSize(20.dip).<<.margin(5.dip).>>
List("первый", "предыдущий", "следующий", "последний").foreach(title => myStyle(SButton(title)))

Расширенный: Стилизация в стиле CSS

Библиотека Scaloid предоставляет метод SViewGroup.style(View => View), чтобы обеспечить более универсальную стилизацию компонентов. Параметр представляет собой функцию, которая принимает представление, требуемое для стилизации, и возвращает представление, завершившее применение стиля. Тогда пример из предыдущего раздела становится таким:

style {
  case b: SButton => b.textSize(20.dip).<<.margin(5.dip).>>
}

SButton("первый")
SButton("предыдущий")
SButton("следующий")
SButton("последний")

Обратите внимание, что отдельное применение myStyle сокращено. Давайте рассмотрим еще один пример:

style {
  case b: SButton => b.textColor(Color.RED).onClick(toast("Bang!"))
  case t: STextView => t.textSize(10.dip)
  case v => v.backgroundColor(Color.YELLOW)
}

STextView("Я высотой 10.dip")
STextView("Мне тоже")
STextView("Я выше вас").textSize(15.dip) // переопределение
SEditText("Жёлтый поле ввода")
SButton("Красный сигнал!")
```Аналогично CSS, вы можете назначать различные стили для каждого типа, используя паттерн матчинг на Scala.
В отличие от стилей Android XML или даже CSS, Scaloid позволяет назначать действия для компонента (смотрите `onClick(toast(...))`) или выполнять любые операции, которые вам требуются.
Также легко переопределять свойства индивидуально, как показано в примере выше.Последнее, что вы могли упустить: Все это безопасно относительно типов данных. Если вы допустили ошибку, компилятор проверит её за вас.

**Дальнейшие чтения:**

 - [Доступ к виджетам в классе представления](http://blog.scaloid.org/2013/04/accessing-widgets-in-view-classes.html)
 - [Подробное руководство по стилизации](http://blog.scaloid.org/2013/01/a-css-like-styling-on-android.html)

## Трейты

### Трейт `UnregisterReceiver`

Когда вы регистрируете `BroadcastReceiver` с помощью `Context.registerReceiver()`, вам следует отменить регистрацию, чтобы предотвратить утечку памяти. Трейт `UnregisterReceiver` выполняет эти задачи за вас. Все, что вам нужно сделать, это прикрепить трейт к вашему классу.

```scala
class MyService extends SService with UnregisterReceiver {
  def func() {
    // ...
    registerReceiver(receiver, intentFilter)
    // Готово! автоматически отменена регистрация в методе onDestroy() класса UnregisterReceiverService.
  }
}

Трейт SActivity

Вместо использования

findViewById(R.id.login).asInstanceOf[Button]

вы можете использовать более короткую запись:

find[Button](R.id.login)

Хотя мы предоставляем эту короткую запись, Scaloid рекомендует プログレッシブなUI生成を推奨するのではなく、XMLを使用すること.

Активность как неявный параметр

Аналогично неявному контексту, для некоторых методов требуется неявный параметр типа Activity. Поэтому вам нужно определить активность как неявное значение:```scala implicit val ctx: Activity = ...


Поскольку класс `Activity` является подклассом `Context`, он также может использоваться как неявный контекст. Когда вы расширяете `SActivity`, объект `this` по умолчанию присваивается как неявная активность.

Здесь показаны некоторые примеры использования неявной активности:

#### Автоматическое назначение уникального идентификатора `View`

Часто `Views` требуют иметь значение идентификатора. Хотя документация Android API указывает, что идентификатор не обязан быть уникальным, назначение уникального идентификатора практически обязательно на практике. Scaloid предоставляет пакетную функцию `getUniqueId`, которая возвращает тип `Int` идентификатора, который ещё не используется ни одним существующим компонентом `View`.

```scala
val newUniqueIdForCurrentActivity = getUniqueId

Используя это, Scaloid также расширяет класс View, добавляя метод uniqueId, который назначает новый уникальный идентификатор, если тот ещё не назначен.

val uniqueIdOfMyView = myView.uniqueId

Одним из хороших случаев применения uniqueId является SRelativeLayout. Некоторые методы этого контекста размещения, такие как below, above, leftOf и rightOf, принимают другой объект View в качестве закрепленного элемента:

new SRelativeLayout {
  val btn = SButton(R.string.hi)
  SButton("There").<<.below(btn)
}

Здесь показана реализация функции below:

def below(anchor: View)(implicit activity: Activity) = {
  addRule(RelativeLayout.BELOW, anchor.uniqueId)
  this
}
```Если уникальный идентификатор для `anchor` ещё не присвоен, ему назначается новый уникальный идентификатор, который затем передается в функцию `addRule`.

## Логгирование

В отличие от других систем логгирования, Android Logging API требует строки с меткой для каждого вызова лога. Мы устраняем это, используя неявный параметр. Определяем неявное значение типа `LoggerTag` следующим образом:

```scala
implicit val loggerTag = LoggerTag("MyAppTag")

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

warn("Что-то произошло!")

Другие функции для каждого уровня логирования (verbose(), debug(), info(), warn(), error() и wtf()) также доступны.

info("Привет " + мир)

Строковый параметр, переданный вместе с info(), является параметром по имени, поэтому он оценивается только при возможности записи лога. Поэтому пример выше эквивалентен следующему:

val tag = "MyAppTag"
if (Log.isLoggable(tag, Log.INFO)) Log.i(tag, "Привет " + мир)

Получатели и установщики в Scala

Вы можете использовать любой из указанных ниже установщиков:

  • obj.setText("Привет") Стиль Java Bean
  • obj.text = "Привет" Стиль присваивания
  • obj text "Привет" Стиль DSL
  • obj.text("Привет") Стиль вызова метода

В сравнении со стилем получения и установки значений в Java, например:

new TextView(context) {
  setText("Привет")
  setTextSize(15)
}
```стиль Scala более явно демонстрирует природу операций, как показано ниже:

```scala
new STextView {
  text = "Привет"
  textSize = 15
}

Или вы можете цепочками связывать установщики:

new STextView text "Привет" textSize 15

что является синтаксическим сахаром для:

new STextView.text("Привет").textSize(15)

Мы рекомендуем использовать "стиль присваивания" и "стиль DSL". Используйте стиль присваивания, когда вы хотите подчеркнуть, что присваиваете значение, или используйте стиль DSL, если длина кода присваивания короткая и требуется цепочка вызовов.

Примечание: Используя метод .apply(String) объекта STextView, можно еще больше уменьшить код выше следующим образом:

STextView("Привет") textSize 15

Дополнительные материалы:

Классы

Класс AlertDialogBuilder

Scala-подобный конструктор для AlertDialog.

new AlertDialogBuilder(R.string.title, R.string.message) {
  neutralButton()
}.show()

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

alert(R.string.title, R.string.message)

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

new AlertDialogBuilder("Выход из приложения", "Вы действительно хотите выйти?") {
  positiveButton("Выход", finishTheApplication())
  negativeButton(android.R.string.cancel)
}.show()
```Код выше эквивалентен следующему:

```scala
new AlertDialog.Builder(context)
  .setTitle("Выход из приложения")
  .setMessage("Вы действительно хотите выйти?")
  .setPositiveButton("Выход", new DialogInterface.OnClickListener {
    def onClick(dialog: DialogInterface, which: Int) {
      finishTheApplication()
    }
  })
  .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener {
    def onClick(dialog: DialogInterface, which: Int) {
      dialog.cancel()
    }
  }).show()

При вызове show() или alert из непользовательского потока вам не нужно беспокоиться о многопоточности.

Класс SArrayAdapter

Допустим, вы хотите позволить пользователю выбрать строку из спиннера, а также чтобы в выпадающем списке использовался больший шрифт. Тогда обычный код Android состоит из куска XML и его связывания:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          style="?android:attr/spinnerDropDownItemStyle"
          android:id="@+id/spinner_textview"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:textSize="25dip" />
val adapter = new ArrayAdapter(context, android.R.layout.simple_spinner_item, Array("Один", "Два", "Три"))
adapter.setDropDownViewResource(R.layout.spinner_dropdown)

В Scaloid эквивалентный код будет таким:

SArrayAdapter("Один", "Два", "Три").dropdownStyle(_.textSize(25.dip))

Если вы хотите сделать цвет текста в спиннере синим, используйте метод style:

SArrayAdapter("Быстрый", "Коричневый", "Кошка").style(_.textColor(Color.BLUE))

Можно ли сделать это проще?

Класс LocalServiceРуководство Android Developer по привязке сервисов указывает, что требуется написать более 60 строк кода для определения и привязки локального сервиса в процессе.

С использованием Scaloid вы можете сосредоточиться на определении и доступе к локальному сервису, как показано ниже:```scala class MyService extends LocalService { private val generator = new Random()

def getRandomNumber() = generator.nextInt(100) }

class Activity extends SActivity { val random = new LocalServiceConnection[MyService]

def onButtonClick(v: View) { random(s => toast("число: " + s.getRandomNumber())) } }


**Дополнительная информация:** Прочтите [этот блоговый пост](http://blog.scaloid.org/2013/03/introducing-localservice.html), чтобы узнать, почему это круто по сравнению с существующими методами.

### Класс `Preferences`

SharedPreference можно получить следующим образом:

```scala
val executionCount = preferenceVar(0) // значение по умолчанию 0
val ec = executionCount() // чтение
executionCount() = ec + 1 // запись
executionCount.remove() // удаление

Дополнительная информация:

Расширение класса View

Часто требуется определить пользовательский виджет для конкретной задачи. Для этого мы определяем класс, который наследует от класса android.widget.View или его подкласса (например, TextView и Button). Чтобы активировать расширения Scaloid для данного пользовательского виджета, вы можете определить класс следующим образом:

class MyView(implicit ctx: Context) extends View(ctx) with TraitView[MyView] {
  def basis = this

  // здесь пишется специфический код для MyView
}

Давайте сделаем это вместе!Scaloid — это проект, распространяемый под лицензией Apache.

Если у вас есть идеи по улучшению Scaloid, смело создавайте issue или отправляйте патчи. Если вам интересна внутренняя работа Scaloid, эта документация может оказаться полезной:* Внутри Scaloid

Список проектов, использующих Scaloid

Мы ищем новых сотрудников!

Компания, стоящая за Scaloid и onsqure, ищет разработчиков на Scala. Мы создаем музыкальное приложение и другие замечательные продукты. Мы широко используем Scaloid в наших продуктах, и это, вероятно, лучший пример его применения. Для получения более подробной информации о нашей компании, пожалуйста, перейдите на наш сайт http://o-n2.com. Если вас интересует работа в onsqure, пожалуйста, отправьте нам свое резюме по электронной почте. Мы расположены в Инcheon, Южная Корея. pocorall@gmail.com

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Scaloid — это библиотека, которая упрощает ваш код на Android. Она делает код более понятным и удобным в сопровождении за счёт использования языка Scala. Развернуть Свернуть
Apache-2.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/scalalibs-scaloid.git
git@api.gitlife.ru:oschina-mirror/scalalibs-scaloid.git
oschina-mirror
scalalibs-scaloid
scalalibs-scaloid
master