自定义ViewGroup中的setMargins不起作用

时间:2014-03-19 14:50:18

标签: android android-layout android-view

我有一个扩展RelativeLayout的自定义视图。我在该视图中有一个子ImageView。 如果我将该视图放在LinearLayot或ListView中,在ImageView上设置边距效果很好。 但是,如果我将它放在自定义AdapterView(它就像ListView)设置边距不起作用。 也就是说,RelativeLayout.LayoutParams.leftMargin字段正在改变,但屏幕上没有变化。

该错误肯定在我的自定义AdapterView中,但我无法确切地看到...在更改ImageView的边距时没有布局。

public class MyListView extends AdapterView<Adapter> {

/** 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;

/** 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;

/**
 * Constructor
 * 
 * @param context The context
 * @param attrs Attributes
 */
public MyListView(final Context context, final AttributeSet attrs) {
    super(context, attrs);
}

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

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

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

    positionItems();
    invalidate();
}

/**
 * Sets and initializes all things that need to when we start a touch
 * gesture.
 * 
 * @param event The down event
 */
private void startTouch(final MotionEvent event) {
    // save the start place
    mTouchStartX = (int)event.getX();
    mTouchStartY = (int)event.getY();
    mListTopStart = getChildAt(0).getTop() - mListTopOffset;

    // start checking for a long press
    startLongPressCheck();

    // we don't know if it's a click or a scroll yet, but until we know
    // assume it's a click
    mTouchState = TOUCH_STATE_CLICK;
}

/**
 * Resets and recycles all things that need to when we end a touch gesture
 */
private void endTouch() {
    // remove any existing check for longpress
    removeCallbacks(mLongPressRunnable);

    // reset touch state
    mTouchState = TOUCH_STATE_RESTING;
}

/**
 * Scrolls the list. Takes care of updating rotation (if enabled) and
 * snapping
 * 
 * @param scrolledDistance The distance to scroll
 */
private void scrollList(final int scrolledDistance) {
    mListTop = mListTopStart + scrolledDistance;
    requestLayout();
}

/**
 * Posts (and creates if necessary) a runnable that will when executed call
 * the long click listener
 */
private void startLongPressCheck() {
    // create the runnable if we haven't already
    if (mLongPressRunnable == null) {
        mLongPressRunnable = new Runnable() {
            public void run() {
                if (mTouchState == TOUCH_STATE_CLICK) {
                    final int index = getContainingChildIndex(mTouchStartX, mTouchStartY);
                    if (index != INVALID_INDEX) {
                        longClickChild(index);
                    }
                }
            }
        };
    }

    // then post it with a delay
    postDelayed(mLongPressRunnable, ViewConfiguration.getLongPressTimeout());
}

/**
 * Checks if the user has moved far enough for this to be a scroll and if
 * so, sets the list in scroll mode
 * 
 * @param event The (move) event
 * @return true if scroll was started, false otherwise
 */
private boolean startScrollIfNeeded(final MotionEvent event) {
    final int xPos = (int)event.getX();
    final int yPos = (int)event.getY();
    if (xPos < mTouchStartX - TOUCH_SCROLL_THRESHOLD
            || xPos > mTouchStartX + TOUCH_SCROLL_THRESHOLD
            || yPos < mTouchStartY - TOUCH_SCROLL_THRESHOLD
            || yPos > mTouchStartY + TOUCH_SCROLL_THRESHOLD) {
        // we've moved far enough for this to be a scroll
        removeCallbacks(mLongPressRunnable);
        mTouchState = TOUCH_STATE_SCROLL;
        return true;
    }
    return false;
}

/**
 * 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 = 0; index < getChildCount(); index++) {
        getChildAt(index).getHitRect(mRect);
        if (mRect.contains(x, y)) {
            return index;
        }
    }
    return INVALID_INDEX;
}

/**
 * Calls the item click listener for the child with at the specified
 * coordinates
 * 
 * @param x The x-coordinate
 * @param y The y-coordinate
 */
private void clickChildAt(final int x, final int y) {
    final int index = getContainingChildIndex(x, y);
    if (index != INVALID_INDEX) {
        final View itemView = getChildAt(index);
        final int position = mFirstItemPosition + index;
        final long id = mAdapter.getItemId(position);
        performItemClick(itemView, position, id);
    }
}

/**
 * Calls the item long click listener for the child with the specified index
 * 
 * @param index Child index
 */
private void longClickChild(final int index) {
    final View itemView = getChildAt(index);
    final int position = mFirstItemPosition + index;
    final long id = mAdapter.getItemId(position);
    final OnItemLongClickListener listener = getOnItemLongClickListener();
    if (listener != null) {
        listener.onItemLongClick(this, itemView, position, id);
    }
}

/**
 * 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(0);
        while (firstChild != null && firstChild.getBottom() + offset < 0) {
            // remove the top view
            removeViewInLayout(firstChild);
            childCount--;
            mCachedItemViews.addLast(firstChild);
            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(0);
            } 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(childCount - 1);
        while (lastChild != null && lastChild.getTop() + offset > getHeight()) {
            // remove the bottom view
            removeViewInLayout(lastChild);
            childCount--;
            mCachedItemViews.addLast(lastChild);
            mLastItemPosition--;

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

/**
 * Fills the list with child-views
 * 
 * @param offset Offset of the visible area
 */
private void fillList(final int offset) {
    final int bottomEdge = getChildAt(getChildCount() - 1).getBottom();
    fillListDown(bottomEdge, offset);

    final int topEdge = getChildAt(0).getTop();
    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() && mLastItemPosition < mAdapter.getCount() - 1) {
        mLastItemPosition++;
        final View newBottomchild = mAdapter.getView(mLastItemPosition, getCachedView(), this);
        addAndMeasureChild(newBottomchild, LAYOUT_MODE_BELOW);
        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_ABOVE);
        final int childHeight = newTopCild.getMeasuredHeight();
        topEdge -= childHeight;

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

/**
 * 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_ABOVE ? 0 : -1;
    addViewInLayout(child, index, params, true);

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

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

    for (int index = 0; index < getChildCount(); index++) {
        final View child = getChildAt(index);

        final int width = child.getMeasuredWidth();
        final int height = child.getMeasuredHeight();
        final int left = (getWidth() - width) / 2;

        child.layout(left, top, left + width, top + height);
        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;
}

}

0 个答案:

没有答案