1

我正在尝试将 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。如何管理列表项视图以免内存不足?

4

1 回答 1

0

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

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

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

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

于 2013-03-03T19:26:01.860 回答