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 ringtone in the support library, so using the standard dialog in the PreferenceFragmentCompat
will 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 settings.xml
file in res/xml
with the following contents:
<?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>
And now let's add these settings to our fragment:
class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(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 case of the standard RingtonePreference
), when you click OK, it is saved in the settings.
So, we create a dialog fragment:
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 } } }
In prefKey
we give the key by which the current ringtone will be extracted, and it will be recorded there by pressing the OK button.
For further work, we will need the Ringtone
helper class, declare it inside our snippet:
private data class Ringtone(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 return us the list from the 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) ) } }
Here, ringtoneManager.cursor
returns the cursor with all available ringtones, we simply go through all the elements and map them to our auxiliary Ringtone
class (this is more convenient to work with).
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, create a dialog, overriding the onCreateDialog
method:
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() }
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):
private fun saveCurrentUri() { 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:
class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(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.mp3
in the res/raw
folder, and we want to display it at the top of the list. Add another method to the dialog class:
private fun getResourceRingtones(): List<Ringtone> = listOf( Ringtone( title = "Sample ringtone", uri = Uri.parse("${ContentResolver.SCHEME_ANDROID_RESOURCE}://${context!!.packageName}/raw/sample") ) )
And we change the first line in the onCreateDialog
method:
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:
private var playingRingtone: android.media.Ringtone? = null
And 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 playback Now when choosing a rinton in the dialogue, it will be played. To stop playback when closing the dialog, override the onPause
method:
override fun onPause() { 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 .
Source: https://habr.com/ru/post/417883/
All Articles