Статьи

Создать приложение для музыкального плеера с Anko

Конечный продукт
Что вы будете создавать

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

Поэтому сегодня мы собираемся использовать Kotlin и Anko для создания приложения музыкального плеера для Android, которое будет автоматически выбирать и воспроизводить случайные песни с устройства пользователя. Его достаточно сложный пользовательский интерфейс, который будет иметь несколько различных виджетов, взаимодействующих друг с другом, должен помочь вам лучше понять, как работают приложения Anko.

Чтобы следовать этому пошаговому руководству, вам потребуется:

  • последняя версия Android Studio
  • телефон или планшет под управлением Android 5.0 или выше
  • и несколько альбомов MP3

Если вы еще этого не сделали, прочитайте следующее руководство, прежде чем продолжить:

  • Android SDK
    Упростите разработку приложений для Android с Anko

Запустите Android Studio и нажмите кнопку « Начать новый проект Android Studio» , чтобы запустить мастер создания проекта. На следующем экране дайте своему приложению имя и убедитесь, что установлен флажок « Включить поддержку Kotlin» .

Project creation wizard

Затем выберите целевой уровень API 21 или выше и выберите шаблон « Пустое действие» . Поскольку нам не понадобятся файлы XML макета, убедитесь, что вы сняли флажок « Создать файл макета» .

Экран настройки активности

Наконец, нажмите Готово, чтобы создать проект.

Чтобы добавить 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 и выберите значки с символами воспроизведения, паузы и воспроизведения в случайном порядке.

AV section of the Vector Asset Studio

На этом этапе в папке res / drawable вашего проекта должны присутствовать следующие файлы:

  • ic_play_arrow_black_24dp.xml
  • ic_pause_black_24dp.xml
  • ic_shuffle_black_24dp.xml

Большинство пользователей хранят свои песни на внешних носителях. Поэтому на устройствах под управлением 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()
    }
}

Настало время определить метод 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.

По умолчанию 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
        }
}

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

  • виджет 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 для позиционирования вышеуказанных виджетов и установления отношений между ними. Следующая диаграмма показывает иерархию представлений, которую мы будем создавать следующим:

View hierarchy of the music player

Поскольку виджет 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() . Мы создадим их в следующие несколько шагов.

К этому моменту мы закончили определять внешний вид нашего приложения.

Наше приложение по-прежнему не может воспроизводить музыку. Давайте исправим это, создав метод 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

На более раннем этапе мы вызвали метод 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 теперь готов.

Просто создать компонент Anko недостаточно. Вы также должны убедиться, что вы отрисовываете его, вызывая его setContentView() и передавая ему действие. Пока вы можете передать основную деятельность ему.

При желании, если вы хотите, чтобы приложение начало воспроизводить песню, как только пользователь ее откроет, вы можете снова вызвать метод playRandom() .

1
2
playerUI.setContentView(this@MainActivity)
playerUI.playRandom()

Наше приложение готово. Если вы запустите его на устройстве, которое имеет один или несколько файлов MP3 с правильно отформатированными тегами ID3 и встроенными обложками альбомов, вы должны увидеть нечто похожее на это:

A screenshot of the app

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

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

И пока вы здесь, ознакомьтесь с некоторыми другими нашими статьями о Kotlin и кодировании приложений для Android!

  • Android SDK
    Совет: пишите более чистый код с помощью Kotlin SAM Conversions
  • Android SDK
    Java против Kotlin: стоит ли использовать Kotlin для разработки под Android?
    Джессика Торнсби
  • Android SDK
    Введение в компоненты архитектуры Android
    Жестяная мегали
  • Android SDK
    Что такое Android Instant Apps?
    Джессика Торнсби
  • Android SDK
    Как создать приложение для Android-чата с помощью Firebase