Creating a dialogue to select a ringtone

  • Tutorial

It took me here for one project to make my dialogue with the choice of a ringtone in the settings. Immediately for 2 reasons - firstly, there is no support library RingtonePreference, so using the standard dialog PreferenceFragmentCompatwill not work. And secondly, I had to add a few sounds from the resources in addition to the standard melodies. So it was decided to write my own dialogue.


I will demonstrate the creation of such a dialogue on the example of a simple application: on one screen there is a button “Play ringtone”, clicking on which plays the ringtone set in the settings, and a link to the screen with settings:



I will not describe the creation of these two screens - everything is as usual. Just in case, at the end there will be a link to the repository with the application code.


So let's start with an xml file with a description of the settings screen. Place the file settings.xmlin res/xmlwith the following contents:


<?xml version="1.0" encoding="utf-8"?><PreferenceScreenxmlns:android="http://schemas.android.com/apk/res/android"><Preferenceandroid:key="ringtone"android:title="Ringtone"/></PreferenceScreen>

And now let's add these settings to our fragment:


classSettingsFragment : PreferenceFragmentCompat() {
    overridefunonCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        setPreferencesFromResource(R.xml.settings, rootKey)
    }
}

Run, open the screen with the settings, see the following:



Introduction to this end, go to the purpose of the article. The plan is this: when you click on "Ringtone", a dialog opens with a list of ringtones and OK and Cancel buttons, when you select a ringtone, it is played (as in the standard one RingtonePreference), if you click OK, it is saved in the settings.


So, we create a dialog fragment:


classRingtonePreferenceDialog : DialogFragment() {
    privateval prefKey: String
        get() = arguments?.getString(ARG_PREF_KEY) ?: throw IllegalArgumentException("ARG_PREF_KEY not set")
    companionobject {
        privateconstval ARG_PREF_KEY = "ARG_PREF_KEY"funcreate(prefKey: String): RingtonePreferenceDialog {
            val fragment = RingtonePreferenceDialog()
            fragment.arguments = Bundle().apply {
                putString(ARG_PREF_KEY, prefKey)
            }
            return fragment
        }
    }
}

In prefKeywe pass the key by which the current ringtone will be extracted, and there it will be recorded by pressing the OK button.


For further work, we will need an auxiliary class Ringtone, we will declare it inside our fragment:


privatedataclassRingtone(val title: String, val uri: Uri)

And we will write an auxiliary function that will pull out all the built-in ringtones in Android, and will return to us a list of Ringtone:


privatefungetAndroidRingtones(): 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)
        )
    }
}

Here we ringtoneManager.cursorreturn the cursor with all the available ringtones, we simply go through all the elements and map them to our auxiliary class Ringtone(it’s more convenient to work with them).


Let's first organize the work with the built-in list of ringtones - then it will be very easy to add our resources. To do this, we create a dialog, overriding the method onCreateDialog:


privatevar ringtones: List<Ringtone> = emptyList()
privatevar currentUri: Uri? = nulloverridefunonCreateDialog(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()
}

The adapter is needed to display a list of items in the dialog, it can be defined as:


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)
    )
}

And you also need an auxiliary method to save the selected position (it will be called when you click on the OK button):


privatefunsaveCurrentUri() {
    PreferenceManager.getDefaultSharedPreferences(context)
            .edit()
            .putString(prefKey, currentUri?.toString())
            .apply()
}

It remains to bind our element to the dialogue, for this we define a helper function in the file with the dialogue:


fun Preference.connectRingtoneDialog(fragmentManager: FragmentManager?) = setOnPreferenceClickListener {
    RingtonePreferenceDialog.create(key).apply {
        fragmentManager?.let { show(it, "SOUND") }
    }
    true
}

And add findPreference("ringtone").connectRingtoneDialog(fragmentManager)to our SettingsFragment, now it should look like this:


classSettingsFragment : PreferenceFragmentCompat() {
    overridefunonCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        setPreferencesFromResource(R.xml.settings, rootKey)
        findPreference("ringtone").connectRingtoneDialog(fragmentManager)
    }
}

If we now go to the screen with the settings and click on "Ringtone", we will see something like this:



Now add ringtones from resources to our dialogue. For example, we have a ringtone sample.mp3in the folder res/raw, and we want to display it at the top of the list. Add another method to the dialog class:


privatefungetResourceRingtones(): List<Ringtone> = listOf(
        Ringtone(
                title = "Sample ringtone",
                uri = Uri.parse("${ContentResolver.SCHEME_ANDROID_RESOURCE}://${context!!.packageName}/raw/sample")
        )
)

And change the first line in the method onCreateDialog:


ringtones = getResourceRingtones() + getAndroidRingtones()

We start, we look, we are glad that everything is so simple:



It remains to add a "preview" for ringtones. To do this, we introduce an additional field:


privatevar playingRingtone: android.media.Ringtone? = null

And we will change the callback method for setSingleChoiceItems:


playingRingtone?.stop()
ringtones[which].also {
    currentUri = it.uri
    playingRingtone = it.uri?.let { RingtoneManager.getRingtone(context, it) }
    playingRingtone?.play()
}

What is happening here: stop the playback of the current ringtone (if it is not null), set the current one as the current one, start the playback. Now when you select a rinton in the dialogue, it will be played. To stop playback when closing the dialog, override the method onPause:


overridefunonPause() {
    super.onPause()
    playingRingtone?.stop()
}

Well, it remains only to tie a button on the main screen to playing a ringtone, for example, like this:


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()
    }
}

That's all. As promised, the source can be taken here .


Also popular now: