IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android ListView -> 正文阅读

[移动开发]Android ListView

1.Listview

Listview是用来显示大量数据的控件,且不会因为展示大量数据而出现内存溢出的现象,原因是相关缓存机制保证了内存的合理使用。

首先看一下ListView的继承结构:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_17,color_FFFFFF,t_70,g_se,x_16

ListView的继承结构还是很复杂的,它直接继承自AbsListView,而AbsListView有两个子实现类,一个是ListView,另一个就是GridView,从这一点就可以看出来,ListView和GridView在工作原理和实现上都是有很多共同点的。然后AbsListView继承自AdapterView,AdapterView继承自ViewGroup。

?

2.RecycleBin机制

RecycleBin是ListView缓存的核心机制,它是ListView能够实现成百上千条数据都不会OOM最重要的原因。

RecycleBin是AbsListView的一个内部类,所有继承自AbsListView的子类,也就是ListView和GridView,都可以使用这个机制。

class RecycleBin {

? ? private RecyclerListener mRecyclerListener;

? ? private int mFirstActivePosition;

? ? //mActiveViews存放正在展示在屏幕上的view ,从显示在屏幕上的第一个view到最后一个view

? ? private View[] mActiveViews = new View[0];

? ??//mScrapViews存放可以由适配器用作convert view的view,是一个数组,数组的每个元素类型为ArrayList<View>

? ??private ArrayList<View>[] mScrapViews;

? ? private int mViewTypeCount;? ? ? ?

? ??//mCurrentScrap是mScrapViews的第0个元素,当view种类数量为1时存放废弃view

? ??private ArrayList<View> mCurrentScrap;? ? ??

? ??//Adapter中可以重写getViewTypeCount方法来表示ListView中有几种类型的数据项,而setViewTypeCount()方法就是为每种类型的数据项都单独启用一个RecycleBin缓存机制

? ??public void setViewTypeCount(int viewTypeCount) {

? ? ? ??if (viewTypeCount < 1) {

? ? ? ? ? ? throw new IllegalArgumentException( "Can't have a viewTypeCount < 1");

? ? ? ? }

? ? ? ? ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];

? ? ? ? for (int i = 0; i < viewTypeCount; i++) {

? ? ? ? ? ? scrapViews[i] = new ArrayList<View>();

? ? ? ? }

? ? ? ??mViewTypeCount = viewTypeCount;

? ?? ? ?mCurrentScrap = scrapViews[0];

? ? ? ??mScrapViews = scrapViews;

? ? }? ? ? ??

?? ?// fillActiveViews()接收两个参数,一个参数表示mActiveViews数组最小要保存的View数量,第二个参数表示ListView中第一个可见元素的position值。?根据传入的参数将ListView中的指定元素存储到mActiveViews数组当中

? ? void fillActiveViews(int childCount, int firstActivePosition) {

? ? ?? ?if (mActiveViews.length < childCount) {

? ? ? ? ? ? mActiveViews = new View[childCount];

? ? ? ? }

? ? ? ? mFirstActivePosition = firstActivePosition;

? ? ? ? final View[] activeViews = mActiveViews;

? ? ?? ?for (int i = 0; i < childCount; i++) {

? ? ? ?? ? ?View child = getChildAt(i);

? ? ? ? ?? ?AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();

? ? ? ? ? ? if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {

? ? ? ? ? ? ? ? activeViews[i] = child;

? ? ? ? ? ? ? ? lp.scrappedFromPosition = firstActivePosition + i;

? ? ? ? ?? ?}

? ? ? ? }

?? ?}? ? ? ??

? ? //getActiveView()方法用于从mActiveViews数组当中取出特定元素。该方法接收一个position参数,表示元素在ListView当中的位置,方法内部会自动将position值转换成mActiveViews数组对应的下标值。mActiveViews当中所存储的View,一旦被获取了之后就会从mActiveViews当中移除,下次获取同样位置的View将会返回null,也就是说mActiveViews不能被重复利用。如果在mActiveViews数组中没有找到,则返回null

? ??View getActiveView(int position) {

? ? ? ? int index = position - mFirstActivePosition;

? ? ? ? final View[] activeViews = mActiveViews;

? ? ? ??if (index >=0 && index < activeViews.length){

? ? ? ? ? ? final View match = activeViews[index];

? ? ? ? ? ? activeViews[index] = null;

? ? ? ? ? ? return match;

? ? ?? ?}

? ? ? ??return null;

? ? }? ? ? ??

? ? //getScrapView()用于从废弃缓存中取出一个View。这些废弃缓存中的View是没有顺序可言的,因此getScrapView()方法中的算法也非常简单,就是直接从mCurrentScrap当中获取尾部的一个scrap view进行返回

? ? View getScrapView(int position) {

? ? ? ??final int whichScrap = mAdapter.getItemViewType(position);

? ? ? ? if (whichScrap < 0) {

? ? ? ? ? ??return null;

? ? ? ? }

? ? ? ??if (mViewTypeCount == 1) {

? ? ? ? ?? ?return retrieveFromScrap(mCurrentScrap, position);

? ? ? ? } else if (whichScrap < mScrapViews.length){

? ? ? ? ? ??return retrieveFromScrap( mScrapViews[whichScrap], position);

? ? ?? ?}

? ? ? ? return null;

? ? }

? ??//addScrapView()用于将一个废弃的View进行缓存。该方法接收一个View参数,当有某个View确定要废弃掉的时候(比如滚动出了屏幕),应该调用这个方法来对View进行缓存。当view类型为1时则用mCurrentScrap存储废弃view,否则使用mScrapViews添加废弃view

? ??void addScrapView(View scrap, int position) {

? ? ? ? final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();

? ? ? ??if (lp == null) {

? ? ? ? ? ? return;

? ? ?? ?}

? ? ? ? lp.scrappedFromPosition = position;

? ? ? ??final int viewType = lp.viewType;

? ? ? ? if (!shouldRecycleViewType(viewType)) {

? ? ? ? ?? ?if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {

? ? ? ? ? ? ? ??getSkippedScrap().add(scrap);

? ? ? ? ? ? }

? ? ? ? ? ? return;

? ? ? ? }

? ? ?? ?scrap.dispatchStartTemporaryDetach();

? ? ? ??notifyViewAccessibilityStateChangedIfNee ded(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);

? ? ? ? final boolean scrapHasTransientState = scrap.hasTransientState();

? ? ? ??if (scrapHasTransientState) {

? ? ? ? ? ? if (mAdapter != null && mAdapterHasStableIds) {

? ? ? ? ? ? ?? ?if (mTransientStateViewsById == null) {

? ? ? ? ? ? ? ? ? ??mTransientStateViewsById = new LongSparseArray<>();

? ? ? ? ? ? ? ? }

? ? ? ? ? ?? ? ?mTransientStateViewsById.put( lp.itemId, scrap);

? ? ? ? ? ? } else if (!mDataChanged) {

? ? ? ? ? ? ? ??if (mTransientStateViews == null) {

? ? ? ? ? ? ? ? ? ? mTransientStateViews = new SparseArray<>();

? ? ? ? ? ? ? ??}

? ? ? ? ? ? ? ? mTransientStateViews.put(position, scrap);

? ? ? ? ?? ?} else {

? ? ? ? ? ? ? ??getSkippedScrap().add(scrap);

? ? ? ? ? ? }

? ? ? ??} else {

? ? ? ? ?? ?if (mViewTypeCount == 1) {

? ? ? ? ? ? ? ??mCurrentScrap.add(scrap);

? ? ? ? ?? ?} else {

? ? ? ? ? ? ? ? mScrapViews[viewType].add(scrap);

? ? ? ? ? ? }

? ? ? ? ? ? if (mRecyclerListener != null) {

? ? ? ? ? ? ? ??mRecyclerListener.onMovedToScra pHeap( scrap);

? ? ? ? ? ??}

? ? ? ??}

? ? }

}

?

3.ListView的绘制流程

ListView本质上还是一个View,因此绘制过程还是分为三步:onMeasure、onLayout、onDraw。onMeasure测出其占用屏幕空间,最大为整个屏幕;onDraw用于将ListView内容绘制到屏幕上,在ListView中无实际意义,因为ListView本身只是提供了一种布局方式,真正的绘制是ListView中的子View完成的;而onLayout方法是最为关键的。

①第一次onLayout

ListView的OnLayout实现在AbsListView中,具体源码如下:

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

? ? super.onLayout(changed, l, t, r, b);

? ? mInLayout = true;

? ? final int childCount = getChildCount();

? ? if (changed) {

? ? ? ? for (int i = 0; i < childCount; i++) {

? ? ? ? ? ? getChildAt(i).forceLayout();

? ? ? ? }

? ? ? ? mRecycler.markChildrenDirty();

? ? }

? ? layoutChildren();

? ? mInLayout = false;

? ? ...

}

从代码可以看出,首先调用了父类的onLayout方法,再判断ListView是否发生了变化(大小、位置),如果ListView发生了变化,则changed变量为true,就会强制每个子布局都进行重新绘制,同时还进行了mRecycler.markChildrenDirty()操作,其中mRecycler就是一个RecycleBin对象,而markChildrenDirty()方法会为每一个scrap view调用forceLayout()。判断完changed变量后又调用了layoutChildren()方法,点进此方法发现它是一个空方法,因为每个子元素的布局实现应该由自己来实现,所以它的具体实现在ListView中。

@Override
?protected void layoutChildren() {
? ? ...
? ??
? ? final int childCount = getChildCount();
? ??
? ? ...
? ??
? ? boolean dataChanged = mDataChanged;
? ? ??
? ? ...
? ??
? ? if (dataChanged) {
? ? ? ? for (int i = 0; i < childCount; i++) {
? ? ? ? ? ? recycleBin.addScrapView(getChildAt(i), firstPosition+i);
? ? ? ? }
? ? } else {
? ? ? ? recycleBin.fillActiveViews(childCount, firstPosition);
? ? }

? ? ...
? ??
? ? switch (mLayoutMode) {
? ? ? ? ...
? ? ? ??
? ? ? ? default:
? ? ? ? ? ? if (childCount == 0) {
? ? ? ? ? ? ? ? if (!mStackFromBottom) {
? ? ? ? ? ? ? ? ? ? final int position = lookForSelectablePosition(0, true);
? ? ? ? ? ? ? ? ? ? setSelectedPositionInt(position);
? ? ? ? ? ? ? ? ? ? sel = fillFromTop(childrenTop);
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? final int position = lookForSelectablePosition(mItemCount - 1,false);
? ? ? ? ? ? ? ? ? ? setSelectedPositionInt(position);
? ? ? ? ? ? ? ? ? ? sel = fillUp(mItemCount - 1, childrenBottom);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
? ? ? ? ? ? ? ? ? ? sel = fillSpecific(mSelectedPosition, oldSel == null ? childrenTop : oldSel.getTop());
? ? ? ? ? ? ? ? } else if (mFirstPosition < mItemCount) {
? ? ? ? ? ? ? ? ? ? sel = fillSpecific(mFirstPosition, oldFirst == null ? childrenTop : oldFirst.getTop());
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? sel = fillSpecific(0, childrenTop);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? break;
? ? }

? ? ...
? ??
?}

它的方法过长,只贴出来一部分,此方法中首先会获取子元素的数量,由于是第一次onLayout,此时ListView中还没有任何子View,因为数据都是由Adapter管理的,还没有展示到界面上。接着又会判断dataChanged这个值,如果数据源发生变化则该值变为true,紧接着调用了RecycleBin的fillActiveViews()方法。可是这时ListView中还没有子View,因此fillActiveViews的缓存功能无法起作用。

接着往下分析,接下来又会判断mLayoutMode的值,默认情况下该值都是LAYOUT_NORMAL,此模式下会直接进入default语句中,其中有多次if条件判断。当前ListView中还没有任何子View,所以当前childCount数量为0,mStackFromBottom变量代表的是布局的顺序,默认的布局顺序是从上至下,因此会进入fillFromTop方法中。

private View fillFromTop(int nextTop) {

? ? mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);

? ? mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);

? ? if (mFirstPosition < 0) {

? ? ? ? mFirstPosition = 0;

? ? }

? ? return fillDown(mFirstPosition, nextTop);

}

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++;

? ? }

? ? setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);

? ? return selectedView;

}

fillFromTop首先计算出了mFirstPosition的值,并从mFirstPosition开始自顶至下调用fillDown填充。

fillDown中采用了while循环来填充,一开始时nextTop的值是第一个子元素顶部距离整个ListView顶部的像素值,pos是传入的mFirstPosition的值,end是ListView底部减去顶部所得的像素值,mItemCount是Adapter中的元素数量,因此nextTop是小于end的,pos也小于mItemCount,每次执行while循环时,pos加1,nextTop也会累加,当nextTop大于end时,也就是子元素超出屏幕了,或者pos大于mItemCount时,即Adapter中所有元素都被遍历了,出现以上两种情况中一种便会跳出while循环。

在此while循环中,调用了makeAndAddView这个方法:

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) {

? ? if (!mDataChanged) {

? ? ? ? // Try to use an existing view for this position.

? ? ? ? final View activeView = mRecycler.getActiveView(position);

? ? ? ? if (activeView != null) {

? ? ? ? ? ? // Found it. We're reusing an existing child, so it just needs?to be positioned like a scrap view.

? ? ? ? ? ? setupChild(activeView, position, y, flow, childrenLeft, selected, true);

? ? ? ? ? ? return activeView;

? ? ? ? }

? ? }

? ? // Make a new view for this position, or convert an unused view if possible.

? ? final View child = obtainView(position, mIsScrap);

? ? // This needs to be positioned and measured.

? ? setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

? ? return child;

}

当Adapter的数据源未发生变化时,会从RecycleBin中获取一个activeView,但是目前RecycleBin中还没有缓存任何的View,因此这里得到的child为null,接着又调用了obtainView方法来获取一个View,obtainView方法在AbsListView里:

View obtainView(int position, boolean[] outMetadata) {

? ? outMetadata[0] = false;? ?

? ? ...? ?

? ? final View scrapView = mRecycler.getScrapView(position);

? ? final View child = mAdapter.getView(position, scrapView, this);

? ? if (scrapView != null) {

? ? ? ? if (child != scrapView) {

? ? ? ? ? ? // Failed to re-bind the data, return scrap to the heap.

? ? ? ? ? ? mRecycler.addScrapView(scrapView, position);

? ? ? ? } else if (child.isTemporarilyDetached()) {

? ? ? ? ? ? outMetadata[0] = true;

? ? ? ? ? ? // Finish the temporary detach started in addScrapView().

? ? ? ? ? ? child.dispatchFinishTemporaryDetach();

? ? ? ? }

? ? }

? ? ...? ?

? ? return child;

}

首先调用RecycleBin的getScrapView方法来尝试获取一个废弃缓存中的View,但是这里是获取不到的;接着又调用了getView方法,即自定义的Adapter中的getView方法,getView方法接收三个参数,第一个是当前子元素位置,第二个参数是convertView,在这里是null,说明没有covertView可以利用,因此在Adapter中判断convertView为null时可以调用LayoutInflater的inflate方法去加载一个布局,并将此view返回。同时可以看到,这个view最终也会作为obtainView方法的返回结果,并传入makeAndAddView方法中后续调用的setupChild()方法中。上面过程可以说明第一次layout过程中,所有子View都是调用LayoutInflater的inflate方法动态加载对应布局而产生的,解析布局的过程肯定是耗时的,但是在后续过程中,这种情况不会出现了。接下来,继续看下setupChild方法:

private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,?boolean selected, boolean isAttachedToWindow) {

? ? ...? ?

? ? addViewInLayout(child, flowDown ? -1 : 0, p, true);? ?

? ? ....

}

在setupChild方法中会调用addViewInLayout方法将它添加到ListView中,那么回到fillDown方法,其中的while循环就会让子元素View将整个ListView控件填满然后跳出,也就是说即使Adapter中有很多条数据,ListView也只会加载第一屏数据。下图是第一次onLayout的过程:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

②第二次onLayout

即使是一个再简单的View,在展示到界面上之前都会经历至少两次onMeasure()和两次onLayout()过程,自然ListView的绘制过程也不例外。

首先还是从layoutChildren()方法看起:

再来看一遍该方法源码:

?@Override

?protected void layoutChildren() {

? ? ...??

? ? final int childCount = getChildCount();? ??

? ? ...? ?

? ? boolean dataChanged = mDataChanged;? ? ?

? ? ...? ??

? ? if (dataChanged) {

? ? ? ? for (int i = 0; i < childCount; i++) {

? ? ? ? ? ? recycleBin.addScrapView(getChildAt(i), firstPosition+i);

? ? ? ? }

? ? } else {

? ? ? ? recycleBin.fillActiveViews(childCount, firstPosition);

? ? }

? ? ...? ??

? ? // Clear out old views

? ? detachAllViewsFromParent();? ?

? ? switch (mLayoutMode) {

? ? ? ? ...? ? ? ?

? ? ? ? default:

? ? ? ? ? ? if (childCount == 0) {

? ? ? ? ? ? ? ? if (!mStackFromBottom) {

? ? ? ? ? ? ? ? ? ? final int position = lookForSelectablePosition(0, true);

? ? ? ? ? ? ? ? ? ? setSelectedPositionInt(position);

? ? ? ? ? ? ? ? ? ? sel = fillFromTop(childrenTop);

? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? final int position = lookForSelectablePosition(mItemCount - 1,false);

? ? ? ? ? ? ? ? ? ? setSelectedPositionInt(position);

? ? ? ? ? ? ? ? ? ? sel = fillUp(mItemCount - 1, childrenBottom);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {

? ? ? ? ? ? ? ? ? ? sel = fillSpecific(mSelectedPosition, oldSel == null ? childrenTop : oldSel.getTop());

? ? ? ? ? ? ? ? } else if (mFirstPosition < mItemCount) {

? ? ? ? ? ? ? ? ? ? sel = fillSpecific(mFirstPosition,? oldFirst == null ? childrenTop : oldFirst.getTop());

? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? sel = fillSpecific(0, childrenTop);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? break;

? ? }

? ? ...? ??

?}

首先还是会获取子元素的数量,不同于第一次onLayout,此时获取到的子View数量不再为0,而是ListView中显示的子元素数量。下面又调用了RecycleBin的fillActiveViews()方法,目前ListView已经有子View了,这样所有的子View都会被缓存到RecycleBin中的mActiveViews数组中,后面会使用到它们。

接下来有一个重要的方法:detachAllViewsFromParent(),这个方法会将ListView中所有子View全部清除掉,从而保证第二次Layout过程不会产生一份重复数据,因为layoutChildren方法会向ListView中添加View,在第一次layout中已经添加了一次,如果第二次layout继续添加,那么必然会出现数据重复的问题,因此这里先调用detachAllViewsFromParent方法将第一次添加的View清除掉。

这样把已经加载好的View又清除掉,待会还要再重新加载一遍,这不是严重影响效率吗?不用担心,刚刚调用了RecycleBin的fillActiveViews()方法来缓存子View,等会将直接使用这些缓存好的View来进行添加子View,而并不会重新执行一遍inflate过程,因此效率方面并不会有什么明显的影响。

再进入判断childCount是否为0的逻辑中,此时会走和第一次layout相反的else逻辑分支,这其中又有三条逻辑分支,第一条一般不成立,因为开始时还没选中任何子View,第二条一般成立,mFirstPosition开始时为0,只要Adapter中数据量大于0即可,所以进入了fillSpecific方法:

private View fillSpecific(int position, int top) {

? ? boolean tempIsSelected = position == mSelectedPosition;

? ? View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);

? ? // Possibly changed again in fillUp if we add rows above this one.

? ? mFirstPosition = position;

? ? View above;

? ? View below;

? ? final int dividerHeight = mDividerHeight;

? ? if (!mStackFromBottom) {

? ? ? ? above = fillUp(position - 1, temp.getTop() - dividerHeight);

? ? ? ? // This will correct for the top of the first view not touching the top of the list

? ? ? ? adjustViewsUpOrDown();

? ? ? ? below = fillDown(position + 1, temp.getBottom() + dividerHeight);

? ? ? ? int childCount = getChildCount();

? ? ? ? if (childCount > 0) {

? ? ? ? ? ? correctTooHigh(childCount);

? ? ? ? }

? ? } else {

? ? ? ? below = fillDown(position + 1, temp.getBottom() + dividerHeight);

? ? ? ? // This will correct for the bottom of the last view not touching the bottom of the list

? ? ? ? adjustViewsUpOrDown();

? ? ? ? above = fillUp(position - 1, temp.getTop() - dividerHeight);

? ? ? ? int childCount = getChildCount();

? ? ? ? if (childCount > 0) {

? ? ? ? ? ? ?correctTooLow(childCount);

? ? ? ? }

? ? }

? ? if (tempIsSelected) {

? ? ? ? return temp;

? ? } else if (above != null) {

? ? ? ? return above;

? ? } else {

? ? ? ? return below;

? ? }

}

fillSpecific()方法的功能和fillUp、fillDown差不多,但是fillSpecific()方法会优先加载指定位置的View,再加载该View上下的其它子View,由于这里传入的position就是第一个子元素的位置,因此此时其效果和上述的fillDown()基本一致。

可以看到,fillSpecific()方法中也调用了makeAndAddView()方法,因为我们之前调用detachAllViewsFromParent()方法把所有ListView当中的子View全部清除掉了,这里肯定要重新再加上,在makeAndAddView()方法中:

再来看一遍此方法源码:

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) {

? ? if (!mDataChanged) {

? ? ? ? // Try to use an existing view for this position.

? ? ? ? final View activeView = mRecycler.getActiveView(position);

? ? ? ? if (activeView != null) {

? ? ? ? ? ? // Found it. We're reusing an existing child, so it just needs?to be positioned like a scrap view.

? ? ? ? ? ? setupChild(activeView, position, y, flow, childrenLeft, selected, true);

? ? ? ? ? ? return activeView;

? ? ? ? }

? ? }

? ? // Make a new view for this position, or convert an unused view if

? ? // possible.

? ? final View child = obtainView(position, mIsScrap);

? ? // This needs to be positioned and measured.

? ? setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

? ? return child;

}

首先还是会从RecycleBin中获取ActiveView,不同于第一次layout,这次能获取到了,那肯定就不会进入obtainView中了,而是直接调用setupChild()方法,此时setupChild()方法的最后一个参数是true,表明当前的view是被回收过的,再来看看setupChild()方法源码:

private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,?boolean selected, boolean isAttachedToWindow) {

? ? ...

? ? if ((isAttachedToWindow && !p.forceAdd) || (p.recycledHeaderFooter?&& p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {

? ? ? ? attachViewToParent(child, flowDown ? -1 : 0, p);

? ? ? ? ...

? ? } else {

? ? ? ? ...

? ? }

? ? ...

}

可以看到,setupChild()方法的最后一个参数是isAttachedToWindow,方法执行过程中会对这个变量进行判断,由于isAttachedToWindow现在是true,所以会执行attachViewToParent()方法,而第一次Layout过程则是执行的else语句中的addViewInLayout()方法。

这两个方法最大的区别在于,如果需要向ViewGroup中添加一个新的子View,应该调用addViewInLayout()方法,而如果是想要将一个之前detach的View重新attach到ViewGroup上,就应该调用attachViewToParent()方法。由于前面在layoutChildren()方法当中调用了detachAllViewsFromParent()方法,这样ListView中所有的子View都是处于detach状态的,所以这里attachViewToParent()方法是正确的选择。

经历了这样一个detach又attach的过程,ListView中所有的子View又都可以正常显示出来了,那么第二次Layout过程结束。

下图展示了第二次onLayout的过程:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

?

?

?

?

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-09 12:51:06  更:2022-05-09 12:53:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 1:52:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码