简体中文简体中文
EnglishEnglish
简体中文简体中文

深入剖析源码ListView:揭秘Android

2025-01-23 06:37:57

列表展示的奥秘

一、引言

ListView作为Android中最常用的组件之一,承担着展示列表数据的重任。无论是在系统设置、应用列表,还是各种应用界面,ListView都无处不在。本文将深入剖析ListView的源码,帮助大家理解其工作原理,提高编程水平。

二、ListView简介

ListView是一种可以展示列表数据的组件,它将数据项以列表形式显示在屏幕上,用户可以通过滑动、点击等方式与数据交互。ListView具有以下特点:

1.可滚动:ListView可以垂直或水平滚动,方便用户浏览大量数据。 2.可复用:ListView采用滚动机制,当用户滚动列表时,可以复用已经显示过的列表项,提高性能。 3.可自定义:ListView允许用户自定义列表项的布局和样式,满足个性化需求。

三、ListView源码分析

1.ListView继承自AdapterView,AdapterView继承自AbsListView,AbsListView继承自AbsListViewBase。下面是ListView的继承关系:

ListView → AbsListView → AbsListViewBase

2.ListView的核心方法如下:

(1)onMeasure:确定ListView的宽度和高度。

(2)onLayout:确定ListView中每个子视图的位置。

(3)fillView:填充ListView中的子视图。

(4)getView:获取ListView中的某个子视图。

(5)setAdapter:设置ListView的适配器。

下面分别对这几个方法进行详细分析:

(1)onMeasure方法:

ListView的onMeasure方法主要确定自身的宽度和高度。具体实现如下:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 获取ListView的宽度和高度模式 int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec);

// 获取适配器中的数据项数量
int count = mAdapter.getCount();
if (count == 0) {
    setMeasuredDimension(0, 0);
    return;
}
// 获取ListView的第一个子视图的高度
int childHeight = getChildAt(0).getMeasuredHeight();
// 根据高度模式确定ListView的高度
if (heightMode == MeasureSpec.AT_MOST) {
    setMeasuredDimension(widthSize, childHeight);
} else if (heightMode == MeasureSpec.EXACTLY) {
    setMeasuredDimension(widthSize, heightSize);
} else {
    setMeasuredDimension(widthSize, childHeight * count);
}

}

从上述代码可以看出,ListView的高度取决于高度模式、子视图的高度和数据项数量。当高度模式为AT_MOST时,ListView的高度与第一个子视图的高度相同;当高度模式为EXACTLY时,ListView的高度由外部传入的高度值决定;当高度模式为UNSPECIFIED时,ListView的高度由子视图的数量和高度决定。

(2)onLayout方法:

ListView的onLayout方法主要确定子视图的位置。具体实现如下:

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int left = getPaddingLeft(); final int top = getPaddingTop(); final int right = getWidth() - getPaddingRight(); final int bottom = getHeight() - getPaddingBottom();

int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
    final View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
        final LayoutParams params = (LayoutParams) child.getLayoutParams();
        final int childWidth = child.getMeasuredWidth();
        final int childHeight = child.getMeasuredHeight();
        int childLeft = left + params.leftMargin;
        int childTop = top + params.topMargin;
        int childRight = childLeft + childWidth;
        int childBottom = childTop + childHeight;
        child.layout(childLeft, childTop, childRight, childBottom);
    }
}

}

从上述代码可以看出,ListView的onLayout方法通过遍历子视图,确定每个子视图的位置。具体位置由左、上、右、下四个边界值决定。

(3)fillView方法:

ListView的fillView方法主要用于填充ListView中的子视图。具体实现如下:

@Override protected void fillFromTop(int firstPosition) { if (firstPosition != mFirstPosition || mHeaderViewsCount == 0) { if (mFirstPosition == -1 && mHeaderViewsCount == 0) { // 无数据,直接返回 return; }

    // 清除已显示的子视图
    removeViews(0, mHeaderViewsCount + mVisibleItemCount);
    mFirstPosition = firstPosition;
    // 填充头部视图
    fillFromTopInternal();
}

}

从上述代码可以看出,fillView方法在填充ListView中的子视图时,首先清除已显示的子视图,然后根据头部视图数量和可见项数量填充子视图。

(4)getView方法:

ListView的getView方法用于获取ListView中的某个子视图。具体实现如下:

@Override public View getView(int position, View convertView, ViewGroup parent) { // 根据位置获取数据项 Object item = getItem(position);

// 判断是否为头部视图
if (item == null) {
    if (position >= mHeaderViewsCount) {
        item = mHeaderViewTemplates[position - mHeaderViewsCount];
    } else {
        item = mHeaderViewTemplates[position];
    }
}
// 获取或创建子视图
View v = convertView;
if (v == null) {
    v = mLayoutInflater.inflate(getLayoutResource(), null);
}
// 绑定数据到子视图
mOutView = v;
bindView(position, item, v, parent);
return v;

}

从上述代码可以看出,getView方法首先根据位置获取数据项,然后根据是否为头部视图决定是否创建子视图。如果子视图已经存在,则复用该子视图;否则,根据布局资源创建子视图。最后,将数据项绑定到子视图上。

(5)setAdapter方法:

ListView的setAdapter方法用于设置ListView的适配器。具体实现如下:

@Override public void setAdapter(Adapter adapter) { if (mAdapter != null) { // 移除旧适配器的监听器 mAdapter.unregisterDataSetObserver(mObserver); }

mAdapter = adapter;
if (adapter != null) {
    // 注册新适配器的监听器
    adapter.registerDataSetObserver(mObserver);
}
// 刷新ListView
notifyDataSetChanged();

}

从上述代码可以看出,setAdapter方法首先移除旧适配器的监听器,然后设置新适配器,并注册新适配器的监听器。最后,刷新ListView。

四、总结

通过对ListView源码的分析,我们了解了ListView的工作原理,包括测量、布局、填充和绑定数据等过程。掌握ListView的源码有助于我们更好地理解Android列表展示的奥秘,为开发出更加高效、美观的应用奠定基础。