最近公司在做一个项目,有一个切换消息提示音的功能,可以切换本应用收到消息的提示音,而不影响系统提示音。我就按照微信的那个样式进行了编程,最终得到想要的效果。
转载请注明出处,谢谢:http://blog.csdn.net/harryweasley/article/details/46408037
怕有些人不知道怎么进入微信的新消息提示音功能,我这里说下操作步骤:
打开微信----我---设置---新消息提醒---新消息提示音。
经过以上的步骤就进入了这样的界面
这个是微信的效果图。
下面是我自己编程的效果图,如下图所示:
可以看到这两效果差别不是很大。
现在开始介绍一下具体实现的步骤。
本功能的最主要的功能是,这也是难点之一:获取到手机系统的提示音,并将它们显示在一个listview里面。
参考如下代码:
// 获得RingtoneManager对象 RingtoneManager manager = new RingtoneManager(this); // 设置RingtoneManager对象的类型为TYPE_NOTIFICATION,这样只会获取到notification的对应内容 manager.setType(RingtoneManager.TYPE_NOTIFICATION); Cursor cursor = manager.getCursor(); int num = cursor.getCount(); Log.i("tag", num + "消息音个数"); // 存储消息音名字的arrayList ArrayList<String> ringtoneList = new ArrayList<String>(); for (int i = 0; i < num; i++) { //获取当前i的铃声信息 Ringtone ringtone = manager.getRingtone(i); //获取当前i的uri,设置notification的自定义铃声要用到 Uri uri = manager.getRingtoneUri(i); //获取到当前铃声的名字 String title = ringtone.getTitle(this); ringtoneList.add(title); }将获取到的消息提示音的名字,加入到arrayList里。
先将主界面的信息贴上来,看一下,我再慢慢解释:
package jz.his.activity; import java.util.ArrayList; import jz.his.adapter.RingtoneAdapter; import jz.his.jzhis.R; import jz.his.util.SharedPreferenceUtil; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.Window; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; public class RingtoneActivity extends Activity { ArrayList<String> ringtoneList; ListView listView; RingtoneManager manager; RingtoneAdapter adapter; String ringName = ""; /** * 选择铃声的uri */ Uri uri = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_ringtone); listView = (ListView) findViewById(R.id.ringtone); getRingtone(); // initRingtoneManager(); // ringtoneList = FunctionActivity.ringtoneList; adapter = new RingtoneAdapter(this, ringtoneList, getIndex()); listView.setAdapter(adapter); // 设置从第getIndex()行开始显示 listView.setSelection(getIndex()); listView.setOnItemClickListener(new OnItemClickListener() { @SuppressWarnings("static-access") @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // 当点击的item是第一个“跟随系统”时 if (position == 0) { // 得到系统默认的消息uri Uri defalutUri = manager .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); // 通过URI获得系统默认的Ringtone发出声音 Ringtone defalutRingtone = manager.getRingtone( RingtoneActivity.this, defalutUri); defalutRingtone.play(); ringName = "跟随系统"; uri = null; } else { // 当点击的item不是第一个“跟随系统”时,获得的铃声要减一才对 Ringtone ringtone = manager.getRingtone(position - 1); uri = manager.getRingtoneUri(position - 1); ringtone.play(); ringName = ringtone.getTitle(RingtoneActivity.this); } adapter.first = new int[ringtoneList.size()]; if (adapter.first[position] == 0) { adapter.first[position] = 1; } else { adapter.first[position] = 0; } adapter.notifyDataSetChanged(); } }); } /** * 初始化RingtoneManager对象,在listview的点击事件里面,用到了 */ private void initRingtoneManager() { manager = new RingtoneManager(this); manager.setType(RingtoneManager.TYPE_NOTIFICATION); manager.getCursor(); } /** * 得到当前铃声的行数 */ private int getIndex() { for (int i = 0; i < ringtoneList.size(); i++) { if (SharedPreferenceUtil.getString(RingtoneActivity.this, SharedPreferenceUtil.RINGTONE_NAME).equals( ringtoneList.get(i))) { return i; } } return 0; } /** * 得到ringtone中的所有消息声音 */ private void getRingtone() { manager = new RingtoneManager(this); manager.setType(RingtoneManager.TYPE_NOTIFICATION); Cursor cursor = manager.getCursor(); int num = cursor.getCount(); Log.i("tag", num + "消息音个数"); ringtoneList = new ArrayList<String>(); for (int i = -1; i < num; i++) { if (i == -1) { ringtoneList.add("跟随系统"); } else { Ringtone ringtone = manager.getRingtone(i); // Uri uri = manager.getRingtoneUri(i); String title = ringtone.getTitle(this); ringtoneList.add(title); } } } public void allClick(View v) { switch (v.getId()) { case R.id.back_button: finish(); break; case R.id.save: if (ringName == "") { // 没有改动铃声直接关闭界面 finish(); } else { // 已经改动uri,如果又选择了跟随系统,则uri为null,其他的就是uri本身 if (uri == null) { SharedPreferenceUtil.setString(RingtoneActivity.this, SharedPreferenceUtil.url_string, ""); } else { SharedPreferenceUtil.setString(RingtoneActivity.this, SharedPreferenceUtil.url_string, uri.toString()); } Intent intent = new Intent(); intent.putExtra("ringName", ringName); intent.setClass(RingtoneActivity.this, FunctionActivity.class); startActivity(intent); } default: break; } } }
解释1.
因为listView显示的第一行是一个“追随系统”的item,所以我在适配数据的时候,有些小改变,在i=-1的时候,将ringtoneList添加为“追随系统”,其他的不变。因为进行了这样的处理,那么在点击各个item时候,获得铃声并进行播放时候,要做这样的处理:
Ringtone ringtone = manager.getRingtone(position - 1);
解释2.
最终将选择的铃声uri路径以String的格式存入到sharedPreference中。
在service里面进行设置,如下所示:主要看15--24行。
NotificationManager notificationManager = (NotificationManager) this .getSystemService(Context.NOTIFICATION_SERVICE); Notification n = new Notification(); Intent intent = new Intent(this, FunctionActivity.class); intent.putExtra("messageData","messageData" ); PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT); n.contentIntent = pi; // n.defaults = Notification.DEFAULT_ALL; if (SharedPreferenceUtil .getBoolean(this, SharedPreferenceUtil.IS_SOUND)) { } else { // 如果消息声音开启 if (!SharedPreferenceUtil.getStringNull(OnlineService.this, SharedPreferenceUtil.url_string).equals("")) { // 如果选择了其他的系统声音 n.sound = Uri.parse(SharedPreferenceUtil.getString( OnlineService.this, SharedPreferenceUtil.url_string)); } else { // 默认的系统声音 n.defaults |= Notification.DEFAULT_SOUND; } } if (SharedPreferenceUtil.getBoolean(this, SharedPreferenceUtil.IS_VIBRATE)) { } else { n.defaults |= Notification.DEFAULT_VIBRATE; } n.flags |= Notification.FLAG_SHOW_LIGHTS; n.flags |= Notification.FLAG_AUTO_CANCEL; // n.sound=Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "6"); n.icon = R.drawable.ic_launcher; n.when = System.currentTimeMillis(); n.tickerText = tickerText; n.setLatestEventInfo(this, title, content, pi); notificationManager.notify(id, n);
注意:如果是要选择其他的声音,直接是n.sound = 其他声音的Uri
这个真的非常重要,就直接这样就可以了,看网上一大堆什么
notification.sound = Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "6"); //使用系统提供的铃音并不能有效果,我也不清楚为什么,如果大家有合理的解释,请告知,嘿嘿。
现在谷歌官方已经不推荐上面的那种notification的做法了,新的做法是下面的这个:
Bitmap btm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); // 这里大图标,小图标刚好相反 NotificationCompat.Builder builder = new NotificationCompat.Builder( this).setSmallIcon(R.drawable.ic_launcher) .setContentTitle(title).setContentText(content) .setTicker(tickerText); if (SharedPreferenceUtil .getBoolean(this, SharedPreferenceUtil.IS_SOUND)) { } else { // 如果消息声音开启 if (!SharedPreferenceUtil.getStringNull(OnlineService.this, SharedPreferenceUtil.url_string).equals("")) { // 如果选择了其他的系统声音 builder.setSound(Uri.parse(SharedPreferenceUtil.getString( OnlineService.this, SharedPreferenceUtil.url_string))); } else { // 默认的系统声音 builder.setDefaults(Notification.DEFAULT_SOUND); } } if (SharedPreferenceUtil.getBoolean(this, SharedPreferenceUtil.IS_VIBRATE)) { } else { builder.setDefaults(Notification.DEFAULT_VIBRATE); } // 构建一个Intent Intent intent = new Intent(this, FunctionActivity.class); intent.putExtra("messageData","messageData" ); sendData(); // 封装一个Intent PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT); // 设置通知主题的意图 builder.setContentIntent(pendingIntent); // 获取通知管理器对象 NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(id, builder.build());
关于这个新的notification的用法,你可以参看这个文章:http://blog.csdn.net/harryweasley/article/details/46348363
不过这个有个问题就是,builder.setDefaults()这个方法设置默认的铃声,闪关灯,震动,每次只能设置一次,不能多次调用这个方法来设置。如果有大神知道,请告知我。
解释3:
当点击保存按钮后,就进入到之前的界面,因为我之前的界面是一个viewpager+fragment的一个界面,一个activity里面加入了四个Fragment的这样的一个界面。进入到主activity时候,进行判断:
/** * 选择消息提示音后,跳转到功能界面后,直接将其跳转设置界面 */ private void selectRingtone() { String ringName = getIntent().getStringExtra("ringName"); Log.e("tag", ringName+"传过来的值"); if (ringName != null) { pager.setCurrentItem(2); } }
直接跳转到第二个Fragment界面。如下图所示:
然后将设置界面的新消息提示音的内容进行改变:
newSound = (TextView) getActivity().findViewById(R.id.new_sounde_text); newSound.setText(SharedPreferenceUtil.getStringSystem(getActivity(), SharedPreferenceUtil.RINGTONE_NAME)); //第一次进入这个页面,下面的方法是不会执行的,因为ringName是null String ringName = getActivity().getIntent().getStringExtra("ringName"); if (ringName != null) { newSound.setText(ringName); Log.e("tag", ringName+"要保存的值"); SharedPreferenceUtil.setString(getActivity(), SharedPreferenceUtil.RINGTONE_NAME, ringName); }
解释4:
你可能注意到,我在RingtoneActivity代码里注释了两行代码,你会发现,当我们每次进入这个RingtoneActivity的时候,都要重新加载数据到arrayList里面,虽然数据不是很多,但是肉眼可以感觉到是有点卡顿的,那么为了防止卡顿,我在进入RingtoneActivity之前的FunctionActivity页面开了一个子线程先加载数据到arrayList里面,定义成static类型。如下所示:
static ArrayList<String> ringtoneList; Runnable run = new Runnable() { @Override public void run() { RingtoneManager manager = new RingtoneManager(FunctionActivity.this); manager.setType(RingtoneManager.TYPE_NOTIFICATION); Cursor cursor = manager.getCursor(); int num = cursor.getCount(); Log.i("tag", num + "消息音个数"); ringtoneList = new ArrayList<String>(); for (int i = -1; i < num; i++) { if (i == -1) { ringtoneList.add("跟随系统"); } else { Ringtone ringtone = manager.getRingtone(i); // Uri uri = manager.getRingtoneUri(i); String title = ringtone.getTitle(FunctionActivity.this); ringtoneList.add(title); } } } };
ringtoneList = FunctionActivity.ringtoneList;就获得了数据,这样就不会有卡顿了。但是我这个方法用到了static定义变量,这样容易造成oom,具体参看http://blog.csdn.net/harryweasley/article/details/45872685 android内存泄露优化总结
所以我弃用了这个方法,不知道有没有大神可以给个建议呢。
解释5:
当你选择了其他的铃声的时候,再次进入新消息提示音界面时候,是从当前选择的铃声开始展示的,如图所示:
当我选择了Clever这个铃声的时候,我再次进入这个页面,会从Clever这行开始显示。这个功能是这样实现的。嘿嘿,并没有想象的那么难吧,其实listview就自带这个方法的,只需要传入当前item的位置是第几个就行了。
// 设置从第getIndex()行开始显示 listView.setSelection(getIndex());
/** * 得到当前铃声的行数 */ private int getIndex() { for (int i = 0; i < ringtoneList.size(); i++) { if (SharedPreferenceUtil.getString(RingtoneActivity.this, SharedPreferenceUtil.RINGTONE_NAME).equals( ringtoneList.get(i))) { return i; } } return 0; }
RingtoneAdapter里的内容是这样的:
package jz.his.adapter; import java.util.ArrayList; import jz.his.adapter.MessageAdapter.ViewHolder; import jz.his.jzhis.R; import android.content.Context; import android.opengl.Visibility; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; public class RingtoneAdapter extends BaseAdapter { Context context; ArrayList<String> list; /** * 建立一个数组,默认都为0 */ public int[] first; int index; public RingtoneAdapter(Context cont, ArrayList<String> arayList, int index) { context = cont; list = arayList; this.index = index; first = new int[list.size()]; } class ViewHolder { TextView title; ImageView image; } @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { holder = new ViewHolder(); convertView = LayoutInflater.from(context).inflate( R.layout.item_ringtone, null); holder.title = (TextView) convertView.findViewById(R.id.title); holder.image = (ImageView) convertView.findViewById(R.id.image); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.title.setText(list.get(position)); if (first[position] == 0) { holder.image.setVisibility(View.GONE); } else { holder.image.setVisibility(View.VISIBLE); // 当点击其他item后,将index置为-1,则下面的if语句则不会再执行进入了 index = -1; } // 第一次进来的时候,让当前item的图片可见 if (position == index) { holder.image.setVisibility(View.VISIBLE); } return convertView; } }
RingtoneAdapter里进行了判断,是否某个item后面的对勾显示出来。这里当时还是纠结了一会的,最终还是解决了。特此记录。
item_ringtone布局的代码如下所示:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="5dp" /> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/text" android:layout_margin="5dp" android:gravity="center_vertical" android:text="mingzi" android:textSize="15sp" /> <TextView android:id="@+id/text2" android:layout_width="wrap_content" android:layout_height="5dp" android:layout_below="@id/title" /> <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_margin="10dp" android:src="@drawable/umeng_socialize_oauth_check_on" android:visibility="gone" /> </RelativeLayout>
里面的有两个空白的textView是为了扩开每个item的高度,为了让铃声名字看起来是在中间位置。
activity_ringtone下面的布局代码如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <RelativeLayout android:layout_width="match_parent" android:layout_height="55dip" android:background="@color/balck" android:orientation="horizontal" > <LinearLayout android:id="@+id/back_button" android:layout_width="wrap_content" android:layout_height="fill_parent" android:gravity="center_vertical" android:onClick="allClick" > <ImageView android:layout_width="10dip" android:layout_height="18dip" android:layout_gravity="center" android:layout_marginLeft="15dip" android:layout_marginRight="10dip" android:src="@drawable/icon_left_arrow" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="新消息提示音" android:textColor="@color/white" android:textSize="20sp" /> </LinearLayout> <Button android:id="@+id/save" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:background="@drawable/account_backg" android:onClick="allClick" android:text="保存" android:textColor="@color/white" /> </RelativeLayout> <ListView android:id="@+id/ringtone" android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none" > </ListView> </LinearLayout>
Button的account_backg代码是:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" > <!-- 内部颜色 --> <solid android:color="@color/account_green" /> <!-- 边缘线条颜色 --> <stroke android:width="1dp" android:color="@color/account_green" /> <!-- 圆角的幅度 --> <corners android:bottomLeftRadius="5dip" android:bottomRightRadius="5dip" android:topLeftRadius="5dip" android:topRightRadius="5dip" /> </shape>
这样基本的功能就写完了,因为是公司整个项目的,所以不能发源码给大家,但是有任何意见或者问题,可以在评论和我沟通,嘿嘿,共同进步嘛。