Android(安卓)ListView改变数据源
原创性声明:本文系作者原创,转载请保留原文链接:
http://blog.csdn.net/a774057695/article/details/48677507
首先先给一个朋友的博客的链接:
http://blog.csdn.net/coder_pig/article/details/48631595
这位仁兄对listview 数据源发生改变 进行了一定的分析,
本文也是和这位仁兄简单沟通后决定去写的,因为我对ListView改变数据源真的是受!够!了!
我现在希望将这系列的问题统统解决,就像有一个标准化的流程一样去处理。
好了,我现在只想静静,不要问我静静是谁,真的是我远房表妹。正文:
我想诸位都受过一个折磨:
样式不好看!样式不好看!样式不好看!
然后我们自定义ListView的适配器,也即是写一个扩展自BaseAdapter的类,然后提供自定义的布局.
/**当我们学会这样干的时候,往往已经对simpleadapter看不上眼了*/
然后我们会在相应的Activity类中定义这样的成员变量:
private ListView mListView = null;
private [自定义的适配器类名] mListViewAdapter = null; //我就假定它叫MyAdapter好吗?除非代码块中出现了实际的类名,我们都用这个约定的名字
一切都顺理成章,然后问题来了:
我们会在onCreate时,通过findViewById方法实现mListView的实例化并将其与视图组件绑定在一起。
顺势实例化适配器,甚至我们定义了绑定数据源的构造器 public MyAdapter(数据源类 data , Context context) { ……} 这样的代码块。
实例化前先得到了数据源,一般方法名命名为GetData
实例化后为 mListView设置了适配器 mListView.setAdapter(mListViewAdapter);
ok,ok,我们看到问题了:数据往往不是一开始就准备好的!
于是代码混乱了,为何会混乱呢?
根源在于:适配器的数据源是定义在MyAdapter中的,虽然他的源头还在我们的Activity中。
但是我们的适配器不认我们认定的源头,它只认MyAdapter中的,生活中看脸,代码中看作用域,规范的做我们是不会让Activity中的数据直接向适配器暴露(我指的是public等,不要认为我们不把数据给适配器),我们都是通过构造器交给适配器的,那么适配器认可的数据源就是MyAdapter中定义的那个成员变量,例如这样的:private LinkedList
于是那位仁兄发现了那些问题。
往往将就的处理就是:Activity中的数据源变了,我们就重新构造一下适配器,或大同小异的方法。
带来的就是代码混乱不堪。
那我们就分析一下怎么处理:
那位仁兄是通过添加成员变量的方法来的,他的代码我就不copy了。、
分析:
先分析那位仁兄的。
MyAdapter类中有成员变量:private LinkedList
我们要处理数据源的变化,也就是要改变成员变量,那么就写几个需要的成员函数来实现需求,思路上很明确,也不错。但是我觉得有一些地方不合适,如下:
并不是ListView都需要改变数据源,至少在我们的项目中应当考虑存在需要改变的和不需要改变的。
从BaseAdapter及其父类,实现的接口类来看,google也是这个意思。那如果我们用成员函数去实现,我认为他不应当直接继承自baseAdapter 而应该是将MyAdapter改成抽象类,这个类再继承自MyAdapter,相应的,还需要有一个继承自MyAdapter的类,它不需要改变数据源的值。 至少我觉得这样才算“规范”(实际上我内心依旧觉得不规范),但是这样太“臃肿了”。
我们都知道,接口是对“行为”的抽象,我们将改变数据源的视作是行为一点问题都没有,而且我认为使用接口来实现会更加合理,我想大家也都会认可,这样做,我们不会将类的扩展性降低、将类的抽象程度变低、将类描述的范围更小。
所以我向那位仁兄提了个建议用接口实现。
改进:
首先还是按照那位仁兄的大致框架来改进,先不考虑将各种方法都写全 也就是: 1主Activity的布局放一些测试性的button,用于触发设定的事件, 2主Activity负责控制生命周期执行的各个事件,绑定视图,获取数据,分发数据,构造适配器,为视图组件绑定适配器和响应事件 3数据类用于描述数据元,并提供对数据元操作的方法 4适配器类继承自BaseAdapter并实现数据源变化接口,重载getCount getView等几个关键方法 5接口类定义了数据源操作的方法(先少写几个) 先贴代码,后分析:主Activity布局:
稍微丑一点,demo嘛,说明关键问题就行了,大家将就下,就不贴图了。
主Activity类:
package [your package name];import java.util.LinkedList;import android.app.Activity;import android.content.Context;import android.os.Bundle;import android.util.Log;import android.view.Menu;import android.view.MenuItem;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.ListView;import android.widget.Toast;public class MainActivity extends Activity implements OnClickListener {private ListView mListView = null;private CustomAdapter mListAdapter = null;private LinkedList mData = null;private Context mContext = null;//==========\ 测试数据 /===============================/** * 我们假定添加到第五条,位置应该是4,我们的位置和数据源一致,从0 开始,不考虑偏移值 * */private final int testPosition = 4;private MyData testData ;private int testDataId = 0;//==========/ 测试数据定义结束 \=====================//=========\测试按钮 /=========================================private Button tBAdd,tBAddAt,tBDeleteAt,tBDeleteAll;private final String Tag = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);BindView();mContext = this;mData = new LinkedList();mListAdapter = new CustomAdapter(mData,mContext);mListView.setAdapter(mListAdapter);}private void BindView() {mListView =(ListView) findViewById(R.id.main_list);tBAdd = (Button) findViewById(R.id.bt_addAtLast);tBAddAt = (Button) findViewById(R.id.bt_addAtPosition);tBDeleteAt = (Button) findViewById(R.id.bt_deleteAtPosition);tBDeleteAll = (Button) findViewById(R.id.bt_deleteAll);tBAdd.setOnClickListener(this);tBAddAt.setOnClickListener(this);tBDeleteAll.setOnClickListener(this);tBDeleteAt.setOnClickListener(this);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();if (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}@Overridepublic void onClick(View v) {// TODO Auto-generated method stubboolean op = true;StringBuffer msg = new StringBuffer();testData = new MyData(R.drawable.ic_launcher, "testData,id:"+(testDataId++));boolean ret = false;switch (v.getId()) {case R.id.bt_addAtLast:ret = mListAdapter.AddItem(testData);msg.append("从尾部添加,成功了吗?").append(ret);break;case R.id.bt_addAtPosition:ret = mListAdapter.AddItem(testPosition, testData);msg.append("添加到第5条,成功了吗?").append(ret);break;case R.id.bt_deleteAtPosition:ret = mListAdapter.DeleteItem(testPosition);msg.append("删除第5条,成功了吗?").append(ret);break;case R.id.bt_deleteAll:mListAdapter.Clear();msg.append("全部清除");break;default:op = false;break;}if(op) {Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();Log.i(Tag, msg.toString());}}}
适配器类:
package [your package name]import java.util.LinkedList;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.TextView;public class CustomAdapter extends BaseAdapter implements ChangeDataSetInterface{private Context mContext;// 依据原楼主的代码,我们定义自己的数据类型,使用类来描述private LinkedList mLiData;public CustomAdapter() {}public CustomAdapter(LinkedList mData, Context mContext) { this.mLiData = mData; this.mContext = mContext; }private class ViewHolder {ImageView mImageView;TextView mTextView;}@Overridepublic int getCount() {return mLiData.size();}/* * 这里我返回数据源中的值。 */@Overridepublic Object getItem(int position) {return mLiData.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder = null; if(convertView == null){ convertView = LayoutInflater.from(mContext).inflate(R.layout.listview_item,parent,false); holder = new ViewHolder(); holder.mImageView = (ImageView) convertView.findViewById(R.id.img_icon); holder.mTextView = (TextView) convertView.findViewById(R.id.txt_content); convertView.setTag(holder); }else{ holder = (ViewHolder) convertView.getTag(); } holder.mImageView.setImageResource(mLiData.get(position).getImgId()); holder.mTextView.setText(mLiData.get(position).getContent()); return convertView;}//=======================================================================///* * 以下是实现接口定义的方法 * * 定义为Boolean 返回类型 是为了方便通知“前台”,成功了没有, * 注意 我们的position按照数据来,从0开始,当然你看着自己需求指定偏移量也可以 * * (⊙﹏⊙)b 我打着插入。。。想想还是改成了添加。。。感觉自己病了*///=======================================================================//@Overridepublic boolean AddItem(MyData data) {if (mLiData == null) mLiData = new LinkedList(); mLiData.add(data); notifyDataSetChanged(); return true;}@Overridepublic boolean AddItem(int position , MyData data) {if (mLiData == null) mLiData = new LinkedList();//注意超过了数据源的实际条目数时,需要的是添加到尾部,而不是直接添加,更不是在中间补充空值//当然,你也可以认为必须朝该位置插入,不能满足时通知前台要求不被许可,不执行。 我的例子中就采用这样处理if (position > getCount())//AddItem(data); //这里对应调整向朝尾部添加return false;elsemLiData.add(position,data); notifyDataSetChanged(); return true;}@Overridepublic boolean DeleteItem(MyData data) {// 效率太低,意义不大,这个方法可以不用去考虑return false;}@Overridepublic boolean DeleteItem(int position) {if (mLiData == null)return false;if (position >= getCount())return false; mLiData.remove(position); notifyDataSetChanged(); return true;}@Overridepublic void Clear() {if (mLiData == null) mLiData = new LinkedList(); mLiData.clear(); notifyDataSetChanged();}}
数据类:
package [your package name]public class MyData {private int imgId; private String content; public MyData() {} public MyData(int imgId, String content) { this.imgId = imgId; this.content = content; } public int getImgId() { return imgId; } public String getContent() { return content; } public void setImgId(int imgId) { this.imgId = imgId; } public void setContent(String content) { this.content = content; }}
接口类:
package [your package name]public interface ChangeDataSetInterface {boolean AddItem(MyData data);boolean AddItem(int position , MyData data);/** * deprecate * 遍历检索效率低,且实际意义不大。 * */boolean DeleteItem(MyData data);boolean DeleteItem(int position);void Clear();}
item的布局
(总觉得将adt升级了之后对我的提示让我很难过):<?xml version="1.0" encoding="utf-8"?>
分析:
接口类,我们定义了几个方法,再行文之时,我觉得应当都定义为boolean返回类型的,来标识操作的成功与否,先不改了。boolean AddItem(MyData data);boolean AddItem(int position , MyData data);boolean DeleteItem(MyData data);boolean DeleteItem(int position);void Clear();
一共这么多方法,朝尾部添加,朝指定位置添加,删除指定的值(考虑到效率和逻辑性,我觉得这个方法不应当被设计),删除指定位置的值,全部清除, 好的,我们还应当添加一个添加全部数据的,先放着。 添加的时候,我们考虑数据源是否被初始化了(从代码上看是可能的,因为无参构造器中没有初始化数据源),如果是朝指定位置添加,要考虑这个位置,嗯,是否合适朝这个位置放,不合适是进行调整,还是拒绝操作,看你的设计了。 删除也要考虑是否有数据可以删啊。(好像我穿越了) 代码上应该没有让大家不能理解的地方吧?
适配器类,继承了BaseAdapter,实现了我们的接口类,因为是非抽象类,所以必须实现接口方法,这也正是我们想要的 我习惯在实现getItem的时候返回关联的数据,当然 return null;也是可以的,大家对这块不清楚的可以去找找相关的博客,csdn上是有的。
Activity类,似乎也没有什么要讲的。。。。
优化:
增加接口方法:添加数据源(这个描述并不是很准确,对应的情景是在原来的数据源后面增加一大波数据(你非要一条也行)) 增加接口方法:替换数据源(这个描述并不是很准确,将原数据源中的数据换掉) 修改适配器类,当数据源没有数据时显示特定布局1 and 2:
void AddAll(LinkedList datas);void ReplaceAll(LinkedList datas);--------------------------------------------------------------------@Overridepublic void AddAll(LinkedList datas) {if (mLiData == null) mLiData = new LinkedList();for(int i = 0;i datas) { mLiData = new LinkedList();for(int i = 0;i
而第三点就值得寻味了,或许有朋友会和我一开始的潜意识一样,在getView方法中怎么怎么样,为什么这么说呢,不管你怎么怎么样,都有getView方法被调用了才行,或许有朋友研究过,getView不是一次性将所有的Item都获得到的,哪怕代码上也看得出不是一次可行的,而且,它多次调用的次数和用户可见的数量有关,当没有数据的时候,它不会被调用。so,是不是发现潜意识不可靠。
问题呢还是没有得到解决的。我想了好久也没有想到切入点,估计今天满脑子都是这个问题了。 代码就不传了。我想上面写的够全了。
等我把上面的问题想明白怎么破。。会再写一篇的。
原创性声明:本文系作者原创,转载请保留原文链接:
http://blog.csdn.net/a774057695/article/details/48677507
更多相关文章
- SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
- 一句话锁定MySQL数据占用元凶
- android脱壳,手机端实现,Fdex2
- android 通过jdbc访问mysql数据库--(含android studio 配置)通过测
- Android复杂自定义Listview实现
- contentprovider的学习实例总结
- android Cursor的自动管理方式
- android中的数据存取 之 File
- Context 传递数据