zoukankan      html  css  js  c++  java
  • 高仿微信新消息提示音功能

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


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




    这样主要的功能就写完了,由于是公司整个项目的,所以不能发源代码给大家。可是有不论什么意见或者问题。能够在评论和我沟通。嘿嘿,共同进步嘛。


  • 相关阅读:
    elasticsearch文档
    swagger 接口文档
    IOS Pod install 443
    Celery 中文文档
    orm 不设置外建进行跨表查询
    There is no current event loop in thread
    django 中文文档
    MJRefresh 下拉刷新 上拉加载
    beeline导出hive数据
    什么是数据湖_二(转载)
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/5397924.html
Copyright © 2011-2022 走看看