Me levou aqui para um projeto para fazer meu diálogo com a escolha do toque nas configurações. Imediatamente por 2 motivos - primeiro, não há RingtonePreference
na biblioteca de suporte, portanto, o uso da caixa de diálogo padrão em PreferenceFragmentCompat
falhará. E segundo, além das melodias padrão, eu tive que adicionar alguns sons dos recursos lá. Por isso, foi decidido escrever seu diálogo.
Demonstrarei a criação desse diálogo usando um exemplo de aplicativo simples: em uma tela, há um botão "Reproduzir toque", clicando no botão que reproduz o toque definido nas configurações e um link para a tela de configurações:
Não vou descrever a criação dessas duas telas - tudo está lá como sempre. Por precaução, no final, haverá um link para o repositório com o código do aplicativo.
Então, vamos começar com o arquivo xml com a descrição da tela de configurações. Coloque o arquivo settings.xml
em res/xml
com o seguinte conteúdo:
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <Preference android:key="ringtone" android:title="Ringtone"/> </PreferenceScreen>
E agora vamos adicionar essas configurações ao nosso fragmento:
class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.settings, rootKey) } }
Inicie, abra a tela de configurações, veja o seguinte:
A introdução termina aqui, passamos ao objetivo do artigo. O plano é o seguinte: quando você clica em "Toque", uma caixa de diálogo é aberta com uma lista de toques e os botões OK e Cancelar; quando você seleciona um toque, ele é reproduzido (como no caso da Preferência de RingtonePreference
padrão); quando você clica em OK, ele é salvo nas configurações.
Então, crie um fragmento de diálogo:
class RingtonePreferenceDialog : DialogFragment() { private val prefKey: String get() = arguments?.getString(ARG_PREF_KEY) ?: throw IllegalArgumentException("ARG_PREF_KEY not set") companion object { private const val ARG_PREF_KEY = "ARG_PREF_KEY" fun create(prefKey: String): RingtonePreferenceDialog { val fragment = RingtonePreferenceDialog() fragment.arguments = Bundle().apply { putString(ARG_PREF_KEY, prefKey) } return fragment } } }
No prefKey
transferimos a tecla pela qual o toque atual será extraído e, em prefKey
será gravado pressionando o botão OK.
Para trabalho adicional, precisamos da classe auxiliar Ringtone
, declare-a dentro do nosso fragmento:
private data class Ringtone(val title: String, val uri: Uri)
E escreva uma função auxiliar que retirará todos os toques embutidos no Android e retornará a lista do Ringtone
:
private fun getAndroidRingtones(): List<Ringtone> { val ringtoneManager = RingtoneManager(context) val cursor = ringtoneManager.cursor return (0 until cursor.count).map { cursor.moveToPosition(it) Ringtone( title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX), uri = ringtoneManager.getRingtoneUri(it) ) } }
Aqui o ringtoneManager.cursor
retornará o cursor com todos os toques disponíveis, basta percorrer todos os elementos e mapeá-los para nossa classe auxiliar Ringtone
(é mais conveniente trabalhar com eles).
Primeiro, vamos organizar o trabalho com a lista interna de toques - adicionar nossos recursos posteriormente será muito simples. Para fazer isso, crie um diálogo substituindo o método onCreateDialog
:
private var ringtones: List<Ringtone> = emptyList() private var currentUri: Uri? = null override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { ringtones = getAndroidRingtones() currentUri = getCurrentRingtoneUri() val currentPosition = ringtones.indexOfFirst { currentUri == it.uri } return AlertDialog.Builder(context!!) .setPositiveButton(android.R.string.ok) { _, _ -> saveCurrentUri() } .setNegativeButton(android.R.string.cancel) { _, _ -> dialog.dismiss() } .setSingleChoiceItems(adapter, currentPosition) { _, which -> currentUri = ringtones[which].uri } .create() }
É necessário um adaptador para exibir uma lista de elementos em um diálogo; ele pode ser definido da seguinte maneira:
private val adapter by lazy { SimpleAdapter( context, ringtones.map { mapOf("title" to it.title) }, R.layout.simple_list_item_single_choice, arrayOf("title"), intArrayOf(R.id.text1) ) }
E você precisa de outro método auxiliar para salvar a posição selecionada (ela será chamada quando você clicar no botão OK):
private fun saveCurrentUri() { PreferenceManager.getDefaultSharedPreferences(context) .edit() .putString(prefKey, currentUri?.toString()) .apply() }
Resta vincular nosso elemento ao diálogo, para isso definimos uma função auxiliar no arquivo com o diálogo:
fun Preference.connectRingtoneDialog(fragmentManager: FragmentManager?) = setOnPreferenceClickListener { RingtonePreferenceDialog.create(key).apply { fragmentManager?.let { show(it, "SOUND") } } true }
E adicione findPreference("ringtone").connectRingtoneDialog(fragmentManager)
ao nosso SettingsFragment
, agora deve ficar assim:
class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.settings, rootKey) findPreference("ringtone").connectRingtoneDialog(fragmentManager) } }
Se agora formos para a tela de configurações e clicar em "Toque", veremos algo como isto:
Agora adicione os toques dos recursos ao nosso diálogo. Por exemplo, temos um ringtone sample.mp3
na pasta res/raw
e queremos exibi-lo no topo da lista. Adicione outro método à classe de diálogo:
private fun getResourceRingtones(): List<Ringtone> = listOf( Ringtone( title = "Sample ringtone", uri = Uri.parse("${ContentResolver.SCHEME_ANDROID_RESOURCE}://${context!!.packageName}/raw/sample") ) )
E altere a primeira linha no método onCreateDialog
:
ringtones = getResourceRingtones() + getAndroidRingtones()
Começamos, olhamos, nos alegramos por tudo ser tão simples:
Resta adicionar uma "visualização" dos toques. Para fazer isso, insira um campo adicional:
private var playingRingtone: android.media.Ringtone? = null
E altere levemente o método de retorno de chamada para setSingleChoiceItems
:
playingRingtone?.stop() ringtones[which].also { currentUri = it.uri playingRingtone = it.uri?.let { RingtoneManager.getRingtone(context, it) } playingRingtone?.play() }
O que acontece aqui: paramos de tocar o toque atual (se não for nulo), definimos o selecionado como atual e iniciamos a reprodução. Agora, quando você seleciona um toque no diálogo, ele toca. Para parar a reprodução quando a caixa de diálogo estiver fechada, redefina o método onPause
:
override fun onPause() { super.onPause() playingRingtone?.stop() }
Bem, tudo o que resta é anexar o botão na tela principal ao toque, por exemplo, assim:
findViewById<Button>(R.id.playRingtone).setOnClickListener { val ringtone = PreferenceManager.getDefaultSharedPreferences(this) .getString("ringtone", null) ?.let { RingtoneManager.getRingtone(this, Uri.parse(it)) } if (ringtone == null) { Toast.makeText(this, "Select ringtone in settings", Toast.LENGTH_SHORT).show() } else { ringtone.play() } }
Isso é tudo. Como prometido, o código fonte pode ser obtido aqui .