Me llevó aquí para un proyecto hacer mi diálogo con la elección del tono de llamada en la configuración. Inmediatamente por 2 razones: en primer lugar, no hay RingtonePreference
en la biblioteca de soporte, por lo que fallará el uso del diálogo estándar en PreferenceFragmentCompat
. Y en segundo lugar, además de las melodías estándar, tuve que agregar algunos sonidos de los recursos allí. Entonces se decidió escribir su diálogo.
Demostraré la creación de dicho diálogo usando un ejemplo de una aplicación simple: en una pantalla hay un botón "Reproducir tono de llamada", haciendo clic en el que reproduce el tono de llamada establecido en la configuración y un enlace a la pantalla de configuración:
No describiré la creación de estas dos pantallas: todo está ahí como siempre. Por las dudas, al final habrá un enlace al repositorio con el código de la aplicación.
Entonces, comencemos con el archivo xml con la descripción de la pantalla de configuración. Coloque el archivo settings.xml
en res/xml
con el siguiente contenido:
<?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>
Y ahora agreguemos estas configuraciones a nuestro fragmento:
class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.settings, rootKey) } }
Inicie, abra la pantalla de configuración, vea lo siguiente:
La introducción termina aquí, pasamos al propósito del artículo. El plan es este: cuando hace clic en "Tono de llamada", se abre un cuadro de diálogo con una lista de tonos de llamada y los botones Aceptar y Cancelar, cuando selecciona un tono de llamada, se reproduce (como en el caso de la Referencia de RingtonePreference
estándar), cuando hace clic en Aceptar, se guarda en la configuración.
Entonces, cree un 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 } } }
En prefKey
transferimos la tecla por la cual se extraerá el tono de llamada actual, y allí se grabará presionando el botón OK.
Para más trabajo, necesitamos la clase auxiliar Ringtone
, declararlo dentro de nuestro fragmento:
private data class Ringtone(val title: String, val uri: Uri)
Y escriba una función auxiliar que extraiga todos los tonos de llamada incorporados en Android, y nos devuelva la lista de Ringtone
de 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) ) } }
Aquí ringtoneManager.cursor
devolverá el cursor con todos los tonos de llamada disponibles, solo revisaremos todos los elementos y los asignaremos a nuestro Ringtone
clase auxiliar (es más conveniente trabajar con ellos).
Primero, organicemos el trabajo con la lista integrada de tonos de llamada; agregar nuestros recursos más adelante será muy simple. Para hacer esto, cree un diálogo anulando el 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() }
Se necesita un adaptador para mostrar una lista de elementos en un cuadro de diálogo; se puede definir de la siguiente manera:
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) ) }
Y necesita otro método auxiliar para guardar la posición seleccionada (se llamará cuando haga clic en el botón Aceptar):
private fun saveCurrentUri() { PreferenceManager.getDefaultSharedPreferences(context) .edit() .putString(prefKey, currentUri?.toString()) .apply() }
Queda por vincular nuestro elemento al diálogo, para esto definimos una función auxiliar en el archivo con el diálogo:
fun Preference.connectRingtoneDialog(fragmentManager: FragmentManager?) = setOnPreferenceClickListener { RingtonePreferenceDialog.create(key).apply { fragmentManager?.let { show(it, "SOUND") } } true }
Y agregue findPreference("ringtone").connectRingtoneDialog(fragmentManager)
a nuestro SettingsFragment
, ahora debería verse así:
class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.settings, rootKey) findPreference("ringtone").connectRingtoneDialog(fragmentManager) } }
Si ahora vamos a la pantalla de configuración y hacemos clic en "Tono de llamada", veremos algo como esto:
Ahora agregue los tonos de llamada de los recursos a nuestro diálogo. Por ejemplo, tenemos un tono de llamada sample.mp3
en la carpeta res/raw
, y queremos mostrarlo en la parte superior de la lista. Agregue otro método a la clase de diálogo:
private fun getResourceRingtones(): List<Ringtone> = listOf( Ringtone( title = "Sample ringtone", uri = Uri.parse("${ContentResolver.SCHEME_ANDROID_RESOURCE}://${context!!.packageName}/raw/sample") ) )
Y cambie la primera línea en el método onCreateDialog
:
ringtones = getResourceRingtones() + getAndroidRingtones()
Comenzamos, miramos, nos alegramos de que todo sea tan simple:
Queda por agregar una "vista previa" de los tonos de llamada. Para hacer esto, ingrese un campo adicional:
private var playingRingtone: android.media.Ringtone? = null
Y cambie ligeramente el método de devolución de llamada para setSingleChoiceItems
:
playingRingtone?.stop() ringtones[which].also { currentUri = it.uri playingRingtone = it.uri?.let { RingtoneManager.getRingtone(context, it) } playingRingtone?.play() }
Lo que sucede aquí: dejamos de reproducir el tono de llamada actual (si no es nulo), configuramos el seleccionado como el actual e iniciamos la reproducción. Ahora, cuando seleccione un tono de llamada en el diálogo, se reproducirá. Para detener la reproducción cuando se cierra el cuadro de diálogo, redefina el método onPause
:
override fun onPause() { super.onPause() playingRingtone?.stop() }
Bueno, todo lo que queda es adjuntar el botón de la pantalla principal al tono de llamada, por ejemplo, así:
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() } }
Eso es todo Según lo prometido, el código fuente se puede tomar aquí .