如何将我应用程序res/raw文件夹中的自定义铃声添加到RingtonePreference中

5
我有这个RingtonePreference(来自Android Studio默认的SettingsActivity): pref_notification.xml:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <RingtonePreference
        android:dependency="notifications_alarm"
        android:key="notifications_alarm_ringtone"
        android:title="@string/pref_title_ringtone"
        android:ringtoneType="notification|all"
        android:defaultValue="content://settings/system/notification_sound" />

SettingsActivity.java:

private void setupSimplePreferencesScreen() {
    if (!isSimplePreferences(this)) {
        return;
    }

    // Add 'general' preferences.
    addPreferencesFromResource(R.xml.pref_general);

    // Add 'notifications' preferences, and a corresponding header.
    PreferenceCategory fakeHeader = new PreferenceCategory(this);
    fakeHeader.setTitle(R.string.pref_header_notifications);
    getPreferenceScreen().addPreference(fakeHeader);
    addPreferencesFromResource(R.xml.pref_notification);

    bindPreferenceSummaryToValue(findPreference("notifications_alarm_ringtone"));
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class NotificationPreferenceFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.pref_notification);

        bindPreferenceSummaryToValue(findPreference("notifications_alarm_ringtone"));
    }
}

我想将我的应用程序的自定义铃声从res/raw文件夹添加到列表中。(我不需要它们对其他应用程序可用。)

1
我认为你需要创建一个自定义首选项,或将它们复制到设备的共享存储中(那么它们也会对其他应用程序可用)。 - Shlomo Zalman Heigh
如果我有偏好,我要如何弹出“铃声选择器”操作的意图选择器?我的意思是,我想我需要将我的自定义PreferenceScreen添加到清单中,并为此“铃声选择器”操作进行过滤,但是这是什么? - Gavriel
我更新了我的答案,添加了另一个(在我看来更好的)选项,将文件复制到设备存储中。 - Shlomo Zalman Heigh
1
请查看我的类“ExtraRingtonePreference”,网址为https://dev59.com/dWgt5IYBdhLWcg3w0wvB#31004356。 - almisoft
@almisoft,非常好!如果你把这个写成答案,我就接受它。 - Gavriel
显示剩余3条评论
2个回答

5

选项1:复制到设备存储

将您的铃声文件复制到设备的存储中。我在GitHub上的一个应用程序中基本上就是这样做的,在这里,我将警报铃声从原始资源复制到设备的警报目录(您需要将所有实例的“alarms”替换为“ringtones”)。然后,我们使用ContentValues创建元数据,告诉系统这些文件是铃声,并使用MediaStore.Audio.Media.getContentUriForPath,然后context.getContentResolver().insert(contentUri, contentValues)将铃声添加到设备的数据库中,这样它们将包含在RingtonePreference的列表中。 您还可以使用RingtoneManager.setActualDefaultRingtoneUri()将铃声设置为默认铃声,但您需要WRITE_SETTINGS权限。

此外,请记得在调用getContentResolver().insert()RingtoneManager.setActualDefaultRingtoneUri()时使用getContentUriForPath()获取的URI。 并且,请确保在您的AndroidManifest.xml中添加:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

选项2:自定义偏好设置

按照此指南的示例创建自定义偏好设置,或使用(或子类化)ListPreference。您需要使用RingtoneManager.getCursor() (文档)检索所有设备铃声并将它们添加到列表中,并包括您自定义的铃声。

然后,在您的preferences.xml文件中,您将不再使用RingtonePreference,而是使用以下代码:

<com.yourapp.CustomPreference android:key="your_key"
    ...

在您的情况下,如果其他应用程序也可以访问铃声,我建议使用第一种方法。总体上,我认为最好不要复制系统已经提供的功能,因为最好利用用户已经熟悉的功能。


4

最终我基于这个答案创建了自己的ExtraRingtonePreference:在偏好设置中,选择我的声音就像使用RingtonePreference一样

我将它包含在这里以供日后参考:

src/main/java/com/fletech/android/preference/ExtraRingtonePreference.java:

package com.fletech.android.preference;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.preference.DialogPreference;
import android.util.AttributeSet;

import com.fletech.android.redalert.R;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

public class ExtraRingtonePreference extends DialogPreference {

    private Context mContext;
    private String mValue;
    private Ringtone ringtone;
    private int mRingtoneType;
    private boolean mShowSilent;
    private boolean mShowDefault;
    private CharSequence[] mExtraRingtones;
    private CharSequence[] mExtraRingtoneTitles;

    public ExtraRingtonePreference(Context context, AttributeSet attrs) {

        super(context, attrs);

        mContext = context;

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ExtraRingtonePreference, 0, 0);

        mRingtoneType = a.getInt(R.styleable.ExtraRingtonePreference_ringtoneType, RingtoneManager.TYPE_RINGTONE);
        mShowDefault = a.getBoolean(R.styleable.ExtraRingtonePreference_showDefault, true);
        mShowSilent = a.getBoolean(R.styleable.ExtraRingtonePreference_showSilent, true);
        mExtraRingtones = a.getTextArray(R.styleable.ExtraRingtonePreference_extraRingtones);
        mExtraRingtoneTitles = a.getTextArray(R.styleable.ExtraRingtonePreference_extraRingtoneTitles);

        a.recycle();
    }

    public ExtraRingtonePreference(Context context) {
        this(context, null);
    }

    public String getValue() {
        return mValue;
    }

    private Map<String, Uri> getSounds(int type) {

        RingtoneManager ringtoneManager = new RingtoneManager(mContext);
        ringtoneManager.setType(type);
        Cursor cursor = ringtoneManager.getCursor();

        Map<String, Uri> list = new TreeMap<String, Uri>();
        while (cursor.moveToNext()) {
            String notificationTitle = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
            Uri notificationUri = ringtoneManager.getRingtoneUri(cursor.getPosition());

            list.put(notificationTitle, notificationUri);
        }

        return list;
    }

    private Uri uriFromRaw(String name) {
        int resId = mContext.getResources().getIdentifier(name, "raw", mContext.getPackageName());
        return Uri.parse("android.resource://" + mContext.getPackageName() + "/" + resId);
    }

    public String getExtraRingtoneTitle(CharSequence name) {
        if (mExtraRingtones != null && mExtraRingtoneTitles != null) {
            int index = Arrays.asList(mExtraRingtones).indexOf(name);
            return mExtraRingtoneTitles[index].toString();
        }

        return null;
    }

    @Override
    public CharSequence getSummary() {

        String ringtoneTitle = null;

        if (mValue != null) {

            if (mValue.length() == 0)
                ringtoneTitle = mContext.getString(R.string.silent);

            Uri mValueUri = Uri.parse(mValue);
            if (ringtoneTitle == null && mExtraRingtones != null && mExtraRingtoneTitles != null) {

                for (int i = 0; i < mExtraRingtones.length; i++) {
                    Uri uriExtra = uriFromRaw(mExtraRingtones[i].toString());
                    if (uriExtra.equals(mValueUri)) {
                        ringtoneTitle = mExtraRingtoneTitles[i].toString();
                        break;
                    }
                }
            }

            if (ringtoneTitle == null) {
                Ringtone ringtone = RingtoneManager.getRingtone(mContext, mValueUri);
                if (ringtone != null) {
                    String title = ringtone.getTitle(mContext);
                    if (title != null && title.length() > 0) {
                        ringtoneTitle = title;
                    }
                }
            }
        }

        CharSequence summary = super.getSummary();

        if (ringtoneTitle != null) {
//            if (summary != null)
//                return String.format(summary.toString(), ringtoneTitle);
//            else
                return ringtoneTitle;
        } else return summary;
    }

    @Override
    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {

        final Map<String, Uri> sounds = new LinkedHashMap<String, Uri>();

        if (mExtraRingtones != null) {
            for (CharSequence extraRingtone : mExtraRingtones) {
                Uri uri = uriFromRaw(extraRingtone.toString());
                String title = getExtraRingtoneTitle(extraRingtone);

                sounds.put(title, uri);
            }
        }

        if (mShowSilent)
            sounds.put(mContext.getString(R.string.silent), Uri.parse(""));

        if (mShowDefault) {
            Uri uriDefault = RingtoneManager.getDefaultUri(mRingtoneType);
            if (uriDefault != null) {
                Ringtone ringtoneDefault = RingtoneManager.getRingtone(mContext, uriDefault);
                if (ringtoneDefault != null) {
                    sounds.put(ringtoneDefault.getTitle(mContext), uriDefault);
                }
            }
        }

        sounds.putAll(getSounds(mRingtoneType));

        final String[] titleArray = sounds.keySet().toArray(new String[0]);
        final Uri[] uriArray = sounds.values().toArray(new Uri[0]);

        int index = mValue != null ? Arrays.asList(uriArray).indexOf(Uri.parse(mValue)) : -1;

        builder.setSingleChoiceItems(titleArray, index, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {

                if (ringtone != null)
                    ringtone.stop();

                Uri uri = uriArray[which];

                if (uri != null) {
                    if (uri.toString().length() > 0) {
                        ringtone = RingtoneManager.getRingtone(mContext, uri);
                        if (ringtone != null) {
                            ringtone.play();
                        }
                    }
                    mValue = uri.toString();
                } else mValue = null;

            }
        });

        builder.setPositiveButton(R.string.dialog_save, this);
        builder.setNegativeButton(R.string.dialog_cancel, this);

    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {

        super.onDialogClosed(positiveResult);

        if (ringtone != null)
            ringtone.stop();

        if (positiveResult && callChangeListener(mValue)) {
            persistString(mValue);
            notifyChanged();
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return a.getString(index);
    }

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
        if (restoreValue) {
            mValue = getPersistedString("");
        } else {
            if (mExtraRingtones != null && defaultValue != null && defaultValue.toString().length() > 0) {

                int index = Arrays.asList(mExtraRingtones).indexOf((CharSequence) defaultValue);
                if (index >= 0) {
                    mValue = uriFromRaw(defaultValue.toString()).toString();
                } else {
                    mValue = (String)defaultValue;
                }

            } else {
                mValue = (String)defaultValue;
            }

            persistString(mValue);
        }
    }
}

src/main/res/values.attrs.xml:

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <declare-styleable name="ExtraRingtonePreference">
        <attr name="ringtoneType"><!-- Should correspond to RingtoneManager -->
            <!-- TYPE_RINGTONE: Ringtones. -->
            <flag name="ringtone" value="1" />
            <!-- TYPE_NOTIFICATION: Notification sounds. -->
            <flag name="notification" value="2" />
            <!-- TYPE_ALARM: Alarm sounds. -->
            <flag name="alarm" value="4" />
            <!-- TYPE_ALL: All available ringtone sounds. -->
            <flag name="all" value="7" />
        </attr>
        <attr name="showSilent" format="boolean"/>
        <attr name="showDefault" format="boolean"/>
        <attr name="extraRingtones" format="reference"/>
        <attr name="extraRingtoneTitles" format="reference"/>
    </declare-styleable>
</resources>

src/res/values/ringtone_preference_strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="ringtone_title">Ringtone</string>
    <string name="silent">Silent</string>
    <string name="dialog_save">Save</string>
    <string name="dialog_cancel">Cancel</string>

    <string-array name="extra_ringtones">
        <item>beep</item>
        <item>beep_beep</item>
        <item>default</item>
        <item>test</item>
    </string-array>

    <string-array name="extra_ringtone_titles">
        <item>Beep</item>
        <item>Beep-Beep</item>
        <item>Default</item>
        <item>Test</item>
    </string-array>
</resources>

并且在src/main/res/xml/pref_alarm.xml中使用:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:auto="http://schemas.android.com/apk/res-auto">
    <com.fletech.android.preference.ExtraRingtonePreference
        android:key="notifications_alarm_ringtone"
        android:title="@string/ringtone_title"
        android:defaultValue="default"
        auto:ringtoneType="alarm"
        auto:showSilent="true"
        auto:showDefault="true"
        auto:extraRingtones="@array/extra_ringtones"
        auto:extraRingtoneTitles="@array/extra_ringtone_titles"/>
</PreferenceScreen>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接