您的位置:首页 > 移动开发 > 微信开发

高仿微信新消息提示音功能

2016-04-16 11:17 609 查看
近期公司在做一个项目。有一个切换消息提示音的功能,能够切换本应用收到消息的提示音,而不影响系统提示音。我就依照微信的那个样式进行了编程,终于得到想要的效果。

转载请注明出处。谢谢: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:

当点击保存button后。就进入到之前的界面,由于我之前的界面是一个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);
}

}

}
};


这样在ringtoneActivity中,通过
ringtoneList = FunctionActivity.ringtoneList;
就获得了数据,这样就不会有卡顿了。可是我这种方法用到了static定义变量,这样easy造成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>


这样主要的功能就写完了,由于是公司整个项目的,所以不能发源代码给大家。可是有不论什么意见或者问题。能够在评论和我沟通。嘿嘿,共同进步嘛。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: