这些博客都是我自己的学习笔记,不是用来教学的。
删除选中的短信:
我们删除短信其实很简单,但是我们要实现一个对话框去显示,还需要一个对话框的进度条。
删除短信操作就是操作数据库就行了。使用内容解析者去操作,但是我们要去看看到底要删除的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;
我们需要在点击联系人的时候应该返回他的号码,到输入框。所以我们要调用
onActivityResult 方法。
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { //data中会携带一个URi 就是你选择的联系人的uri Uri uri = data.getData(); if (uri!=null) { CursorUtils.printCursor(getContentResolver().query(uri, null, null, null, null)); } super.onActivityResult(requestCode, resultCode, data); }
我们发现通过data返回的是一个uri通过这个uri我们可以查看返回的数据有哪些。
查看后发现,有display_name:联系人名字
has_phone_number:联系人是否有电话。
_id:其实就是联系人的contact_id.
但是没有我们需要的号码,所以我们只能通过上面的查询,再去查询一次,这一次通过_id去查询即可。
如果没有号码就不会查了
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // data中会携带一个URi 就是你选择的联系人的uri Uri uri = data.getData(); if (uri != null) { // 查询联系人id和是否有号码 String[] projection = { "_id", "has_phone_number" }; // 不需要查询条件,因为这个 uri是指定你点击的联系人的uri 只有一条。不需要条件。 Cursor cursor = getContentResolver().query(uri, projection, null, null, null); cursor.moveToFirst(); String _id = cursor.getString(0); int has_phone_number = cursor.getInt(1); if (has_phone_number == 0) { ToastUtils.showToast(getApplicationContext(), "没有号码"); } else { Cursor query = getContentResolver().query(Phone.CONTENT_URI, new String[] { "data1" }, "contact_id=" + _id, null, null); query.moveToFirst(); String address = query.getString(0); actv_newmsg_address.setText(address); } } super.onActivityResult(requestCode, resultCode, data); }
这样查询就完毕了。但是我们有一个问题就是为什么cursor没有判断是否为空,这是因为,我们是通过选择一个联系人去做查询的,既然我们都能选择得到,那么就肯定存在。
还有一个问题就是我们在选择完联系人,号码会现在输入框,但是有时候会弹出来自动查询的列表,这说明我们的焦点还在输入框里面。所以我们应该把焦点放在输入内容的组件里面。
只需要在下面加上et_newmsg_body.requestFocus();即可
——————————————————
发送短信:
直接调用上面我们写好的发送方法即可。
case R.id.bt_newmsg_send: String body = et_newmsg_body.getText().toString().trim(); String address = actv_newmsg_address.getText().toString().trim(); if (!TextUtils.isEmpty(address)&&!TextUtils.isEmpty(body)) { SmsDao.sendSms(address, body, this); } break;
但是有一个小问题,就是如果我们用软键盘输入的时候,软键盘的框会挡住发送的按钮。
所以我们需要让他可以滚动。
<ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" >
包裹其他的内容即可。这里我们设置一个LinearLayout就是因为我们要里面的布局为垂直的。