ListView解析
Adapter:数据来源构造器
ResourceCursorAdapter
ListView listView = new ListView(this);
String[] proj1 = new String[] {ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME};
Cursor cur = this.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, proj1, null, null, null);
ResourceCursorAdapter resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.rca, cur) {
@Override
public void bindView(View view, Context context, Cursor cursor) {
String data = cursor.getString(1);
((TextView) view.findViewById(R.id.text1)).setText(data);
}
};
listView.setAdapter(resourceCursorAdapter);
SimpleAdapter
List<Map<String, Object>> listItems = new ArrayList<Map<String, Object>>();
for (int i = 0; i < names.length; i++) {
Map<String, Object> item = new HashMap<String, Object>();
item.put("header", imageIds[i]);
item.put("personname", names[i]);
item.put("desc", descs[i]);
listItems.add(item);
}
SimpleAdapter simpleAdapter = new SimpleAdapter(this, listItems,R.layout.frag, new String[] { "header", "personname", "desc" },new int[] { R.id.header, R.id.name, R.id.desc });
list=(ListView)findViewById(R.id.mylist);
list.setAdapter(simpleAdapter);
SimpleCursorAdapter
final Cursor c = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,null, null, null, null);
String[] from = new String[]{Contacts.DISPLAY_NAME_PRIMARY};
int[] to = new int[]{R.id.contact_item};
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.contact_item, c, from, to);
ArrayAdapter
new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1, getData())
private ArrayList<String> getData(){
}
HeaderViewListAdpater
// TODO
非标红的(除ResourceCursorAdapter外),是不能直接实例化的。
Adapter的数据通知
Adapter的数据通知采用了观察者模式。
在setAdapter时,就注册了observer:

触发通知:
- notifyDataSetChanged
- notifyDataSetInvalidated
RecycleBin机制
- fillActiveViews():这个方法接收两个参数,第一个参数表示要存储的view的数量,第二个参数表示ListView中第一个可见元素的position值。RecycleBin当中使用mActiveViews这个数组来存储View,调用这个方法后就会根据传入的参数来将ListView中的指定元素存储到mActiveViews数组当中。
- getActiveView():这个方法和fillActiveViews()是对应的,用于从mActiveViews数组当中获取数据。该方法接收一个position参数,表示元素在ListView当中的位置,方法内部会自动将position值转换成mActiveViews数组对应的下标值。需要注意的是,mActiveViews当中所存储的View,一旦被获取了之后就会从mActiveViews当中移除,下次获取同样位置的View将会返回null,也就是说mActiveViews不能被重复利用。
- addScrapView:用于将一个废弃的View进行缓存,该方法接收一个View参数,和一个对应的position。当有某个View确定要废弃掉的时候(比如滚动出了屏幕),就应该调用这个方法来对View进行缓存,RecycleBin当中使用mScrapViews和mCurrentScrap这两个List来存储废弃View。
- mCurrentScrap只是mScrapViews arraylist数组中的第一个,在ViewTypeCount=1的时候,他们其实是相等的。mCurrentScrap的记录的是当前正在操作的那个viewtype。
- getScrapView():用于从废弃缓存中取出一个View,这些废弃缓存中的View是没有顺序可言的,因此getScrapView()方法中的算法也非常简单,就是直接从mCurrentScrap当中获取尾部的一个scrap view进行返回。
- setViewTypeCount():我们都知道Adapter当中可以重写一个getViewTypeCount()来表示ListView中有几种类型的数据项,而setViewTypeCount()方法的作用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。实际上,getViewTypeCount()方法通常情况下使用的并不是很多,所以我们只要知道RecycleBin当中有这样一个功能就行了。
首次加载
- onLayout():首先会去判断ListView的大小或者位置发生了变化,如果变化,那么首先要强制所有的子布局进行重新layout;然后就是对listview的各个子元素进行布局;
- layoutChildren():这里做了一坨事情,暂时只需关注这个:

- fillFromTop():它所负责的主要任务就是从mFirstPosition开始,自顶至底去填充ListView。具体实现是由
fillDown做的。 - fillDown():一个绘制的循环,条件是:子元素已经超出当前屏幕了,或者所有Adapter中的元素都被遍历结束了。
end和nextTop是屏幕的像素值。 - makeAndAddView():它首先会判断是不是数据变化,如果是,那么直接从recyclebin的ActiveViews里取出view;否则走下一步;

- obtainView():获取子view

- setupChild():这个类其实主要就是准确的将item插入到listview中,主要是调用ViewGroup的
addViewInLayout
整个的流程:
二次加载
- 差别一:不是调用
fillFromTop,而是调用fillSpecific,本质上是一样的,区别在于它先建立当前位置的view,然后分别向上和向下构建view; - 差别二:makeAndAddView可以直接从
getActiveView获取到view,那么就不用调用obtainView了; - 差别三:
首次的时候是调用下面的,是需要计算布局什么的;而后面就直接attach上就好了。
加载更多
加载更多是在onTouchEvent为入口的。
- scrollIfNeeded:由于
TOUCH_MODE_SCROLL和TOUCH_MODE_OVERSCROLL都会走到这个方法里,所以这个方法里对这两个事件做了不同处理,不过感觉逻辑上差不多。主要是判断了incrementalDeltaY是否为0,为0啥都不处理,大于0是下滑,小于0是上滑。 - trackMotionScroll:

- 下滑时,从上往下依次获取子view,如果子View的bottom值已经小于top值了,就说明这个子View已经移出屏幕了,所以会调用RecycleBin的addScrapView()方法将这个View加入到废弃缓存当中,并将count计数器加1,计数器用于记录有多少个子View被移出了屏幕;否则直接break;
- 上滑时,从下往上依次获取子view,View的top值是不是大于bottom值了,如果大于的话说明子View已经移出了屏幕,同样把它加入到废弃缓存中,并将计数器加1;
- detachViewsFromParent:它的作用就是把所有移出屏幕的子View全部detach掉,由于有recycle机制,就没有必要保存了;
- offsetChildrenTopAndBottom:将incrementalDeltaY作为参数传入,这个方法的作用是让ListView中所有的子View都按照传入的参数值进行相应的偏移,这样就实现了随着手指的拖动,ListView的内容也会随着滚动的效果。
- fillGap:填充listview,里面会调用
fillDown或fillUp。里面可以通过ScrapView获取数据。
具体滑动的流程:
效果图:
异步加载乱序问题
问题的产生?
问题的产生就是由于listview的view回收引起的。
当我们快速滑动ListView的时候就很有可能出现这样一种情况,某一个位置上的元素进入屏幕后开始从网络上请求图片,但是还没等图片下载完成,它就又被移出了屏幕。这样按照listview的recycle机制,被移出屏幕的控件将会很快被新进入屏幕的元素重新利用起来,而如果在这个时候刚好前面发起的图片请求有了响应,就会将刚才位置上的图片显示到当前位置上,因为虽然它们位置不同,但都是共用的同一个ImageView实例,这样就出现了图片乱序的情况。
解决方案
使用findViewWithTag
由于ListView中的ImageView控件都是重用的,移出屏幕的控件很快会被进入屏幕的图片重新利用起来,那么getView()方法就会再次得到执行,而在getView()方法中会为这个ImageView控件设置新的Tag,这样老的Tag就会被覆盖掉,于是这时再调用findVIewWithTag()方法并传入老的Tag,就只能得到null了,而我们判断只有ImageView不等于null的时候才会设置图片,这样图片乱序的问题也就不存在了。
关键代码:
public class ImageAdapter extends ArrayAdapter<String> {
private ListView mListView;
......
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (mListView == null) {
mListView = (ListView) parent;
}
String url = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.image_item, null);
} else {
view = convertView;
}
ImageView image = (ImageView) view.findViewById(R.id.image);
image.setImageResource(R.drawable.empty_photo);
image.setTag(url);
BitmapDrawable drawable = getBitmapFromMemoryCache(url);
if (drawable != null) {
image.setImageDrawable(drawable);
} else {
BitmapWorkerTask task = new BitmapWorkerTask();
task.execute(url);
}
return view;
}
......
/**
* 异步下载图片的任务。
*
* @author guolin
*/
class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable> {
String imageUrl;
@Override
protected BitmapDrawable doInBackground(String... params) {
imageUrl = params[0];
// 在后台开始下载图片
Bitmap bitmap = downloadBitmap(imageUrl);
BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap);
addBitmapToMemoryCache(imageUrl, drawable);
return drawable;
}
@Override
protected void onPostExecute(BitmapDrawable drawable) {
ImageView imageView = (ImageView) mListView.findViewWithTag(imageUrl);
if (imageView != null && drawable != null) {
imageView.setImageDrawable(drawable);
}
}
......
}
}
使用弱引用关联
就是建立ImageView和DownloadTask之间的关联关系,互相持有对方的引用,为了防止出现内存泄露的问题,双向关联采用弱引用建立。
public class ImageAdapter extends ArrayAdapter<String> {
private ListView mListView;
private Bitmap mLoadingBitmap;
/**
* 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
*/
private LruCache<String, BitmapDrawable> mMemoryCache;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
mLoadingBitmap = BitmapFactory.decodeResource(context.getResources(),
R.drawable.ic_launcher);
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, BitmapDrawable>(cacheSize) {
@Override
protected int sizeOf(String key, BitmapDrawable drawable) {
return drawable.getBitmap().getByteCount();
}
};
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (mListView == null) {
mListView = (ListView) parent;
}
String url = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.image_item, null);
} else {
view = convertView;
}
ImageView image = (ImageView) view.findViewById(R.id.image);
BitmapDrawable drawable = getBitmapFromMemoryCache(url);
if (drawable != null) {
image.setImageDrawable(drawable);
} else if (cancelPotentialWork(url, image)) {
BitmapWorkerTask task = new BitmapWorkerTask(image);
AsyncDrawable asyncDrawable = new AsyncDrawable(getContext()
.getResources(), mLoadingBitmap, task);
image.setImageDrawable(asyncDrawable);
task.execute(url);
}
return view;
}
/**
* 自定义的一个Drawable,让这个Drawable持有BitmapWorkerTask的弱引用。
*/
class AsyncDrawable extends BitmapDrawable {
private WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(
bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
/**
* 获取传入的ImageView它所对应的BitmapWorkerTask。
*/
private BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
/**
* 取消掉后台的潜在任务,当认为当前ImageView存在着一个另外图片请求任务时
* ,则把它取消掉并返回true,否则返回false。
*/
public boolean cancelPotentialWork(String url, ImageView imageView) {
BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
String imageUrl = bitmapWorkerTask.imageUrl;
if (imageUrl == null || !imageUrl.equals(url)) {
bitmapWorkerTask.cancel(true);
} else {
return false;
}
}
return true;
}
/**
* 将一张图片存储到LruCache中。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @param drawable
* LruCache的值,这里传入从网络上下载的BitmapDrawable对象。
*/
public void addBitmapToMemoryCache(String key, BitmapDrawable drawable) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, drawable);
}
}
/**
* 从LruCache中获取一张图片,如果不存在就返回null。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @return 对应传入键的BitmapDrawable对象,或者null。
*/
public BitmapDrawable getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
/**
* 异步下载图片的任务。
*
* @author guolin
*/
class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable> {
String imageUrl;
private WeakReference<ImageView> imageViewReference;
public BitmapWorkerTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
protected BitmapDrawable doInBackground(String... params) {
imageUrl = params[0];
// 在后台开始下载图片
Bitmap bitmap = downloadBitmap(imageUrl);
BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap);
addBitmapToMemoryCache(imageUrl, drawable);
return drawable;
}
@Override
protected void onPostExecute(BitmapDrawable drawable) {
ImageView imageView = getAttachedImageView();
if (imageView != null && drawable != null) {
imageView.setImageDrawable(drawable);
}
}
/**
* 获取当前BitmapWorkerTask所关联的ImageView。
*/
private ImageView getAttachedImageView() {
ImageView imageView = imageViewReference.get();
BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask) {
return imageView;
}
return null;
}
/**
* 建立HTTP请求,并获取Bitmap对象。
*
* @param imageUrl
* 图片的URL地址
* @return 解析后的Bitmap对象
*/
private Bitmap downloadBitmap(String imageUrl) {
Bitmap bitmap = null;
HttpURLConnection con = null;
try {
URL url = new URL(imageUrl);
con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(5 * 1000);
con.setReadTimeout(10 * 1000);
bitmap = BitmapFactory.decodeStream(con.getInputStream());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (con != null) {
con.disconnect();
}
}
return bitmap;
}
}
}
上面两种方法都是将当前下载的task和实际的view要一一对应上,而不是用到回收的view。
ImageView自己处理下载逻辑
类似这样
<?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" >
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="120dp"
android:src="@drawable/empty_photo"
android:scaleType="fitXY"/>
</LinearLayout>
Android ListView 优化之局部刷新(更新)
ListView局部刷新方法一:更新对应view的内容
这种方法先通过listView.getChildAt(position)拿到要更新的对应的item布局文件,然后再通过findViewById找到对应的控件进行设置。
private void updateSingle(int position) {
/**第一个可见的位置**/
int firstVisiblePosition = listView.getFirstVisiblePosition();
/**最后一个可见的位置**/
int lastVisiblePosition = listView.getLastVisiblePosition();
/**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/
if (position >= firstVisiblePosition && position <= lastVisiblePosition) {
/**获取指定位置view对象**/
View view = listView.getChildAt(position);
TextView textView = (TextView) view.findViewById(R.id.textView);
textView.setText(datas.get(position));
}
}
ListView局部刷新方法二:调用一次getView()方法
这种方法是调用适配器对应的getView方法,用它里面的代码对界面进行刷新。这也是google在IO大会上推荐的做法。
private void updateItem(int position) {
/**第一个可见的位置**/
int firstVisiblePosition = listView.getFirstVisiblePosition();
/**最后一个可见的位置**/
int lastVisiblePosition = listView.getLastVisiblePosition();
/**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/
if (position >= firstVisiblePosition && position <= lastVisiblePosition) {
/**获取指定位置view对象**/
View view = listView.getChildAt(position);
commonAdapter.getView(position, view, listView);
}
}
Loader(SDK3.0)
使用Loaders可以非常简单的在Activity或者Fragment中异步加载数据,一般适用于大量的数据查询,或者需要经常修改并及时展示的数据显示到UI上,这样可以避免查询数据的时候,造成UI主线程的卡顿。
- LoaderManager:在Activity或者Fragment中,可以通过getLoaderManager()方法获取LoaderManager对象,它是一个单例模式。
- initLoader:初始化一个Loader,并注册回调事件
- restartLoader:重新启动或创建一个Loader,并注册回调事件
- getLoader:返回给定Id的Loader,如果没有找到则返回Null。
- destroyLoader:根据指定Id,停止和删除Loader。
- LoaderManager.LoaderCallbacks:LoaderManager和Loader之间的回调接口。
- onCreateLoader:根据指定Id,初始化一个新的Loader。
- onLoadFinished:当Loader被加载完毕后被调用。
- onLoaderReset:当Loader被销毁的时候被调用,在其中可以使Loader的数据不可用。
- Loader:一个抽象的类,用于执行异步加载数据,这个Loader对象可以监视数据源的改变和在内容改变后,以新数据的内容改变UI的展示。