Custom ViewGroup size not change after content size changed

462 views Asked by At

I'm writing a custom ViewGroup to add a header for ViewPager (like Google+ app with tab swipe), and I found a problem. Child view inside this ViewGroup doesn't re-layout after size change.

What I expect:

enter image description here

What I really get:

enter image description here

The view hierarchy is

+ UserProfileDrawer extends ViewGroup
|
+--+ UserProfileDrawer$InternalContainer extends ViewGroup
   |
   +--+ Header view
   |
   +--+ Content view

Source code of the custom layout:

package org.mariotaku.userprofiledrawer;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by mariotaku on 14/11/27.
 */
public class UserProfileDrawerSimplified extends ViewGroup {

    static final String LOGTAG = "UserProfileDrawer";

    private final ViewDragHelper mDragHelper;

    private final InternalContainer mContainer;

    private DrawerCallback mDrawerCallback;

    public UserProfileDrawerSimplified(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UserProfileDrawer);
        final int headerLayoutId = a.getResourceId(R.styleable.UserProfileDrawer_headerLayout, 0);
        final int contentLayoutId = a.getResourceId(R.styleable.UserProfileDrawer_contentLayout, 0);
        addView(mContainer = new InternalContainer(this, context, headerLayoutId, contentLayoutId),
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        a.recycle();
        mDragHelper = ViewDragHelper.create(this, new DragCallback(this));
    }

    public UserProfileDrawerSimplified(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public UserProfileDrawerSimplified(Context context) {
        this(context, null);
    }

    @Override
    public void computeScroll() {
        boolean invalidate = mDragHelper.continueSettling(true);
        if (invalidate) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    protected void onFinishInflate() {
        if (getChildCount() != 1) {
            throw new IllegalArgumentException("Add subview by XML is not allowed.");
        }
    }

    public void setDrawerCallback(DrawerCallback callback) {
        mDrawerCallback = callback;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0, j = getChildCount(); i < j; i++) {
            final View child = getChildAt(i);
            final int left = child.getLeft() + getPaddingLeft(), right = left + child.getMeasuredWidth() - getPaddingRight();
            final int top = (i == 0 ? child.getTop() : getChildAt(i - 1).getBottom()) + getPaddingTop();
            final int bottom = top + child.getMeasuredHeight() - getPaddingBottom();
            child.layout(left, top, right, bottom);
        }
    }

    public View getHeader() {
        return mContainer.getHeader();
    }

    public View getContent() {
        return mContainer.getContent();
    }

    private int getScrollRange() {
        return mContainer.getScrollRange();
    }

    public int getHeaderTop() {
        return mContainer.getTop();
    }

    public int getHeaderTopMaximum() {
        return mContainer.getHeaderTopMaximum();
    }

    public int getHeaderTopMinimum() {
        return mContainer.getHeaderTopMinimum();
    }

    private void notifyOffsetChanged() {
        mDrawerCallback.topChanged(getHeaderTop());
    }

    public static interface DrawerCallback {

        void topChanged(int offset);
    }

    private static class DragCallback extends ViewDragHelper.Callback {

        private final UserProfileDrawerSimplified mDrawer;

        public DragCallback(UserProfileDrawerSimplified drawer) {
            mDrawer = drawer;
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            mDrawer.mDragHelper.flingCapturedView(mDrawer.getPaddingLeft(),
                    mDrawer.getHeaderTopMinimum(), mDrawer.getPaddingLeft(),
                    mDrawer.getHeaderTopMaximum());
            ViewCompat.postInvalidateOnAnimation(mDrawer);
        }

        @Override
        public int getViewVerticalDragRange(View child) {
            return mDrawer.getScrollRange();
        }

        @Override
        public boolean tryCaptureView(View view, int pointerId) {
            return view == mDrawer.mContainer;
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return mDrawer.getPaddingLeft();
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            final int min = mDrawer.getHeaderTopMinimum(), max = mDrawer.getHeaderTopMaximum();
            return MathUtils.clamp(top, min, max);
        }

    }


    @SuppressLint("ViewConstructor")
    private static class InternalContainer extends ViewGroup {

        private final UserProfileDrawerSimplified mParent;
        private final View mHeaderView, mContentView;

        public InternalContainer(UserProfileDrawerSimplified parent, Context context, int headerLayoutId, int contentLayoutId) {
            super(context);
            mParent = parent;
            final LayoutInflater inflater = LayoutInflater.from(context);
            addView(mHeaderView = inflater.inflate(headerLayoutId, this, false));
            addView(mContentView = inflater.inflate(contentLayoutId, this, false));
        }

        public View getHeader() {
            return mHeaderView;
        }

        public View getContent() {
            return mContentView;
        }

        public int getHeaderTopMinimum() {
            return mParent.getPaddingTop() - mHeaderView.getHeight();
        }

        public int getHeaderTopMaximum() {
            return mParent.getPaddingTop();
        }

        public int getScrollRange() {
            return getHeaderTopMaximum() - getHeaderTopMinimum();
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            measureChildren(widthMeasureSpec, heightMeasureSpec);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            for (int i = 0, j = getChildCount(); i < j; i++) {
                final View child = getChildAt(i);
                final int left = child.getLeft(), right = left + child.getMeasuredWidth();
                final int top = i == 0 ? child.getTop() : getChildAt(i - 1).getBottom();
                final int bottom = top + child.getMeasuredHeight();
                child.layout(left, top, right, bottom);
            }
        }

        @Override
        public void offsetTopAndBottom(int offset) {
            super.offsetTopAndBottom(offset);
            mParent.notifyOffsetChanged();
        }
    }
}
0

There are 0 answers