listview_listview控件 - CSDN
listview 订阅
ListView 控件可使用四种不同视图显示项目。通过此控件,可将项目组成带有或不带有列标头的列,并显示伴随的图标和文本。 可使用 ListView 控件将称作 ListItem 对象的列表条目组织成下列四种不同的视图之一:1.大(标准)图标2.小图标3.列表4.报表 View 属性决定在列表中控件使用何种视图显示项目。还可用 LabelWrap 属性控制列表中与项目关联的标签是否可换行显示。另外,还可管理列表中项目的排序方法和选定项目的外观。 展开全文
ListView 控件可使用四种不同视图显示项目。通过此控件,可将项目组成带有或不带有列标头的列,并显示伴随的图标和文本。 可使用 ListView 控件将称作 ListItem 对象的列表条目组织成下列四种不同的视图之一:1.大(标准)图标2.小图标3.列表4.报表 View 属性决定在列表中控件使用何种视图显示项目。还可用 LabelWrap 属性控制列表中与项目关联的标签是否可换行显示。另外,还可管理列表中项目的排序方法和选定项目的外观。
信息
功    能
可使用四种不同视图显示项目
属    性
计算机技术
外文名
LISTVIEW
类    型
控件
LISTVIEW属性
DropHighlight属性(ListView, TreeView控件),LabelEdit属性,SelectedItem属性(ActiveX控件),ColumnHeaderIcons属性,Checkboxes属性,FullRowSelect属性,AllowColumnRecorder属性,FlatScrollBar属性,GridLines属性(ListView控件),HoverSelection属性,PictureAlignment属性,HotTracking属性,TextBackground属性,Arrange属性(ListView控件),ColumnHeaders属性(ListView控件),HideColumnHeaders属性(ListView控件),Icons,SmallIcons属性,ListItems属性(ListView控件),LabelWrap属性(ListView控件),MultiSelect属性(ListView,TabStrip控件),SorKey属性(ListView控件),SortOrder属性(ListView控件),View属性(ListView控件),Sorted属性(ListView控件),TabIndex属性,DragIcon属性,DragMode属性,MouseIcon属性,TabStop属性,HelpContextID属性,Name属性,Parent属性,Font属性,Container属性,ToolTipText属性,WhatsThisHelpID属性,OLEDragMode属性(ActiveX控件),OLEDropMode属性(ActiveX控件),Picture属性(ActiveX控件),Height,Width属性(ActiveX控件),Index属性(ActiveX控件),Left, Top属性(ActiveX控件),Tag属性(ActiveX控件),Object属性(ActiveX控件),Appearance属性(ActiveX控件),BackColor, ForeColor属性(ActiveX控件),BorderStyle属性(ActiveX控件),Enabled属性(ActiveX控件),HideSelection属性(ActiveX控件),hWnd属性(ActiveX控件),MousePointer属性(ActiveX控件)。
收起全文
  • 在Android所有常用的原生控件当中,用法最复杂的应该就是ListView了,它专门用于处理那种内容元素很多,手机屏幕无法展示出所有内容的情况。 ListView可以使用列表的形式来展示内容,超出屏幕部分的内容只需要通过...
  • ListView 组件

    2019-09-25 23:53:15
    简介: ListView是最常用的可滚动组件之一 有三种构建方式: ListView ListView.builder ListView.separated 主要参数说明: scrollDirection: Axis.horizontal 水平列表 Axis.vertical 垂直列表 padding: 内...

    简介: ListView是最常用的可滚动组件之一
    有三种构建方式:

    • ListView
    • ListView.builder
    • ListView.separated

    主要参数说明:

    scrollDirection: Axis.horizontal 水平列表
    				 Axis.vertical 垂直列表
    padding: 内边距
    resolve: 组件反向排序
    children: 列表元素
    itemBuilder:(ListView.builder、ListView.separated固有的) 它是列表项的构建器,类型为IndexedWidgetBuilder,(ListView.builder、ListView.separated固有的) 返回值为一个widget。当列表滚动到具体的index位置时,会调用该构建器构建列表项。
    itemCount:列表项的数量,如果为null,则为无限列表。
    

    一、ListView
    适合场景: 适合只有少量的子组件的情况,需要将所有children都提前创建好,即通过默认构造函数构建的ListView没有应用基于Sliver的懒加载模型

    示例代码:

    ListView(
      shrinkWrap: true, 
      padding: const EdgeInsets.all(20.0),
      children: <Widget>[
        const Text('I\'m dedicating every day to you'),
        const Text('Domestic life was never quite my style'),
        const Text('When you smile, you knock me out, I fall apart'),
        const Text('And I thought I was so smart'),
      ],
    );
    

    二、ListView.builder
    适合场景: 适合列表项比较多(或者无限)的情况,因为只有当子组件真正显示的时候才会被创建,也就说通过该构造函数创建的ListView是支持基于Sliver的懒加载模型的。

    示例代码:

    ListView.builder(
        itemCount: 100,
        itemExtent: 50.0, //强制高度为50.0
        itemBuilder: (BuildContext context, int index) {
          return ListTile(title: Text("$index"));
        }
    );
    

    运行效果如下图:
    在这里插入图片描述
    三、ListView.separated
    适合场景: 和ListView.builder适用场景一样的,它比ListView.builder多了一个separatorBuilder参数,该参数是一个分割组件生成器,可用在生成的列表项之间。

    下面我们看一个例子:奇数行添加一条蓝色下划线,偶数行添加一条绿色下划线。

    class ListView3 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        //下划线widget预定义以供复用。  
        Widget divider1=Divider(color: Colors.blue,);
        Widget divider2=Divider(color: Colors.green);
        return ListView.separated(
            itemCount: 100,
            //列表项构造器
            itemBuilder: (BuildContext context, int index) {
              return ListTile(title: Text("$index"));
            },
            //分割器构造器
            separatorBuilder: (BuildContext context, int index) {
              return index%2==0?divider1:divider2;
            },
        );
      }
    }
    

    效果图如下:
    在这里插入图片描述
    四、实战一:添加固定列表头的处理方式
    在弹性布局中,可以使用Expanded自动拉伸组件大小,并且Column是继承自Flex的,所以可以直接使用Column+Expanded来实现

    @override
    Widget build(BuildContext context) {
      return Column(children: <Widget>[
        ListTile(title:Text("商品列表")),
        Expanded(
          child: ListView.builder(itemBuilder: (BuildContext context, int index) {
            return ListTile(title: Text("$index"));
          }),
        ),
      ]);
    }
    

    在这里插入图片描述
    五、实战二:无限加载列表
    假设我们要从数据源异步分批拉取一些数据,然后用ListView展示,当我们滑动到列表末尾时,判断是否需要再去拉取数据,如果是,则去拉取,拉取过程中在表尾显示一个loading,拉取成功后将数据插入列表;如果不需要再去拉取,则在表尾提示"没有更多"。代码如下:

    class InfiniteListView extends StatefulWidget {
      @override
      _InfiniteListViewState createState() => new _InfiniteListViewState();
    }
    
    class _InfiniteListViewState extends State<InfiniteListView> {
      static const loadingTag = "##loading##"; //表尾标记
      var _words = <String>[loadingTag];
    
      @override
      void initState() {
        super.initState();
        _retrieveData();
      }
    
      @override
      Widget build(BuildContext context) {
        return ListView.separated(
          itemCount: _words.length,
          itemBuilder: (context, index) {
            //如果到了表尾
            if (_words[index] == loadingTag) {
              //不足100条,继续获取数据
              if (_words.length - 1 < 100) {
                //获取数据
                _retrieveData();
                //加载时显示loading
                return Container(
                  padding: const EdgeInsets.all(16.0),
                  alignment: Alignment.center,
                  child: SizedBox(
                      width: 24.0,
                      height: 24.0,
                      child: CircularProgressIndicator(strokeWidth: 2.0)
                  ),
                );
              } else {
                //已经加载了100条数据,不再获取数据。
                return Container(
                    alignment: Alignment.center,
                    padding: EdgeInsets.all(16.0),
                    child: Text("没有更多了", style: TextStyle(color: Colors.grey),)
                );
              }
            }
            //显示单词列表项
            return ListTile(title: Text(_words[index]));
          },
          separatorBuilder: (context, index) => Divider(height: .0),
        );
      }
    
      void _retrieveData() {
        Future.delayed(Duration(seconds: 2)).then((e) {
          _words.insertAll(_words.length - 1,
              //每次生成20个单词
              generateWordPairs().take(20).map((e) => e.asPascalCase).toList()
          );
          setState(() {
            //重新构建列表
          });
        });
      }
    
    }
    

    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 虽然现在大家使用ListView的机会相对RecyclerView的机会较少,但官方并没有标注 ListView 类过期,哈哈,就说明它一定还是有他的特殊之处,这篇文章就来分析下,ListView的内部机制以及几个重要的点。 将从下面几个...

    虽然现在大家使用ListView的机会相对RecyclerView的机会较少,但官方并没有标注 ListView 类过期,哈哈,就说明它一定还是有他的特殊之处,这篇文章就来分析下,ListView的内部机制以及几个重要的点。
    将从下面几个方面开始着手分析ListView,将会分两篇文章进行解析

    1. 类的继承关系
    2. ListView 的构造方法
    3. RecycleBin主要方法讲解
    4. ListView 的绘制原理以及缓存回收过程 (onMeasure onLayout onDraw)
    5. Listview 的滑动过程
    6. setAdapter 原理
    7. notifyDataSetChanged 原理

    (1)ListView 的继承关系

    类的继承关系

    (2)ListView 的构造函数

    public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            // 这里是调用的父类AbsListView的构造方法
            super(context, attrs, defStyleAttr, defStyleRes);
    
            final TypedArray a = context.obtainStyledAttributes(
                    attrs, R.styleable.ListView, defStyleAttr, defStyleRes);
    
            final CharSequence[] entries = a.getTextArray(R.styleable.ListView_entries);
            if (entries != null) {
                // ListView 的默认Adapter
                setAdapter(new ArrayAdapter<>(context, R.layout.simple_list_item_1, entries));
            }
    
            // 设置分割线样式 用户可以重写ListView_divider样式
            final Drawable d = a.getDrawable(R.styleable.ListView_divider);
            if (d != null) {
                // Use an implicit divider height which may be explicitly
                // overridden by android:dividerHeight further down.
                setDivider(d);
            }
    
            // 设置 ListView 的footer和header
            final Drawable osHeader = a.getDrawable(R.styleable.ListView_overScrollHeader);
            if (osHeader != null) {
                setOverscrollHeader(osHeader);
            }
    
            final Drawable osFooter = a.getDrawable(R.styleable.ListView_overScrollFooter);
            if (osFooter != null) {
                setOverscrollFooter(osFooter);
            }
    
            // 分割线高度
            // Use an explicit divider height, if specified.
            if (a.hasValueOrEmpty(R.styleable.ListView_dividerHeight)) {
                final int dividerHeight = a.getDimensionPixelSize(
                        R.styleable.ListView_dividerHeight, 0);
                if (dividerHeight != 0) {
                    setDividerHeight(dividerHeight);
                }
            }
    
            mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
            mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
    
            a.recycle();
        }
    AbsListView.java 的构造方法
    
     public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            initAbsListView();
            ...
     }
    
     private void initAbsListView() {
            // Setting focusable in touch mode will set the focusable property to true
            // 设置可点击
            setClickable(true);
            // 设置 触摸可以获取焦点
            setFocusableInTouchMode(true);
            // 设置可以绘制
            setWillNotDraw(false);
            // 设置对于透明的地方,显示最底层的背景
            setAlwaysDrawnWithCacheEnabled(false);
            // 设置 是否缓存卷动项
            setScrollingCacheEnabled(true);
            // 设置相关事件变量初始化
            final ViewConfiguration configuration = ViewConfiguration.get(mContext);
            mTouchSlop = configuration.getScaledTouchSlop();
            mVerticalScrollFactor = configuration.getScaledVerticalScrollFactor();
            mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
            mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
            mOverscrollDistance = configuration.getScaledOverscrollDistance();
            mOverflingDistance = configuration.getScaledOverflingDistance();
    
            mDensityScale = getContext().getResources().getDisplayMetrics().density;
        }
    

    ListView 的构造方法中主要做一些资源的初始化工作。

    (3) RecycleBin主要方法讲解

    RecycleBin是 AbsListView的内部类,它的代码不多,这里主要介绍几个变量含义和方法功能

    class RecycleBin {
            private RecyclerListener mRecyclerListener;
    
            /**
             * The position of the first view stored in mActiveViews.
             */
            private int mFirstActivePosition;
    
            /**
             * Views that were on screen at the start of layout. This array is populated at the start of
             * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
             * Views in mActiveViews represent a contiguous range of Views, with position of the first
             * view store in mFirstActivePosition.
             */
            private View[] mActiveViews = new View[0];
    
            /**
             * Unsorted views that can be used by the adapter as a convert view.
             */
            private ArrayList<View>[] mScrapViews;
    
            private int mViewTypeCount;
    
            private ArrayList<View> mCurrentScrap;
    
            private ArrayList<View> mSkippedScrap;
    
            private SparseArray<View> mTransientStateViews;
            private LongSparseArray<View> mTransientStateViewsById;
    
            public void setViewTypeCount(int viewTypeCount) {
                if (viewTypeCount < 1) {
                    throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
                }
                //noinspection unchecked
                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;
            }
    
            public void markChildrenDirty() {
                if (mViewTypeCount == 1) {
                    final ArrayList<View> scrap = mCurrentScrap;
                    final int scrapCount = scrap.size();
                    for (int i = 0; i < scrapCount; i++) {
                        scrap.get(i).forceLayout();
                    }
                } else {
                    final int typeCount = mViewTypeCount;
                    for (int i = 0; i < typeCount; i++) {
                        final ArrayList<View> scrap = mScrapViews[i];
                        final int scrapCount = scrap.size();
                        for (int j = 0; j < scrapCount; j++) {
                            scrap.get(j).forceLayout();
                        }
                    }
                }
                if (mTransientStateViews != null) {
                    final int count = mTransientStateViews.size();
                    for (int i = 0; i < count; i++) {
                        mTransientStateViews.valueAt(i).forceLayout();
                    }
                }
                if (mTransientStateViewsById != null) {
                    final int count = mTransientStateViewsById.size();
                    for (int i = 0; i < count; i++) {
                        mTransientStateViewsById.valueAt(i).forceLayout();
                    }
                }
            }
    
            public boolean shouldRecycleViewType(int viewType) {
                return viewType >= 0;
            }
    
            /**
             * Clears the scrap heap.
             */
            void clear() {
                if (mViewTypeCount == 1) {
                    final ArrayList<View> scrap = mCurrentScrap;
                    clearScrap(scrap);
                } else {
                    final int typeCount = mViewTypeCount;
                    for (int i = 0; i < typeCount; i++) {
                        final ArrayList<View> scrap = mScrapViews[i];
                        clearScrap(scrap);
                    }
                }
    
                clearTransientStateViews();
            }
    
            /**
             * Fill ActiveViews with all of the children of the AbsListView.
             *
             * @param childCount The minimum number of views mActiveViews should hold
             * @param firstActivePosition The position of the first view that will be stored in
             *        mActiveViews
             */
            void fillActiveViews(int childCount, int firstActivePosition) {
                if (mActiveViews.length < childCount) {
                    mActiveViews = new View[childCount];
                }
                mFirstActivePosition = firstActivePosition;
    
                //noinspection MismatchedReadAndWriteOfArray
                final View[] activeViews = mActiveViews;
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
                    // Don't put header or footer views into the scrap heap
                    if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                        // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
                        //        However, we will NOT place them into scrap views.
                        activeViews[i] = child;
                        // Remember the position so that setupChild() doesn't reset state.
                        lp.scrappedFromPosition = firstActivePosition + i;
                    }
                }
            }
    
            /**
             * Get the view corresponding to the specified position. The view will be removed from
             * mActiveViews if it is found.
             *
             * @param position The position to look up in mActiveViews
             * @return The view if it is found, null otherwise
             */
            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;
            }
    
            View getTransientStateView(int position) {
                if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
                    long id = mAdapter.getItemId(position);
                    View result = mTransientStateViewsById.get(id);
                    mTransientStateViewsById.remove(id);
                    return result;
                }
                if (mTransientStateViews != null) {
                    final int index = mTransientStateViews.indexOfKey(position);
                    if (index >= 0) {
                        View result = mTransientStateViews.valueAt(index);
                        mTransientStateViews.removeAt(index);
                        return result;
                    }
                }
                return null;
            }
    
            /**
             * Dumps and fully detaches any currently saved views with transient
             * state.
             */
            void clearTransientStateViews() {
                final SparseArray<View> viewsByPos = mTransientStateViews;
                if (viewsByPos != null) {
                    final int N = viewsByPos.size();
                    for (int i = 0; i < N; i++) {
                        removeDetachedView(viewsByPos.valueAt(i), false);
                    }
                    viewsByPos.clear();
                }
    
                final LongSparseArray<View> viewsById = mTransientStateViewsById;
                if (viewsById != null) {
                    final int N = viewsById.size();
                    for (int i = 0; i < N; i++) {
                        removeDetachedView(viewsById.valueAt(i), false);
                    }
                    viewsById.clear();
                }
            }
    
            /**
             * @return A view from the ScrapViews collection. These are unordered.
             */
            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;
            }
    
            /**
             * Puts a view into the list of scrap views.
             * <p>
             * If the list data hasn't changed or the adapter has stable IDs, views
             * with transient state will be preserved for later retrieval.
             *
             * @param scrap The view to add
             * @param position The view's position within its parent
             */
            void addScrapView(View scrap, int position) {
                final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
                if (lp == null) {
                    // Can't recycle, but we don't know anything about the view.
                    // Ignore it completely.
                    return;
                }
    
                lp.scrappedFromPosition = position;
    
                // Remove but don't scrap header or footer views, or views that
                // should otherwise not be recycled.
                final int viewType = lp.viewType;
                if (!shouldRecycleViewType(viewType)) {
                    // Can't recycle. If it's not a header or footer, which have
                    // special handling and should be ignored, then skip the scrap
                    // heap and we'll fully detach the view later.
                    if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                        getSkippedScrap().add(scrap);
                    }
                    return;
                }
    
                scrap.dispatchStartTemporaryDetach();
    
                // The the accessibility state of the view may change while temporary
                // detached and we do not allow detached views to fire accessibility
                // events. So we are announcing that the subtree changed giving a chance
                // to clients holding on to a view in this subtree to refresh it.
                notifyViewAccessibilityStateChangedIfNeeded(
                        AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
    
                // Don't scrap views that have transient state.
                final boolean scrapHasTransientState = scrap.hasTransientState();
                if (scrapHasTransientState) {
                    if (mAdapter != null && mAdapterHasStableIds) {
                        // If the adapter has stable IDs, we can reuse the view for
                        // the same data.
                        if (mTransientStateViewsById == null) {
                            mTransientStateViewsById = new LongSparseArray<>();
                        }
                        mTransientStateViewsById.put(lp.itemId, scrap);
                    } else if (!mDataChanged) {
                        // If the data hasn't changed, we can reuse the views at
                        // their old positions.
                        if (mTransientStateViews == null) {
                            mTransientStateViews = new SparseArray<>();
                        }
                        mTransientStateViews.put(position, scrap);
                    } else {
                        // Otherwise, we'll have to remove the view and start over.
                        clearScrapForRebind(scrap);
                        getSkippedScrap().add(scrap);
                    }
                } else {
                    clearScrapForRebind(scrap);
                    if (mViewTypeCount == 1) {
                        mCurrentScrap.add(scrap);
                    } else {
                        mScrapViews[viewType].add(scrap);
                    }
    
                    if (mRecyclerListener != null) {
                        mRecyclerListener.onMovedToScrapHeap(scrap);
                    }
                }
            }
    
            private ArrayList<View> getSkippedScrap() {
                if (mSkippedScrap == null) {
                    mSkippedScrap = new ArrayList<>();
                }
                return mSkippedScrap;
            }
    
            /**
             * Finish the removal of any views that skipped the scrap heap.
             */
            void removeSkippedScrap() {
                if (mSkippedScrap == null) {
                    return;
                }
                final int count = mSkippedScrap.size();
                for (int i = 0; i < count; i++) {
                    removeDetachedView(mSkippedScrap.get(i), false);
                }
                mSkippedScrap.clear();
            }
    
            /**
             * Move all views remaining in mActiveViews to mScrapViews.
             */
            void scrapActiveViews() {
                final View[] activeViews = mActiveViews;
                final boolean hasListener = mRecyclerListener != null;
                final boolean multipleScraps = mViewTypeCount > 1;
    
                ArrayList<View> scrapViews = mCurrentScrap;
                final int count = activeViews.length;
                for (int i = count - 1; i >= 0; i--) {
                    final View victim = activeViews[i];
                    if (victim != null) {
                        final AbsListView.LayoutParams lp
                                = (AbsListView.LayoutParams) victim.getLayoutParams();
                        final int whichScrap = lp.viewType;
    
                        activeViews[i] = null;
    
                        if (victim.hasTransientState()) {
                            // Store views with transient state for later use.
                            victim.dispatchStartTemporaryDetach();
    
                            if (mAdapter != null && mAdapterHasStableIds) {
                                if (mTransientStateViewsById == null) {
                                    mTransientStateViewsById = new LongSparseArray<View>();
                                }
                                long id = mAdapter.getItemId(mFirstActivePosition + i);
                                mTransientStateViewsById.put(id, victim);
                            } else if (!mDataChanged) {
                                if (mTransientStateViews == null) {
                                    mTransientStateViews = new SparseArray<View>();
                                }
                                mTransientStateViews.put(mFirstActivePosition + i, victim);
                            } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                                // The data has changed, we can't keep this view.
                                removeDetachedView(victim, false);
                            }
                        } else if (!shouldRecycleViewType(whichScrap)) {
                            // Discard non-recyclable views except headers/footers.
                            if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                                removeDetachedView(victim, false);
                            }
                        } else {
                            // Store everything else on the appropriate scrap heap.
                            if (multipleScraps) {
                                scrapViews = mScrapViews[whichScrap];
                            }
    
                            lp.scrappedFromPosition = mFirstActivePosition + i;
                            removeDetachedView(victim, false);
                            scrapViews.add(victim);
    
                            if (hasListener) {
                                mRecyclerListener.onMovedToScrapHeap(victim);
                            }
                        }
                    }
                }
                pruneScrapViews();
            }
    
            /**
             * At the end of a layout pass, all temp detached views should either be re-attached or
             * completely detached. This method ensures that any remaining view in the scrap list is
             * fully detached.
             */
            void fullyDetachScrapViews() {
                final int viewTypeCount = mViewTypeCount;
                final ArrayList<View>[] scrapViews = mScrapViews;
                for (int i = 0; i < viewTypeCount; ++i) {
                    final ArrayList<View> scrapPile = scrapViews[i];
                    for (int j = scrapPile.size() - 1; j >= 0; j--) {
                        final View view = scrapPile.get(j);
                        if (view.isTemporarilyDetached()) {
                            removeDetachedView(view, false);
                        }
                    }
                }
            }
    
            /**
             * Makes sure that the size of mScrapViews does not exceed the size of
             * mActiveViews, which can happen if an adapter does not recycle its
             * views. Removes cached transient state views that no longer have
             * transient state.
             */
            private void pruneScrapViews() {
                final int maxViews = mActiveViews.length;
                final int viewTypeCount = mViewTypeCount;
                final ArrayList<View>[] scrapViews = mScrapViews;
                for (int i = 0; i < viewTypeCount; ++i) {
                    final ArrayList<View> scrapPile = scrapViews[i];
                    int size = scrapPile.size();
                    while (size > maxViews) {
                        scrapPile.remove(--size);
                    }
                }
    
                final SparseArray<View> transViewsByPos = mTransientStateViews;
                if (transViewsByPos != null) {
                    for (int i = 0; i < transViewsByPos.size(); i++) {
                        final View v = transViewsByPos.valueAt(i);
                        if (!v.hasTransientState()) {
                            removeDetachedView(v, false);
                            transViewsByPos.removeAt(i);
                            i--;
                        }
                    }
                }
    
                final LongSparseArray<View> transViewsById = mTransientStateViewsById;
                if (transViewsById != null) {
                    for (int i = 0; i < transViewsById.size(); i++) {
                        final View v = transViewsById.valueAt(i);
                        if (!v.hasTransientState()) {
                            removeDetachedView(v, false);
                            transViewsById.removeAt(i);
                            i--;
                        }
                    }
                }
            }
    
            /**
             * Puts all views in the scrap heap into the supplied list.
             */
            void reclaimScrapViews(List<View> views) {
                if (mViewTypeCount == 1) {
                    views.addAll(mCurrentScrap);
                } else {
                    final int viewTypeCount = mViewTypeCount;
                    final ArrayList<View>[] scrapViews = mScrapViews;
                    for (int i = 0; i < viewTypeCount; ++i) {
                        final ArrayList<View> scrapPile = scrapViews[i];
                        views.addAll(scrapPile);
                    }
                }
            }
    
            /**
             * Updates the cache color hint of all known views.
             *
             * @param color The new cache color hint.
             */
            void setCacheColorHint(int color) {
                if (mViewTypeCount == 1) {
                    final ArrayList<View> scrap = mCurrentScrap;
                    final int scrapCount = scrap.size();
                    for (int i = 0; i < scrapCount; i++) {
                        scrap.get(i).setDrawingCacheBackgroundColor(color);
                    }
                } else {
                    final int typeCount = mViewTypeCount;
                    for (int i = 0; i < typeCount; i++) {
                        final ArrayList<View> scrap = mScrapViews[i];
                        final int scrapCount = scrap.size();
                        for (int j = 0; j < scrapCount; j++) {
                            scrap.get(j).setDrawingCacheBackgroundColor(color);
                        }
                    }
                }
                // Just in case this is called during a layout pass
                final View[] activeViews = mActiveViews;
                final int count = activeViews.length;
                for (int i = 0; i < count; ++i) {
                    final View victim = activeViews[i];
                    if (victim != null) {
                        victim.setDrawingCacheBackgroundColor(color);
                    }
                }
            }
    
            private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
                final int size = scrapViews.size();
                if (size > 0) {
                    // See if we still have a view for this position or ID.
                    // Traverse backwards to find the most recently used scrap view
                    for (int i = size - 1; i >= 0; i--) {
                        final View view = scrapViews.get(i);
                        final AbsListView.LayoutParams params =
                                (AbsListView.LayoutParams) view.getLayoutParams();
    
                        if (mAdapterHasStableIds) {
                            final long id = mAdapter.getItemId(position);
                            if (id == params.itemId) {
                                return scrapViews.remove(i);
                            }
                        } else if (params.scrappedFromPosition == position) {
                            final View scrap = scrapViews.remove(i);
                            clearScrapForRebind(scrap);
                            return scrap;
                        }
                    }
                    final View scrap = scrapViews.remove(size - 1);
                    clearScrapForRebind(scrap);
                    return scrap;
                } else {
                    return null;
                }
            }
    
            private void clearScrap(final ArrayList<View> scrap) {
                final int scrapCount = scrap.size();
                for (int j = 0; j < scrapCount; j++) {
                    removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
                }
            }
    
            private void clearScrapForRebind(View view) {
                view.clearAccessibilityFocus();
                view.setAccessibilityDelegate(null);
            }
    
            private void removeDetachedView(View child, boolean animate) {
                child.setAccessibilityDelegate(null);
                AbsListView.this.removeDetachedView(child, animate);
            }
        }

    变量:

    RecycleBin中 mActiveViews:当前在屏幕上展示的view
    mScrapViews :存储滑出屏幕的View; mViewTypeCount>1的时候,ScrapViews数组就会有多个值,分别缓存一个类型的item
    mCurrentScrap :和mScrapViews 功能类似,mViewTypeCount=1,也就是listView中只有一种类型的item时(不包括header和footer),item是存入到mCurrentScrap中。
    mViewTypeCount : 当前ListView 的item的种类

                    if (mViewTypeCount == 1) {
                        mCurrentScrap.add(scrap);
                    } else {
                        mScrapViews[viewType].add(scrap);
                    }

    方法:

    fillActiveViews:根据传入的参数,要存储view的数量以及第一个可见元素的位置,往mActiveViews添加View
    getActiveView: 和fillActiveViews相对,从mActiveViews获取View,从getActiveView方法可以看到,在读取match之后,就 activeViews[index] = null; 说明 activeViews 中的view只能复用一次。
    getScrapView: 从mScrapViews 或者mCurrentScrap中获取尾部的View
    addScrapView: 将滚出屏幕的View添加到mScrapViews 或者mCurrentScrap中

    (4) ListView 的绘制原理以及缓存回收过程

    在看过ListView的初始化操作之后,之后就是由ViemRootImpl的 performTraversals 方法主持View的整个绘制过程, 这里简单介绍下View的启动过程,用UML图表示他们的调用关系,具体的源码大家可以再细细研究。
    绘制流程

    ViemRootImpl的 performTraversals 完成依次执行performMeasure performLayout performDraw三个方法,其中performMeasure 中调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中,这样就完成了一次measure过程。接着子元素会重复父容器的measure过程,如此反复完成整个View树的遍历。同理,performLayout和performDraw传递流程和perwformMeasure类似,唯一不同的是,performDraw过程实在draw方法中通过dispatchOnDraw来实现的,之后再进行onDraw,不过这并没有本质的差别。

    接下来就来剖析ListView的绘制过程。

    onMeasure 测量过程和其他View功能类似,测量好view的宽度和高度以及对应的SpecMode SpecSize
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // Sets up mListPadding
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            // 初始化 宽度和高度 的模式和大小
            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            int childWidth = 0;
            int childHeight = 0;
            int childState = 0;
    
            ...
    
            setMeasuredDimension(widthSize, heightSize);
    
            mWidthMeasureSpec = widthMeasureSpec;
        }

    ListView 的绘制过程主要在onLayout,ListView 中没有onLayout在父类AbsListView 中

    AbsListView 中的onLayout
    @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();
    
            mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
    
            // TODO: Move somewhere sane. This doesn't belong in onLayout().
            if (mFastScroll != null) {
                mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
            }
            mInLayout = false;
        }
     //  AbsListView 中的layoutChildren的实现分别在其子类中
      protected void layoutChildren() {
      }

    ListView 的 layoutChildren

       @Override
        protected void layoutChildren() {
            final boolean blockLayoutRequests = mBlockLayoutRequests;
            if (blockLayoutRequests) {
                return;
            }
    
            mBlockLayoutRequests = true;
    
            try {
                super.layoutChildren();
    
                invalidate();
    
                if (mAdapter == null) {
                    resetList();
                    invokeOnItemScrollListener();
                    return;
                }
    
                final int childrenTop = mListPadding.top;
                final int childrenBottom = mBottom - mTop - mListPadding.bottom;
                // 一开始获取 count = 0;
                final int childCount = getChildCount();
    
                int index = 0;
                int delta = 0;
    
                View sel;
                View oldSel = null;
                View oldFirst = null;
                View newSel = null;
    
                // Remember stuff we will need down below
                switch (mLayoutMode) {
                case LAYOUT_SET_SELECTION:
                    index = mNextSelectedPosition - mFirstPosition;
                    if (index >= 0 && index < childCount) {
                        newSel = getChildAt(index);
                    }
                    break;
                case LAYOUT_FORCE_TOP:
                case LAYOUT_FORCE_BOTTOM:
                case LAYOUT_SPECIFIC:
                case LAYOUT_SYNC:
                    break;
                case LAYOUT_MOVE_SELECTION:
                default:
                    // Remember the previously selected view
                    index = mSelectedPosition - mFirstPosition;
                    if (index >= 0 && index < childCount) {
                        oldSel = getChildAt(index);
                    }
    
                    // Remember the previous first child
                    oldFirst = getChildAt(0);
    
                    if (mNextSelectedPosition >= 0) {
                        delta = mNextSelectedPosition - mSelectedPosition;
                    }
    
                    // Caution: newSel might be null
                    newSel = getChildAt(index + delta);
                }
    
    
                boolean dataChanged = mDataChanged;
                if (dataChanged) {
                    handleDataChanged();
                }
    
                // Handle the empty set by removing all views that are visible
                // and calling it a day
                if (mItemCount == 0) {
                    resetList();
                    invokeOnItemScrollListener();
                    return;
                } else if (mItemCount != mAdapter.getCount()) {
                    throw new IllegalStateException("The content of the adapter has changed but "
                            + "ListView did not receive a notification. Make sure the content of "
                            + "your adapter is not modified from a background thread, but only from "
                            + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
                            + "when its content changes. [in ListView(" + getId() + ", " + getClass()
                            + ") with Adapter(" + mAdapter.getClass() + ")]");
                }
    
                setSelectedPositionInt(mNextSelectedPosition);
    
                AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
                View accessibilityFocusLayoutRestoreView = null;
                int accessibilityFocusPosition = INVALID_POSITION;
    
                // Remember which child, if any, had accessibility focus. This must
                // occur before recycling any views, since that will clear
                // accessibility focus.
                final ViewRootImpl viewRootImpl = getViewRootImpl();
                if (viewRootImpl != null) {
                    final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
                    if (focusHost != null) {
                        final View focusChild = getAccessibilityFocusedChild(focusHost);
                        if (focusChild != null) {
                            if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
                                    || (focusChild.hasTransientState() && mAdapterHasStableIds)) {
                                // The views won't be changing, so try to maintain
                                // focus on the current host and virtual view.
                                accessibilityFocusLayoutRestoreView = focusHost;
                                accessibilityFocusLayoutRestoreNode = viewRootImpl
                                        .getAccessibilityFocusedVirtualView();
                            }
    
                            // If all else fails, maintain focus at the same
                            // position.
                            accessibilityFocusPosition = getPositionForView(focusChild);
                        }
                    }
                }
    
                View focusLayoutRestoreDirectChild = null;
                View focusLayoutRestoreView = null;
    
                // Take focus back to us temporarily to avoid the eventual call to
                // clear focus when removing the focused child below from messing
                // things up when ViewAncestor assigns focus back to someone else.
                final View focusedChild = getFocusedChild();
                if (focusedChild != null) {
                    // TODO: in some cases focusedChild.getParent() == null
    
                    // We can remember the focused view to restore after re-layout
                    // if the data hasn't changed, or if the focused position is a
                    // header or footer.
                    if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)
                            || focusedChild.hasTransientState() || mAdapterHasStableIds) {
                        focusLayoutRestoreDirectChild = focusedChild;
                        // Remember the specific view that had focus.
                        focusLayoutRestoreView = findFocus();
                        if (focusLayoutRestoreView != null) {
                            // Tell it we are going to mess with it.
                            focusLayoutRestoreView.dispatchStartTemporaryDetach();
                        }
                    }
                    requestFocus();
                }
    
                // Pull all children into the RecycleBin.
                // These views will be reused if possible
               //mFirstPosition是ListView的成员变量,存储着第一个显示的child所对应的adapter的position
                final int firstPosition = mFirstPosition;
                final RecycleBin recycleBin = mRecycler;
                if (dataChanged) {
                    //如果数据发生了变化,那么就把ListView的所有子View都放入到RecycleBin的mScrapViews数组中
                    for (int i = 0; i < childCount; i++) {
                        //addScrapView方法会传入一个View,以及这个View所对应的position
                        recycleBin.addScrapView(getChildAt(i), firstPosition+i);
                    }
                } else {
                    //如果数据没发生变化,那么把ListView的所有子View都放入到RecycleBin的mActiveViews数组中
                    recycleBin.fillActiveViews(childCount, firstPosition);
                }
    
                // Clear out old views
                detachAllViewsFromParent();
                recycleBin.removeSkippedScrap();
    
                // mLayoutMode的值来决定布局模式,默认情况下都是普通模式LAYOUT_NORMAL
                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:
                    final int selectedPosition = reconcileSelectedPosition();
                    sel = fillSpecific(selectedPosition, mSpecificTop);
                    /**
                     * When ListView is resized, FocusSelector requests an async selection for the
                     * previously focused item to make sure it is still visible. If the item is not
                     * selectable, it won't regain focus so instead we call FocusSelector
                     * to directly request focus on the view after it is visible.
                     */
                    if (sel == null && mFocusSelector != null) {
                        final Runnable focusRunnable = mFocusSelector
                                .setupFocusIfValid(selectedPosition);
                        if (focusRunnable != null) {
                            post(focusRunnable);
                        }
                    }
                    break;
                case LAYOUT_MOVE_SELECTION:
                    sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
                    break;
                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;
                }
    
                // Flush any cached views that did not get reused above
                recycleBin.scrapActiveViews();
    
                // remove any header/footer that has been temp detached and not re-attached
                removeUnusedFixedViews(mHeaderViewInfos);
                removeUnusedFixedViews(mFooterViewInfos);
    
                if (sel != null) {
                    // The current selected item should get focus if items are
                    // focusable.
                    if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
                        final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
                                focusLayoutRestoreView != null &&
                                focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
                        if (!focusWasTaken) {
                            // Selected item didn't take focus, but we still want to
                            // make sure something else outside of the selected view
                            // has focus.
                            final View focused = getFocusedChild();
                            if (focused != null) {
                                focused.clearFocus();
                            }
                            positionSelector(INVALID_POSITION, sel);
                        } else {
                            sel.setSelected(false);
                            mSelectorRect.setEmpty();
                        }
                    } else {
                        positionSelector(INVALID_POSITION, sel);
                    }
                    mSelectedTop = sel.getTop();
                } else {
                    final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP
                            || mTouchMode == TOUCH_MODE_DONE_WAITING;
                    if (inTouchMode) {
                        // If the user's finger is down, select the motion position.
                        final View child = getChildAt(mMotionPosition - mFirstPosition);
                        if (child != null) {
                            positionSelector(mMotionPosition, child);
                        }
                    } else if (mSelectorPosition != INVALID_POSITION) {
                        // If we had previously positioned the selector somewhere,
                        // put it back there. It might not match up with the data,
                        // but it's transitioning out so it's not a big deal.
                        final View child = getChildAt(mSelectorPosition - mFirstPosition);
                        if (child != null) {
                            positionSelector(mSelectorPosition, child);
                        }
                    } else {
                        // Otherwise, clear selection.
                        mSelectedTop = 0;
                        mSelectorRect.setEmpty();
                    }
    
                    // Even if there is not selected position, we may need to
                    // restore focus (i.e. something focusable in touch mode).
                    if (hasFocus() && focusLayoutRestoreView != null) {
                        focusLayoutRestoreView.requestFocus();
                    }
                }
    
                // Attempt to restore accessibility focus, if necessary.
                if (viewRootImpl != null) {
                    final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
                    if (newAccessibilityFocusedView == null) {
                        if (accessibilityFocusLayoutRestoreView != null
                                && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
                            final AccessibilityNodeProvider provider =
                                    accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
                            if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
                                final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
                                        accessibilityFocusLayoutRestoreNode.getSourceNodeId());
                                provider.performAction(virtualViewId,
                                        AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
                            } else {
                                accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
                            }
                        } else if (accessibilityFocusPosition != INVALID_POSITION) {
                            // Bound the position within the visible children.
                            final int position = MathUtils.constrain(
                                    accessibilityFocusPosition - mFirstPosition, 0,
                                    getChildCount() - 1);
                            final View restoreView = getChildAt(position);
                            if (restoreView != null) {
                                restoreView.requestAccessibilityFocus();
                            }
                        }
                    }
                }
    
                // Tell focus view we are done mucking with it, if it is still in
                // our view hierarchy.
                if (focusLayoutRestoreView != null
                        && focusLayoutRestoreView.getWindowToken() != null) {
                    focusLayoutRestoreView.dispatchFinishTemporaryDetach();
                }
    
                mLayoutMode = LAYOUT_NORMAL;
                mDataChanged = false;
                if (mPositionScrollAfterLayout != null) {
                    post(mPositionScrollAfterLayout);
                    mPositionScrollAfterLayout = null;
                }
                mNeedSync = false;
                setNextSelectedPositionInt(mSelectedPosition);
    
                updateScrollIndicators();
    
                if (mItemCount > 0) {
                    checkSelectionChanged();
                }
    
                invokeOnItemScrollListener();
            } finally {
                if (mFocusSelector != null) {
                    mFocusSelector.onLayoutComplete();
                }
                if (!blockLayoutRequests) {
                    mBlockLayoutRequests = false;
                }
            }
        }

    看到这个 layoutChildren()这么复杂,这里找重要的类去分析:
    一开始还没有设置数据源, getChildCount 的值等于0;
    之后会判断数据源是否变化, 分别使用RecyclerBin 的 addScrapView fillActiveViews 对数据进行缓存和删除处理.
    之后根据mLayoutMode 的值判定布局模式, 一般会进入default 中处理,由于默认的布局顺序是从上往下,所以会进入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);
        }

    从 fillFromTop 方法可以看出主要方法是在filDown方法中,接下来再看filDown中是什么…

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

    fillDown 方法通过一个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;
        }

    ListView 中的View的创建以及复用主要是在obtainView中实现

    View obtainView(int position, boolean[] outMetadata) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
    
            outMetadata[0] = false;
    
            // Check whether we have a transient state view. Attempt to re-bind the
            // data and discard the view if we fail.
            final View transientView = mRecycler.getTransientStateView(position);
            if (transientView != null) {
                final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
    
                // If the view type hasn't changed, attempt to re-bind the data.
                if (params.viewType == mAdapter.getItemViewType(position)) {
                    final View updatedView = mAdapter.getView(position, transientView, this);
    
                    // If we failed to re-bind the data, scrap the obtained view.
                    if (updatedView != transientView) {
                        setItemViewLayoutParams(updatedView, position);
                        mRecycler.addScrapView(updatedView, position);
                    }
                }
    
                outMetadata[0] = true;
    
                // Finish the temporary detach started in addScrapView().
                transientView.dispatchFinishTemporaryDetach();
                return transientView;
            }
    
            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();
                }
            }
    
            if (mCacheColorHint != 0) {
                child.setDrawingCacheBackgroundColor(mCacheColorHint);
            }
    
            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
            }
    
            setItemViewLayoutParams(child, position);
    
            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
                if (mAccessibilityDelegate == null) {
                    mAccessibilityDelegate = new ListItemAccessibilityDelegate();
                }
                if (child.getAccessibilityDelegate() == null) {
                    child.setAccessibilityDelegate(mAccessibilityDelegate);
                }
            }
    
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    
            return child;
        }

    obtainView 方法中 从 RecyclerBin中getScrapView 在数据展示过程中第一次读取的时候一定是null,因为第一屏数据还没有展示完全,不会缓存数据; 之后就调用adapter中的getView那去child,getView就是我们在自定义Adapter时重写的getView方法,最后通过调用LayoutInflater的inflate()方法来去加载一个布局。拿到View之后,将回到makeAndAddView方法中,之后将child传入到setupChild中。

    private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
                boolean selected, boolean isAttachedToWindow) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");
    
            final boolean isSelected = selected && shouldShowSelector();
            final boolean updateChildSelected = isSelected != child.isSelected();
            final int mode = mTouchMode;
            final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL
                    && mMotionPosition == position;
            final boolean updateChildPressed = isPressed != child.isPressed();
            final boolean needToMeasure = !isAttachedToWindow || updateChildSelected
                    || child.isLayoutRequested();
    
            // Respect layout params that are already in the view. Otherwise make
            // some up...
            AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
            if (p == null) {
                p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
            }
            p.viewType = mAdapter.getItemViewType(position);
            p.isEnabled = mAdapter.isEnabled(position);
    
            // Set up view state before attaching the view, since we may need to
            // rely on the jumpDrawablesToCurrentState() call that occurs as part
            // of view attachment.
            if (updateChildSelected) {
                child.setSelected(isSelected);
            }
    
            if (updateChildPressed) {
                child.setPressed(isPressed);
            }
    
            if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
                if (child instanceof Checkable) {
                    ((Checkable) child).setChecked(mCheckStates.get(position));
                } else if (getContext().getApplicationInfo().targetSdkVersion
                        >= android.os.Build.VERSION_CODES.HONEYCOMB) {
                    child.setActivated(mCheckStates.get(position));
                }
            }
    
            if ((isAttachedToWindow && !p.forceAdd) || (p.recycledHeaderFooter
                    && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
                attachViewToParent(child, flowDown ? -1 : 0, p);
    
                // If the view was previously attached for a different position,
                // then manually jump the drawables.
                if (isAttachedToWindow
                        && (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition)
                                != position) {
                    child.jumpDrawablesToCurrentState();
                }
            } else {
                p.forceAdd = false;
                if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                    p.recycledHeaderFooter = true;
                }
                addViewInLayout(child, flowDown ? -1 : 0, p, true);
                // add view in layout will reset the RTL properties. We have to re-resolve them
                child.resolveRtlPropertiesIfNeeded();
            }
    
            if (needToMeasure) {
                final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
                        mListPadding.left + mListPadding.right, p.width);
                final int lpHeight = p.height;
                final int childHeightSpec;
                if (lpHeight > 0) {
                    childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
                } else {
                    childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
                            MeasureSpec.UNSPECIFIED);
                }
                child.measure(childWidthSpec, childHeightSpec);
            } else {
                cleanupLayoutState(child);
            }
    
            final int w = child.getMeasuredWidth();
            final int h = child.getMeasuredHeight();
            final int childTop = flowDown ? y : y - h;
    
            if (needToMeasure) {
                final int childRight = childrenLeft + w;
                final int childBottom = childTop + h;
                child.layout(childrenLeft, childTop, childRight, childBottom);
            } else {
                child.offsetLeftAndRight(childrenLeft - child.getLeft());
                child.offsetTopAndBottom(childTop - child.getTop());
            }
    
            if (mCachingStarted && !child.isDrawingCacheEnabled()) {
                child.setDrawingCacheEnabled(true);
            }
    
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

    setupChild 方法主要是调用addViewInLayout 将 view 添加到ListView中,这样通过fillDown的循环,就依次填满ListView 一屏的数据。 这样第一次新建View 添加到ListView 相对来说比较耗时,但之后将通过RecyclerBin回收和缓冲机制来展示LsitView。

    在之后绘制过程中,从上面提到的layoutChildren 方法中的getChildCount将不是0,而是ListView中一屏可以显示的子View数量, 之前我们会在fillActiveViews方法中存储我们目前屏幕中展示的View; 之后又调用了调用了detachAllViewsFromParent()方法,这个方法会将所有ListView当中的子View全部清除掉,从而保证第二次Layout过程不会产生一份重复的数据。那有的朋友可能会问了,这样把已经加载好的View又清除掉,待会还要再重新加载一遍,这不是严重影响效率吗?不用担心,还记得我们刚刚调用了RecycleBin的fillActiveViews()方法来缓存子View吗,待会儿将会直接使用这些缓存好的View来进行加载,而并不会重新执行一遍inflate过程,因此效率方面并不会有什么明显的影响。

    之后的分居布局模式判断将走进default的else中,执行fillSpecific方法, 不过其实它和fillUp()、fillDown()方法功能也是差不多的,主要的区别在于,fillSpecific()方法会优先将指定位置的子View先加载到屏幕上,然后再加载该子View往上以及往下的其它子View。那么由于这里我们传入的position就是第一个子View的位置,于是fillSpecific()方法的作用就基本上和fillDown()方法是差不多的了,这里我们就不去关注太多它的细节,而是将精力放在makeAndAddView()方法上面。再次回到makeAndAddView()方法;
    之后会从RecycleBin当中获取Active View,然而这次就一定可以获取到了,因为前面我们调用了RecycleBin的fillActiveViews()方法来缓存子View。那么既然如此,就不会再进入到obtainView()方法,而是会直接进入setupChild()方法当中,setupChild()方法的最后一个参数传入的是true,这个参数表明当前的View是之前被回收过的;
    setupChild()方法的最后一个参数是isAttachedToWindow,然后在第32行会对这个变量进行判断,由于isAttachedToWindow现在是true,所以会执行attachViewToParent()方法,而第一次Layout过程则是执行的else语句中的addViewInLayout()方法。这两个方法最大的区别在于,如果我们需要向ViewGroup中添加一个新的子View,应该调用addViewInLayout()方法,而如果是想要将一个之前detach的View重新attach到ViewGroup上,就应该调用attachViewToParent()方法。那么由于前面在layoutChildren()方法当中调用了detachAllViewsFromParent()方法,这样ListView中所有的子View都是处于detach状态的,所以这里attachViewToParent()方法是正确的选择。

    这一部分的分析是参考 郭神的博客分析的,这是郭神原创

    ListView的onDraw过程和其他View类似。

    下一篇将会分析 ListView 剩余的 5,6,7部分

    展开全文
  • Android从View衍生出一个子类:ListView,来协助App开发者,让其轻易地开发出UI画面上的ListBox来。然而,仅仅一个ListView子类,并无法实现一个漂亮的ListBox画面。于是,规划一个小框架来达成这个目标,对App...
  •  ListView在Android众多控件中占有比较重要的地位,也是面试官热爱提问的控件之一,特别是关于它的性能优化。这一块我想着把他留到最后再说,我们先来谈谈ListView的简单应用,毕竟什么东西都是由浅入深的嘛。 ...

        犹豫了几天,觉得还是把这个教程写一下吧。虽然在网上已经一大堆了,但是这是我学习的历程我觉得我还是该记录下来,以后也可以温故而知新。

        ListView在Android众多控件中占有比较重要的地位,也是面试官热爱提问的控件之一,特别是关于它的性能优化。这一块我想着把它留到最后再说,我们先来谈谈ListView的简单应用,毕竟什么东西都是由浅入深的嘛。

        首先我们要先创建一个项目,打开Android studio点击File—New—New Project创建一个名为ListViewTest的项目。接着找到res—layout文件夹下的activity_main.xml,打开它并且在里面添加ListView控件如下:

        <ListView
            android:id="@+id/listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </ListView>

    此时你如果运行项目你会发现里面什么都没有,和刚开始创建的这个项目时没多大区别,因为我们还没有往里面添加数据并且在View中实现它。所以我们回到MainActivity这个类里面通过findViewById()这个方法找到这个控件并且实现。我们先定义一个名为data的一维字符串数组,用来存放我们的假数据。然后通过新建一个ArrayAdapter并根据要求配置它,再Adapte通过setAdapter给ListView,代码如下:

      private String data[] = {"aa","bb","cc","dd","aa","bb","cc","dd","aa","bb","cc","dd","aa","bb","cc","dd"};//假数据
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ListView listView = (ListView) findViewById(R.id.listview);//在视图中找到ListView
            ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data);//新建并配置ArrayAapeter
            listView.setAdapter(adapter);
        }

    点击运行项目你就能看到一个简单的ListView:


    现在看到了界面了但是离我们的预想还是有点差距,我们希望的是除了能看还能点击响应某些事件,因此我们再为它添加一个监听点击的方法。代码如下:

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                    switch (i){
                        case 0:
                            Toast.makeText(MainActivity.this,"你点击了"+i+"按钮",Toast.LENGTH_SHORT).show();
                            break;//当我们点击某一项就能吐司我们点了哪一项
    
                        case 1:
                            Toast.makeText(MainActivity.this,"你点击了"+i+"按钮",Toast.LENGTH_SHORT).show();
                            break;
    
                        case 2:
                            Toast.makeText(MainActivity.this,"你点击了"+i+"按钮",Toast.LENGTH_SHORT).show();
                            break;
    
                        case 3:
                            Toast.makeText(MainActivity.this,"你点击了"+i+"按钮",Toast.LENGTH_SHORT).show();
                            break;
    
                        case 4:
                            Toast.makeText(MainActivity.this,"你点击了"+i+"按钮",Toast.LENGTH_SHORT).show();
                            break;
                    }
                }
            });

    这里我就给了5项Item做了响应,当然也可以让每一项都有响应的,有兴趣自己可以去尝试。这样一个非常简单的ListView就完成了,接下来我们来深入一点点。

         现在我们要定制一个有图片有文字有选择框的ListView,怎么做呢?第一个我们肯定要把数据改一下,但是我们肯定不可能说把数组data改成二维数据就可以的,因为图片不是字符串的形式啊。那要包含字符串又能包含图片的数据格式有什么呢?这时Bean类就出现了,我们可以把这些数据封装到一个Bean类里面啊,当我们需要的时候就直接拿出来就好。说做就做然后我们定义一个myBean类,代码如下:

    public class myBean {
        private String text;//用来放文字的
        private int ImageID;//用来放图片的
    
    
        public myBean(String text,int imageID){
            this.ImageID = imageID;
            this.text = text;
    
        }
    
        public String getText() {
            return text;
        }
    
        public void setText(String text) {
            this.text = text;
        }
    
        public int getImageID() {
            return ImageID;
        }
    
        public void setImageID(int imageID) {
            ImageID = imageID;
        }
    
    
    }

            然后我们就可以通过初始化不断的New一个一个的数据了,但是我们怎么放进ListView里面呢?因为我们刚才用的是系统的ArrayAdapter来适配到ListView的,我们甚至连要适配的XML的界面都没。那我们先去做个我们要适配的界面去看看,于是:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <LinearLayout
            android:id="@+id/ll_view"
            android:gravity="center"
            android:layout_margin="10dp"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        <ImageView
            android:background="@mipmap/ic_launcher"
            android:id="@+id/headimage"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView
           android:layout_marginLeft="20dp"
            android:layout_weight="1"
            android:text="你是SB"
            android:id="@+id/headtext"
            android:layout_width="0dp"
            android:layout_height="wrap_content" />
            <RadioGroup
                android:id="@+id/radioBtn"
                android:orientation="horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
        <RadioButton
            android:text="打他"
            android:id="@+id/radio2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <RadioButton
            android:text="不打"
            android:id="@+id/radio1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
            </RadioGroup>
        </LinearLayout>
    </LinearLayout>

        于是我们把之前的R.layout.simple_list_item_1这XML换成我们直接做的,运行程序你就会发现程序崩了。哈哈,不要紧这是正常的因为我们传入的数据都没用适配到我们的界面上。所以我们就只能自己写过一个适配器来适配我们自己的数据。

    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data);

        适配器代码如下:

    public class myAdapter extends ArrayAdapter {
    
        private final int ImageId;
        private String radiotext;
        public myAdapter(Context context, int headImage, List<myBean> obj){
            super(context,headImage,obj);
            ImageId = headImage;//这个是传入我们自己定义的界面
    
        }
    
        @NonNull
        @Override
        public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
            myBean myBean = (myBean) getItem(position);
            View view = LayoutInflater.from(getContext()).inflate(ImageId,parent,null);//这个是实例化一个我们自己写的界面Item
            LinearLayout linearLayout = view.findViewById(R.id.ll_view);
            ImageView headImage = view.findViewById(R.id.headimage);
            TextView headText = view.findViewById(R.id.headtext);
            RadioGroup radio = view.findViewById(R.id.radioBtn);
            headImage.setImageResource(myBean.getImageID());
            headText.setText(myBean.getText());
            radio.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {//检查Radio Button那个被点击了
                @Override
                public void onCheckedChanged(RadioGroup radioGroup, @IdRes int i) {
                    switch (i){
                        case R.id.radio1:
                            radiotext = "不打";
                            break;
                        case R.id.radio2:
                            radiotext = "打他";
                            break;
                    }
                }
            });
            linearLayout.setOnClickListener(new View.OnClickListener() {//检查哪一项被点击了
                @Override
                public void onClick(View view) {
                    Toast.makeText(getContext(),"你点击了第"+position+"项"+"你选择"+radiotext,Toast.LENGTH_SHORT).show();
                }
            });
            return view;
        }
    }

            现在适配器也写好了,你看定制ListView的2个步骤是不是就这样就被我们解决了,然后我们就差适配了。接下来我们来做一下适配:

    public class MainActivity extends AppCompatActivity {
    
        private List<myBean> myBeanList = new ArrayList<>();//用来存放数据的数组
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ListView listView = (ListView) findViewById(R.id.listview);
            init();
            myAdapter adapter = new myAdapter(MainActivity.this,R.layout.myitem,myBeanList);
          
            listView.setAdapter(adapter);
        }
        private void init(){//初始化数据
            myBean bean1 = new myBean("aa",R.mipmap.ic_launcher);
            myBeanList.add(bean1);
    
            myBean bean2 = new myBean("ss",R.mipmap.ic_launcher);
            myBeanList.add(bean2);
    
            myBean bean3 = new myBean("jj",R.mipmap.ic_launcher);
            myBeanList.add(bean3);
    
            myBean bean4 = new myBean("hh",R.mipmap.ic_launcher);
            myBeanList.add(bean4);
    
            myBean bean5 = new myBean("dd",R.mipmap.ic_launcher);
            myBeanList.add(bean5);
    
            myBean bean6 = new myBean("cc",R.mipmap.ic_launcher);
            myBeanList.add(bean6);
    
            myBean bean7 = new myBean("bb",R.mipmap.ic_launcher);
            myBeanList.add(bean7);
            myBean bean8 = new myBean("jj",R.mipmap.ic_launcher);
            myBeanList.add(bean8);
            myBean bean9 = new myBean("kk",R.mipmap.ic_launcher);
            myBeanList.add(bean9);
        }

            做到这里我想大家都几乎初步掌握了怎么定制ListView了吧?哦对了!我在写适配器的时候顺便把监听事件写进去了,当然在主类写也是可以的,但是不建议这样做。至于为什么?你自己试试就知道了,因为纸上得来终觉浅嘛。

            最后上一张效果图吧:


        发现有好多BUG呢!不过那都不是事,毕竟没有哪个APP是没有BUG的是不是?各位如果有解决办法欢迎留言我哈,一起探讨探讨哲学,哦不,是BUG。

    原创链接:https://blog.csdn.net/weixin_40600325/article/details/80660780

    GItHub:https://github.com/muzhilei/ListViewDemo

    展开全文
  • ListView

    2019-03-19 13:22:38
    列表视图以垂直列表的方式列出需要显示的列表项。 android:dividerHeight="30dp" 间隔高度 android:divider="@color/colorPrimary" 间距背景色 android:fadingEdge="vertical" 设置上下方阴影,none为无 ...

    列表视图以垂直列表的方式列出需要显示的列表项。

    android:dividerHeight="30dp"  间隔高度
    android:divider="@color/colorPrimary"  间距背景色
    android:fadingEdge="vertical"  设置上下方阴影,none为无
    android:scrollbars="horizontal"  值为horizontallvertical时显示滚动条
    android:drawSelectorOnTop="false"  点击某条记录,颜色成为背景色
    android:fastScrollEnabled="true"  快速滚动效果
    android:listSelector="@color/colorPrimary"  选中时的颜色
    android:entries="@array/city"  设置列表填充的内容

    基础的使用

    <ListView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true"
            android:layout_marginStart="0dp"
            android:layout_marginTop="0dp"
            android:id="@+id/listview"
            android:dividerHeight="30dp"
            android:divider="@color/colorBlue"
            android:fadingEdge="vertical"
            android:fastScrollEnabled="true"
            android:listSelector="@color/colorPurple"
            android:entries="@array/name"/>
    
    //arrays.xml
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <array name="name">
            <item>贝贝</item>
            <item>晶晶</item>
            <item>欢欢</item>
            <item>迎迎</item>
            <item>妮妮</item>
        </array>
    </resources>
    public class MainActivity_Listview extends AppCompatActivity {
    
        private ListView listView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main__listview);
            listView = (ListView)findViewById(R.id.listview);
            listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    TextView tv=(TextView)view;
                    Toast.makeText(MainActivity_Listview.this,tv.getText(),Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

    ListActivity类的使用:

    //不需要设置布局
    public class MainActivity_listact extends ListActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ArrayAdapter arrayAdapter = ArrayAdapter.createFromResource(
                    this,R.array.name,android.R.layout.simple_list_item_1);
            setListAdapter(arrayAdapter);
        }
    
        @Override
        protected void onListItemClick(ListView l, View v,int position,long id){
            Toast.makeText(this,((TextView)v).getText(),Toast.LENGTH_SHORT).show();
        }
    }
    

    单选和多选的使用

    public class MainActivity_lvch extends AppCompatActivity {
    
        private ListView listView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main_lvch);
            listView = (ListView)findViewById(R.id.listview);
    
            String[] arr = getResources().getStringArray(R.array.name);
    
            //单选模式
            ArrayAdapter<String> aa1 = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_single_choice,arr);
            listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
    
            //多选模式
            //ArrayAdapter<String> aa2 = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_multiple_choice,arr);
            //listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    
            listView.setAdapter(aa1);
        }
    }

    ListView实现图文列表

    //list_item_sa.xml,作为每一项的布局使用,两个方式都需要
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:padding="16dp">
    
        <ImageView
            android:id="@+id/imageView_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@android:drawable/btn_star_big_on" />
    
        <TextView
            android:id="@+id/textView_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="TextView"
            android:layout_marginLeft="10dp"/>
    </LinearLayout>

    1)SimpleAdapter的使用

    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.widget.ListView;
    import android.widget.SimpleAdapter;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class MainActivity_sa extends AppCompatActivity {
    
        private ListView listView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main_sa);
            listView = (ListView)findViewById(R.id.listview);
    
            //准备数据,每一个HashMap是一条记录
            HashMap<String,Object> title1 = new HashMap<>();
            title1.put("title","title-1");
            title1.put("icon",android.R.drawable.star_big_off);
            HashMap<String,Object> title2 = new HashMap<>();
            title2.put("title","title-2");
            title2.put("icon",android.R.drawable.star_on);
            HashMap<String,Object> title3 = new HashMap<>();
            title3.put("title","title-3");
            title3.put("icon",android.R.drawable.star_big_on);
    
            ArrayList<Map<String,Object>> list = new ArrayList<>();
            list.add(title1);
            list.add(title2);
            list.add(title3);
    
            //把数据填充到Adapter
            SimpleAdapter sa = new SimpleAdapter(
                    this,list,R.layout.list_item_sa,new String[]{"title","icon"},new int[]{R.id.textView_title,R.id.imageView_icon});
            listView.setAdapter(sa);
        }
    }
    

    2)自定义Adapter

    import android.content.Context;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.BaseAdapter;
    import android.widget.ImageView;
    import android.widget.ListView;
    import android.widget.TextView;
    import android.widget.Toast;
    
    public class MainActivity_uda extends AppCompatActivity {
    
        private ListView listView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main_uda);
            listView = (ListView)findViewById(R.id.listview);
            listView.setAdapter(new MyAdapter(this));
        }
    
        static class MyAdapter extends BaseAdapter{
            private String[] titles = {"title-1","title-2","title-3","title-4","title-5"};
            private int[] icons = {android.R.drawable.btn_star,android.R.drawable.star_big_on,
                    android.R.drawable.btn_star_big_on,android.R.drawable.star_off,android.R.drawable.star_on};
    
            private Context context;
            public MyAdapter(Context context){
                this.context = context;
            }
            @Override
            public int getCount() {
                return titles.length;
            }
    
            @Override
            public Object getItem(int position) {
                return titles[position];
            }
    
            @Override
            public long getItemId(int position) {
                return position;
            }
    
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
    
                LayoutInflater inflater = LayoutInflater.from(context);
                View view = inflater.inflate(R.layout.list_item_uda,null);
    
                TextView tv_title = (TextView) view.findViewById(R.id.textView_title);
                ImageView iv_icon = (ImageView) view.findViewById(R.id.imageView_icon);
    
                tv_title.setText(titles[position]);
                iv_icon.setImageResource(icons[position]);
    
                return view;
            }
        }
    }
    

    优化:

    1.使用convertView减少对象组件

    if(convertView==null) {
                    LayoutInflater inflater = LayoutInflater.from(context);
                    //实例化一个布局文件
                    convertView = inflater.inflate(R.layout.list_item_uda,null);
                }
                System.out.println(position+"----"+convertView);
                TextView tv_title = (TextView) convertView.findViewById(R.id.textView_title);
                ImageView iv_icon = (ImageView) convertView.findViewById(R.id.imageView_icon);
    
                tv_title.setText(titles[position]);
                iv_icon.setImageResource(icons[position]);
    
                return convertView;

    2.View Holder优化

    static class MyAdapter extends BaseAdapter{
            private String[] titles = {"title-1","title-2","title-3","title-4","title-5",
                    "title-6","title-7","title-8","title-9","title-10",
                    "title-11","title-12","title-13"};
            private int[] icons = {android.R.drawable.btn_star,android.R.drawable.star_big_on,
                    android.R.drawable.btn_star_big_on,android.R.drawable.star_off,
                    android.R.drawable.star_on,android.R.drawable.btn_star,
                    android.R.drawable.star_big_on, android.R.drawable.btn_star_big_on,
                    android.R.drawable.star_off, android.R.drawable.star_on,
                    android.R.drawable.btn_star,android.R.drawable.star_big_on,
                    android.R.drawable.btn_star_big_on};
    
            private Context context;
            public MyAdapter(Context context){
                this.context = context;
            }
            @Override
            public int getCount() {
                return titles.length;
            }
    
            @Override
            public Object getItem(int position) {
                return titles[position];
            }
    
            @Override
            public long getItemId(int position) {
                return position;
            }
    
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                ViewHolder vh;
                if(convertView==null) {
                    LayoutInflater inflater = LayoutInflater.from(context);
                    //实例化一个布局文件
                    convertView = inflater.inflate(R.layout.list_item_uda,null);
                    vh = new ViewHolder();
                    vh.iv_icon = (ImageView)convertView.findViewById(R.id.imageView_icon);
                    TextView tv_title = (TextView)convertView.findViewById(R.id.textView_title);
                    convertView.setTag(vh);
                }else {
                    vh = (ViewHolder) convertView.getTag();
                }
                
                vh.tv_title.setText(titles[position]);
                vh.iv_icon.setImageResource(icons[position]);
    
                return convertView;
            }
    
            //用于保存第一次查找的组件,避免下次重复查找
            static class ViewHolder{
                ImageView iv_icon;
                TextView tv_title;
            }

    listView刷新分页

    1.当前 Activity implements OnScrollListener

    2.实现接口的方法

    3.listView注册滚动监听

    4.Adapter中添加增加数据的函数

    5.获得2页以后的数据后,adapter增加数据,并刷新notifyDateSetChanged();

    <!--activity_main_fy.xml-->
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity_fy">
    
        <ListView
            android:id="@+id/listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_alignParentTop="true"
            android:layout_alignParentStart="true"/>
    </RelativeLayout>
    
    <!--list_item.fy.xml-->
    <?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="wrap_content"
        android:gravity="center_vertical"
        android:padding="16px">
    
        <TextView
            android:id="@+id/textView_title"
            android:layout_width="64dp"
            android:layout_height="60dp"
            android:layout_marginLeft="10dp"
            android:text="title" />
    
        <TextView
            android:id="@+id/textView_content"
            android:layout_width="100dp"
            android:layout_height="60dp"
            android:layout_marginLeft="10dp"
            android:text="content" />
    </LinearLayout>
    
    <!--loading.xml-->
    <?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="wrap_content"
        android:orientation="horizontal"
        android:gravity="center">
    
        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="正在玩命加载数据..." />
    </LinearLayout>
    
    
    //MainActivity_fy.java
    import android.os.Handler;
    import android.os.Message;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.AbsListView;
    import android.widget.BaseAdapter;
    import android.widget.ListView;
    import android.widget.TextView;
    
    import java.util.Vector;
    
    public class MainActivity_fy extends AppCompatActivity implements AbsListView.OnScrollListener {
        private ListView listView;
        private Vector<News> news = new Vector<>();
        private MyAdapter myAdapter;
        private static final int DATA_UPDATE = 0x1;//数据更新完成后的标记
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main_fy);
            listView = (ListView) findViewById(R.id.listview);
            listView.setOnScrollListener(this);
            View footerView = getLayoutInflater().inflate(R.layout.loading,null);
            listView.addFooterView(footerView);
            initDate();
            myAdapter = new MyAdapter();
            listView.setAdapter(myAdapter);
        }
    
        private int index = 1;
        /***
         * 初始化数据
         */
        private void initDate(){
            for (int i=0;i<10;i++){
                News n = new News();
                n.title = "title--"+index;
                n.content = "content--"+index;
                index++;
                news.add(n);
            }
        }
    
        private int visibleLeastIndex;//用来可显示的最后一条数据d的索引
        //状态改变,三个状态,fling,idl,touch_scroll
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if(myAdapter.getCount() == visibleLeastIndex && scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE){
                new LoadDataThread().start();
            }
        }
    
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            visibleLeastIndex = firstVisibleItem+visibleItemCount-1;
        }
    
        //线程之间通讯的机制
        private Handler handler = new Handler() {
            //处理消息
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case DATA_UPDATE:
                        myAdapter.notifyDataSetChanged();
                        break;
                }
            }
        };
    
        //模拟加载数据的线程
        class LoadDataThread extends Thread{
            @Override
            public void run(){
                initDate();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    //            myAdapter.notifyDataSetChanged();
                //通过handler给主线程发送一个消息标记
                handler.sendEmptyMessage(DATA_UPDATE);
            }
        }
    
        class MyAdapter extends BaseAdapter{
    
            @Override
            public int getCount() {
                return news.size();
            }
    
            @Override
            public Object getItem(int position) {
                return news.get(position);
            }
    
            @Override
            public long getItemId(int position) {
                return position;
            }
    
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                ViewHolder vh;
                if(convertView==null){
                    convertView = getLayoutInflater().inflate(R.layout.list_item_fy,null);
                    vh = new ViewHolder();
                    vh.tv_title = (TextView) convertView.findViewById(R.id.textView_title);
                    vh.tv_content = (TextView) convertView.findViewById(R.id.textView_content);
                    convertView.setTag(vh);
                }else {
                    vh = (ViewHolder)convertView.getTag();
                }
                News n = news.get(position);
                vh.tv_title.setText(n.title);
                vh.tv_content.setText(n.content);
                return convertView;
            }
    
            class ViewHolder{
                TextView tv_title;
                TextView tv_content;
            }
        }
    }
    
    //news.java
    public class News {
        String title;
        String content;
    }
    
    
    

     

    展开全文
  • 安卓ListView总结(一)

    2018-05-04 13:39:35
    安卓ListView总结(一) 安卓ListView性能优化(二) 安卓ListView下拉刷新和加载(三)我们经常会在应用程序中使用列表的形式来展现一些内容,所以学好ListView是非常必需的。ListView也是Android中比较难以使用的控件...

        安卓ListView总结(一)

        安卓ListView性能优化(二)

        安卓ListView下拉刷新和加载(三)

    我们经常会在应用程序中使用列表的形式来展现一些内容,所以学好ListView是非常必需的。ListView也是Android中比较难以使用的控件,这节内容就将详细解读ListView的用法。

    一个ListView通常有两个职责。

    1)将数据填充到布局。

    2)处理用户的选择点击等操作。

    第一点很好理解,ListView就是实现这个功能的。第二点也不难做到,在后面的学习中读者会发现,这非常简单。

    一个ListView的创建需要3个元素。

    1ListView中的每一列的View

    2)填入View的数据或者图片等。

    3)连接数据与ListView的适配器。

    也就是说,要使用ListView,首先要了解什么是适配器。适配器是一个连接数据和AdapterViewListView就是一个典型的AdapterView,后面还会学习其他的)的桥梁,通过它能有效地实现数据与AdapterView的分离设置,使AdapterView与数据的绑定更加简便,修改更加方便

    Android中提供了很多的Adapter,表4-5列出了常用的几个。

    4-5常用适配器

    Adapter

    ArrayAdapter<T>

    用来绑定一个数组,支持泛型操作

    SimpleAdapter

    用来绑定在xml中定义的控件对应的数据

    SimpleCursorAdapter

    用来绑定游标得到的数据

    BaseAdapter

    通用的基础适配器

     

     其实适配器还有很多,要注意的是,各种Adapter只不过是转换的方式和能力不一样而已。下面就通过使用不同的Adapter来为ListView绑定数据(SimpleCursorAdapter暂且不讲,后面讲SQLite时会介绍)。

    4.12.1 ListView使用ArrayAdapter

    ArrayAdapter可以实现简单的ListView的数据绑定。默认情况下,ArrayAdapter绑定每个对象的toString值到layout中预先定义的TextView控件上。ArrayAdapter的使用非常简单。

    实例:

    工程目录:EX_04_12

    在布局文件中加入一个ListView控件。

    <?xmlversion="1.0"encoding="utf-8"?>
    <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width
    ="fill_parent"
    android:layout_height
    ="fill_parent"
    >
    <!-- 添加一个ListView控件 -->
    <ListView
        android:id="@+id/lv"
    android:layout_width
    ="fill_parent"
    android:layout_height
    ="fill_parent"
    />
    </LinearLayout>

    然后在Activity中初始化。

    publicclass MyListView extends Activity {

    privatestaticfinal String[] strs = new String[] {
    "first", "second", "third", "fourth", "fifth"
    };//定义一个String数组用来显示ListView的内容
    private ListView lv;

    /** Called when the activity is first created. */
    @Override
    publicvoid onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    lv = (ListView) findViewById(R.id.lv);//得到ListView对象的引用
    /*
    为ListView设置Adapter来绑定数据*/
    lv.setAdapter(new ArrayAdapter<String>(this,
    android.R.layout.simple_list_item_1, strs));

    }
    }

                                              

                                    

                                            图4-29 ListView使用ArrayAdapter运行效果

    代码非常的简单,运行效果如图4-29所示。

     

    分析一下使用的步骤。

    1)定义一个数组来存放ListViewitem的内容。

    2)通过实现ArrayAdapter的构造函数来创建一个ArrayAdapter的对象。

    3)通过ListViewsetAdapter()方法绑定ArrayAdapter

    其中第二步有必要说一下的是,ArrayAdapter有多个构造函数,例子中实现的是最常用的一种。第一个参数为上下文,第二个参数为一个包含TextView,用来填充ListView的每一行的布局资源ID。第三个参数为ListView的内容。其中第二个参数可以自定义一个layout,但是这个layout必须要有TextView控件。通常我们使用Android提供的资源,除了例子中所用的,常用的还有如下几种,可实现带RadioButtonCheckBoxListView

    1)通过指定android.R.layout.simple_list_item_checked这个资源,实现带选择框的ListView。需要用setChoiceMode()方法设定选择为多选还是单选,否则将不能实现选择效果,运行效果如图4-30所示。

    实现代码如下:

    lv.setAdapter(new ArrayAdapter<String>(this,
    android.R.layout.simple_list_item_checked, strs));
    lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

     (2)通过指定android.R.layout.simple_list_item_multiple_choice这个资源实现带CheckBoxListView。同样的,需要用setChoiceMode()方法来设置单选或者多选,运行效果如图4-31所示。

    实现代码如下:

    lv.setAdapter(new ArrayAdapter<String>(this,
    android.R.layout.simple_list_item_multiple_choice, strs));
    lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

    3)通过指定android.R.layout.simple_list_item_single_choice这个资源实现带RadioButtonListView。这里要注意的是,这里并不是指定了单选。是多选还是单选要通过setChoiceMode()方法来指定,运行效果如图4-32所示。

    实现代码如下:

     

    lv.setAdapter(newArrayAdapter<String>(this,

    android.R.layout.simple_list_item_single_choice,strs));

    lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

     

              

         图4-30 带选择框的ListView                                           4-31 CheckBoxListView   

        4-32 RadioButtonListView

     

     在前面讲到过,ListView的职责除了填充数据外,还要处理用户的操作。通过如下的代码就可以为ListView绑定一个点击监听器,点击后在标题栏显示点击的行数。


    lv.setOnItemClickListener(new OnItemClickListener() {

    @Override
    publicvoid onItemClick(AdapterView<?> arg0, View arg1, int arg2,long arg3) {
                        //点击后在标题上显示点击了第几行
    setTitle("你点击了第"+arg2+"行");
    }
    });

     

    4.12.2 ListView使用SimpleAdapter

    很多时候需要在列表中展示一些除了文字以外的东西,比如图片等。这时候可以使用SimpleAdapterSimpleAdapter的使用也非常简单,同时它的功能也非常强大。可以通过它自定义ListView中的item的内容,比如图片、多选框等。看一个例子,实现一个每一行都有一个ImageViewTextViewListView。先看一下运行效果,如图4-34所示。

                                                    

                                                     

                                                        4-34 带图标的ListView

     

    首先在布局文件中增加一个ListView控件。

     还需要定义一个ListView中每一行的布局,用RelativeLayout来实现一个带两行字和一个图片的布局。

    item.xml:

    <?xmlversion="1.0"encoding="utf-8"?>
    <RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height
    ="fill_parent"
    android:layout_width
    ="fill_parent">
    <ImageView
    android:layout_alignParentRight="true"
    android:layout_width
    ="wrap_content"
    android:layout_height
    ="wrap_content"
    android:id
    ="@+id/ItemImage"
    />
    <TextView
    android:id="@+id/ItemTitle"
    android:layout_height
    ="wrap_content"
    android:layout_width
    ="fill_parent"
    android:textSize
    ="20sp"
    />
    <TextView
    android:id="@+id/ItemText"
    android:layout_height
    ="wrap_content"
    android:layout_width
    ="fill_parent"
    android:layout_below
    ="@+id/ItemTitle"
    />
    </RelativeLayout>

     配置完毕,就可以在Java代码中为ListView绑定数据。

    publicclass MyListViewSimple extends Activity {

    private ListView lv;

    /** Called when the activity is first created. */
    @Override
    publicvoid onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

            lv = (ListView) findViewById(R.id.lv);
    /*定义一个动态数组*/
    ArrayList<HashMap<String, Object>> listItem = new ArrayList<HashMap<String, Object>>();
    /*在数组中存放数据*/
    for(int i=0;i<10;i++)
    {
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("ItemImage", R.drawable.icon);//加入图片
    map.put("ItemTitle", "第"+i+"行");
    map.put("ItemText", "这是第"+i+"行");
    listItem.add(map);
    }

    SimpleAdapter mSimpleAdapter = new SimpleAdapter(this,listItem,//需要绑定的数据
    R.layout.item,//每一行的布局
    //动态数组中的数据源的键对应到定义布局的View中
    new String[] {"ItemImage","ItemTitle", "ItemText"},
    newint[] {R.id.ItemImage,R.id.ItemTitle,R.id.ItemText});

    lv.setAdapter(mSimpleAdapter);//为ListView绑定适配器

    lv.setOnItemClickListener(new OnItemClickListener() {
    @Override
    publicvoid onItemClick(AdapterView<?> arg0, View arg1, int arg2,long arg3) {
    setTitle("你点击了第"+arg2+"行");//设置标题栏显示点击的行
    }
    });
    }
    }

     使用simpleAdapter的数据一般都是用HashMap构成的列表,列表的每一节对应ListView的每一行。通过SimpleAdapter的构造函数,将HashMap的每个键的数据映射到布局文件中对应控件上。这个布局文件一般根据自己的需要来自己定义。梳理一下使用SimpleAdapter的步骤。

    1)根据需要定义ListView每行所实现的布局。

    2)定义一个HashMap构成的列表,将数据以键值对的方式存放在里面。

    3)构造SimpleAdapter对象。

    4)将LsitView绑定到SimpleAdapter上。

    4.12.3 ListView使用BaseAdapterListView的优化

    ListView的使用中,有时候还需要在里面加入按钮等控件,实现单独的操作。也就是说,这个ListView不再只是展示数据,也不仅仅是这一行要来处理用户的操作,而是里面的控件要获得用户的焦点。读者可以试试用SimpleAdapter添加一个按钮到ListView的条目中,会发现可以添加,但是却无法获得焦点,点击操作被ListViewItem所覆盖。这时候最方便的方法就是使用灵活的适配器BaseAdapter了。

     

                                                                           

     

    4-35 BaseAdapter中的方法

    使用BaseAdapter必须写一个类继承它,同时BaseAdapter是一个抽象类,继承它必须实现它的方法。BaseAdapter的灵活性就在于它要重写很多方法,看一下有哪些方法,如图4-35所示为继承自BaseAdapterSpeechListAdapter所实现的方法,其中最重要的即为getView()方法。这些方法都有什么作用呢?我们通过分析ListView的原理来为读者解答。

     

    当系统开始绘制ListView的时候,首先调用getCount()方法。得到它的返回值,即ListView的长度。然后系统调用getView()方法,根据这个长度逐一绘制ListView的每一行。也就是说,如果让getCount()返回1,那么只显示一行。而getItem()getItemId()则在需要处理和取得Adapter中的数据时调用。那么getView如何使用呢?如果有10000行数据,就绘制10000次?这肯定会极大的消耗资源,导致ListView滑动非常的慢,那应该怎么做呢?通过一个例子来讲解如何在使用BaseAdapter的时候优化ListView的显示。例子中将上一节中的ImageView换成Button,并且处理Button的点击事件,其中对ListView的显示做了优化。

     

    布局文件和上一例类同,读者可以在光盘的工程目录中查看,这里只给出Activity类。

    publicclass MyListViewBase extends Activity {

    private ListView lv;
    /*定义一个动态数组*/
    ArrayList<HashMap<String, Object>>listItem;


    /** Called when the activity is first created. */
    @Override
    publicvoid onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    lv = (ListView) findViewById(R.id.lv);
    MyAdapter mAdapter = new MyAdapter(this);//得到一个MyAdapter对象
    lv.setAdapter(mAdapter);//为ListView绑定Adapter
    /*
    为ListView添加点击事件*/
    lv.setOnItemClickListener(new OnItemClickListener() {

    @Override
    publicvoid onItemClick(AdapterView<?> arg0, View arg1, int arg2,long arg3) {
    Log.v("MyListViewBase", "你点击了ListView条目" + arg2);//在LogCat中输出信息
    }
    });

    }
    /*添加一个得到数据的方法,方便使用*/
    private ArrayList<HashMap<String, Object>> getDate(){

    ArrayList<HashMap<String,Object>> listItem = new ArrayList<HashMap<String,Object>>();
    /*为动态数组添加数据*/
    for(int i=0;i<30;i++)
    {
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("ItemTitle", "第"+i+"行");
    map.put("ItemText", "这是第"+i+"行");
    listItem.add(map);
    }
    return listItem;

    }
    /*
    * 新建一个类继承BaseAdapter,实现视图与数据的绑定
    */
    privateclass MyAdapter extends BaseAdapter {

            private LayoutInflater mInflater;//得到一个LayoutInfalter对象用来导入布局

    /*构造函数*/
    public MyAdapter(Context context) {
    this.mInflater = LayoutInflater.from(context);
    }

    @Override
    publicint getCount() {

    return getDate().size();//返回数组的长度
    }

    @Override
    public Object getItem(int position) {
    return null;
    }

    @Override
    publiclong getItemId(int position) {
    return 0;
    }
    /*书中详细解释该方法*/
    @Override
    public View getView(finalint position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    //观察convertView随ListView滚动情况
    Log.v("MyListViewBase", "getView " + position + " " + convertView);
    if (convertView == null) {
    convertView = mInflater.inflate(R.layout.item,null);
    holder = new ViewHolder();
    /*得到各个控件的对象*/
    holder.title = (TextView) convertView.findViewById(R.id.ItemTitle);
    holder.text = (TextView) convertView.findViewById(R.id.ItemText);
    holder.bt = (Button) convertView.findViewById(R.id.ItemButton);
    convertView.setTag(holder);//绑定ViewHolder对象
    }else{
    holder = (ViewHolder)convertView.getTag();//取出ViewHolder对象
    }
                /*设置TextView显示的内容,即我们存放在动态数组中的数据*/
    holder.title.setText(getDate().get(position).get("ItemTitle").toString());
    holder.text.setText(getDate().get(position).get("ItemText").toString());

                /*为Button添加点击事件*/
    holder.bt.setOnClickListener(new OnClickListener() {

    @Override
    publicvoid onClick(View v) {
    Log.v("MyListViewBase", "你点击了按钮" + position);//打印Button的点击信息
    }
    });

            return convertView;
            }

    }
        /*存放控件*/
        publicfinalclass ViewHolder{
        public TextView title;
        public TextView text;
        public Button bt;
    }
    }

     运行效果如图4-36所示。还需要注意的是,Button会抢夺ListView的焦点,需要将Button设置为没有焦点。设置非常简单,只需要在xmlButton标签下加入一行:android:focusable=“false”代码就可以了。在LogCat观察点击后输出的信息,如图4-37所示。

                                                      

                                图4-36 使用BaseAdapterListView

                            

                                 4-37 点击ListView条目和Button得到的输出

    代码中getView()方法不容易理解。其实完全可以不用所谓的convertViewViewHolder,直接导入布局并且设置控件显示的内容就可以了。但是这意味着有多少行数据就需要绘制多少行ListView,这显然是不可取的。这里采用了一种优化的方法。代码中,在getView()方法中加入了一行log输出convertView的内容。滚动ListView,输出信息如图4-38所示。

    从图4-38中可以看出,当启动Activity呈现第一屏ListView的时候,convertView为零。当用户向下滚动ListView时,上面的条目变为不可见,下面出现新的条目。这时候convertView不再为空,而是创建了一系列的convertView的值。当又往下滚一屏的时候,发现第11行的容器用来容纳第22行,第12行的容器用来容纳第23行。也就是说convertView相当于一个缓存,开始为0,当有条目变为不可见,它缓存了它的数据,后面再出来的条目只需要更新数据就可以了,这样大大节省了系统资料的开销。

    还可以继续优化。虽然重复利用了已经绘制的view,但是要得到其中的控件,需要在控件的容器中通过findViewById的方法来获得。如果这个容器非常复杂,这显然会增加系统资源的开销。在上面的例子中,引入了Tag的概念。或许不是最好的办法,但是它确实能使ListView变得更流畅。代码中,当convertView为空时,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象。当convertView不为空,重复利用已经创建的view的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById对控件的层层查询,而是快速定位到控件。

                             

    4-38 滚动ListView输出的convertView的值

    总结一下,这节介绍了用BaseAdapter来绑定ListView的数据。因为BaseAdapter非常灵活,使用也相对较其他控件麻烦。同时ListView的优化问题也值得读者去研究,一个流畅的ListView会带来更好的用户体验。

    转载于:https://www.cnblogs.com/sowhat4999/p/4439842.html

    展开全文
  • 达到
  • ListView原来这么好用

    2016-12-07 08:33:46
    App技术 2016-12-06 18:05 闲话中心 本来是要推送RxJava知识的,但是出了点意外,因为我要使用retrofit2和RxJava2.0,本来知识都已经准备的差不多了,最后发现,出意外了,retrofit2中引用的Rxjava的版本不是...
  • 一、ListView的简单用法 2. 训练目标 1) 掌握 ListView 控件的使用 2) 掌握 Adapter 桥梁的作用 二、定制ListView界面 1.训练目标 1) 掌握 ListView 控件的使用 2) 掌握如何自定义 Adapter 的使用
  • C# ListView用法详解

    2013-04-02 12:14:30
    一、ListView类  1、常用的基本属性:  (1)FullRowSelect:设置是否行选择模式。(默认为false) 提示:只有在Details视图该属性才有意义。  (2) GridLines:设置行和列之间是否显示网格线。(默认为false)...
  • 经过前面两篇文章的学习,我们已经对ListView进行了非常深层次的剖析,不仅了解了ListView的源码和它的工作原理,同时也将ListView中常见的一些问题进行了归纳和总结。 那么本篇文章是我们ListView系列三部曲的最后...
  • 本文来自http://blog.csdn.net/hellogv/ ListView是一个经常用到的控件,ListView里面的每个子项Item可以使...先说说ListView的实现:1.准备ListView要显示的数据;2.使用一维或多维动态数组保存数据;2.构建适配器,
  • 我在上一篇文章中Android 带你从源码的角度解析Scroller的滚动...例如侧滑菜单,launcher,ListView的下拉刷新等等效果,我今天实现的是ListView的item的左右滑动删除item的效果,现在很多朋友看到这个效果应该是在Andr
  • 一、ListView类  1、常用的基本属性:  (1)FullRowSelect:设置是否行选择模式。(默认为false) 提示:只有在Details视图该属性才有意义。  (2) GridLines:设置行和列之间是否显示网格线。(默认为false...
  • 还在用ListView

    2016-05-11 23:14:09
    还在用Lisview?RecyclerView都已经出来...ListView我用的挺好的,为什么要换RecyclerView? ListView稳定,熟悉,还知道很多开源库,特别的好用! RecyclerView不能添加头部,ListView能! RecyclerView RecyclerVi
  • 前天在工作中遇到在ListView中的Item需要用ListView来展现处理后的内容,然后就遇到了一个很头疼的问题,作为Item的ListView没法进行滑动,而且显示也不正常,只是显示几个子Item。不能将子Item全部显示,原因是在...
  • Android ListView使用详解

    2012-11-19 10:10:21
    最近在做android上有关采用ListView进行信息显示的开发工作,发现有很多实现的方法,不过思想基本是一致的。网上大部分的测试程序采用的是数组存储要显示的信息,但是为了更好地对信息进行管理,我采用的是用对象来...
  • ListView中每个Item项之间都有分割线,设置android:footerDividersEnabled表示是否显示分割线,此属性默认为true。 1.不显示分割线只要在ListView控件中添加android:footerDividersEnabled="false"即可。 ...
  • 今天主要介绍在工作中遇到的,在ScrollView布局中嵌套Listview显示不正常,和在Listview中嵌套Listview的滑动冲突的问题。 1.ScrollView布局中嵌套Listview显示不正常的解决方案 目前来说,解决这个问题有好几种解决...
1 2 3 4 5 ... 20
收藏数 190,660
精华内容 76,264
关键字:

listview