高仿新訊息提示音功能,高仿訊息
最近公司在做一個項目,有一個切換訊息提示音的功能,可以切換本應用收到訊息的提示音,而不影響系統提示音。我就按照的那個樣式進行了編程,最終得到想要的效果。
轉載請註明出處,謝謝: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 + "訊息音個數");// 儲存訊息音名字的arrayListArrayList<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;@Overrideprotected 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")@Overridepublic void onItemClick(AdapterView<?> parent, View view,int position, long id) {// 當點擊的item是第一個“跟隨系統”時if (position == 0) {// 得到系統預設的訊息uriUri 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"); //使用系統提供的鈴音
並不能有效果,我也不清楚為什麼,如果大家有合理的解釋,請告知,嘿嘿。
現在Google官方已經不推薦上面的那種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);}// 構建一個IntentIntent intent = new Intent(this, FunctionActivity.class);intent.putExtra("messageData","messageData" );sendData();// 封裝一個IntentPendingIntent 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是nullString 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() {@Overridepublic 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定義變數,這樣容易造成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;}@Overridepublic int getCount() {return list.size();}@Overridepublic Object getItem(int position) {return null;}@Overridepublic long getItemId(int position) {return 0;}@Overridepublic 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>
這樣基本的功能就寫完了,因為是公司整個項目的,所以不能發源碼給大家,但是有任何意見或者問題,可以在評論和我溝通,嘿嘿,共同進步嘛。