您的位置:首页 > 移动开发 > Android开发

Android ListView改变数据源

2015-09-23 11:14 495 查看
原创性声明:本文系作者原创,转载请保留原文链接:
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<MyData> mLiData;

于是那位仁兄发现了那些问题。

往往将就的处理就是:Activity中的数据源变了,我们就重新构造一下适配器,或大同小异的方法。

带来的就是代码混乱不堪。

那我们就分析一下怎么处理:

那位仁兄是通过添加成员变量的方法来的,他的代码我就不copy了。、

分析:

先分析那位仁兄的。

MyAdapter类中有成员变量:private LinkedList<MyData> mLiData; 对应数据源

我们要处理数据源的变化,也就是要改变成员变量,那么就写几个需要的成员函数来实现需求,思路上很明确,也不错。但是我觉得有一些地方不合适,如下:

并不是ListView都需要改变数据源,至少在我们的项目中应当考虑存在需要改变的和不需要改变的。

从BaseAdapter及其父类,实现的接口类来看,google也是这个意思。那如果我们用成员函数去实现,我认为他不应当直接继承自baseAdapter 而应该是将MyAdapter改成抽象类,这个类再继承自MyAdapter,相应的,还需要有一个继承自MyAdapter的类,它不需要改变数据源的值。 至少我觉得这样才算“规范”(实际上我内心依旧觉得不规范),但是这样太“臃肿了”。

我们都知道,接口是对“行为”的抽象,我们将改变数据源的视作是行为一点问题都没有,而且我认为使用接口来实现会更加合理,我想大家也都会认可,这样做,我们不会将类的扩展性降低、将类的抽象程度变低、将类描述的范围更小。

所以我向那位仁兄提了个建议用接口实现。

改进:

首先还是按照那位仁兄的大致框架来改进,先不考虑将各种方法都写全
也就是:
1主Activity的布局放一些测试性的button,用于触发设定的事件,
2主Activity负责控制生命周期执行的各个事件,绑定视图,获取数据,分发数据,构造适配器,为视图组件绑定适配器和响应事件
3数据类用于描述数据元,并提供对数据元操作的方法
4适配器类继承自BaseAdapter并实现数据源变化接口,重载getCount getView等几个关键方法
5接口类定义了数据源操作的方法(先少写几个)
先贴代码,后分析:

主Activity布局:

<span style="font-size:18px;"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="20dp"
    tools:context="com.example.customlistview.MainActivity" >

    <Button
        android:id="@+id/bt_addAtLast"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="25dp"
        android:layout_marginStart="25dp"
        android:text="addAtLast" />

    <Button
        android:id="@+id/bt_addAtPosition"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/bt_addAtLast"
        android:layout_toEndOf="@+id/bt_addAtLast"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="25dp"
        android:layout_marginStart="25dp"
        android:text="addAtPosition" />

    <Button
        android:id="@+id/bt_deleteAtPosition"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/bt_addAtLast"
        android:layout_marginLeft="25dp"
        android:layout_marginStart="25dp"
        android:text="deleteAtPosition" />

    <Button
        android:id="@+id/bt_deleteAll"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/bt_deleteAtPosition"
        android:layout_toEndOf="@+id/bt_deleteAtPosition"
        android:layout_below="@+id/bt_addAtLast"
        android:layout_marginLeft="25dp"
        android:layout_marginStart="25dp"
        android:text="deleteAll" />

    <ListView
        android:id="@+id/main_list"
        android:layout_below="@+id/bt_deleteAtPosition"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </ListView>

</RelativeLayout></span>


稍微丑一点,demo嘛,说明关键问题就行了,大家将就下,就不贴图了。

主Activity类:

<span style="font-size:18px;">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<MyData> 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";
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		BindView();
		
		mContext = this;
		mData = new LinkedList<MyData>();
		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);
		
	}

	@Override
	public 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;
	}

	@Override
	public 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);
	}

	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		boolean 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());
		}
	}
}
</span>


适配器类:

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<MyData> mLiData;
	
	public CustomAdapter() {}
	
	public CustomAdapter(LinkedList<MyData> mData, Context mContext) {
        this.mLiData = mData;
        this.mContext = mContext;
    }

	
	private class ViewHolder {
		ImageView mImageView;
		TextView mTextView;
	}

	@Override
	public int getCount() {
		return mLiData.size();
	}

	/* 
	 * 这里我返回数据源中的值。
	 */
	@Override
	public Object getItem(int position) {
		return mLiData.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public 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 我打着插入。。。想想还是改成了添加。。。感觉自己病了
	*/
	//=======================================================================//
	@Override
	public boolean AddItem(MyData data) {
		if (mLiData == null) 
            mLiData = new LinkedList<MyData>();
        mLiData.add(data);
        notifyDataSetChanged();	
        return true;
	}

	@Override
	public boolean AddItem(int position , MyData data) {
		if (mLiData == null) 
            mLiData = new LinkedList<MyData>();
		//注意超过了数据源的实际条目数时,需要的是添加到尾部,而不是直接添加,更不是在中间补充空值
		//当然,你也可以认为必须朝该位置插入,不能满足时通知前台要求不被许可,不执行。 我的例子中就采用这样处理
		if (position > getCount())
//			AddItem(data); //这里对应调整向朝尾部添加
			return false;
		else
			mLiData.add(position,data);
        notifyDataSetChanged();
        return true;
	}

	@Override
	public boolean DeleteItem(MyData data) {
		// 效率太低,意义不大,这个方法可以不用去考虑
		return false;
	}

	@Override
	public boolean DeleteItem(int position) {
		if (mLiData == null)
			return false;
		if (position >= getCount())
			return false;
        mLiData.remove(position);
        notifyDataSetChanged();
        return true;
	}

	@Override
	public void Clear() {
		if (mLiData == null) 
            mLiData = new LinkedList<MyData>();
        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"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/img_icon"
        android:contentDescription="@string/hello_world"
        android:layout_width="56dp"
        android:layout_height="56dp"/>

    <TextView
        android:id="@+id/txt_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:layout_marginLeft="10dp"
        android:layout_marginStart="10dp"
        android:textSize="18sp" />

</LinearLayout>


分析:

接口类,我们定义了几个方法,再行文之时,我觉得应当都定义为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<MyData> datas);
void ReplaceAll(LinkedList<MyData> datas);

--------------------------------------------------------------------

@Override
	public void AddAll(LinkedList<MyData> datas) {
		if (mLiData == null) 
            mLiData = new LinkedList<MyData>();
		for(int i = 0;i<datas.size();i++) {
			mLiData.add(datas.get(i));
		}
	}

	@Override
	public void ReplaceAll(LinkedList<MyData> datas) {
		 mLiData = new LinkedList<MyData>();
			for(int i = 0;i<datas.size();i++) {
				mLiData.add(datas.get(i));
			}
	}


而第三点就值得寻味了,或许有朋友会和我一开始的潜意识一样,在getView方法中怎么怎么样,为什么这么说呢,不管你怎么怎么样,都有getView方法被调用了才行,或许有朋友研究过,getView不是一次性将所有的Item都获得到的,哪怕代码上也看得出不是一次可行的,而且,它多次调用的次数和用户可见的数量有关,当没有数据的时候,它不会被调用。so,是不是发现潜意识不可靠。

问题呢还是没有得到解决的。我想了好久也没有想到切入点,估计今天满脑子都是这个问题了。
代码就不传了。我想上面写的够全了。

等我把上面的问题想明白怎么破。。会再写一篇的。

原创性声明:本文系作者原创,转载请保留原文链接:
http://blog.csdn.net/a774057695/article/details/48677507
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: