1. 程式人生 > >Android 智慧簡訊_第二天

Android 智慧簡訊_第二天

這些部落格都是我自己的學習筆記,不是用來教學的。

刪除選中的簡訊:

我們刪除簡訊其實很簡單,但是我們要實現一個對話方塊去顯示,還需要一個對話方塊的進度條。

刪除簡訊操作就是操作資料庫就行了。使用內容解析者去操作,但是我們要去看看到底要刪除的uri是什麼。

我們發現我們要刪除一個就需要刪除一個聯絡人,所以我們直接刪除這個人名下的所有簡訊就可以了。我們可以找到他的id去刪除。

對簡訊內容進行操作需要寫簡訊的許可權。

Uri URI_SMS=Uri.parse("content://sms");

我們先找到uri然後我們在Fragment裡面定義一個方法。
		case R.id.bt_conversation_delete:
			selectedConversationId = adapter.getSelectedConversationId();
    if(selectedConversationId.size()==0)
    {
    	return;
    }
    deleteAll();

我們先獲取到集合。

然後如果不為空就呼叫刪除方法。

	public void deleteAll() {
		for (Integer integer : selectedConversationId) {
			String where = "thread_id=" + integer;
			getActivity().getContentResolver().delete(ConstantValues.URI_SMS,
					where, null);
		}

	}
——————————————————————————————
確定取消對話方塊:

我們要自定義樣式,所以我們要先建立一個包dialog包。然後建立一個類。但是由於一個專案對話方塊有很多。所以我們也要建立一個基類。

public abstract class BaseDialog extends AlertDialog implements android.view.View.OnClickListener{

	public BaseDialog(Context context) {
		super(context);
	}

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	initView();
	initListener();
	initData();
}
public abstract void initView();
public abstract  void initListener();
public abstract void initData();
public abstract void processClick(View v);
@Override
public void onClick(View v) {
	processClick(v);
	
};

}

注意這裡我們要用到的點選事件是,View 類的,不是Dialog類的。原因是因為我們使用,的對話方塊是自定義的,所以對話方塊上面的按鈕也是自己定義的Button.

所以我們要使用View類的。

然後我們需要建立一個對話方塊的佈局檔案。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="240dp"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:background="@drawable/dialog_confrim"
    android:gravity="center"
    android:orientation="vertical" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingTop="15dp"
        android:text="標題"
        android:textSize="20sp" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:padding="5dp"
        android:text="nihao1111111111111111111111111111111111111111" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#000000" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="15dp"
            android:layout_weight="1"
            android:text="取消" />

        <View
            android:layout_width="1dp"
            android:layout_height="match_parent"
            android:background="#000000" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="15dp"
            android:layout_weight="1"
            android:text="確定" />
    </LinearLayout>

</LinearLayout>


我們為了實現讓這個自定義的dialog佈局變成圓角的,所以我們建立drawable,選擇shape(形狀)  。
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
    <corners android:radius="8dp"/>
    <solid android:color="#cccccc"/>

</shape>

先定義android:shape為長方形

corners就是角:定義角的弧度為8dp

solid實體:顏色為xxxx:

這樣寫的話每一個對話方塊都需要家這樣一行程式碼。

如果我們需要每一個對話方塊都是這種樣式。

我們就需要在style中去定義一個主題。

 <style name="BaseDialog" parent="@android:style/Theme.Dialog">
     
      <item name="android:windowBackground">@drawable/dialog_confrim</item>
 </style>

我們這樣就設定了一個自定義的style 使用的形狀就是我們自己定義的。

我們的BaseDialog繼承了AlertDialog他有一個構造方法:

	public BaseDialog(Context context) {
		
		super(context, R.style.BaseDialog);
	
	}


這裡注意由於AlertDialog有三個建構函式,其中有一個是可以接受 Theme引數的。

所以我們直接super(context,Theme)把Theme傳遞給他即可。

然後我們在我們自己定義的dialog檔案中,把android:background=""刪除。因為你在baseDialog中已經定義了,如果你在佈局檔案中在定義就會被覆蓋。

然後我們在我們定義個Dialog類中 設定佈局檔案給他。

	@Override
	public void initView() {
		setContentView(R.layout.dialog_comfirm);

	}

然後我們在Fragment檔案中給他設定方法去呼叫
	public void showDelete() {
		ComfirmDialog dialog = new ComfirmDialog(getActivity());
		dialog.show();
	}

——————————————————————

確定取消對話方塊按鈕背景:

我們先給Button設定背景,同樣我們給他設定選擇器

android:background="@drawable/selector_bt_bg"

然後我們發現設定完之後左下角和右下角都不是圓角了。

所以我們要自己定義一個selector.

我們要單獨給他設定兩個shape.

<shape xmlns:android="http://schemas.android.com/apk/res/android" >
    <corners android:bottomRightRadius="8dp"/>
    <solid android:color="#ffffff"/>

</shape>

<shape xmlns:android="http://schemas.android.com/apk/res/android" >
    <corners android:bottomRightRadius="8dp"/>
    <solid android:color="#33666666"/>

</shape>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/bg_right_bt" android:state_pressed="false"></item>
    <item android:drawable="@drawable/bg_right_bt_press" android:state_pressed="true"></item>

</selector>
然後給button設定  android:background="@drawable/bg_rigth_bt_sec"即可。
————————————————————————————————

給確定取消對話方塊設定屬性和按鈕偵聽:

我們通過建構函式傳遞 Title 和Message 進來。

public class ComfirmDialog extends BaseDialog {

	private String title;

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	private String message;
	private TextView tv_dialog_title;
	private TextView tv_dialog_msg;

	public ComfirmDialog(Context context, String title, String message) {
		super(context);
		this.title = title;
		this.message = message;
	}

	@Override
	public void initView() {
		setContentView(R.layout.dialog_comfirm);
		tv_dialog_title = (TextView) findViewById(R.id.tv_dialog_title);
		tv_dialog_msg = (TextView) findViewById(R.id.tv_dialog_msg);

	}

	@Override
	public void initListener() {

	}

	@Override
	public void initData() {
		tv_dialog_title.setText(getTitle());
		tv_dialog_msg.setText(getMessage());
	}

	@Override
	public void processClick(View v) {

	}

}

然後我們在new 的時候把引數傳遞進來就行了。

ComfirmDialog dialog = new ComfirmDialog(getActivity(),"提示","請確定是否要刪除");

設定好了之後我們要去設定 按鈕的監聽事件

為了讓我們這個對話方塊還能實現其他的點選功能我們不能寫死 點選按鈕的事件。

所以我們在定義一個用於監聽的介面。

public interface DialogBtListener {

	void onCancle();

	void onConfrim();

}
然後我們在自定義的Dialog類裡面的建構函式接收這個介面;
private String message;
	private TextView tv_dialog_title;
	private TextView tv_dialog_msg;
	private Button bt_dialog_cancel;
	private Button bt_dialog_confirm;
	private DialogBtListener onConfrimListener;

	public void setOnConfrimListener(DialogBtListener onConfrimListener) {
		this.onConfrimListener = onConfrimListener;
	}

	public ComfirmDialog(Context context, String title, String message,
			DialogBtListener onConfrimListener) {
		super(context);
		this.title = title;
		this.message = message;
		this.onConfrimListener = onConfrimListener;
	}

	@Override
	public void initView() {
		setContentView(R.layout.dialog_comfirm);
		tv_dialog_title = (TextView) findViewById(R.id.tv_dialog_title);
		tv_dialog_msg = (TextView) findViewById(R.id.tv_dialog_msg);
		bt_dialog_cancel = (Button) findViewById(R.id.bt_dialog_cancel);
		bt_dialog_confirm = (Button) findViewById(R.id.bt_dialog_confirm);
	}

	@Override
	public void initListener() {
		bt_dialog_cancel.setOnClickListener(this);
		bt_dialog_confirm.setOnClickListener(this);
	}

	@Override
	public void initData() {
		tv_dialog_title.setText(getTitle());
		tv_dialog_msg.setText(getMessage());
	}

	@Override
	public void processClick(View v) {
		switch (v.getId()) {
		case R.id.bt_dialog_cancel:
			if (onConfrimListener != null) {
				onConfrimListener.onCancle();
			 }
			break;
		case R.id.bt_dialog_confirm:
			if (onConfrimListener != null) {
				onConfrimListener.onConfrim();
			}
			break;

		}
		dismiss();
	}


不管我們點確定還是取消 我們都要關閉對話方塊 所以最後都要呼叫dismiss();

然後我們可以判斷傳遞進來的這個監聽介面是不是空,不是空我們在呼叫。

如果我們要實現其他的監聽介面 我們可以呼叫set介面的方法去改變介面,然後去判斷就可以了。


然後我們在Fragment裡面去實現方法就可以了。

	public void showDelete() {
		ComfirmDialog dialog = new ComfirmDialog(getActivity(),"提示","請確定是否要刪除",new DialogBtListener() {
			
			@Override
			public void onConfrim() {
				deleteAll();
			}
			
			@Override
			public void onCancle() {
			}
		});
		dialog.show();
	}

_______________________________________

刪除的優化:

我們還要注意一點:就是我們操作資料庫其實就是一個耗時操作。如果資料很多,我們很可能ANR所以我們需要在子執行緒進行。

還有一個注意要點就是,如果我們刪除了集合裡面選擇的簡訊,那麼這些id就沒用了。

所以我們必須清空集合,不然我們下次再去查的時候就會出問題。

	public void deleteAll() {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				
				for (Integer integer : selectedConversationId) {
					String where = "thread_id=" + integer;
					getActivity().getContentResolver().delete(ConstantValues.URI_SMS,
							where, null);
				}
				selectedConversationId.clear();
				
			}
		}).start();

	}
handler.sendEmptyMessage(WHAT_DELETE_COMPLETE);
當我們刪除完畢後應該退出。選擇模式,然後選單應該也變成編輯模式選單。

所以我們需要更新UI,但是不能在子執行緒更新。所以我們在主執行緒new Handler來處理訊息。

	private static final int WHAT_DELETE_COMPLETE = 0;
	private Handler handler = new Handler() {
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case WHAT_DELETE_COMPLETE:
				adapter.setIsSelectMode(false);
				showEditMenu();
				break;

			}
		}
	};

我們定義一個全域性的常量。

然後判斷如果是這個常量就進行退出選擇模式。顯示編輯選單。

——————————————————————————

刪除進度條對話方塊:

我們的刪除對話方塊其實和確定取消對話方塊是一樣的,所以我們只需要把中間的文字。

換成對話方塊。按鈕去掉一個,然後改成取消中斷。

我們把中間的textView改成:

    <ProgressBar 
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

但是我們現在的需求是需要 顯示進度的時候 顏色是紅色。

所以我們需要去改變進度條的顏色,所以我們現在先去styles.xml檔案中定義一個style.

   <style name="BaseDialog" parent="@android:style/Widget.ProgressBar.Horizontal">
      
    </style>

然後我們直接去看看progerssBar的原始碼 看看谷歌怎麼定義的。
    <style name="Widget.ProgressBar.Horizontal">
        <item name="android:indeterminateOnly">false</item>
        <item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
        <item name="android:indeterminateDrawable">@android:drawable/progress_indeterminate_horizontal</item>
        <item name="android:minHeight">20dip</item>
        <item name="android:maxHeight">20dip</item>
        <item name="android:mirrorForRtl">true</item>
    </style>


然後我們發現有這樣一行:

item name="android:progressDrawable">@android:drawable/progress_horizontal</item>

這一行就是和顏色相關的。

所以我們點進去看看原始碼怎麼定義的。

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    
    <item android:id="@android:id/background">
        <shape>
            <corners android:radius="5dip" />
            <gradient
                    android:startColor="#ff9d9e9d"
                    android:centerColor="#ff5a5d5a"
                    android:centerY="0.75"
                    android:endColor="#ff747674"
                    android:angle="270"
            />
        </shape>
    </item>
    
    <item android:id="@android:id/secondaryProgress">
        <clip>
            <shape>
                <corners android:radius="5dip" />
                <gradient
                        android:startColor="#80ffd300"
                        android:centerColor="#80ffb600"
                        android:centerY="0.75"
                        android:endColor="#a0ffcb00"
                        android:angle="270"
                />
            </shape>
        </clip>
    </item>
    
    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <corners android:radius="5dip" />
                <gradient
                        android:startColor="#ffffd300"
                        android:centerColor="#ffffb600"
                        android:centerY="0.75"
                        android:endColor="#ffffcb00"
                        android:angle="270"
                />
            </shape>
        </clip>
    </item>
    
</layer-list>

我們發現他定義了這樣一個層, 用來設定 進度條的屬性。

所以我們自己也定義一個這樣的層,然後修改屬性,我們就可以直接使用它了。

老方法直接去drawable建立一個layer-list

注意,裡面有一個gradient屬性,這個屬性就是用來控制漸變的。進度條的顏色其實就是漸變的。

我們直接去progress裡面把gradient屬性刪除。建立一個 <solid  android:color="#f00"/>

一個固定的顏色。

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:id="@android:id/background">
        <shape>
            <corners android:radius="5dip" />
            <!--
                 <gradient
                    android:startColor="#ff9d9e9d"
                    android:centerColor="#ff5a5d5a"
                    android:centerY="0.75"
                    android:endColor="#ff747674"
                    android:angle="270"
            />
            -->
            <solid android:color="#aa666666" />
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <corners android:radius="5dip" />
                <!--
                     <gradient
                        android:startColor="#ffffd300"
                        android:centerColor="#ffffb600"
                        android:centerY="0.75"
                        android:endColor="#ffffcb00"
                        android:angle="270"
                />
                -->
                <solid android:color="#f00" />
            </shape>
        </clip>
    </item>

</layer-list>

然後我們就可以建立一個自定義的Dialog了 和建立取消確定對話方塊步驟一樣。
public class DeleteDialog extends BaseDialog {

	private ProgressBar pb_delete_;
	private TextView tv_delete_title;
	private Button bt_dialog_interrupe;
	private DeleteDialogListener onDeleteLintener;
	//最大值
	private int Maxprogerss;

	public DeleteDialog(Context context,int Maxprogerss,DeleteDialogListener onLintener) {
		super(context);
		this.onDeleteLintener=onLintener;
		this.Maxprogerss=Maxprogerss;
	}

	@Override
	public void initView() {
		setContentView(R.layout.dialog_delete);
		pb_delete_ = (ProgressBar) findViewById(R.id.pb_delete_);
		tv_delete_title = (TextView) findViewById(R.id.tv_delete_title);
		bt_dialog_interrupe = (Button) findViewById(R.id.bt_dialog_interrupe);
	}

	@Override
	public void initListener() {
		bt_dialog_interrupe.setOnClickListener(this);
	}

	@Override
	public void initData() {
		//初始化
		tv_delete_title.setText("正在刪除(0/"+Maxprogerss+")");
		pb_delete_.setMax(Maxprogerss);
	}

	@Override
	public void processClick(View v) {
		switch (v.getId()) {
		case R.id.bt_dialog_interrupe:
			if (onDeleteLintener!=null) {
				onDeleteLintener.interrupe();
			}
			dismiss();
			break;

		}
	}
	//暴露一個方法給外界呼叫這個方法設定進度條
	public void serProgress(int progerss){
		tv_delete_title.setText("正在刪除("+progerss+"/"+Maxprogerss+")");
		pb_delete_.setProgress(progerss);
	}

}

接下來我們就去Fragment裡面呼叫。
boolean isStop = false;
	public void deleteAll() {
		dialog = new DeleteDialog(getActivity(), selectedConversationId.size(),
				new DeleteDialogListener() {

					@Override
					public void interrupe() {
                        isStop=true;
					}
				});
dialog.show();
		new Thread(new Runnable() {

			@Override
			public void run() {
				for (Integer integer : selectedConversationId) {
					SystemClock.sleep(1000);
					if(isStop){
						isStop=false;
						break;
					}
					String where = "thread_id=" + integer;
					getActivity().getContentResolver().delete(
							ConstantValues.URI_SMS, where, null);
					Message msg = Message.obtain();
					msg.what = WHAT_UPDATA_PROGRESS;
					msg.arg1 = count++;
					handler.sendMessage(msg);
				}
				selectedConversationId.clear();
				handler.sendEmptyMessage(WHAT_DELETE_COMPLETE);
			}
		}).start();
		
	}

因為我們是在刪除的時候顯示對話方塊。所以我們直接在刪除的方法裡面 呼叫對話方塊的建構函式。

然後我們建立一個 標籤。。當我們點選中斷的時候我們就把他設定為true。

然後線上程中,檢查標籤是否為true,如果是就直接跳出迴圈。

因為我們要修改 UI 。然後要獲取當前的進度條的當前程序。所以我們要攜帶資料去傳遞訊息,那麼就不能使用空訊息。

	private static final int WHAT_UPDATA_PROGRESS = 1;
	private int count = 1;
	private Handler handler = new Handler() {
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case WHAT_DELETE_COMPLETE:
				adapter.setIsSelectMode(false);
				adapter.notifyDataSetChanged();
				showEditMenu();
				dialog.dismiss();
				break;
			case WHAT_UPDATA_PROGRESS:
				
				dialog.serProgress(msg.arg1);
				break;
			}
		}
	};
當我們完成的時候要關閉對話方塊。

————————————————————————

建立會話詳細Acticity並傳遞資料:

我們需要傳遞adress和會話id過去。因為每一個條目的資料都不一樣,所以我們應該在

會話詳情的activity在對這些資料在

	public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				// 因為我們使用CursorAdapter這個類,他幫我們封裝好了getItem可以返回你指定的Cursor物件
				if (adapter.getIsSelectMode()) {
					adapter.SelectSingle(position);
				} else {
					//會話詳情
					Intent intent = new Intent(getActivity(),
							ConversationDataActivity.class);
					Cursor cursor = (Cursor) adapter.getItem(position);
					ConversationBean creatConversationCursor = ConversationBean
							.creatConversationCursor(cursor);

					intent.putExtra("address",
							creatConversationCursor.getAddress());
					intent.putExtra("thead_id",
							creatConversationCursor.getThread_id());
					startActivity(intent);
				}

			}
		});

	}

public class ConversationDataActivity extends BaseActivity {

	@Override
	public void initView() {

	}

	@Override
	public void initListener() {

	}

	@Override
	public void initData() {
		Intent intent = getIntent();
		if (intent != null) {
			String address = intent.getStringExtra("address");
			int thread_id = intent.getIntExtra("thread_id", -1);
			LogUtils.i("yss", address+thread_id);
		}
	}

	@Override
	public void progressClick(View v) {

	}

}


——————————————————————

定義ConversationDetailActivity的佈局檔案:

我們先要定義一個標題欄,因為標題欄我們可以複用,所以我們直接專門建立一個標題欄的XML.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="#292B28" >

    <ImageView
        android:id="@+id/iv_titlebar_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:background="@drawable/selector_titlebar_bg" />

    <TextView
        android:id="@+id/tv_titlebar_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="標題"
        android:textColor="#ffffff"
        android:textSize="18sp" />

</RelativeLayout>


然後我們在建立activity的xml.

我們可以直接使用    <include layout="@layout/layout_titlebar"/>

呼叫我們定義好的佈局 標題欄檔案。顯示到當前的佈局檔案中。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <include layout="@layout/layout_titlebar" />

    <ListView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" >
    </ListView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#292B28"
        android:orientation="horizontal"
        android:padding="5dp" >

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/bg_btn_normal"
            android:maxLines="3"
            android:minHeight="32dp" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:background="@drawable/selector_bt_bg"
            android:minHeight="32dp"
            android:text="傳送" />
    </LinearLayout>

</LinearLayout>
這裡注意一下我們要設定一個 minHeiget:這個屬性就是你的最低寬度是這麼多。

如果你的控制元件沒有其他東西的時候他就會變成minHeiget.


當我們的控制元件高要是其他控制元件佔用完後,剩下的全部都是它的。那麼我們就需要設定權重。

——————————————————————————

初始化標題欄:

public class ConversationDataActivity extends BaseActivity {

	private String address;
	private int thread_id;

	@Override
	public void initView() {
		setContentView(R.layout.activity_desconversation);
	}

	@Override
	public void initListener() {

	}

	@Override
	public void initData() {
		Intent intent = getIntent();
		if (intent != null) {
			address = intent.getStringExtra("address");
			thread_id = intent.getIntExtra("thread_id", -1);
			initTitlebar();
		}
	}

	public void initTitlebar() {
		findViewById(R.id.iv_titlebar_back).setOnClickListener(this);
		String name = ContactDao.getNameforAddress(getContentResolver(),
				address);
		((TextView) findViewById(R.id.tv_titlebar_title)).setText(TextUtils
				.isEmpty(name) ? address : name);

	}

	@Override
	public void progressClick(View v) {
		switch (v.getId()) {
		case R.id.iv_titlebar_back:
			finish();
			break;

		}
	}

}

當我們intent不為空的時候呼叫。初始化titlebar.   

————————————————————————

非同步查詢會話所屬的簡訊:

		String [] projection ={
			"_id",
			"body",
			"type",
			"date"
		};
		String selection="thread_id ="+thread_id;
		System.out.println(selection);
		Cursor cursor = getContentResolver().query(ConstantValues.URI_SMS, projection, selection, null, null);
		CursorUtils.printCursor(cursor);
	}

我們先查詢看看能不能查詢出資料。

我們現在需要sms表裡面的這些資料,第一個是應為CursorAdapter必須要查的_id

剩下三個是我們在介面需要使用的資料。

但是我們這裡使用的是同步的查詢,所以下面我們用非同步查詢來實現。

		String [] projection ={
			"_id",
			"body",
			"type",
			"date"
		};
		String selection="thread_id ="+thread_id;
//		System.out.println(selection);
		SimpleQueryHandler queryHandler = new SimpleQueryHandler(getContentResolver());
		queryHandler.startQuery(0, null, ConstantValues.URI_SMS, projection, selection, null, "date");
		

記得這裡我們需要用升序。所以我們直接使用預設排序 即可。升序就是最新的在下面。

定義佈局檔案:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView 
        android:id="@+id/tv_desconversation_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="12:34pm"
        android:textSize="14sp"
        android:textColor="#ffffff"
        android:background="@drawable/db_desconversation"
        android:paddingLeft="6dp"
        android:paddingRight="6dp"
        android:paddingTop="3dp"
        android:paddingBottom="3dp"
        android:layout_marginTop="8dp"
        android:layout_gravity="center_horizontal"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="5dp"
        android:background="@drawable/bg_receive_sms"
        android:paddingBottom="8dp"
        android:paddingLeft="25dp"
        android:paddingRight="8dp"
        android:paddingTop="8dp"
        android:minWidth="70dp"
        android:text="你好帥哥"
        android:textColor="#fff" />
    <TextView
        android:layout_gravity="right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="5dp"
        android:background="@drawable/bg_send_sms"
        android:paddingBottom="8dp"
        android:paddingLeft="8dp"
        android:paddingRight="25dp"
        android:paddingTop="8dp"
        android:minWidth="70dp"
        android:text="你好美女"
        android:textColor="#000" />
</LinearLayout>

同樣當你字很少的時候,我們需要定義一個minWidth。

————————————————————————————

ConversationDetailActivity的listView內容顯示:

我們先建立了Adapter,這裡和昨天步驟都是一樣的。

public class DesConversaionAdapter extends CursorAdapter {

	public DesConversaionAdapter(Context context, Cursor c) {
		super(context, c);

	}

	@Override
	public View newView(Context context, Cursor cursor, ViewGroup parent) {

		return View.inflate(context, R.layout.desconversation, null);
	}

	@Override
	public void bindView(View view, Context context, Cursor cursor) {
          ViewHolder holder = getHolder(view);
          
	}
	public ViewHolder getHolder(View view){
		ViewHolder holder = (ViewHolder) view.getTag();
		if (holder==null) {
			 holder = new ViewHolder(view);
			 view.setTag(holder);
		}
		return holder;
		
	}

	class ViewHolder {
		private TextView tv_desconversation_date;
		private TextView tv_desconversation_receive;
		private TextView tv_desconversation_send;

		public ViewHolder(View view) {
			tv_desconversation_date = (TextView) view
					.findViewById(R.id.tv_desconversation_date);
			tv_desconversation_receive = (TextView) view
					.findViewById(R.id.tv_desconversation_receive);
			tv_desconversation_send = (TextView) view
					.findViewById(R.id.tv_desconversation_send);

		}
	}
}

然後我們在去activity裡面給ListView設定adapter
	adapter = new DesConversaionAdapter(this, null);
		//顯示會話的所有簡訊。
		lv_desconversation.setAdapter(adapter);
		
		String[] projection = { "_id", "body", "type", "date" };
		
		String selection = "thread_id =" + thread_id;
		// System.out.println(selection);
		SimpleQueryHandler queryHandler = new SimpleQueryHandler(
				getContentResolver());
		//這裡cookie傳遞adapter過去
		queryHandler.startQuery(0, adapter, ConstantValues.URI_SMS, projection,
				selection, null, "date");

接著我們就去adapter裡面先把Cursor封裝到bean裡面。因為這樣可以比較方便的操作資料。

步驟和上一天的差不多 :

	public void bindView(View view, Context context, Cursor cursor) {
		ViewHolder holder = getHolder(view);

		// 這裡的cursor不需要移動,因為原始碼已經幫我們做了
		SmsBean smsBean = SmsBean.createFormCursor(cursor);
		if (DateUtils.isToday(smsBean.getDate())) {
			holder.tv_desconversation_date.setText(DateFormat.getTimeFormat(
					context).format(smsBean.getDate()));

		} else {
			holder.tv_desconversation_date.setText(DateFormat.getDateFormat(
					context).format(smsBean.getDate()));
		}
		holder.tv_desconversation_receive
				.setVisibility(smsBean.getType() == ConstantValues.TYPE_RECEIVE ? View.VISIBLE
						: View.GONE);
		holder.tv_desconversation_send
				.setVisibility(smsBean.getType() == ConstantValues.TYPE_SEND ? View.VISIBLE
						: View.GONE);
		if (smsBean.getType() == ConstantValues.TYPE_RECEIVE) {
			holder.tv_desconversation_receive.setText(smsBean.getBody());
		}
		holder.tv_desconversation_send.setText(smsBean.getBody());
	}

但是這樣我們做完 發現條目不是很好看,條目還能點選,且還有線。所以我們需要去去掉它。
    <ListView
        android:id="@+id/lv_desconversation"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:divider="@android:color/transparent"
        android:listSelector="@android:color/transparent" >
    </ListView>
<!--  android:divider="@android:color/transparent" //分隔線設定顏色為透明
android:listSelector="@android:color/transparent" //點選時候的顏色 設定為透明-->

但是還有一個問題就是每一條都會顯示時間,這樣非常不好看。我們可以設定相隔多少時間就不會去顯示時間了。比如你第一條和第二條都是在同一個時間發的,我們就顯示一個時間。這個間隔可以自己按需求定義。

我們先把判斷時間顯示的函式,封裝成一個類,因為我們要多次呼叫。

	private void showDate(Context context, ViewHolder holder, SmsBean smsBean) {
		if (DateUtils.isToday(smsBean.getDate())) {
			holder.tv_desconversation_date.setText(DateFormat.getTimeFormat(
					context).format(smsBean.getDate()));

		} else {
			holder.tv_desconversation_date.setText(DateFormat.getDateFormat(
					context).format(smsBean.getDate()));
		}
	}

然後我們定義一個常量

private static final int DURTION = 3 * 60 * 1000;

用來設定我們的時間間隔。

		// 先判斷是否是第一條,第一條沒有上一條應該直接顯示
		if (cursor.getPosition() == 0) {
			showDate(context, holder, smsBean);
		} else {
			// 判斷上一條簡訊和當前的簡訊是否相差三分鐘
			long date = getPreviousDate(cursor.getPosition());
			if (smsBean.getDate() - date > DURTION) {
				holder.tv_desconversation_date.setVisibility(View.VISIBLE);
				showDate(context, holder, smsBean);

			} else {
				holder.tv_desconversation_date.setVisibility(View.GONE);
			}
		}

	private long getPreviousDate(int postion) {
		//獲取上一條的內容所以需要減1
		Cursor cursor = (Cursor) getItem(postion-1);
		SmsBean smsBean = SmsBean.createFormCursor(cursor);

		return smsBean.getDate();
	}

一些小問題:

當我們進入會話詳情頁面的時候。發現簡訊不能移動到最下面要手動移。

解決:lv_desconversation.setSelection(cursor.getCount());

我們使用這個方法就可以讓ListView初始的位置在你指定的位置。我們使用cursor條目,是因為最大值就是他的條目數。

但是有一個問題就是,我們到底應該在哪呼叫這個方法,因為如果你的cursor沒有值。那麼這個方法就是無效的。

public class SimpleQueryHandler extends AsyncQueryHandler {

	public SimpleQueryHandler(ContentResolver cr) {
		super(cr);
	
	}
	//這個方法就是用來接收剛剛傳遞的兩個引數,當查詢完成的時候呼叫
     @Override
    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
   
    	super.onQueryComplete(token, cookie, cursor);
//    	CursorUtils.printCursor(cursor);
    	if(cookie!=null&& cookie instanceof CursorAdapter){
			//查詢得到的cursor,交給CursorAdapter,由它把cursor的內容顯示至listView
			((CursorAdapter)cookie).changeCursor(cursor);
    	}
    }
}

最終查詢到Cursor且將值傳遞回去,我們是使用了changCursor方法。

所以我們應該去重寫這個方法,然後在這個方法裡面加上。

所以我們就去繼承CursorAdatper的類裡面重寫這個方法。

第一步就是要將lv傳遞給Adapter.在建構函式的時候傳遞進來。

	@Override
	public void changeCursor(Cursor cursor) {
		super.changeCursor(cursor);
		lv.setSelection(cursor.getCount());

	}

ChangeCursor是第一次查詢的時候會呼叫,但是當你簡訊內容改變的時候不會呼叫。

所以:

問題2:

當我們點選編輯框的時候想輸入字元。發現輸入法框擋住了我們的介面顯示的內容。

解決:

我們可以給listview設定

lv_desconversation.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL

這個屬性可以讓listview在內容改變的時候 移動到最新的位置。

還有一個方法就是通過設定Activity的屬性來設定軟鍵盤和activity之間的一些屬性。

在清單檔案中:

        <activity android:name="com.chen.textttt.activity.ConversationDataActivity"
            android:windowSoftInputMode="stateUnspecified|adjustResize"
            >
            
            
        </activity>

我們可以使用 windowSoftInputMode這個屬性來給 activity和軟體盤設定 效果。

通常我們都是一個state和一個adjust 組合在一起使用。

各值的含義:

stateUnspecified:軟鍵盤的狀態並沒有指定,系統將選擇一個合適的狀態或依賴於主題的設定

stateUnchanged:當這個activity出現時,軟鍵盤將一直保持在上一個activity裡的狀態,無論是隱藏還是顯示

stateHidden:使用者選擇activity時,軟鍵盤總是被隱藏

stateAlwaysHidden:當該Activity主視窗獲取焦點時,軟鍵盤也總是被隱藏的

stateVisible:軟鍵盤通常是可見的

stateAlwaysVisible:使用者選擇activity時,軟鍵盤總是顯示的狀態

adjustUnspecified:預設設定,通常由系統自行決定是隱藏還是顯示

adjustResize:該Activity總是調整螢幕的大小以便留出軟鍵盤的空間

adjustPan:當前視窗的內容將自動移動以便當前焦點從不被鍵盤覆蓋和使用者能總是看到輸入內容的部分


————————————————————

傳送簡訊:

我們建立一個SmsDao類,然後建立一個方法用來發送簡訊。

然後我們去實現傳送簡訊的邏輯:

public class SmsDao {
	public static void sendSms(String address,String body){
		SmsManager manager = SmsManager.getDefault();
		ArrayList<String> smss = manager.divideMessage(body);
		for (String text : smss) {
			manager.sendTextMessage(address, null, text, sentIntent, null);
		}
		
	}

}

這裡我們要使用一個pendingIntent 去實現 廣播

對於pendingIntent的理解可以看下面這個部落格。

http://blog.csdn.net/yuzhiboyi/article/details/8484771

public class SmsDao {
	public static void sendSms(String address,String body,Context context){
		SmsManager manager = SmsManager.getDefault();
		Intent intent = new Intent("com.chen.text.sendsms");
		ArrayList<String> smss = manager.divideMessage(body);
		PendingIntent sentIntent=PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_ONE_SHOT
);
		for (String text : smss) {
			manager.sendTextMessage(address, null, text, sentIntent, null);
		}
		
	}

}


這裡我們需要注意,我們PendingIntent裡面的intent引數 是一個廣播接收者,接收的action屬性。

最後的Flage有如下引數:

FLAG_ONE_SHOT:利用 FLAG_ONE_SHOT獲取的PendingIntent只能使用一次,即使再次利用上面三個方法重新獲取,再使用PendingIntent也將失敗。

FLAG_NO_CREATE:利用FLAG_NO_CREAT獲取的PendingIntent,若描述的Intent不存在則返回NULL值.

FLAG_CANCEL_CURRENT:如果描述的PendingIntent已經存在,則在產生新的Intent之前會先取消掉當前的。你可用使用它去檢索新的Intent,如果你只是想改變Intent中的額外資料的話。通過取消先前的Intent,可用確保只有最新的實體可用啟動它。如果這一保證不是問題,考慮flag_update_current。

FLAG_UPDATE_CURRENT:最經常使用的是FLAG_UPDATE_CURRENT,因為描述的Intent有 更新的時候需要用到這個flag去更新你的描述,否則元件在下次事件發生或時間到達的時候extras永遠是第一次Intent的extras。

使用 FLAG_CANCEL_CURRENT也能做到更新extras,只不過是先把前面的extras清除,另外FLAG_CANCEL_CURRENT和 FLAG_UPDATE_CURRENT的區別在於能否新new一個Intent,FLAG_UPDATE_CURRENT能夠新new一個 Intent,而FLAG_CANCEL_CURRENT則不能,只能使用第一次的Intent。

建立一個廣播接受者去接收這些訊息。

public class SmsSendReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		int code = getResultCode();
		if (code == Activity.RESULT_OK) {
			ToastUtils.showToast(context, "傳送成功");
		} else {
			ToastUtils.showToast(context, "傳送失敗");

		}

	}

}

清單檔案中定義如下:
 <receiver
            android:name="com.chen.textttt.receiver.SmsSendReceiver"
            android:permission="android.permission.SEND_SMS" >
            <intent-filter>
                <action android:name="com.chen.text.sendsms" />
            </intent-filter>
        </receiver>

但是由於我們這樣傳送簡訊沒有進入到資料庫,所以沒有顯示到我們的介面上面。
public class SmsDao {
	public static void sendSms(String address, String body, Context context) {
		SmsManager manager = SmsManager.getDefault();
		Intent intent = new Intent("com.chen.text.sendsms");
		ArrayList<String> smss = manager.divideMessage(body);
		PendingIntent sentIntent = PendingIntent.getBroadcast(context, 0,
				intent, PendingIntent.FLAG_ONE_SHOT);
		for (String text : smss) {
			// 這個API 只能傳送,但是不進入資料庫
			manager.sendTextMessage(address, null, text, sentIntent, null);
			addSms(context, text, address);
		}

	}

	public static void addSms(Context context, String text, String address) {
		ContentValues values = new ContentValues();
		values.put("address", address);
		values.put("body", text);
		values.put("type", ConstantValues.TYPE_SEND);
		context.getContentResolver().insert(ConstantValues.URI_SMS, values);

	}
}

我們需要新增這三個資料進去,這樣才能在我們的頁面輸出。

——————————————————————————

新建簡訊介面:

先建立Activity的佈局檔案,我們先設定一個框架然後在改。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#44cccccc"
    android:orientation="vertical" >

    <include layout="@layout/layout_titlebar" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:gravity="center_vertical"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="傳送給:"
            android:textSize="18sp" />

        <EditText
            android:id="@+id/et_newmsg_address"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:layout_weight="1"
            android:background="@drawable/bg_btn_normal"
            android:hint="請輸入號碼"
            android:singleLine="true" />

        <ImageView
            android:id="@+id/iv_newmsg_select_contacts"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:background="@drawable/select_contact_bg" />
    </LinearLayout>

    <EditText
        android:id="@+id/et_newmsg_body"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="15dp"
        android:background="@drawable/bg_btn_normal"
        android:gravity="top"
        android:hint="請輸入內容"
        android:lines="5"
        android:padding="5dp" />

    <Button
      android:id="@+id/bt_newmsg_send"
        android:layout_width="125dp"
        android:layout_height="30dp"
        android:layout_gravity="center_horizontal"
        android:layout_margin="20dp"
        android:background="@drawable/selector_bt_bg"
        android:text="傳送"
        android:textColor="#99000000" />

</LinearLayout>

然後在 Fragment裡面設定點選事件。
		case R.id.bt_conversation_newSms:
			Intent intent = new Intent(getActivity(),NewsActivity.class);
			startActivity(intent);
			break;

_______________________________________

輸入框自動搜尋號碼:

我們在activity先設定的標題欄。和上一天的一樣。

我們為了實現自動搜尋:AutoCompleteTextView。使用這個控制元件,我們就可以實現。

其實這個控制元件繼承於EditView.所以其實他就是一個 編輯框。

這個元件顯示內容也是使用Adapter來給他設定顯示的內容。

因為我們顯示的資料也是來自於資料庫,所以我們還是使用CursorAdapter。

我們需要給他設定一個條目的佈局。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="4dp" >
    <TextView 
        android:id="@+id/tv_autosreach_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="姓名"
        android:textSize="16sp"
        />
    
    <TextView 
        android:id="@+id/tv_autosreach_address"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="12312312"
        android:textSize="14sp"
        />
</LinearLayout>


實現顯示搜尋框的下拉欄的資料顯示:

		AutoSreachAdapter adapter = new AutoSreachAdapter(this, null);
		//給我們的輸入框設定Adapter,用於輸入框的下拉欄顯示資料。
		actv_newmsg_address.setAdapter(adapter);
		
		adapter.setFilterQueryProvider(new FilterQueryProvider() {
			
			//這是一個回撥函式。就是當我們在這個輸入框裡面輸入資料的時候就會呼叫這個方法。用於查詢資料
			@Override
			public Cursor runQuery(CharSequence constraint) {
				
				return null;
			}
		});
		

這個方法就是當我們在輸入框字元的時候就會查詢。

你輸入了之後刪除一個字元他也會呼叫這個方法。

如果你設定元件是2個字元開始查詢。那麼就會顯示你輸入的字元。

但是你輸入一個字元的時候顯示的是null.

當我們在xml裡面新增這個屬性的時候就會一個字元的時候查詢;

  android:completionThreshold="1"

所以我們在輸入框中輸入的是號碼,也就是我們要進行模糊查詢的條件。

我們可以使用官方的提供的 URI

Phone.CONTENT_URI

這個URI可以查詢出很多資料。我們需要的是

display_name:姓名

data1:號碼

contact_id:聯絡人的id

我們直接進行模糊查詢,只要包含我們輸入的數字就輸出。

	public void initData() {
		AutoSreachAdapter adapter = new AutoSreachAdapter(this, null);
		// 給我們的輸入框設定Adapter,用於輸入框的下拉欄顯示資料。
		actv_newmsg_address.setAdapter(adapter);

		adapter.setFilterQueryProvider(new FilterQueryProvider() {

			// 這是一個回撥函式。就是當我們在這個輸入框裡面輸入資料的時候就會呼叫這個方法。用於查詢資料
			// constraint當前輸入框的文字。

			@Override
			public Cursor runQuery(CharSequence constraint) {
				String[] projection = { "data1", "display_name", "_id" };
				String selection="data1 like '%"+constraint+"%'";
				Cursor cursor = getContentResolver().query(Phone.CONTENT_URI,
						projection, selection, null, null);

				
				return cursor;
			}
		});

		initTitleBar();

	}

返回的Cursor物件,就是直接把這個cursor交給adapter

然後我們就可以去adapter 裡面去設定引數。

public class AutoSreachAdapter extends CursorAdapter {

	public AutoSreachAdapter(Context context, Cursor c) {
		super(context, c);

	}

	@Override
	public View newView(Context context, Cursor cursor, ViewGroup parent) {

		return View.inflate(context, R.layout.item_autotv_sreach, null);
	}

	@Override
	public void bindView(View view, Context context, Cursor cursor) {
		ViewHolder holder = getHolder(view);
		holder.tv_autosreach_name.setText(cursor.getString(cursor.getColumnIndex("display_name")));
		holder.tv_autosreach_address.setText(cursor.getString(cursor.getColumnIndex("data1")));
	}

	public ViewHolder getHolder(View view) {
		ViewHolder holder = (ViewHolder) view.getTag();
		if (holder == null) {
			holder = new ViewHolder(view);
			view.setTag(holder);
		}
		return holder;
	}

	class ViewHolder {
		private TextView tv_autosreach_name;
		private TextView tv_autosreach_address;

		public ViewHolder(View view) {
			tv_autosreach_name = (TextView) view
					.findViewById(R.id.tv_autosreach_name);
			tv_autosreach_address = (TextView) view
					.findViewById(R.id.tv_autosreach_address);
		}

	}

}

還有一個問題就是我們點選 條目的時候應該把他的號碼顯示到我們的輸入框上。

這個條目你點選的時候它預設呼叫的是。

	@Override
	public CharSequence convertToString(Cursor cursor) {
		// TODO Auto-generated method stub
		return super.convertToString(cursor);
	}

這個方法,他會預設的吧cursor.tostring()輸出到輸入框上。我們直接修改它要他輸出號碼。

我們還可以去設定下拉欄的背景圖片,間隔之類的。


偏移就是你的下拉欄離你的輸入框的位移量。

_____________________________________________

通過系統提供的Activity選擇聯絡人:

我們先檢視原始碼的聯絡人的activity

找到ContactsListActivity

然後我們找到過濾器。


PICK (選擇)

型別我們隨便選擇一個寫即可。

		case R.id.bt_newmsg_send:
			Intent intent = new Intent(Intent.ACTION_PICK);
			intent.setType("vnd.android.cuosor.dir/phone");
			startActivityForResult(intent, 0);
			break;
我們需要在點選聯絡人的時候應該返回他的號碼,