-2

私が拡張したカスタム AdapterView があります。これは、データを表示する Gallery ビューを含む RelativeLayouts をホストします。そのデータは、親アダプター ビューのアイテムごとに置き換える必要があります。
親 AdapterView のアダプター クラスの実装では
、リサイクルされた convertView 引数を取得するときに、getView() メソッドでネストされた Gallery のアダプターを変更する必要があります (親の adapterview の各項目は、異なるデータ セットに対応します。ネストされたギャラリー ビューに表示されます)。

このコードは、アダプター自体のデータ セットを内部的に変更しようとします。以前のデータセットには 3 つの項目があり、新しいデータセットには 1 つの項目があるとします。データセットの最初のビューを更新しますが、余分なビュー (削除する必要があります) をその場所に残します。余分な不要なビューにスクロールすることはできません (ギャラリーは、実際にはビューが 1 つしかない場合でも、ビューが 1 つしかないかのように動作します)。

スクリーンショット:
ここに画像の説明を入力

コード:

getView:

        @Override
    protected View getView(int position, View convertView) {
        CollectionGalleryAdapter sla;

        IMediaItem item = mCollection.get(position);

        ViewHolder holder = null;

        if (convertView == null) {
            convertView = View.inflate(mContext, layout.stream_item, null);

            holder = new ViewHolder();
            convertView.setTag(holder);

            holder.mGallery = (Gallery) convertView
                    .findViewById(android.R.id.list);
            holder.mTime = (TextView) convertView.findViewById(id.txt_time);
            holder.mTitle = (TextView) convertView.findViewById(id.title);
            holder.mBg = (ImageView) convertView.findViewById(id.thumb);

            sla = new CollectionGalleryAdapter(mContext,
                    (MediaCollection) item.getExtra(), App.instance()
                            .getImageLoader(), holder.mGallery);
            final View fv = convertView;
            sla.setBitmapLoadedListener(new OnBitmapLoadedListener() {

                @Override
                public void onBitmapLoaded(View v, int position, Bitmap b) {
                    mStreamView.invalidateChild(fv);
                }
            });
            holder.mGallery.setAdapter(sla);
        } else {
            holder = (ViewHolder) convertView.getTag();

            sla = (CollectionGalleryAdapter) holder.mGallery.getAdapter();
            sla.setCollection((MediaCollection) item.getExtra());
        }
    }

CollectionGalleryAdapter.setCollection (notifyDataSetChanged の呼び出しに注意してください)

public void setCollection(MediaCollection collection) {
    mCollection = collection;
    mAdapterView.setSelection(0);
    notifyDataSetChanged(); // *****
    clearLoaderQueue();
    postLoad(); // load bitmaps for gallery images
}

カスタム AdapterView:

public class StreamView extends AdapterView<Adapter> implements
        OnScrollListener {

    /** Represents an invalid child index */
    private static final int INVALID_INDEX = -1;

    /** Distance to drag before we intercept touch events */
    private static final int TOUCH_SCROLL_THRESHOLD = 10;

    /** Children added with this layout mode will be added below the last child */
    private static final int LAYOUT_MODE_BELOW = 0;

    /** Children added with this layout mode will be added above the first child */
    private static final int LAYOUT_MODE_ABOVE = 1;

    /** User is not touching the list */
    private static final int TOUCH_STATE_RESTING = 0;

    /** User is touching the list and right now it's still a "click" */
    private static final int TOUCH_STATE_CLICK = 1;

    /** User is scrolling the list */
    private static final int TOUCH_STATE_SCROLL = 2;

    private static final int BASE_ALPHA = 0x22;

    private static final float HEIGHT_FACTOR = 1.7f;

    /** The adapter with all the data */
    private Adapter mAdapter;

    /** Current touch state */
    private int mTouchState = TOUCH_STATE_RESTING;

    /** X-coordinate of the down event */
    private int mTouchStartX;

    /** Y-coordinate of the down event */
    private int mTouchStartY;

    /**
     * The top of the first item when the touch down event was received
     */
    private int mListTopStart;

    /** The current top of the first item */
    private int mListTop;

    /**
     * The offset from the top of the currently first visible item to the top of
     * the first item
     */
    private int mListTopOffset;

    /** The adaptor position of the first visible item */
    private int mFirstItemPosition;

    /** The adaptor position of the last visible item */
    private int mLastItemPosition;

    /** A list of cached (re-usable) item views */
    private final LinkedList<View> mCachedItemViews = new LinkedList<View>();

    /** Used to check for long press actions */
    private Runnable mLongPressRunnable;

    /** Reusable rect */
    private Rect mRect = new Rect();

    private Canvas mCanvas = new Canvas();

    private Paint mPaint;

    private ElasticScroller mScroller;

    private int mChildHeight;
    private int mLayoutHeight;

    private DisplayMetrics mDisplayMetrics;

    // private WeakHashMap<View, Bitmap> mDrawingCache;
    // private WeakHashMap<View, Boolean> mInvalidateMap;

    /**
     * Constructor
     * 
     * @param context
     *            The context
     * @param attrs
     *            Attributes
     */
    public StreamView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        mScroller = new ElasticScroller(new Handler(), this,
                ElasticScroller.AXIS_VERTICAL, 0, getHeight() / 3);
    }

    View mMotionTarget;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        final float xf = ev.getX();
        final float yf = ev.getY();
        final Rect frame = mRect;

        if (action == MotionEvent.ACTION_DOWN) {
            if (mMotionTarget != null) {
                // this is weird, we got a pen down, but we thought it was
                // already down!
                // XXX: We should probably send an ACTION_UP to the current
                // target.
                mMotionTarget = null;
            }
            // If we're disallowing intercept or if we're allowing and we didn't
            // intercept
            if (!onInterceptTouchEvent(ev)) {
                // reset this event's action (just to protect ourselves)
                ev.setAction(MotionEvent.ACTION_DOWN);
                // We know we want to dispatch the event down, find a child
                // who can handle it, start with the front-most child.
                final int scrolledXInt = (int) xf;
                final int scrolledYInt = (int) yf;
                final int count = getChildCount();
                for (int i = count - 1; i >= 0; i--) {
                    final View child = getChildAt(i);
                    final int actualtop = getActualChildTop(child);
                    frame.set(0, 0, getWidth(), mChildHeight);
                    frame.offset(0, actualtop);
                    if (frame.contains(scrolledXInt, scrolledYInt)) {
                        // offset the event to the view's coordinate system
                        ev.setLocation(xf, yf - actualtop);
                        if (child.dispatchTouchEvent(ev)) {
                            // Event handled, we have a target now.
                            child.setTag(string.tag_invalidate, true);
                            // mInvalidateMap.put(child, true);
                            invalidate();
                            mMotionTarget = child;
                            return true;
                        }
                        // The event didn't get handled, try the next view.
                        // Don't reset the event's location, it's not
                        // necessary here.
                    }
                }
            }
        }

        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP)
                || (action == MotionEvent.ACTION_CANCEL);

        // The event wasn't an ACTION_DOWN, dispatch it to our target if
        // we have one.
        final View target = mMotionTarget;
        if (target == null) {
            // We don't have a target, this means we're handling the
            // event as a regular view.
            ev.setLocation(xf, yf);
            return onTouchEvent(ev);
        }

        // if have a target, see if we're allowed to and want to intercept its
        // events
        if (onInterceptTouchEvent(ev)) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            ev.setLocation(xf, yf - getActualChildTop(mMotionTarget));
            if (!target.dispatchTouchEvent(ev)) {
                // target didn't handle ACTION_CANCEL. not much we can do
                // but they should have.
            } else {
                target.setTag(string.tag_invalidate, true);
            }
            // clear the target
            mMotionTarget = null;
            // Don't dispatch this event to our own view, because we already
            // saw it when intercepting; we just want to give the following
            // event to the normal onTouchEvent().
            return true;
        }

        if (isUpOrCancel) {
            mMotionTarget = null;
        }

        // finally offset the event to the target's coordinate system and
        // dispatch the event.
        ev.setLocation(xf, yf - getActualChildTop(target));

        if (target.dispatchTouchEvent(ev)) {
            target.setTag(string.tag_invalidate, true);
            invalidate();
            return true;
        } else {
            return false;
        }
    }

    MotionEvent mDownEvent;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int index = getContainingChildIndex((int) ev.getX(), (int) ev.getY());
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mTouchStartX = (int) ev.getX();
            mTouchStartY = (int) ev.getY();
            mTouchState = TOUCH_STATE_CLICK;
            mDownEvent = MotionEvent.obtain(ev);
            return false;
        case MotionEvent.ACTION_MOVE:
            int ydistance = (int) Math.abs(ev.getY() - mTouchStartY);
            int xdistance = (int) Math.abs(ev.getX() - mTouchStartX);
            if ((index == getChildCount() - 1 && ydistance <= xdistance)
                    || ydistance <= ViewConfiguration.getTouchSlop()) {
                return false;
            } else {
                mScroller.onTouch(this, mDownEvent);
                mTouchState = TOUCH_STATE_SCROLL;
                return true;
            }
        case MotionEvent.ACTION_UP:
            if (index != getChildCount() - 1
                    && mTouchState == TOUCH_STATE_CLICK) {
                mScroller
                        .scrollByDistance(
                                (Integer) getChildAt(index).getTag(
                                        string.tag_top), 400);
                return false;
            }
            // mTouchState = TOUCH_STATE_RESTING;
            return false;
        default:

            break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public void setAdapter(final Adapter adapter) {
        mAdapter = adapter;
        removeAllViewsInLayout();
        requestLayout();
    }

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

    @Override
    public void setSelection(final int position) {
        throw new UnsupportedOperationException("Not supported");
    }

    @Override
    public View getSelectedView() {
        throw new UnsupportedOperationException("Not supported");
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mScroller.onTouch(this, event);
        return true;
    }

    /*
     * @Override public boolean onInterceptTouchEvent(final MotionEvent event) {
     * switch (event.getAction()) { case MotionEvent.ACTION_DOWN:
     * startTouch(event); return false;
     * 
     * case MotionEvent.ACTION_MOVE: return startScrollIfNeeded(event);
     * 
     * default: endTouch(); return false; } return true; }
     * 
     * @Override public boolean onTouchEvent(final MotionEvent event) { /* if
     * (getChildCount() == 0) { return false; } switch (event.getAction()) {
     * case MotionEvent.ACTION_DOWN: startTouch(event); break;
     * 
     * case MotionEvent.ACTION_MOVE: if (mTouchState == TOUCH_STATE_CLICK) {
     * startScrollIfNeeded(event); } if (mTouchState == TOUCH_STATE_SCROLL) {
     * scrollList((int) event.getY() - mTouchStartY); } break;
     * 
     * case MotionEvent.ACTION_UP: if (mTouchState == TOUCH_STATE_CLICK) {
     * clickChildAt((int) event.getX(), (int) event.getY()); } endTouch();
     * break;
     * 
     * default: endTouch(); break; } return true; }
     */
    @Override
    protected void onLayout(final boolean changed, final int left,
            final int top, final int right, final int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        // if we don't have an adapter, we don't need to do anything
        if (mAdapter == null) {
            return;
        }

        mLayoutHeight = bottom - top;

        if (getChildCount() == 0) {
            mLastItemPosition = -1;
            fillListDown(mListTop, 0);
        } else {
            final int offset = mListTop
                    + mListTopOffset
                    - ((Integer) getChildAt(getChildCount() - 1).getTag(
                            string.tag_top));
            removeNonVisibleViews(offset);
            fillList(offset);
        }

        positionItems(changed);
        invalidate();
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        if (mTouchState != TOUCH_STATE_SCROLL) {
            if ((Integer) child.getTag(string.tag_top) == 0) {
                invalidate();
                child.draw(canvas);
                return true;
            }
        }

        Bitmap b = (Bitmap) child.getTag(string.tag_drawing_cache);// child.getDrawingCache();
        Boolean invalidated = (Boolean) child.getTag(string.tag_invalidate);
        if (b == null || (invalidated != null && invalidated == true)) {
            if (b == null) {
                do {
                    try {
                        b = Bitmap.createBitmap(getWidth(), mChildHeight,
                                Config.ARGB_8888);
                        break;
                    } catch (OutOfMemoryError e) {
                        System.gc();
                    }
                } while (App.instance(getContext()).freeCacheMemory());
                if (b == null) {
                    return false;
                }
            } else {
                b.eraseColor(Color.TRANSPARENT);
            }
            mCanvas.setBitmap(b);
            child.draw(mCanvas);
            child.setTag(string.tag_drawing_cache, b);
            child.setTag(string.tag_invalidate, false);
        }

        if (mPaint == null) {
            mPaint = new Paint();
        }

        if (mDisplayMetrics == null) {
            mDisplayMetrics = getResources().getDisplayMetrics();
        }

        final int top = (Integer) child.getTag(string.tag_top);
        final int cheight = mChildHeight;

        final int height = getHeight();
        final int bottom = height - cheight;

        final float x = Math.min(1f, (float) top / (height * HEIGHT_FACTOR));
        final float y = (float) top / cheight;

        final int newtop;

        final float t = (float) (1 - (Math
                .pow((1 - (Math.min(1f, x * 1.2))), 2)));

        if (x >= 0) {
            if (x <= 1f) {
                newtop = (int) (bottom * t);
            } else {
                newtop = bottom;
            }
        } else {
            newtop = (int) ((cheight - mDisplayMetrics.density * 65) * y);
        }

        final int alpha = BASE_ALPHA
                + (int) ((255 - BASE_ALPHA) * (x < 0 ? 1f : (1f - t)));
        mPaint.setColorFilter(new LightingColorFilter(alpha | (alpha << 8)
                | (alpha << 16), 0));
        mPaint.setAlpha((int) (255 * (x >= 0.8 ? (float) Math.pow(
                1 - (x - 0.8) / 0.2, 1 / 2f) : (x >= 0 ? 1f : 1f - Math.pow(y,
                4)))));
        canvas.drawBitmap(b, 0, newtop, mPaint);

        return false;
    }

    private int getActualChildTop(View child) {
        final int top = (Integer) child.getTag(string.tag_top);
        final int cheight = mChildHeight;

        final int height = getHeight();
        final int bottom = height - cheight;

        final float x = Math.min(1f, (float) top / (height * HEIGHT_FACTOR));
        final float y = (float) top / cheight;

        final float t = (float) (1 - (Math
                .pow((1 - (Math.min(1f, x * 1.2))), 2)));

        if (x >= 0) {
            if (x <= 1f) {
                return (int) (bottom * t);
            } else {
                return bottom;
            }
        } else {
            return (int) ((cheight - mDisplayMetrics.density * 65) * y);
        }
    }

    /**
     * Returns the index of the child that contains the coordinates given.
     * 
     * @param x
     *            X-coordinate
     * @param y
     *            Y-coordinate
     * @return The index of the child that contains the coordinates. If no child
     *         is found then it returns INVALID_INDEX
     */
    private int getContainingChildIndex(final int x, final int y) {
        if (mRect == null) {
            mRect = new Rect();
        }
        for (int index = getChildCount() - 1; index >= 0; index--) {
            View child = getChildAt(index);
            mRect.set(0, 0, getWidth(), mChildHeight);
            mRect.offset(0, getActualChildTop(child));
            if (mRect.contains(x, y)) {
                return index;
            }
        }
        return INVALID_INDEX;
    }

    /**
     * Removes view that are outside of the visible part of the list. Will not
     * remove all views.
     * 
     * @param offset
     *            Offset of the visible area
     */
    private void removeNonVisibleViews(final int offset) {
        // We need to keep close track of the child count in this function. We
        // should never remove all the views, because if we do, we loose track
        // of were we are.
        int childCount = getChildCount();

        // if we are not at the bottom of the list and have more than one child
        if (mLastItemPosition != mAdapter.getCount() - 1 && childCount > 1) {
            // check if we should remove any views in the top
            View firstChild = getChildAt(childCount - 1);
            while (firstChild != null
                    && ((Integer) firstChild.getTag(string.tag_top))
                            + mChildHeight + offset <= 0) {
                // remove the top view
                removeViewInLayout(firstChild);
                childCount--;
                mCachedItemViews.addLast(firstChild);
                firstChild.setTag(string.tag_invalidate, true);
                mFirstItemPosition++;

                // update the list offset (since we've removed the top child)
                mListTopOffset += firstChild.getMeasuredHeight();

                // Continue to check the next child only if we have more than
                // one child left
                if (childCount > 1) {
                    firstChild = getChildAt(childCount - 1);
                } else {
                    firstChild = null;
                }
            }
        }

        // if we are not at the top of the list and have more than one child
        if (mFirstItemPosition != 0 && childCount > 1) {
            // check if we should remove any views in the bottom
            View lastChild = getChildAt(0);
            while (lastChild != null
                    && ((Integer) lastChild.getTag(string.tag_top)) + offset > getHeight()
                            * HEIGHT_FACTOR) {
                // remove the bottom view
                removeViewInLayout(lastChild);
                childCount--;
                mCachedItemViews.addLast(lastChild);
                lastChild.setTag(string.tag_invalidate, true);
                mLastItemPosition--;

                // Continue to check the next child only if we have more than
                // one child left
                if (childCount > 1) {
                    lastChild = getChildAt(0);
                } else {
                    lastChild = null;
                }
            }
        }
    }

    /**
     * Fills the list with child-views
     * 
     * @param offset
     *            Offset of the visible area
     */
    private void fillList(final int offset) {
        if (mChildHeight == 0) {
            mChildHeight = getChildAt(0).getMeasuredHeight();
        }
        final int bottomEdge = ((Integer) getChildAt(0).getTag(string.tag_top))
                + mChildHeight;
        fillListDown(bottomEdge, offset);

        final int topEdge = (Integer) getChildAt(getChildCount() - 1).getTag(
                string.tag_top);
        fillListUp(topEdge, offset);
    }

    /**
     * Starts at the bottom and adds children until we've passed the list bottom
     * 
     * @param bottomEdge
     *            The bottom edge of the currently last child
     * @param offset
     *            Offset of the visible area
     */
    private void fillListDown(int bottomEdge, final int offset) {
        while (bottomEdge + offset < getHeight() * HEIGHT_FACTOR
                && mLastItemPosition < mAdapter.getCount() - 1) {
            mLastItemPosition++;
            final View newBottomchild = mAdapter.getView(mLastItemPosition,
                    getCachedView(), this);
            addAndMeasureChild(newBottomchild, LAYOUT_MODE_ABOVE);
            bottomEdge += newBottomchild.getMeasuredHeight();
        }
    }

    /**
     * Starts at the top and adds children until we've passed the list top
     * 
     * @param topEdge
     *            The top edge of the currently first child
     * @param offset
     *            Offset of the visible area
     */
    private void fillListUp(int topEdge, final int offset) {
        while (topEdge + offset > 0 && mFirstItemPosition > 0) {
            mFirstItemPosition--;
            final View newTopCild = mAdapter.getView(mFirstItemPosition,
                    getCachedView(), this);
            addAndMeasureChild(newTopCild, LAYOUT_MODE_BELOW);
            final int childHeight = newTopCild.getMeasuredHeight();
            topEdge -= childHeight;

            // update the list offset (since we added a view at the top)
            mListTopOffset -= childHeight;
        }
    }

    @Override
    public int getFirstVisiblePosition() {
        return mFirstItemPosition;
    }

    @Override
    public int getLastVisiblePosition() {
        return mLastItemPosition;
    }

    @Override
    protected int getChildDrawingOrder(int childCount, int i) {
        return childCount - 1 - i;
    }

    /**
     * Adds a view as a child view and takes care of measuring it
     * 
     * @param child
     *            The view to add
     * @param layoutMode
     *            Either LAYOUT_MODE_ABOVE or LAYOUT_MODE_BELOW
     */
    private void addAndMeasureChild(final View child, final int layoutMode) {
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = new LayoutParams(LayoutParams.WRAP_CONTENT,
                    LayoutParams.WRAP_CONTENT);
        }
        final int index = layoutMode == LAYOUT_MODE_BELOW ? -1 : 0;
        // child.setDrawingCacheEnabled(true);
        addViewInLayout(child, index, params, true);

        final int itemWidth = getWidth();
        child.measure(MeasureSpec.EXACTLY | itemWidth, MeasureSpec.UNSPECIFIED
                | mChildHeight);

        if (mChildHeight == 0) {
            mChildHeight = child.getMeasuredHeight();
            mScroller.setRange(mAdapter.getCount() * mChildHeight
                    + (int) (mLayoutHeight * HEIGHT_FACTOR));
            mScroller.setBounceRange(getHeight() / 2);
            mScroller.setMaxFlingVelocity(2500);
        }

        invalidateChild(child);
    }

    /**
     * Positions the children at the "correct" positions
     */
    private void positionItems(boolean newLayout) {
        int top = mListTop + mListTopOffset;

        final int width = getWidth();
        final int height = mChildHeight;
        for (int index = getChildCount() - 1; index >= 0; index--) {
            final View child = getChildAt(index);

            if (newLayout || child.getHeight() == 0) {
                child.layout(0, top, width, top + height);
            }
            child.setTag(string.tag_top, top);
            top += height;
        }

    }

    /**
     * Checks if there is a cached view that can be used
     * 
     * @return A cached view or, if none was found, null
     */
    private View getCachedView() {
        if (mCachedItemViews.size() != 0) {
            return mCachedItemViews.removeFirst();
        }
        return null;
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        mListTop = -firstVisibleItem;
        requestLayout();
    }

    private boolean mSnap = false;

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == SCROLL_STATE_IDLE && mSnap) {
            final View c1 = getChildAt(getChildCount() - 1);
            final View c2 = getChildAt(getChildCount() - 2);
            final int dist1 = (Integer) c1.getTag(string.tag_top);
            final int dist2 = (Integer) c2.getTag(string.tag_top);
            if (Math.abs(dist1) < Math.abs(dist2)) {
                mScroller.scrollByDistance(dist1, 400);
                OnItemSelectedListener listener = getOnItemSelectedListener();
                if (listener != null) {
                    listener.onItemSelected(this, c1, c1.getId(), 0);
                }
            } else {
                mScroller.scrollByDistance(dist2, 400);
                OnItemSelectedListener listener = getOnItemSelectedListener();
                if (listener != null) {
                    listener.onItemSelected(this, c2, c2.getId(), 0);
                }
            }
            mSnap = false;
        } else if (scrollState == SCROLL_STATE_TOUCH_SCROLL) {
            mSnap = true;
        }
    }

    public int getChildHeight() {
        return mChildHeight;
    }

    public void invalidateChild(View child) {
        child.setTag(string.tag_invalidate, true);
        invalidate();
    }
}
4

2 に答える 2

2

を呼び出しnotifyDataSetInvalidated()notifyDataSetChanged()問題を解決しないため、問題はStreamViewカスタム アダプター ビュー クラスにあるようです。

于 2012-07-11T15:39:23.467 に答える
1

まず、ビューホルダーを使用します。これにより、コードのこの奇妙な部分を回避できます。

for (int i = layout.getChildCount() - 1; i >= 0; i--) {
     View child = layout.getChildAt(i);
     if (child instanceof Gallery) {
          hlv = (Gallery) child;
          break;
     }
}

ところで、なぜあなたは を見つけるのにそのような奇妙なアプローチを使うのViewですか?

hlv = (Gallery)layout.findViewById(android.R.id.list)で十分でしょう。

次に、新しい Gallery を作成hlv = new Gallery(mContext)してから初期化し、レイアウトに追加しないでください。これは非効率的で、エラーが発生しやすくなります。

既に存在するギャラリーを再利用する必要があります。これhlv = (Gallery)layout.findViewById(android.R.id.list)は、ビュー ホルダー オブジェクトのフィールドにアクセスするだけで取得できます。

于 2012-07-11T07:45:18.610 に答える