ListView中的Horizo​​ntalListView

时间:2013-03-03 00:08:41

标签: android android-listview

我正在尝试将Horizo​​ntalListView(http://www.dev-smart.com/archives/34)添加到ListView。 ListView使用适配器动态附加新的Horizo​​ntalListView行以进行连续垂直滚动。

它几乎可以工作:问题是当滚动Horizo​​ntalListView时,ListView会自动向上或向下滚动并将Horizo​​ntalListView放在视口的顶部或底部边缘,具体取决于它最接近的位置。如何阻止Horizo​​ntalListView行在滚动时捕捉到位?

我使用Horizo​​ntalListView作为我的“VerticalScrollView”的参考:

public class VerticalScrollView extends ListView {

public static interface ScrollListener {
    void onScrollEnd();
}

public VerticalScrollView(Context context) {
    super(context);
    initView();
}

public VerticalScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initView();
}

private synchronized void initView() {
    mTopViewIndex = -1;
    mBottomViewIndex = 0;
    mDisplayOffset = 0;
    mCurrentY = 0;
    mNextY = 0;
    mMaxY = Integer.MAX_VALUE;
    mScroller = new Scroller(getContext());
    setFadingEdgeLength(0);
    setItemsCanFocus(false);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
    case MotionEvent.ACTION_DOWN:
        mXDistance = mYDistance = 0f;
        mLastX = ev.getX();
        mLastY = ev.getY();
        break;
    case MotionEvent.ACTION_MOVE:
        final float curX = ev.getX();
        final float curY = ev.getY();
        mXDistance += Math.abs(curX - mLastX);
        mYDistance += Math.abs(curY - mLastY);
        mLastX = curX;
        mLastY = curY;
        if(mXDistance > mYDistance) {
            return false;
        }
    }
    return super.onInterceptTouchEvent(ev);
}

@Override
public ListAdapter getAdapter() {
    return mAdapter;
}

@Override
public View getSelectedView() {
    //TODO: implement
    return null;
}

@Override
public void setAdapter(ListAdapter adapter) {
    if(mAdapter != null) {
        mAdapter.unregisterDataSetObserver(mDataObserver);
    }
    mAdapter = adapter;
    mAdapter.registerDataSetObserver(mDataObserver);
    reset();
    super.setAdapter(adapter);
}

private synchronized void reset(){
    initView();
    removeAllViewsInLayout();
    requestLayout();
}

@Override
public void setSelection(int position) {
    //TODO: implement
}

private void addAndMeasureChild(final View child, int viewPos) {
    android.view.ViewGroup.LayoutParams params = child.getLayoutParams();
    if(params == null) {
        params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    addViewInLayout(child, viewPos, params, true);
    child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
            MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
}

@Override
protected synchronized void onLayout(
        boolean changed,
        int left,
        int top,
        int right,
        int bottom) {
    super.onLayout(changed, left, top, right, bottom);

    if(mAdapter == null){
        return;
    }

    if(mDataChanged){
        int oldCurrentY = mCurrentY;
        initView();
        removeAllViewsInLayout();
        mNextY = oldCurrentY;
        mDataChanged = false;
    }

    if(mScroller.computeScrollOffset()){
        int scrollY = mScroller.getCurrY();
        mNextY = scrollY;
    }

    if(mNextY <= 0){
        mNextY = 0;
        mScroller.forceFinished(true);
    }
    if(mNextY >= mMaxY) {
        mNextY = mMaxY;
        mScroller.forceFinished(true);
    }

    int dy = mCurrentY - mNextY;

    removeNonVisibleItems(dy);
    fillList(dy);
    positionItems(dy);

    mCurrentY = mNextY;

    if(!mScroller.isFinished()){
        post(mRequestLayoutRunnable);
    }
}

private void fillList(final int dy) {
    int edge = 0;
    View child = getChildAt(getChildCount()-1);
    if(child != null) {
        edge = child.getBottom();
    }
    fillListBottom(edge, dy);

    edge = 0;
    child = getChildAt(0);
    if(child != null) {
        edge = child.getTop();
    }
    fillListTop(edge, dy);
}

private void fillListBottom(int bottomEdge, final int dy) {
    while(bottomEdge + dy < getHeight() && mBottomViewIndex < mAdapter.getCount()) {

        View child = mAdapter.getView(mBottomViewIndex, mRemovedViewQueue.poll(), this);
        addAndMeasureChild(child, -1);
        bottomEdge += child.getMeasuredHeight();

        if(mBottomViewIndex == mAdapter.getCount()-1) {
            mMaxY = mCurrentY + bottomEdge - getHeight();
        }

        if (mMaxY < 0) {
            mMaxY = 0;
        }
        mBottomViewIndex++;
    }
}

private void fillListTop(int topEdge, final int dy) {
    while(topEdge + dy > 0 && mTopViewIndex >= 0) {
        View child = mAdapter.getView(mTopViewIndex, mRemovedViewQueue.poll(), this);
        addAndMeasureChild(child, 0);
        topEdge -= child.getMeasuredHeight();
        mTopViewIndex--;
        mDisplayOffset -= child.getMeasuredHeight();
    }
}

private void removeNonVisibleItems(final int dy) {
    View child = getChildAt(0);
    while(child != null && child.getBottom() + dy <= 0) {
        mDisplayOffset += child.getMeasuredHeight();
        mRemovedViewQueue.offer(child);
        removeViewInLayout(child);
        mTopViewIndex++;
        child = getChildAt(0);

    }

    child = getChildAt(getChildCount()-1);
    while(child != null && child.getLeft() + dy >= getHeight()) {
        mRemovedViewQueue.offer(child);
        removeViewInLayout(child);
        mBottomViewIndex--;
        child = getChildAt(getChildCount()-1);
    }
}

private void positionItems(final int dy) {
    if(getChildCount() > 0){
        mDisplayOffset += dy;
        int top = mDisplayOffset;
        for(int i=0;i<getChildCount();i++){
            View child = getChildAt(i);
            int childHeight = child.getMeasuredHeight();
            child.layout(0, top, child.getMeasuredWidth(), top + childHeight);
            top += childHeight + child.getPaddingBottom();
        }
    }
}

public synchronized void scrollTo(int y) {
    mScroller.startScroll(0, mNextY, 0, y - mNextY);
    requestLayout();
}

public boolean mAlwaysOverrideTouch = true;
protected ListAdapter mAdapter;
private int mTopViewIndex = -1;
private int mBottomViewIndex = 0;
protected int mCurrentY;
protected int mNextY;
private int mMaxY = Integer.MAX_VALUE;
private int mDisplayOffset = 0;
protected Scroller mScroller;
private Queue<View> mRemovedViewQueue = new LinkedList<View>();
private boolean mDataChanged = false;
private float mXDistance;
private float mYDistance;
private float mLastX;
private float mLastY;

private Runnable mRequestLayoutRunnable = new Runnable(){

    @Override
    public void run() {
        requestLayout();
    }
};

private DataSetObserver mDataObserver = new DataSetObserver() {

    @Override
    public void onChanged() {
        synchronized(VerticalScrollView.this){
            mDataChanged = true;
        }
        invalidate();
        requestLayout();
    }

    @Override
    public void onInvalidated() {
        reset();
        invalidate();
        requestLayout();
    }
};
编辑:我可以通过使用普通的ListView而不是我的自定义VerticalScrollView来完美地工作。当用户向下滚动时,我将从网络填充ListView。如何管理列表项视图,以便我不会耗尽内存?

1 个答案:

答案 0 :(得分:0)

通过将Horizo​​ntalListViews附加到ListView,我能够获得我想要的行为。然后我将我的适配器包装起来,创建了带有EndlessAdapter的Horizo​​ntalListViews以进行连续滚动:

https://github.com/commonsguy/cwac-endless

我确保在我的适配器的getView方法中回收视图以解决内存问题。但是,如果我的模型层在堆上占用太多空间,我仍然会耗尽内存。

有人知道一个好的框架,当它们离开屏幕时剔除模型对象,并在需要再次显示它们时重新加载它们吗?