Хороший подход к овладению новым языком программирования или библиотекой — попытаться создать что-то полезное с ним. В моем уроке по упрощению разработки под Android с помощью Anko я познакомил вас с предметно-ориентированным языком и вспомогательными функциями Anko . Хотя я уверен, что вы нашли их впечатляющими, вы все равно можете опасаться их использования в больших и сложных приложениях, поскольку они сильно отличаются от традиционных классов и методов Android.
Поэтому сегодня мы собираемся использовать Kotlin и Anko для создания приложения музыкального плеера для Android, которое будет автоматически выбирать и воспроизводить случайные песни с устройства пользователя. Его достаточно сложный пользовательский интерфейс, который будет иметь несколько различных виджетов, взаимодействующих друг с другом, должен помочь вам лучше понять, как работают приложения Anko.
Предпосылки
Чтобы следовать этому пошаговому руководству, вам потребуется:
- последняя версия Android Studio
- телефон или планшет под управлением Android 5.0 или выше
- и несколько альбомов MP3
Если вы еще этого не сделали, прочитайте следующее руководство, прежде чем продолжить:
1. Создание нового проекта
Запустите Android Studio и нажмите кнопку « Начать новый проект Android Studio» , чтобы запустить мастер создания проекта. На следующем экране дайте своему приложению имя и убедитесь, что установлен флажок « Включить поддержку Kotlin» .
Затем выберите целевой уровень API 21 или выше и выберите шаблон « Пустое действие» . Поскольку нам не понадобятся файлы XML макета, убедитесь, что вы сняли флажок « Создать файл макета» .
Наконец, нажмите Готово, чтобы создать проект.
2. Добавление зависимостей
Чтобы добавить Anko в проект, добавьте следующую зависимость implementation
в файл build.gradle модуля app
:
1
|
implementation ‘org.jetbrains.anko:anko:0.10.1’
|
Мы будем использовать сопрограммы Kotlin для асинхронного выполнения нескольких операций, поэтому добавим для них зависимость.
1
|
implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-android:0.19.3’
|
Наш музыкальный проигрыватель, поскольку он воспроизводит песни в произвольном порядке, нуждается в списке всех песен, доступных на устройстве. Чтобы избежать реализации логики создания такого списка, добавьте DroidMelody , библиотеку, которую я создал специально для этого урока, в качестве последней зависимости. Поскольку он одобрен и опубликован jcenter , репозиторием Android Studio по умолчанию, его добавление ничем не отличается от добавления любых других зависимостей.
1
|
implementation ‘com.progur.droidmelody:droidmelody:1.0.2’
|
Кроме того, нам понадобятся несколько значков, связанных с медиа, поэтому откройте Vector Asset Studio. В нем перейдите к категории AV и выберите значки с символами воспроизведения, паузы и воспроизведения в случайном порядке.
На этом этапе в папке res / drawable вашего проекта должны присутствовать следующие файлы:
- ic_play_arrow_black_24dp.xml
- ic_pause_black_24dp.xml
- ic_shuffle_black_24dp.xml
3. Запрос разрешений
Большинство пользователей хранят свои песни на внешних носителях. Поэтому на устройствах под управлением Android Marshmallow или выше нам потребуется явно запросить разрешение READ_EXTERNAL_STORAGE
во время выполнения.
Однако прежде чем запрашивать разрешение, вы должны проверить, предоставил ли пользователь его уже. Вы можете сделать это, вызвав метод ContextCompat.checkSelfPermission()
внутри метода onCreate()
вашей деятельности. Если разрешение не было предоставлено, вы можете запросить его, вызвав метод ActivityCompat.requestPermissions()
.
Соответственно добавьте следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
if(ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// Ask for the permission
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
0)
} else {
// Start creating the user interface
createPlayer()
}
|
Обратите внимание, что приведенный выше код вызывает метод с именем createPlayer()
если разрешение уже предоставлено. Мы будем создавать этот метод на следующем шаге.
После запроса разрешения необходимо переопределить метод onRequestPermissionsResult()
действия, чтобы определить, принял ли пользователь ваш запрос. Если они это сделали, вы должны снова вызвать метод createPlayer()
. Если этого не произошло, отобразите сообщение об ошибке с помощью помощника longToast()
Anko и закройте приложение.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode,
permissions, grantResults)
if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
createPlayer()
} else {
longToast(«Permission not granted. Shutting down.»)
finish()
}
}
|
4. Получение всех песен
Настало время определить метод createPlayer()
, который будет отвечать как за внешний вид, так и за функциональность нашего приложения.
1
2
3
|
private fun createPlayer() {
// More code here
}
|
Внутри метода первое, что вам нужно сделать, это создать список всех песен, доступных на устройстве. Поскольку это, как и следовало ожидать, потенциально длительная операция, вы можете запустить ее в новой сопрограмме с помощью функции async()
.
Внутри сопрограммы создайте новый экземпляр класса SongFinder в SongFinder
и вызовите его метод prepare()
, который использует преобразователь содержимого вашей деятельности, чтобы фактически найти все песни и поместить их в список с именем allSongs
. После завершения метода вы можете просто вернуть все allSongs
из сопрограммы. Следующий код показывает вам, как:
1
2
3
4
5
|
val songsJob = async {
val songFinder = SongFinder(contentResolver)
songFinder.prepare()
songFinder.allSongs
}
|
Вышеупомянутая сопрограмма работает асинхронно. Чтобы дождаться его результата, вы должны вызвать метод await()
внутри другой сопрограммы, созданной с помощью функции launch()
. Поскольку мы будем использовать результат для создания пользовательского интерфейса нашего приложения, новая сопрограмма должна выполняться в потоке пользовательского интерфейса. Это указывается передачей kotlinx.coroutines.experimental.android.UI
в качестве аргумента для launch()
.
1
2
3
4
5
|
launch(kotlinx.coroutines.experimental.android.UI) {
val songs = songsJob.await()
// More code here
}
|
Теперь у вас есть список объектов Song
. У каждого объекта Song
будет несколько важных деталей о песне, на которую он ссылается, например, название, исполнитель, обложка альбома и URI.
5. Создание компонента Anko
По умолчанию DSL Anko напрямую доступен только внутри onCreate()
действия. Чтобы иметь возможность использовать его внутри createPlayer()
, вы можете либо зависеть от функции UI()
либо создать новый компонент Anko. В этом уроке мы пойдем с последним подходом, потому что он более пригоден для повторного использования.
Чтобы создать новый компонент Anko, вы должны расширить абстрактный класс AnkoComponent
и переопределить его createView()
, внутри которого у вас будет доступ к DSL.
1
2
3
4
5
6
|
val playerUI = object:AnkoComponent<MainActivity> {
override fun createView(ui: AnkoContext<MainActivity>)
= with(ui) {
// DSL code here
}
}
|
6. Определение интерфейса пользователя
Поскольку наше приложение представляет собой случайный музыкальный проигрыватель, а не тот, который может работать со списками воспроизведения, оно будет иметь немного нестандартный пользовательский интерфейс. Вот видимые виджеты, которые он будет содержать:
- виджет
ImageView
для отображения обложки альбома текущей воспроизводимой песни - виджет
ImageButton
который позволяет пользователю приостановить или возобновить песню - виджет
ImageButton
который позволяет пользователю выбрать другую случайную песню - виджет
TextView
для отображения названия песни - виджет
TextView
для отображения имени исполнителя песни
Соответственно, добавьте следующие поля в компонент Anko:
1
2
3
4
5
6
7
|
var albumArt: ImageView?
var playButton: ImageButton?
var shuffleButton:ImageButton?
var songTitle: TextView?
var songArtist: TextView?
|
Кроме того, нам понадобится RelativeLayout
и пара контейнеров LinearLayout
для позиционирования вышеуказанных виджетов и установления отношений между ними. Следующая диаграмма показывает иерархию представлений, которую мы будем создавать следующим:
Поскольку виджет RelativeLayout
находится в корне иерархии представления, вы должны сначала создать его, добавив следующий код в метод createView()
компонента Anko:
1
2
3
4
5
|
relativeLayout {
backgroundColor = Color.BLACK
// More code here
}
|
Обратите внимание, что мы использовали свойство backgroundColor
виджета, чтобы придать ему черный цвет. В этом уроке мы будем использовать несколько таких свойств, чтобы наше приложение выглядело лучше. Вы можете изменить их значения в соответствии с вашими предпочтениями.
Внутри виджета RelativeLayout
добавьте виджет ImageView
для обложки альбома. Он должен занимать все пространство, доступное на экране, поэтому используйте метод lparams()
после добавления и передайте matchParent
константу matchParent
дважды, один раз для ширины и один раз для высоты. Вот как:
1
2
3
|
albumArt = imageView {
scaleType = ImageView.ScaleType.FIT_CENTER
}.lparams(matchParent, matchParent)
|
Метод lparams()
, как следует из его названия, позволяет вам указать параметры макета, которые должны быть связаны с виджетом. Используя его, вы можете быстро указать детали, такие как поля, которые должен иметь виджет, его размеры и положение.
Затем создайте виджет LinearLayout
с вертикальной ориентацией, вызвав функцию verticalLayout()
и добавьте в TextView
виджеты TextView
. Макет должен быть расположен в нижней части экрана, поэтому вы должны вызывать alignParentBottom()
при указании параметров макета.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
verticalLayout {
backgroundColor = Color.parseColor(«#99000000»)
songTitle = textView {
textColor = Color.WHITE
typeface = Typeface.DEFAULT_BOLD
textSize = 18f
}
songArtist = textView {
textColor = Color.WHITE
}
// More code here
}.lparams(matchParent, wrapContent) {
alignParentBottom()
}
|
Аналогичным образом создайте виджет LinearLayout
с горизонтальной ориентацией, вызвав linearLayout()
, и добавьте в ImageButton
два виджета ImageButton
. Убедитесь, что вы используете свойство imageResource
кнопок, чтобы указать значки, которые они должны отображать. Следующий код показывает вам, как:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
linearLayout {
playButton = imageButton {
imageResource = R.drawable.ic_play_arrow_black_24dp
onClick {
playOrPause()
}
}.lparams(0, wrapContent, 0.5f)
shuffleButton = imageButton {
imageResource = R.drawable.ic_shuffle_black_24dp
onClick {
playRandom()
}
}.lparams(0, wrapContent, 0.5f)
}.lparams(matchParent, wrapContent) {
topMargin = dip(5)
}
|
Вы можете видеть, что в приведенном выше коде есть обработчики событий click для обоих виджетов ImageButton
. Внутри обработчиков есть вызовы двух интуитивно названных методов: playOrPause()
и playRandom()
. Мы создадим их в следующие несколько шагов.
К этому моменту мы закончили определять внешний вид нашего приложения.
7. Воспроизведение песен
Наше приложение по-прежнему не может воспроизводить музыку. Давайте исправим это, создав метод playRandom()
.
1
2
3
|
fun playRandom() {
// More code here
}
|
Мы будем использовать экземпляр класса MediaPlayer
для воспроизведения музыки. Это довольно дорогой ресурс, который должен быть выпущен, когда пользователь закрывает приложение. Следовательно, оно должно быть определено как поле действия, а не как компонент Anko, и должно быть onDestroy()
метод onDestroy()
действия.
1
2
3
4
5
6
|
private var mediaPlayer: MediaPlayer?
override fun onDestroy() {
mediaPlayer?.release()
super.onDestroy()
}
|
Внутри playRandom()
теперь мы можем выбрать произвольную песню из списка песен, сгенерированных нами ранее, просто перетасовывая список и выбирая первый элемент. Этот подход не очень эффективен, но он очень лаконичен.
1
2
|
Collections.shuffle(songs)
val song = songs[0]
|
Теперь вы можете инициализировать медиапроигрыватель с помощью URI вновь выбранной песни. Кроме того, используйте метод setOnCompletionListener()
чтобы убедиться, что новая произвольная песня начинает воспроизводиться сразу после завершения текущей песни.
1
2
3
4
5
|
mediaPlayer?.reset()
mediaPlayer = MediaPlayer.create(ctx, song.uri)
mediaPlayer?.setOnCompletionListener {
playRandom()
}
|
Содержимое виджетов ImageView
и TextView
тоже должно быть обновлено, чтобы отображать детали, связанные с новой песней.
1
2
3
|
albumArt?.imageURI = song.albumArt
songTitle?.text = song.title
songArtist?.text = song.artist
|
Наконец, чтобы начать воспроизведение песни, вы можете вызвать метод start()
медиаплеера. Теперь также самое время обновить виджет ImageButton
чтобы изменить значок воспроизведения на значок паузы.
1
2
|
mediaPlayer?.start()
playButton?.imageResource = R.drawable.ic_pause_black_24dp
|
8. Приостановка и возобновление
На более раннем этапе мы вызвали метод playOrPause()
внутри обработчика щелчка одного из виджетов ImageButton
. Определите это как другой метод компонента Anko.
1
2
3
|
fun playOrPause() {
// More code here
}
|
Логика, которую вы должны реализовать внутри этого метода, должна быть достаточно очевидной. Если медиаплеер уже воспроизводит песню, которую вы можете проверить с помощью свойства isPlaying
, вызовите его метод pause()
и отобразите значок «воспроизведения» в ImageButton
. В противном случае вызовите метод start()
и отобразите значок «пауза».
1
2
3
4
5
6
7
8
9
|
val songPlaying: Boolean?
if(songPlaying == true) {
mediaPlayer?.pause()
playButton?.imageResource = R.drawable.ic_play_arrow_black_24dp
} else {
mediaPlayer?.start()
playButton?.imageResource = R.drawable.ic_pause_black_24dp
}
|
Наш компонент Anko теперь готов.
9. Отображение компонента Anko
Просто создать компонент Anko недостаточно. Вы также должны убедиться, что вы отрисовываете его, вызывая его setContentView()
и передавая ему действие. Пока вы можете передать основную деятельность ему.
При желании, если вы хотите, чтобы приложение начало воспроизводить песню, как только пользователь ее откроет, вы можете снова вызвать метод playRandom()
.
1
2
|
playerUI.setContentView(this@MainActivity)
playerUI.playRandom()
|
Наше приложение готово. Если вы запустите его на устройстве, которое имеет один или несколько файлов MP3 с правильно отформатированными тегами ID3 и встроенными обложками альбомов, вы должны увидеть нечто похожее на это:
Вывод
В этом уроке вы узнали, как создать приложение музыкального проигрывателя со сложной иерархией представлений, используя только Anko и Kotlin. При этом вы работали с несколькими дополнительными функциями, такими как сопрограммы и помощники параметров макета. Вы также узнали, как сделать код Anko более модульным и многократно используемым, создав компоненты Anko.
Не стесняйтесь добавлять больше функциональности в приложение или изменять его внешний вид, чтобы придать ему индивидуальность!
И пока вы здесь, ознакомьтесь с некоторыми другими нашими статьями о Kotlin и кодировании приложений для Android!
-
Android SDKСовет: пишите более чистый код с помощью Kotlin SAM Conversions
-
Android SDKJava против Kotlin: стоит ли использовать Kotlin для разработки под Android?
-
Android SDKВведение в компоненты архитектуры Android
-
Android SDKЧто такое Android Instant Apps?
-
Android SDKКак создать приложение для Android-чата с помощью Firebase