`
daojin
  • 浏览: 689966 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

安卓高手之路之图形系统(6)ListView继续

 
阅读更多

综述:

本篇首先介绍了ListView的实现细节。然后介绍了Gallery,ListView,ViewPager的效率对比分析。以及效率低下的原因。最后给出了一些解决方案。

 

1.在上一篇讨论了requestLayout的效率问题。对如何避免这个问题也进行了深入探讨。本篇就内存问题进行讨论。一般情况下,安卓的ListView实现方式上,就存在要移动childView位置的需求。

 

如果对childView进行了回收并且回收的childView必须仍然在原来的位置上,那么childView的位置可能要出现在两个位置上。这非常有挑战性,因为安卓的很多东西都是基于一个View一个位置的这样的思想。现在突然出现两个位置。那么也就是说,回收的View不能再呆在原来的位置了。必须被remove掉。remove掉之后呢,其他的View必须挤过去。总之所有的children都得改变位置。

 

          也就是说必须改变布局。但是改变布局是否就一定要reqestLayout,也是个问题。刚才说了不需要。只要调用一下onLayout就行了。onLayout调用的时候,必须知道childView的位置吧。、那还得改变child的位置,那又调用什么呢?

            那么,仅仅改变childView位置的函数是什么呢?这个函数就是offset系列函数。在Gallery那边是offsetChildrenLeftAndRight另外还有个setX和setY。这些都是改变位置的函数。

         ListView的实现时非常高效的。既保证了回收childiew,有能不进行layout。非常高效。

第一。View的回收机制。

第二。View不进行requestLayout

    

2.Gallery的实现方式

 

         Gallery在实现的时候没有采用回收机制。经过测试的Adapter.getView方法参数,View都是null。也就是说不对View进行复用。其实上Gallery中的View是越来越多。而且每一个View都不会进行回收。这跟一个

ScrollView+ViewPager的实现方式是一样的。唯一的区别是Gallery采用了Adapter机制,并且使用了ListView的滚动原理。但是Gallery没有对View进行回收,全部保存了起来。在内存不够用的时候,尽量不要使用这个。下面拿Gallery和ListView的回收机制进行了对比,

先看trackMotionScroll方法:

    Gallery:

    /**
     * Tracks a motion scroll. In reality, this is used to do just about any
     * movement to items (touch scroll, arrow-key scroll, set an item as selected).
     * 
     * @param deltaX Change in X from the previous event.
     */
    void trackMotionScroll(int deltaX) {

        if (getChildCount() == 0) {
            return;
        }
        
        boolean toLeft = deltaX < 0; 
        
        int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
        if (limitedDeltaX != deltaX) {
            // The above call returned a limited amount, so stop any scrolls/flings
            mFlingRunnable.endFling(false);
            onFinishedMovement();
        }
        
        offsetChildrenLeftAndRight(limitedDeltaX);
        
        detachOffScreenChildren(toLeft);
        
        if (toLeft) {
            // If moved left, there will be empty space on the right
            fillToGalleryRight();
        } else {
            // Similarly, empty space on the left
            fillToGalleryLeft();
        }
        
        // Clear unused views
        mRecycler.clear();
        
        setSelectionToCenterChild();

        onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.

        invalidate();
    }

 ListView

  

   /**
     * Track a motion scroll
     *
     * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
     *        began. Positive numbers mean the user's finger is moving down the screen.
     * @param incrementalDeltaY Change in deltaY from the previous event.
     * @return true if we're already at the beginning/end of the list and have nothing to do.
     */
    boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
        final int childCount = getChildCount();
        if (childCount == 0) {
            return true;
        }

        final int firstTop = getChildAt(0).getTop();
        final int lastBottom = getChildAt(childCount - 1).getBottom();

        final Rect listPadding = mListPadding;

        // "effective padding" In this case is the amount of padding that affects
        // how much space should not be filled by items. If we don't clip to padding
        // there is no effective padding.
        int effectivePaddingTop = 0;
        int effectivePaddingBottom = 0;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            effectivePaddingTop = listPadding.top;
            effectivePaddingBottom = listPadding.bottom;
        }

         // FIXME account for grid vertical spacing too?
        final int spaceAbove = effectivePaddingTop - firstTop;
        final int end = getHeight() - effectivePaddingBottom;
        final int spaceBelow = lastBottom - end;

        final int height = getHeight() - mPaddingBottom - mPaddingTop;
        if (deltaY < 0) {
            deltaY = Math.max(-(height - 1), deltaY);
        } else {
            deltaY = Math.min(height - 1, deltaY);
        }

        if (incrementalDeltaY < 0) {
            incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
        } else {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      = Math.min(height - 1, incrementalDeltaY);
        }

        final int firstPosition = mFirstPosition;

        // Update our guesses for where the first and last views are
        if (firstPosition == 0) {
            mFirstPositionDistanceGuess = firstTop - listPadding.top;
        } else {
            mFirstPositionDistanceGuess += incrementalDeltaY;
        }
        if (firstPosition + childCount == mItemCount) {
            mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
        } else {
            mLastPositionDistanceGuess += incrementalDeltaY;
        }

        final boolean cannotScrollDown = (firstPosition == 0 &&
                firstTop >= listPadding.top && incrementalDeltaY >= 0);
        final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
                lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);

        if (cannotScrollDown || cannotScrollUp) {
            return incrementalDeltaY != 0;
        }

        final boolean down = incrementalDeltaY < 0;

        final boolean inTouchMode = isInTouchMode();
        if (inTouchMode) {
            hideSelector();
        }

        final int headerViewsCount = getHeaderViewsCount();
        final int footerViewsStart = mItemCount - getFooterViewsCount();

        int start = 0;
        int count = 0;

        if (down) {
            int top = -incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                top += listPadding.top;
            }
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                if (child.getBottom() >= top) {
                    break;
                } else {
                    count++;
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        mRecycler.addScrapView(child, position);

                        if (ViewDebug.TRACE_RECYCLER) {
                            ViewDebug.trace(child,
                                    ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
                                    firstPosition + i, -1);
                        }
                    }
                }
            }
        } else {
            int bottom = getHeight() - incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                bottom -= listPadding.bottom;
            }
            for (int i = childCount - 1; i >= 0; i--) {
                final View child = getChildAt(i);
                if (child.getTop() <= bottom) {
                    break;
                } else {
                    start = i;
                    count++;
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        mRecycler.addScrapView(child, position);

                        if (ViewDebug.TRACE_RECYCLER) {
                            ViewDebug.trace(child,
                                    ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
                                    firstPosition + i, -1);
                        }
                    }
                }
            }
        }

        mMotionViewNewTop = mMotionViewOriginalTop + deltaY;

        mBlockLayoutRequests = true;

        if (count > 0) {
            detachViewsFromParent(start, count);
        }
        offsetChildrenTopAndBottom(incrementalDeltaY);

        if (down) {
            mFirstPosition += count;
        }

        invalidate();

        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
            fillGap(down);
        }

        if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
            final int childIndex = mSelectedPosition - mFirstPosition;
            if (childIndex >= 0 && childIndex < getChildCount()) {
                positionSelector(mSelectedPosition, getChildAt(childIndex));
            }
        } else if (mSelectorPosition != INVALID_POSITION) {
            final int childIndex = mSelectorPosition - mFirstPosition;
            if (childIndex >= 0 && childIndex < getChildCount()) {
                positionSelector(INVALID_POSITION, getChildAt(childIndex));
            }
        } else {
            mSelectorRect.setEmpty();
        }

        mBlockLayoutRequests = false;

        invokeOnItemScrollListener();
        awakenScrollBars();

        return false;
    }

 

 

 可以看到Gallery处理View的关键代码:

      offsetChildrenLeftAndRight(limitedDeltaX);
        detachOffScreenChildren(toLeft);
       。。。。
        mRecycler.clear();

 

 

 

而ListView中为:

 

  

        if (count > 0) {
            detachViewsFromParent(start, count);
        }
        offsetChildrenTopAndBottom(incrementalDeltaY);

 

 

detachOffScreenChildren和detachViewsFromParent跟ListView功能一样。但是Gallery多出一个 mRecycler.clear()。等待重复利用的mRecycler被回收了,这就造成了再Gallery中无法复用之前的View的情况,由此可见Gallery在内存的使用上存在很大的设计缺陷。

 

再看Gallery与ListView的makeAndAddView方法

 Gallery

    private View makeAndAddView(int position, int offset, int x, boolean fromLeft) {

        View child;
        if (!mDataChanged) {
            child = mRecycler.get(position);
            if (child != null) {
                // Can reuse an existing view
                int childLeft = child.getLeft();
                
                // Remember left and right edges of where views have been placed
                mRightMost = Math.max(mRightMost, childLeft 
                        + child.getMeasuredWidth());
                mLeftMost = Math.min(mLeftMost, childLeft);

                // Position the view
                setUpChild(child, offset, x, fromLeft);

                return child;
            }
        }

        // Nothing found in the recycler -- ask the adapter for a view
        child = mAdapter.getView(position, null, this);

        // Position the view
        setUpChild(child, offset, x, fromLeft);

        return child;
    }

 

 

 ListView:

   

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


        if (!mDataChanged) {
            // Try to use an existing view for this position
            child = mRecycler.getActiveView(position);
            if (child != null) {
                if (ViewDebug.TRACE_RECYCLER) {
                    ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
                            position, getChildCount());
                }

                // Found it -- we're using an existing child
                // This just needs to be positioned
                setupChild(child, position, y, flow, childrenLeft, selected, true);

                return child;
            }
        }

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

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

        return child;
    }
											

由此可见Gallery每次都将不可见的View进行了清理。对比ListView来说,在滚动过程中多了new一个View的开销。那么就存在一个避免此类问题的方法,自己对View建一个recyler从而弥补这个缺点。

另外安卓的ViewPager+ScrollView 采用了传统的整个容器进行滚动算法,而不是调整childView的位置,但是也存在卡顿问题。

ViewPager的用法见下文:

http://blog.csdn.net/wangjinyu501/article/details/816

ViewPager的卡顿见下文:

http://marspring.mobi/viewpager-majorization/

里面google提供了一个接口解决卡顿问题。至少目前看来,只是五十步笑百步。不能根本解决问题。默认当前正中位置前后缓存一个View。改后可以缓存多个View。当缓存的多个View被用完的时候,仍然是卡顿。

 

另外,

有人说用异步加载,异步加载解决的是数据加载问题,跟这个new View造成的卡顿问题是两码事儿。

 

如何解决?

自己做类似ListView的回收机制,对View进行复用,从而根本上解决这个问题。

 

另外,安卓没有采用享元模式。。。。很遗憾。。。也许google大牛们会想到,希望如此。希望google能体谅我们,再提供一个类似View的轻量级显示控件来直接支持享元模式。这在Brew平台是有的。。。。。。。。。。。。。。。。。。。。。。郁闷。

 

 

分享到:
评论

相关推荐

    android的listview嵌套listview,列表嵌套列表 android studio版本

    在Android开发中,ListView是一种常用的视图组件,用于展示大量数据列表。当需求涉及列表中的每个条目本身也需要展示一个子列表时,我们就会遇到ListView的嵌套问题。本教程将详细讲解如何在Android Studio环境下...

    Android之listview属性

    在Android开发中,ListView是一个非常重要的组件,常用于展示大量数据。它允许用户滚动查看列表项,具有良好的可定制性。以下将详细介绍ListView的一些关键属性及其用途: 1. **stackFromBottom属性**: - `...

    Android ListView边框圆角美化

    在Android开发中,ListView是常用的一种控件,用于展示大量数据列表。为了提升用户体验和界面设计的美观性,我们常常需要对ListView进行定制化,包括设置边框和实现圆角效果。本文将深入探讨如何在Android中实现...

    Android listview嵌套listview

    在Android开发中,ListView是一种常用的组件,用于展示可滚动的列表数据。然而,有时我们可能需要在一个ListView的项中再嵌套另一个ListView,这被称为ListView的嵌套。这样的设计可以用于展示复杂的数据结构,比如...

    Android listview自动换行

    在Android开发中,ListView是一种非常常见的控件,用于展示大量数据列表。它的高效性和可滚动性使得它在显示数据集合时十分有用。然而,当ListView的子项(item)宽度超过屏幕宽度时,如何实现自动换行就成了一个...

    android关于listview之列表分组

    android关于listview之列表分组,像Q上一样显示列表项

    android ListView 网格布局

    在Android开发中,ListView是一种非常常见的控件,用于展示大量数据列表。它的网格布局模式使得数据以多列的形式展示,增强了界面的可读性和用户体验。本文将深入探讨如何在Android中实现一个基于ArrayAdapter的网格...

    安卓手机 ListView控件的三种使用方式

    在Android开发中,ListView是应用界面中非常常见的一种控件,它用于展示大量数据的列表形式,具有良好的滚动性能和可扩展性。本教程将详细讲解如何在安卓手机上使用ListView控件,从基础到进阶,通过四个案例来帮助...

    Android-滑动listview标题置顶listview吸顶效果

    本知识点主要探讨如何实现“滑动ListView时标题置顶”和“ListView吸顶效果”,以及如何使安卓系统状态栏透明化。这些功能可以为用户带来更流畅、更具吸引力的交互体验。 首先,滑动ListView时标题置顶的效果通常被...

    android中一个简单的LIstView用法例子

    在Android开发中,ListView是一个非常重要的组件,它用于展示大量数据列表,通常用于实现滚动效果。这个简单的ListView用法例子将帮助初学者理解和掌握如何在实际项目中运用ListView。 首先,我们来了解一下...

    安卓Android源码——listView.rar

    这份"安卓Android源码——listView.rar"压缩包很可能包含了一份关于ListView自定义实现和优化的源代码示例。下面我们将深入探讨ListView在Android中的工作原理、常见用法以及如何进行性能优化。 首先,ListView基于...

    Android中ListView添加头部

    在Android应用开发中,ListView是一种常用的控件,用于展示大量数据列表。它的可滚动特性使得它在各种场景下都非常实用,比如展示联系人、菜单项等。然而,仅仅使用ListView来展示列表数据并不足够,很多时候我们...

    Android之ListView列表视图和界面跳转实现

    在Android开发中,ListView是一种非常常见的控件,用于展示大量数据的列表形式。它具有高度可定制性,能够实现各种自定义布局和交互效果。本教程将深入讲解如何实现一个基本的ListView,并在用户点击列表项时跳转到...

    Android应用源码 ListView下拉刷新 Demo

    在Android开发中,ListView是一种常用的组件,用于展示大量的数据列表。ListView下拉刷新功能是现代移动应用中的一个常见特性,允许用户通过下拉列表来获取最新的数据,如社交媒体的新消息或天气更新。这个"Android...

    VB6 ListView处理海量数据

    在VB6(Visual Basic 6)编程环境中,ListView控件是一种常见的用户界面元素,用于显示信息列表,可以包含多项属性,如图标、子项、复选框等。当需要处理海量数据时,直接将所有数据一次性加载到ListView可能会导致...

    Android之用PopupWindow实现弹出listview形式的菜单

    与`Dialog`不同,`PopupWindow`不占用系统窗口层级,而是直接绘制在当前Activity的窗口之上,因此它可以实现更灵活的布局效果。 在`PopupWindow`中集成`ListView`,我们可以展示一系列可选择的选项,用户点击每个...

    VB6 ListView与ImageList实例

    在VB6(Visual Basic 6)中,ListView控件是一个常用且功能强大的组件,它允许开发者在用户界面上展示列表形式的数据,同时支持多种视图模式,如图标、列表、小图标和详细信息等。ImageList组件则为ListView提供图标...

    android dialog嵌套ListView

    `Dialog`是Android系统提供的一个组件,它以半透明的弹出窗口形式展示在当前活动(Activity)之上,用于提示用户进行一些操作或显示额外信息。创建`Dialog`通常有两种方式:通过继承`Dialog`类或使用`AlertDialog....

    Listview 可自由调整行高 Vb6源码

    API是一组预定义的函数,开发者可以调用这些函数来执行操作系统级别的任务,而VB6的标准库中并不直接提供调整`ListView`行高的功能,因此需要借助API调用来实现。 主要涉及的API函数可能包括以下几个: 1. `...

    Android-ListView中嵌套(ListView)控件兼容问题

    ### Android-ListView中嵌套(ListView)控件兼容问题 #### 背景与问题描述 在Android开发中,有时我们需要在`ListView`中嵌套另一个`ListView`以实现更复杂的用户界面设计。然而,在实际操作过程中可能会遇到一些...

Global site tag (gtag.js) - Google Analytics