ListView使用总结
2018-04-03 16:36
399 查看
ListView使用总结 https://blog.csdn.net/lisdye2/article/details/51449707
虽然随着RecyclerView的不断普及,相应的资源也越来越多,许多的项目都在使用
RecyclerView,但作为他的前辈
ListView,加深对
ListView的使用有助于我们更好的适应到
RecyclerView的使用中。首先看一下我们实现的效果一些简单效果
这只是前面的一些简单效果,后面会有一些进阶的效果,希望能耐心的看下去。
ListView的优化
ListView的优化主要包括两个方面,分别是对自身的优化以及其适配器(
Adapter)的优化。
ListView
自身的优化
主要包括一条。对于ListView的
layout_height和
layout_width设置为
match_parent,如果设置为
match_parent,一般
ListView的宽高会测量三次以上。具体的源码没有深入研究。但为什么会要测量多次,如果对于自定义
View稍微有点基础的会知道,对于
View的测量大小有三个类型:
-
UNSPECIFIED:未指定的,父类不对子类施加任何限制。
-
EXACTLY:确定的,父类确定其子类控件的大小。
-
AT_MOST:最大值,需要子类去测量自身大小确定。如果我们设置宽高为
wrap_content,即
AT_MOST,表示其宽高有控件本身去测量确定,而如果是
match_parent,则
EXACTLY,表示确定的大小。
Adapter
优化。
对于Adapter的优化,主要包括以下几个步骤:复用convertView,减少子布局的生成。
定义
ViewHolder,减少
findViewById()的次数。
下面看一下代码
/** * 最基础的adapter * Created by Alex_MaHao on 2016/5/17. */ public class SimpleBaseAdapter extends BaseAdapter { private List<String> datas; public SimpleBaseAdapter(List<String> datas) { this.datas = datas; } @Override public int getCount() { return datas==null?0:datas.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 vHolder; if(convertView==null){ //初始化item布局 convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_listview_sample,parent,false); //创建记事本类 vHolder = new ViewHolder(); //查找控件,保存控件的引用 vHolder.tv = ((TextView) convertView.findViewById(R.id.listview_sample_tv)); //将当前viewHolder与converView绑定 convertView.setTag(vHolder); }else{ //如果不为空,获取 vHolder = (ViewHolder) convertView.getTag(); } vHolder.tv.setText(datas.get(position)); return convertView; } /** * 笔记本类,保存对象的引用 */ class ViewHolder{ TextView tv;根据代码分析思路:
-
ListView中的
item滑出屏幕时,滑出的
item会在
getView()方法中返回,及
converView参数。所以在这里判断,是否为null,如果不为null,表示我们可以对该布局重新设置数据并返回到列表中显示。
-
ViewHolder,保存
item中子控件的引用。因为我们复用了
converView,那么对于同一个
converView布局,其子控件的引用应该是不变的。所以我们可以获取到其上的所有子控件的引用并通过
ViewHolder进行保存。
-
setTag(),getTag():该方法实现了
ViewHolder和
converView的绑定,就类似于通过
setTag()方法,将
ViewHolder打包成一个包裹,放在了
converView上,再通过
getTag()方法,将这个包裹取出。在很多优化中,会将
ViewHolder定义为
static。但在这里我并没加上。因为经过测试,加或不加,
ViewHolder的创建次数不变。网上查了很多资料,也没有找到一个让我信服的理由。唯一有点理的就是基于java的特性。静态内部类的对象不依赖于其所在的外部类对象。
Adapter的封装–BaseAppAdapter
上一节说了Adapter的优化,但如果我们每次写都要写这么多的优化代码,这不符合程序员懒惰的天性。那我们只能把他封装,提取出一个公共的基类,在基类中,我们把布局加载,优化等都默认实现,只需让子类构造布局文件,以及绑定数据。那么,从我们上一节的代码看,有以下模块都可以提取为基类:数据集合datas:对于数据集合,我们通常都是一个
List集合,在这里定义泛型来表述其所包含的内容。
数据优化:布局的复用以及
ViewHolder与
converView的绑定。
ViewHolder:定义一个
ViewHolder,通过
map保存控件与id;
子类所需实现的:数据的初始化
确定
item的布局文件
将数据与视图绑定。
那么直接看一下我们继承好的代码:
public abstract class BaseAppAdapter<T> extends BaseAdapter { /** * 泛型,保存数据 */ protected List<T> datas; /** * 构造方法,子类必须实现其构造方法,并初始化数据 * @param datas */ public BaseAppAdapter(List<T> datas) { this.datas = datas; } @Override public int getCount() { return datas==null?0:datas.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) { BaseViewHolder vHolder; if(convertView==null){ convertView = LayoutInflater.from(parent.getContext()).inflate(getItemLayoutId(),parent,false); vHolder = new BaseViewHolder(convertView); convertView.setTag(vHolder); }else{ vHolder = (BaseViewHolder) convertView.getTag(); } /** * 数据绑定的回调 */ bindData(vHolder,datas.get(position)); return convertView; } /** * 子类实现,获取item布局文件的id * @return */ protected abstract int getItemLayoutId(); /** * 子类实现,绑定数据 * @param vHolder 对应position的ViewHolder * @param data 对应的数据绑定 */ protected abstract void bindData(BaseViewHolder vHolder,T data); /** * ViewHolder类 */ class BaseViewHolder{ /** * 保存view,以id-view的形式 */ Map<Integer,View> mapView; View rootView; public BaseViewHolder(View rootView){ this.rootView = rootView; mapView = new HashMap<Integer,View>(); } /** * 通过id查找控件 * @param id * @return */ public View getView(Integer id){ View view = mapView.get(id); if(view==null){ view = rootView.findViewById(id); mapView.put(id,view); } return view; } } }在以上代码中有几个关键点:有参的构造方法:子类实现时,必须调用父类的构造方法,用以保存数据。
getItemLayoutId():
item的布局文件id。抽象方法,子类必须实现。对于每一个
BaseAppAdapter的子类,都需要通过该方法返回他们所特有的
item的id。
bindData(BaseViewHolder vHolder,T data):数据绑定方法,子类必须实现。子类在此方法中将数据设置到试图上
BaseViewHolder中的
getView(Integer id):该方法设计比较巧妙,因为,对于基类我们不知道有哪些
id需要查询,如果直接通过
findViewById()方法,则并没有减少查询次数。在这里,通过保存一个
map对象,通过
id先从
map中取,如果不存在,说明是第一次获取,则使用
findViewById查找控件,并将控件以键值对的形式保存到
map中,则下次查找,会从
map中取,减少了
findViewById的次数。可能有人会认为,添加了一个
map对象,内存占用不是增大了吗,但是,第一,我们
map中保存的都是引用。第二,
findViewById()是按照深度优先遍历查询的,如果不懂,遍历肯定能明白吧。
ListView 分割线的高度和颜色设置。
简单的两个属性android:divider:设置颜色,背景
android:dividerHeight:设置高度
如果设置无分割线,可以设置
android:divider="@null"。该两个属性必须同时使用,如果只设置
divider,则没有效果,同时默认的分割线也会消失。当然,我们也可以在
item布局文件中添加分割线(在底部添加一个线),麻烦点而已。
隐藏ListView 的滚动条
android:scrollbars="none"
取消ListView的点击反馈效果
对于ListView,在android5.0一下是一个变色,在android5.0以上是一个波纹动画。我们可以通过一些设置取消掉他的反馈效果。使用如下代码
android:listSelector="#0000":点击颜色设置为透明
设置空试图
ListView作为列表显示的控件,那么肯定会存在数据为空的情况,如果我们就直接显示一面白无疑是不友好的,
ListView可以通过设置一个视图,当列表为null,显示该视图。关键方法为
setEmpty(View)。具体使用方法如下:xml文件中,在
ListView下设置一个图片,如下
<com.mahao.alex.systemwidgetdemo.listView.MyListView android:id="@+id/listview_lv" android:divider="#f00" android:scrollbars="none" android:listSelector="#0000" android:dividerHeight="3dp" android:layout_width="match_parent" android:layout_height="match_parent"/> <ImageView android:id="@+id/listview_empty_view" android:layout_width="match_parent" android:src="@mipmap/ic_launcher" android:layout_height="match_parent" />在java代码中设置如下
//设置空视图,调用此方法会默认隐藏Empty_view mLv.setEmptyView(findViewById(R.id.listview_empty_view));注意:我们无需设置
ImageView的
visible属性,因为通过
setEmpty()方法,已将其显示与隐藏于
ListView所绑定,由
ListView来管理。
ListView
的滚动
对于显示数据,我们可能会有一些特殊的要求,比如初始显示到第几列,或者要调到第几条显示。对于此种要求,ListView已经实现了相应的方法让我调用。
public void setSelection(int position):
position为需要显示在第一条的数据。该方法为瞬间滚动,无滑动效果,类似图片上瞬间滑动按钮实现的效果。
public void smoothScrollToPosition(int position):与上面方法作用相同,不过其有过度效果,类似平滑滚动实现的效果。
ListView 下拉顶部空白回弹效果
在ListView中,有如下方法,他控制了回弹设置的值:
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)
maxOverScrollY参数便是设置我们可以下拉的空白区域的高度。具体实现如下
public class MyListView extends ListView { /** * 下拉回弹效果,下拉的最大距离 */ private int mMaxOverDistance; public MyListView(Context context) { super(context); } public MyListView(Context context, AttributeSet attrs) { super(context, attrs); //初始化最大距离 DisplayMetrics metrics = context.getResources().getDisplayMetrics(); float density = metrics.density; mMaxOverDistance = (int) (100*density); } public MyListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { //注意第九个参数,设置为了我们自定义的值 return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxOverDistance, isTouchEvent); } }该方法是保护类型的,所以我们需要通过继承
ListView来修改此方法的实现。在看到此方法时,第一反应是
ScrollView有没有该方法,查了以后,发现
ScrollView也有该方法。那么,同理,
ScrollView也能通过重写
overScrollBy实现下拉回弹的效果。
ListView 进阶–实现跟随滑动消失和显现的ToolBar
首先看一下效果图:分析其逻辑:当我们向上滑动列表时,
toolbar慢慢消失到顶部。
当我们向下滑动列表时,
toolbar慢慢的从顶部显现。
实现逻辑:
-
setOnTouchListener,设置触摸监听。
- 对当前手指移动事件进行判断。
- 如果是向下移动,且
ToolBar是隐藏的,则给
toolbar设置一个向下显现的动画。
- 如果是向上移动,且
toolbar是显现的,则给
toolbar设置一个向上隐藏的动画。看一下实现代码:
/** * Created by Alex_MaHao on 2016/5/17. */ public class ToorBarListViewActivity extends AppCompatActivity implements View.OnTouchListener { private Toolbar mToolBar; private ListView mLv; private SimpleBaseAdapter mAdapter; private List<String> datas = new ArrayList<>(); //系统默认的滑动最小偏移量 private int mTouchSlop; //手指初次触摸时的Y坐标 private float mFirstY; //当前手指触摸的Y坐标 private float mCurrentY; //手指移动的方向,0代表向下滑动,1代表向上滑动 private int direction; //toobar的显示状态 private boolean isShow = true; //toobar显示和隐藏的动画 private Animator mAnimator; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_listview_toobar); // 把toolbar替代ActionBar mToolBar = ((Toolbar) findViewById(R.id.toolbar)); setSupportActionBar(mToolBar); mLv = ((ListView) findViewById(R.id.listview_toolbar_lv)); initDatas(); mAdapter = new SimpleBaseAdapter(datas); mLv.setAdapter(mAdapter); /** * 添加一个头部View,不然使用RelativeLayout,会被遮挡 */ addHeadView(); //最小移动距离,判断是否滑动 mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop(); //设置触摸监听 mLv.setOnTouchListener(this); } /** * 添加头布局,避免被遮挡 */ private void addHeadView() { //创建一个与ToolBar等高的空白view,添加到ListView的头布局中 View head = new View(this); TypedArray actionbarSizeTypedArray = getApplicationContext().obtainStyledAttributes(new int[] { android.R.attr.actionBarSize }); head.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, (int) actionbarSizeTypedArray.getDimension(0, 0))); mLv.addHeaderView(head); } private void initDatas() { for(int i = 0;i<20;i++){ datas.add("添加数据~~"+i); } } @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: //记录初次触摸的Y坐标 mFirstY = event.getY(); break; case MotionEvent.ACTION_MOVE: mCurrentY = event.getY(); if(mCurrentY-mFirstY>mTouchSlop){ //向下滑动 direction = 0; }else if(mFirstY-mCurrentY>mTouchSlop){ //向上滑动 direction = 1; } if(direction==1){ //向上滑动,且toolbar在显示状态,则隐藏 if(isShow){ toolbarAnim(false); isShow = !isShow; } }else if(direction==0){ //向下滑动,且toolbar在隐藏状态,则显示 if(!isShow){ toolbarAnim(true); isShow = !isShow; } } break; } return false; } public void toolbarAnim(boolean isShow){ if(mAnimator!=null&&mAnimator.isRunning()){ mAnimator.cancel(); } if(isShow){ //显现toobar mAnimator = ObjectAnimator.ofFloat(mToolBar,"translationY",mToolBar.getTranslationY(),0); }else{ //隐藏toobar mAnimator = ObjectAnimator.ofFloat(mToolBar,"translationY",mToolBar.getTranslationY(),-mToolBar.getHeight()); } //启动动画 mAnimator.start(); } }代码注释非常详细,需要注意的有以下三点:因为我们的父布局是
RelativeLayout,如果不给
ListView设置一个
headView,则在最顶部时,
ListView顶部数据会被隐藏。当然,可能会想到为什么不用垂直布局,把
ListView放在
toolbar的下面呢,这样会导致另一个问题,即当
toobar消失时,会导致顶部有一个空白效果。如下图所示:
监听两个条件,滑动方向和
toolbar当前显示的状态,如果不监听当前
toobar状态,会导致动画重复调用。
会存在一种情况,及
toolbar处在某一个动画的时候,突然该表方向,我们需要先停止当前动画,并以当前偏移量上为基础,创建新的动画。这也是创建动画时使用
mToolBar.getTranslationY()作为参数的原因。
ListView 进阶–聊天列表的实现
在显示数据中,可能会有这种需求,显示的列表的布局不同,例如聊天界面,根据不同的聊天人,显示的位置不同,及加载不同的itemView那么我们如何实现呢。先看一下实现效果:
请忽略他的丑陋,目的是为了实现效果,下面直接上源码,看一下实现过程,最后在总结实现的关键点:聊天的实体类,在聊天实体类中,我们必须通过类型区分该聊天内容是自己还是朋友。
/** * 聊天的实体类 * Created by Alex_MaHao on 2016/5/18. */ public class ChatBean { /** * 聊天的两种类型,自己和朋友 */ public static final int CHAT_MYSELF = 0; public static final int CHAT_FIRENDS = 1; /** * 聊天的头像 */ private Drawable userIcon; /** * 发送的消息 */ private String message; /** * 确定是否为当前聊天者 */ private int type; public ChatBean() { } public ChatBean(String message, int type, Drawable userIcon) { this.message = message; this.type = type; this.userIcon = userIcon; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public int getType() { return type; } public void setType(int type) { this.type = type; } public Drawable getUserIcon() { return userIcon; } public void setUserIcon(Drawable userIcon) { this.userIcon = userIcon; } }
ListView所在的布局文件
activity_listview_chat.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/listview_chat_lv" android:divider="#0000" android:listSelector="#0000" android:dividerHeight="10dp" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="20dp" /> </RelativeLayout>因为聊天条目没有点击反馈效果,所以通过
android:listSelector设置无反馈效果。使用
android:divider="#0000"和
android:dividerHeight="10dp"设置每一个聊天条目的间隔。我们需要加载不同的布局,所以我们有两个
item布局文件,用来显示自己的消息和朋友的消息:
item_listview_chat_left,显示在左边,朋友消息显示布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_alignParentRight="true" android:layout_marginRight="20dp" android:id="@+id/chat_right_icon" android:src="@mipmap/qq" android:scaleType="fitXY" android:layout_width="40dp" android:layout_height="40dp" /> <TextView android:id="@+id/chat_right_msg" android:gravity="center" android:text="dsadsadsasdasdasd" android:textColor="#fff" android:layout_marginLeft="10dp" android:background="@drawable/listview_chat_right" android:layout_toLeftOf="@id/chat_right_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
item_listview_chat_right,显示在右边,自己消息的显示布局
<?xml version="1.0" encoding="utf-8"?>关键点来了,构造
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_alignParentRight="true" android:layout_marginRight="20dp" android:id="@+id/chat_right_icon" android:src="@mipmap/qq" android:scaleType="fitXY" android:layout_width="40dp" android:layout_height="40dp" /> <TextView android:id="@+id/chat_right_msg" android:gravity="center" android:text="dsadsadsasdasdasd" android:textColor="#fff" android:layout_marginLeft="10dp" android:background="@drawable/listview_chat_right" android:layout_toLeftOf="@id/chat_right_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
ChatAdapter,用以显示数据。
/** * 聊天的Adapter,实现加载不同的布局 * Created by Alex_MaHao on 2016/5/18. */ public class ChatAdapter extends BaseAdapter { List<ChatBean> chatBeens ; public ChatAdapter(List<ChatBean> chatBeens) { this.chatBeens = chatBeens; } @Override public int getCount() { return chatBeens.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public int getItemViewType(int position) { //返回值需要从0开始,对应数据所要加载的对应布局标识 return chatBeens.get(position).getType(); } @Override public int getViewTypeCount() { //不同布局的种类数量 return 2; } @Override public View getView(int position, View convertView, ViewGroup parent) { //获取当前数据的类型 int type = getItemViewType(position); ViewHolder vHolder = null; if(convertView==null){ switch (type){ case ChatBean.CHAT_MYSELF: //加载自己消息显示的布局 convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_listview_chat_right,parent,false); vHolder = new ViewHolder(); vHolder.icon = ((ImageView) convertView.findViewById(R.id.chat_right_icon)); vHolder.msg = ((TextView) convertView.findViewById(R.id.chat_right_msg)); break; case ChatBean.CHAT_FIRENDS: //加载朋友消息显示的布局 convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_listview_chat_left,parent,false); vHolder = new ViewHolder(); vHolder.icon = ((ImageView) convertView.findViewById(R.id.chat_left_icon)); vHolder.msg = ((TextView) convertView.findViewById(R.id.chat_left_msg)); break; } //控件应用与控件绑定 convertView.setTag(vHolder); }else{ //获取控件引用 vHolder = (ViewHolder) convertView.getTag(); } //设置数值 vHolder.icon.setImageDrawable(chatBeens.get(position).getUserIcon()); vHolder.msg.setText(chatBeens.get(position).getMessage()); return convertView; } class ViewHolder{ ImageView icon; TextView msg; } }在
adapter中有两个关键的方法:
getViewTypeCount():返回布局的种类数,比如当前我们分为自己的消息和朋友的消息两个布局,那么就返回2.
-
getItemViewType():返回对应条目下,
item布局的类型。
ListView通过该方法,实现在
getView()中返回相同类型的
converView复用。注意,该返回类型要从0开始,不然会数组越界。因为在这里,比较巧合,我们的
item布局控件都一样,只不过显示不一样,所以只定义了一个
ViewHolder类。关键的实现了,那么看一下
activity中的代码吧
public class ChatListViewActivity extends AppCompatActivity { private ListView mChatLv; private List<ChatBean> chatBeanList = new ArrayList<>(); private ChatAdapter adapter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_listview_chat); mChatLv = ((ListView) findViewById(R.id.listview_chat_lv)); initChatBeanList(); adapter = new ChatAdapter(chatBeanList); mChatLv.setAdapter(adapter); mChatLv.postDelayed(new Runnable() { @Override public void run() { //设置我们ListView显示在底部 mChatLv.setSelection(adapter.getCount()); } },200); } @Override protected void onStart() { super.onStart(); } /** * 初始化聊天内容 */ private void initChatBeanList() { for (int i = 0;i<10;i++){ ChatBean chat = new ChatBean(); if(i%2==0){ chat.setType(ChatBean.CHAT_MYSELF); chat.setUserIcon(getApplicationContext().getResources().getDrawable(R.mipmap.qq)); chat.setMessage("微信,微信,我是QQ"); }else{ chat.setType(ChatBean.CHAT_FIRENDS); chat.setUserIcon(getApplicationContext().getResources().getDrawable(R.mipmap.weixin)); chat.setMessage("QQ,QQ,我是微信"); } chatBeanList.add(chat); } } }实现该布局的两个关键方法(adapter中的方法)
-
getViewTypeCount():返回布局的种类数,比如当前我们分为自己的消息和朋友的消息两个布局,那么就返回2.
-
getItemViewType():返回对应条目下,
item布局的类型。
ListView通过该方法,实现在
getView()中返回相同类型的
converView复用。注意,该返回类型要从0开始,不然会数组越界。
ListView 的上拉加载和下拉刷新
决定使用多种方式实现下拉刷新和上拉加载,会在下一篇博客中实现。绝对满满的干货。ListView上拉加载和下拉刷新多种实现方式已经发表,欢迎观看,学习,并留下您宝贵的意见。相关文章推荐
- Android关于ListView的总结--使用ListActivity
- Context Menu的使用+为ListView绑定监听器——开源项目Standup Timer学习总结(二)
- ListView与SimpleAdapter的结合使用的总结
- android编程之ExpandableListView使用总结
- ExpandableListView的使用总结
- android编程之ExpandableListView使用总结
- ListView使用总结1
- Listview控件使用SimpleAdapter适配器实现屏幕下滑增加一个item选项以及用到的方法总结
- JSON 解析 + listView + AsyncTask 的结合使用做个菜谱应用(总结)
- android listview使用总结
- Android ListView使用总结
- Android中的ListView属性使用总结
- 学习笔记4: ListView使用总结
- android ListView组件使用常见问题或形式总结(一)
- android:ListView使用总结
- Android之PullToRefresh(ListView 、GridView 、WebView)使用详解和总结
- Android关于ListView的总结--使用ListActivity
- ListView,adapter使用总结
- ListView的使用及常用问题的总结
- android知识点总结 包括数据库的使用 listview适配 contextmenu的使用 和 contentprovider使用