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_SCROLLTOUCH_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,里面会调用fillDownfillUp。里面可以通过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的展示。

results matching ""

    No results matching ""