ListView详解--绘图、优化、适配器、观察者

Android源码之ListView的适配器模式

Adapter Pattern
适配器模式分为两种,即类适配器,对象适配器模式。

类适配器是通过实现Target接口以及继承Adaptee类来实现接口转换;而对象适配器模式是通过实现Target接口和代理Adaptee的某个方法来实现(即在类内部定义Adaptee的变量)。

角色介绍:
目标(Target)角色:这就是所期待得到的接口。
源(Adaptee)角色:需要适配的接口。
适配器(Adapter)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。


ListView中的Adapter模式

在开发过程中,ListView的Adapter是我们最为常见的类型之一。一般的用法大致如下:

ListView myListView = (ListView)findViewById(listview_id);
//设置适配器
myListView.setAdapter(new MyAdapter(context, myDatas));
//适配器
public class MyAdapter extends BaseAdapter{

private LayoutInflater mInflater;
List<string> mDatas;

public MyAdatper(Context context, List<string> datas){
this.mInflater = LayoutInflater.from(context);
mDatas = datas;
}
@Override
public int getCount() {
return mDatas.size();
}
@Override
public String getItem(int pos){
return mDatas.get(pos);
}
@Override
public long getItemId(int pos){
return pos;
}

//解析、设置、缓存convertView以及相关内容
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
//Item View的复用
if(converView == null) {
convertView = mInflater.inflate(R.layout.my_listview_item, null);
//获取title
holder.title = (TextView)convertView.findViewById(R.id.title);
convertView.setTag(holder);
}else {
holder = (ViewHolder)convertView.getTag();
}
holder.title.setText(mDatas.get(position));
return convertView;
}
} 

通过代理数据集来告知ListView数据的个数(getCount函数)以及每个数据的类型(getItem函数),最重要的是要解决Item View的输出。Item View千变万化,但终究它都是View类型,Adapter统一将Item View输出为View(getView函数),这样就很好的应对了Item View的可变性。

那么ListView是如何通过Adapter模式(不止Adapter模式)来运作的呢?ListView继承自AbsListView,Adapter定义在AbsListView中,我们看一下这个类

public abstract class AbsListView extends AdapterView<listadapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,ViewTreeOberserver.OnTouchModeChangeListener,RemoteViewAdapter.RemoteAdapterConnectionCallback{

/** 
* The adapter containing the data to be displayed by this view 
*/    
ListAdapter mAdapter;

//关联到Window时调用的函数
@Override
protected void onAttachedToWindow() {
//代码省略
//给适配器注册一个观察者,该模式下一篇介绍。
if(mAdapter != null && mDataSetObserver == null ){
mDataSetObserver = new AdapterDataSetObserver();            mAdapter.registerDataSetObserver(mDataSetObserver);

        // Data may have changed while we were detached. Refresh.
        mDataChanged = true;
        mOldItemCount = mItemCount
        // 获取Item的数量,调用的是mAdapter的getCount方法
        mItemCount = mAdapter.getCount();
}
mIsAttached = true;
}
  /**
 * 子类需要覆写layoutChildren()函数来布局child view,也就是Item View
 */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    mInLayout = true;
    if (changed) {
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).forceLayout();
        }
        mRecycler.markChildrenDirty();
    }

    if (mFastScroller != null && mItemCount != mOldItemCount) {
        mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
    }
    // 布局Child View
    layoutChildren();
    mInLayout = false;

    mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
}

// 获取一个Item View
View obtainView(int position, boolean[] isScrap) {
    isScrap[0] = false;
    View scrapView;
    // 从缓存的Item View中获取,ListView的复用机制就在这里
    scrapView = mRecycler.getScrapView(position);

    View child;
    if (scrapView != null) {
        // 代码省略
        child = mAdapter.getView(position, scrapView, this);
        // 代码省略
    } else {
        child = mAdapter.getView(position, null, this);
        // 代码省略
    }

    return child;
}

AbsListView定义了集合视图的框架,比如Adapter模式的应用、复用Item View的逻辑、布局Item View的逻辑等。子类只需要覆写特定的方法即可实现集合视图的功能,例如ListView。

ListView中的相关方法。

@Override
protected void layoutChildren() {
    // 代码省略

    try {
        super.layoutChildren();
        invalidate();
        // 代码省略
        // 根据布局模式来布局Item View
        switch (mLayoutMode) {
        case LAYOUT_SET_SELECTION:
            if (newSel != null) {
                sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
            } else {
                sel = fillFromMiddle(childrenTop, childrenBottom);
            }
            break;
        case LAYOUT_SYNC:
            sel = fillSpecific(mSyncPosition, mSpecificTop);
            break;
        case LAYOUT_FORCE_BOTTOM:
            sel = fillUp(mItemCount - 1, childrenBottom);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_FORCE_TOP:
            mFirstPosition = 0;
            sel = fillFromTop(childrenTop);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_SPECIFIC:
            sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
            break;
        case LAYOUT_MOVE_SELECTION:
            sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
            break;
        default:
            // 代码省略
            break;
        }

}

// 从上到下填充Item View  [ 只是其中一种填充方式 ]
private View fillDown(int pos, int nextTop) {
    View selectedView = null;

    int end = (mBottom - mTop);
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
        end -= mListPadding.bottom;
    }

    while (nextTop < end && pos < mItemCount) {
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

        nextTop = child.getBottom() + mDividerHeight;
        if (selected) {
            selectedView = child;
        }
        pos++;
    }

    return selectedView;
}

// 添加Item View
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
        boolean selected) {
    View child;

    // 代码省略 
    // Make a new view for this position, or convert an unused view if possible
    child = obtainView(position, mIsScrap);

    // This needs to be positioned and measured
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

    return child;
}

ListView覆写了AbsListView中的layoutChilden函数,在该函数中根据布局模式来布局Item View。Item View的个数、样式都通过Adapter对应的方法来获取,获取个数、Item View之后,将这些Item View布局到ListView对应的坐标上,再加上Item View的复用机制,整个ListView就基本运转起来了。

当然这里的Adapter并不是经典的适配器模式,但是却是对象适配器模式的优秀示例,也很好的体现了面向对象的一些基本原则。这里的Target角色和Adapter角色融合在一起,Adapter中的方法就是目标方法;而Adaptee角色就是ListView的数据集与Item View,Adapter代理数据集,从而获取到数据集的个数、元素。

通过增加Adapter一层来将Item View的操作抽象起来,ListView等集合视图通过Adapter对象获得Item的个数、数据元素、Item View等,从而达到适配各种数据、各种Item视图的效果。因为Item View和数据类型千变万化,Android的架构师们将这些变化的部分交给用户来处理,通过getCount、getItem、getView等几个方法抽象出来,也就是将Item View的构造过程交给用户来处理,灵活地运用了适配器模式,达到了无限适配、拥抱变化的目的。


Android设计模式系列(9)--SDK源码之适配器模式

Adapter.jpg

适配器原理解析

为了简明直接,省略了相关的其他适配器,只以此两个适配器为例。

ListView中有一个变量ListAdapter mAdapter(在AbsListView中定义);是显示在view视图上的数据;

/** 
* The adapter containing the data to be displayed by this view 
*/ 
ListAdapter mAdapter;

ListView作为Client,他所需要的目标接口(target interface)就是ListAdapter,包含getCount(),getItem(),getView()等几个基本方法,为了兼容List<T>,Cursor等数据源,我们专门定义两个适配器来适配它们:ArrayAdapter和CursorAdapter。这两个适配器,说白了就是针对目标接口对数据源进行兼容修饰。这就是适配器模式。
其中BaseAdapter实现了isEmpty()方法,使子类在继承BaseAdapter后不需要再实现此方法,这就是缺省适配器,这也是缺省适配器的一个最明显的好处。


ListView适配器Adapter介绍与优化
Adapter继承结构关系

Adapter2.JPG

Android学习四、Android中的Adapter
在实际应用中,adapter的继承体系应用广泛,所以,要对Adapter的方法有所了解:

public interface Adapter {  
// 为了避免产生大量的View浪费内存,在Android中,AdapterView中的View是可回收的使用的。比如你有100项数据要显示,而你的屏幕一次只能显示10条数据,则   
// 只产生10个View,当往下拖动要显示第11个View时,会把第1个View的引用传递过去,更新里面的数据再显示,也就是说View可重用,只是更新视图中的数据用于显示新 
// 的一项,如果一个视图的视图类型是IGNORE_ITEM_VIEW_TYPE的话,则此视图不会被重用    
static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE; 
static final int NO_SELECTION = Integer.MIN_VALUE;

// 注册一个Observer,当Adapter所表示的数据改变时会通知它,DataSetObserver是一个抽象类,定义了两个方法:onChanged与onInvalidated 
void registerDataSetObserver(DataSetObserver observer); 

// 取消注册一个Observer   
void unregisterDataSetObserver(DataSetObserver observer);   

// 所表示的数据的项数    
int getCount(); 
// 返回指定位置的数据项   
Object getItem(int position);   
// 返回指定位置的数据项的ID    
long getItemId(int position);   
// 表示所有数据项的ID是否是稳定的,在BaseAdapter中默认返回了false,假设是不稳定的,在CursorAdapter中返回了true,Cursor中的_ID是不变的  
boolean hasStableIds(); 
// 为每一个数据项产生相应的视图   
View getView(int position, View convertView, ViewGroup parent); 
// 获得相应位置的这图类型  
int getItemViewType(int position);  
// getView可以返回的View的类型数量。(在HeaderViewListAdapter中可以包含Header和Footer,getView可以返回Header、Footer及Adapter 
// 中的视图,但其getViewTypeCount的实现只是调用了内部Adapter的getViewTypeCount,忽略了Header、Footer中的View 
// Type,不懂。 
int getViewTypeCount(); 
//是否为空  
boolean isEmpty();
}

ListAdapter中定义了所需要的接口函数:

public interface ListAdapter extends Adapter {
public boolean areAllItemsEnabled();
boolean isEnabled(int position);
}

抽象类BaseAdapter,我省略其他代码,只列出两个方法,以作示意:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
//...
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public boolean isEmpty() {
return getCount()==0;
}
}

ArrayAdapter对List<T>进行封装成ListAdapter的实现,满足ListView的调用:

public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
private List<T> mObjects;
//我只列出这一个构造函数,大家懂这个意思就行
public ArrayAdapter(Context context, int textViewResourceId, T[] objects){
init(context, textViewResourceId, 0,     Arrays.asList(objects));
}
private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
mContext = context;
mInflater = (LayoutInFlater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mResource = mDropDownResource = resource;
mObjects = objects; //引用对象,也是表达了组合优于继承的意思
mFieldId = textViewResourceId;
}
public int getCount() {
return mObjects.size();
}
public T getItem(int position){
return mObjects.get(position);
}
public View getView(int position, View convertView,     ViewGroup parent) {
return createViewFromResource(position,convertView,parent,mResource);
}
//...
}

我们就如此成功的把List<T>作为数据源以ListView想要的目标接口的样子传给了ListView,同理CursorAdapter也是一模一样的道理,就不写具体代码了。


BaseAdapter

类BaseAdapter(省略了部分继承和要实现的方法)

BaseAdapter中增加了两个方法,即通知数据改变和通知数据无效。定义了一个变量,通过改变量实现了注册、取消注册、通知等。

public abstract class BaseAdapter 
extends Object
implements ListAdapter, SpinnerAdapter{

private final DataSetObservable mDataSetObservable = new DataSetObservable();
//...省略不必要的代码
public BaseAdapter();
public void registerDataSetObserver(DataSetObserver observer){
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
public void notifyDataChanged(){
mDataSetObservable.notifychanged();
}
//Notifies the attached View that the underlying data has been changed and it should refresh itself
public void notifyDataSetInvalidated(){
mDataSetObservable.notifyInvalidated();
}
//...省略不必要的代码
}

BaseAdapter是一个抽象类,实现了ListAdapter和SpinnerAdapter两个接口,这两个接口都继承自Adapter接口。在这个接口中申明了我们需要实现的四个重要方法。

接下来,我们进入ListView中查看setAdapter方法,ListView就是通过调用这个方法与适配器联系起来的。该方法的传入参数为ListAdapter类型,ListAdapter同样继承了adapter,也就是说,我们只重写adapter中的一些回调方法才会起效。该方法中,首先会调用它的父类ABSListView中的requestLayout方法,该方法调用后会回调其中的onLayout方法,该方法会调用ListView中的layoutChildren方法,该方法会调用fillSpecific方法,fillSpecific方法通过调用makeAndAddView方法得到需要的view,然后将view放入list。查看makeAndAddView方法,它会调用父类的obtainView方法,而该方法会调用适配器中重写的getView方法。

AbsListView中定义了变量 ListAdapter mAdapter;


![ListView和Adapter之间的关系.JPG]](http://upload-images.jianshu.io/upload_images/1563413-dc5bcf46fbbfcb65.JPG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

其实说到底,控件就是为了交互和展示数据用的,只不过ListView更加特殊,它是为了展示很多很多数据用的,但是ListView只承担交互和展示工作而已,至于这些数据来自哪里,ListView并不关心。

ListView显示出来需要3个东西

  1. ListView(用来显示数据的列表)
  2. Data(需要显示的数据)
  3. 一个绑定data和ListView的适配器ListAdapter

ListView的每一项其实都是TextView。
通过setAdapter方法来调用一个ListAdapter来绑定数据

ListView.JPG

Android ViewGroup系列控件的使用

public abstract class AdapterView<T extends Adapter>extends ViewGroup{
//AdapterView继承ViewGroup,但AdapterView的child view由Adapter决定,不能通过addView()来添加。 
//setAdapter()来设置Adapter,getAdapter()获取。

//部分方法
abstract T getAdapter() 
abstract void setAdapter(T adapter) 
}

Adapter的getView方法详解
getView详解 Recycler机制

ListView_Recycler.JPG

getView的API:

public abstract View getView(int position, View convertView, ViewGroup parent)

Get a View that displays the data at the specified position in the data set. You can either create a View manually or inflate it from an XML layout file. When the View is inflated, the parent View (GridView, ListView...) will apply default layout parameters unless you use [inflate(int, android.view.ViewGroup, boolean)](http://localhost:8080/android/docs/reference/android/view/LayoutInflater.html#inflate(int, android.view.ViewGroup, boolean))
to specify a root view and to prevent attachment to the root.

Parameters

  • position
    The position of the item within the adapter's data set of the item whose view we want.
  • convertView
    The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see getViewTypeCount()
    and getItemViewType(int)
    ).
  • parent
    The parent that this view will eventually be attached to

Returns
A View corresponding to the data at the specified position.

position是指当前dataset的位置,通过getCount和getItem来使用。如果list向下滑动的话那么就是最低端的item的位置,如果是向上滑动的话那就是最上端的item的位置。convert是指可以重用的视图,即刚刚出队的视图。parent应该就是list。


Android ListView工作原理解析

RecycleBin机制

这个机制是ListView能够实现成百上千条数据都不会OOM最重要的原因。RecycleBin是写在ABSListView中的一个内部类。所以所有继承自AbsListView的子类,也就是ListView和GridView,都可以使用这个机制。

方法简介:

  • 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参数,当有某个View确定要废弃掉的时候(比如滚动出了屏幕),就应该调用这个方法来对View进行缓存,RecycleBin当中使用mScrapViews和mCurrentScrap这两个List来存储废弃View。
  • getScrapView 用于从废弃缓存中取出一个View,这些废弃缓存中的View是没有顺序可言的,因此getScrapView()方法中的算法也非常简单,就是直接从mCurrentScrap当中获取尾部的一个scrap view进行返回。
  • setViewTypeCount() 我们都知道Adapter当中可以重写一个getViewTypeCount()来表示ListView中有几种类型的数据项,而setViewTypeCount()方法的作用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。实际上,getViewTypeCount()方法通常情况下使用的并不是很多,所以我们只要知道RecycleBin当中有这样一个功能就行了
第一次Layout

不管怎么说,ListView是继承自View的,因此它的执行流程还将会按照View的规则来执行。可参考Android视图绘制流程完全解析,带你一步步深入了解View(二)

View的执行流程无非分为三步,onMeasure()用于测量View的大小,onLayout()用于确定View的布局,onDraw()用于将View绘制到界面上。ListView当中,onMeasure()并没有什么特殊的地方,因为它终归是一个View。onDraw()在ListView当中也没有什么意义,因为ListView本身也不负责绘制,而是由ListView当中的子元素来进行绘制的。那么ListView大部分的神奇功能其实都是在onLayout()方法中进行的了。

onLayout()方法在AbsListView中覆写

/**
 * Subclasses should NOT override this method but {@link #layoutChildren()}
 * instead.
 */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
if (changed) {
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        getChildAt(i).forceLayout();
    }
    mRecycler.markChildrenDirty();
}
layoutChildren();
mInLayout = false;
}

可以看到,onLayout()方法中并没有做什么复杂的逻辑操作,主要就是一个判断,如果ListView的大小或者位置发生了变化,那么changed变量就会变成true,此时会要求所有的子布局都强制进行重绘。除此之外倒没有什么难理解的地方了,不过我们注意到,在第16行调用了layoutChildren()这个方法,从方法名上我们就可以猜出这个方法是用来进行子元素布局的,不过进入到这个方法当中你会发现这是个空方法,没有一行代码。这当然是可以理解的了,因为子元素的布局应该是由具体的实现类来负责完成的,而不是由父类完成。那么进入ListView的layoutChildren()方法

然后调用fillFromTop()方法

然后调用fillDown()方法

调用makeAndAddView()方法

调用obtainView()
在obtainView中调用了mAdapter.getView(position,null,this);

mAdapter就是当前ListView关联的适配器
mAdapter是ListAdapter类型,在AbsListView中定义。
在setAdapter函数中,传入我们定义的适配器adapter。有adapter构造mAdapter.从而在调用mAdapter.getView函数时调用是我们覆写的函数。从而完成了适配器模式

在setAdapter函数的最后调用了 requestLayout();请求重新布局,重新调用:onMeasure,onLayout,onDraw;从而开始了ListView的绘制过程。

Android中View的生命周期,调用invalidate()和requestLayout()会触发哪些方法,一图道破天机。

requestLayout.JPG
第二次Layout
滑动加载更多数据

ListView初始化简单分析

ListView.onLayout过程与普通视图的layout过程完全不同,下面是一个流程的思维导图

ListView_init.JPG
  1. 先看构造函数,上图中1.1就不分析了,主要是读取一些ListView参数,直接来看1.2 ViewGroup构造函数源码

     private void initViewGroup() { 
     ...... 
     // 初始化保存当前ViewGroup中所有View的数组 
     mChildren = new View[ARRAY_INITIAL_CAPACITY]; 
     // 初始时其Child个数为0 
     mChildrenCount = 0; 
     ...... 
     }
    
  2. 接着2即ListView.onMeasure方法,只是获取当前ListView的宽高

     @Override  
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
     // Sets up mListPadding  
     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
    
     // 获取当前ListView总宽高  
     int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
     int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
    
     ......  
    
     setMeasuredDimension(widthSize , heightSize);  
     mWidthMeasureSpec = widthMeasureSpec;          
     }  
    
  3. ListView的onLayout

  4. ListView.layoutChildren

  5. ListView.fillFromTop

  6. ListView.fillDown

  7. ListView.makeAndAddView

  8. ListView.setupChild


Android ListView使用BaseAdapter与ListView的优化

当系统开始绘制ListView的时候,首先调用getCount()方法,得到它的返回值,即ListView的长度,然后系统调用getView()方法,根据这个长度逐一绘制ListView的每一行。也就是说,如果让getCount()返回1,那么只显示一行。而getItem()和getItemId()则在需要处理和取得Adapter中的数据时调用。

系统显示列表时,首先需要实例化一个适配器.当手动完成适配时,必须手动映射数据,这需要重写getView方法。系统在绘制列表的每一行的时候将调用此方法。


  1. AdapterView
    adapter的相关抽象函数getAdapter、setAdapter
    mEmptyView
    观察者模式
    Accessibility

  2. AbsListView
    AbsListView定义了集合视图的框架
    比如Adapter模式的应用
    复用Item View的逻辑
    布局Item View的逻辑
    滑动事件相关
    定义了变量: ListAdapter mAdapter;
    使用了对象适配器。
    (1) 拥有RecycleBin类,负责处理view的生成和回收,没有子视图的空间定位信息
    (2) 滑动事件相关--加载数据并显示更多数据等
    onTouchEvent

  1. ListView
    ListView覆写了AbsListView中的layoutChilden函数,在该函数中根据布局模式来布局Item View。Item View的个数、样式都通过Adapter对应的方法来获取,获取个数、Item View之后,将这些Item View布局到ListView对应的坐标上,再加上Item View的复用机制,整个ListView就基本运转起来了。

补充知识点:

无论抽象类还是接口,抽象方法都需要在子类中实现,而且在子类中实现这些方法一个都不能少。而抽象类里面的非抽象方法,则在子类可以不重写实现里面的行为。


参考

Android ViewGroup系列控件的使用

Android源码学习之适配器模式应用


Android开发优化-Adapter优化

Android高手进阶:Adapter深入理解与优化

AndroidAdapter用法总结
主要提供了ArrayAdapter,SimpleAdapter、simpleCursorAdapter实例

Android ListView工作原理完全解析,带你从源码的角度彻底理解,androidlistview
http://www.android100.org/html/201507/26/168809.html
http://android.jobbole.com/81834/

ListView源代码分析

Android视图绘制流程完全解析,带你一步步深入了解View(二)

Android设计模式源码解析之ListView观察者模式

Android AdapterView 源码分析以及其相关回收机制的分析

Android LayoutInflater原理分析,带你一步步深入了解View(一)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容