ivanovd422
ivanovd422

Reputation: 357

How to programmatically reMeasure view?

I have CustomView with 2 RelativeLayouts. There are RecyclerViews in each RelativeLayout. When I add new element to RecyclerView it doesn`t change height.

If I change screen orientation then android measures it well. So my question is how to programmatically tell android that he needs remeasure both childs and parent elements.

requestlayout() and invalidate() doesn`t work

CustomView:

public class ExpandableView extends LinearLayout {


    private Settings mSettings ;
    private int mExpandState;
    private ValueAnimator mExpandAnimator;
    private ValueAnimator mParentAnimator;
    private AnimatorSet mExpandScrollAnimatorSet;
    private  int mExpandedViewHeight;
    private  boolean mIsInit = true;
    private int defaultHeight;

    private boolean isAllowedExpand = false;

    private ScrolledParent mScrolledParent;
    private OnExpandListener mOnExpandListener;


    public ExpandableView(Context context) {
        super(context);

        init(null);

    }

    public ExpandableView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(attrs);

    }

    public ExpandableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

    private void init(AttributeSet attrs) {
        Log.w("tag", "init");
        setOrientation(VERTICAL);
        this.setClipChildren(false);
        this.setClipToPadding(false);

        mExpandState = ExpandState.PRE_INIT;
        mSettings = new Settings();
        if(attrs!=null) {
            TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableView);
            mSettings.expandDuration = typedArray.getInt(R.styleable.ExpandableView_expDuration, Settings.EXPAND_DURATION);
            mSettings.expandWithParentScroll = typedArray.getBoolean(R.styleable.ExpandableView_expWithParentScroll,false);
            mSettings.expandScrollTogether = typedArray.getBoolean(R.styleable.ExpandableView_expExpandScrollTogether,true);
            typedArray.recycle();
        }


    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.w("tag", "onMeasure");
        Log.w("tag", "widthMeasureSpec - " + widthMeasureSpec);
        Log.w("tag", "heightMeasureSpec - " + heightMeasureSpec);
        int childCount = getChildCount();
        if(childCount!=2) {
            throw new IllegalStateException("ExpandableLayout must has two child view !");
        }
        if(mIsInit) {

            ((MarginLayoutParams)getChildAt(0).getLayoutParams()).bottomMargin=0;
            MarginLayoutParams marginLayoutParams = ((MarginLayoutParams)getChildAt(1).getLayoutParams());
            marginLayoutParams.bottomMargin=0;
            marginLayoutParams.topMargin=0;
            marginLayoutParams.height = 0;
            mExpandedViewHeight = getChildAt(1).getMeasuredHeight();
            defaultHeight = mExpandedViewHeight;

            mIsInit =false;
            mExpandState = ExpandState.CLOSED;
            View view = getChildAt(0);
            if (view != null){
                view.setOnClickListener(v -> toggle());
            }
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.w("tag", "onSizeChanged");
        if(mSettings.expandWithParentScroll) {
            mScrolledParent = Utils.getScrolledParent(this);
        }
    }

    private int getParentScrollDistance () {
        int distance = 0;
        Log.w("tag", "getParentScrollDistance");
        if(mScrolledParent == null) {
            return distance;
        }
        distance = (int) (getY() + getMeasuredHeight() + mExpandedViewHeight - mScrolledParent.scrolledView.getMeasuredHeight());
        for(int index = 0; index < mScrolledParent.childBetweenParentCount; index++) {
            ViewGroup parent = (ViewGroup) getParent();
            distance+=parent.getY();
        }

        return distance;
    }

    private void verticalAnimate(final int startHeight, final int endHeight ) {
        int distance = getParentScrollDistance();  

        final View target = getChildAt(1);

        mExpandAnimator = ValueAnimator.ofInt(startHeight,endHeight);
        mExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                target.getLayoutParams().height = (int) animation.getAnimatedValue();
                target.requestLayout();
            }
        });

        mExpandAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if(endHeight-startHeight < 0) {
                    mExpandState = ExpandState.CLOSED;
                    if (mOnExpandListener != null) {
                        mOnExpandListener.onExpand(false);
                    }
                } else {
                    mExpandState=ExpandState.EXPANDED;
                    if(mOnExpandListener != null) {
                        mOnExpandListener.onExpand(true);
                    }
                }
            }
        });


        mExpandState=mExpandState==ExpandState.EXPANDED?ExpandState.CLOSING :ExpandState.EXPANDING;

        mExpandAnimator.setDuration(mSettings.expandDuration);

        if(mExpandState == ExpandState.EXPANDING && mSettings.expandWithParentScroll && distance > 0) {

            mParentAnimator = Utils.createParentAnimator(mScrolledParent.scrolledView, distance, mSettings.expandDuration);

            mExpandScrollAnimatorSet = new AnimatorSet();

            if(mSettings.expandScrollTogether) {
                mExpandScrollAnimatorSet.playTogether(mExpandAnimator,mParentAnimator);
            } else {
                mExpandScrollAnimatorSet.playSequentially(mExpandAnimator,mParentAnimator);
            }
            mExpandScrollAnimatorSet.start();

        } else {
            mExpandAnimator.start();
        }


    }

    public void setExpand(boolean expand) {
        if (mExpandState == ExpandState.PRE_INIT) {return;}

        getChildAt(1).getLayoutParams().height = expand ? mExpandedViewHeight : 0;
        requestLayout();
        mExpandState=expand?ExpandState.EXPANDED:ExpandState.CLOSED;
    }

    public boolean isExpanded() {
        return mExpandState==ExpandState.EXPANDED;
    }

    public void toggle() {
        if (isAllowedExpand){
            if(mExpandState==ExpandState.EXPANDED) {
                close();
            }else if(mExpandState==ExpandState.CLOSED) {
                expand();
            }
        }
    }

    public void expand() {
        verticalAnimate(0,mExpandedViewHeight);
    }

    public void close() {
        verticalAnimate(mExpandedViewHeight,0);
    }

    public interface OnExpandListener {
        void onExpand(boolean expanded) ;
    }

    public void setOnExpandListener(OnExpandListener onExpandListener) {
        this.mOnExpandListener = onExpandListener;
    }


    public void setExpandScrollTogether(boolean expandScrollTogether) {
        this.mSettings.expandScrollTogether = expandScrollTogether;
    }

    public void setExpandWithParentScroll(boolean expandWithParentScroll) {
        this.mSettings.expandWithParentScroll = expandWithParentScroll;
    }

    public void setExpandDuration(int expandDuration) {
        this.mSettings.expandDuration = expandDuration;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        Log.w("tag", "onDetachedFromWindow");
        if(mExpandAnimator!=null&&mExpandAnimator.isRunning()) {
            mExpandAnimator.cancel();
            mExpandAnimator.removeAllUpdateListeners();
        }
        if(mParentAnimator!=null&&mParentAnimator.isRunning()) {
            mParentAnimator.cancel();
            mParentAnimator.removeAllUpdateListeners();
        }
        if(mExpandScrollAnimatorSet!=null) {
            mExpandScrollAnimatorSet.cancel();
        }
    }

    public void setAllowedExpand(boolean allowedExpand) {
        isAllowedExpand = allowedExpand;
    }

    public void increaseDistance(int size){

        if(mExpandState==ExpandState.EXPANDED) {
            close();
        }

        mExpandedViewHeight = defaultHeight + size;

    }


//func just for loggs
    public void showParams(){

        RelativeLayout relativeLayout = (RelativeLayout) getChildAt(1);
        /*relativeLayout.requestLayout();
        relativeLayout.invalidate();*/



        RecyclerView recyclerView = (RecyclerView) relativeLayout.getChildAt(1);

        Log.d("tag", "height - " + relativeLayout.getHeight());
        Log.d("tag", "childs - " +  relativeLayout.getChildCount());
        Log.d("tag", "recycler height - " +   recyclerView.getHeight());

        recyclerView.requestLayout();
        recyclerView.invalidateItemDecorations();
        recyclerView.invalidate();


        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        layoutManager.getHeight();



        Log.d("tag", " layoutManager.getHeight() - " + layoutManager.getHeight());
        layoutManager.requestLayout();
        layoutManager.generateDefaultLayoutParams();
        layoutManager.onItemsChanged(recyclerView);


        Log.d("tag", " layoutManager.getHeight()2- " + layoutManager.getHeight());


        layoutManager.getChildCount();
        recyclerView.getChildCount();
        Log.d("tag", "manager childs - " + layoutManager.getChildCount());
        Log.d("tag", "recycler childs - " + recyclerView.getChildCount());
    }
}

Layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@color/grey_light_color"
    >


    <com.example.develop.project.Utils.ExpandableView.ExpandableView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/test_custom_view"
        app:expWithParentScroll="true"
        android:layout_gravity="center"
        android:background="@color/grey_color"

        >

        <android.support.v7.widget.CardView
            android:id="@+id/start_card"
            android:layout_width="match_parent"
            android:layout_height="70dp"
            android:background="@color/white_color"
            android:layout_marginTop="5dp"
            android:layout_marginStart="5dp"
            android:layout_marginEnd="5dp"
            >

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <TextView
                    android:id="@+id/stage_tv4"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/grey_deep_color"
                    android:text="Запуск"
                    android:layout_centerVertical="true"
                    android:layout_marginStart="15dp"
                    android:textSize="18sp"

                    />



            </RelativeLayout>

        </android.support.v7.widget.CardView>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TextView
                android:id="@+id/start_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/start_accept"
                android:textSize="18sp"
                android:layout_marginStart="15dp"
                android:layout_marginTop="15dp"
                android:layout_marginBottom="15dp"
                android:textColor="@color/grey_deep_color"
                android:layout_marginEnd="15dp"

                />

                <android.support.v7.widget.RecyclerView
                    android:id="@+id/my_test_tv"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/start_tv"
                    android:layout_marginTop="10dp"
                    android:layout_marginStart="15dp"
                    android:layout_marginEnd="15dp"
                    >

                </android.support.v7.widget.RecyclerView>

        </RelativeLayout>



    </com.example.develop.project.Utils.ExpandableView.ExpandableView>

    <Button
        android:id="@+id/add_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true"
        android:text="Add elem"
        android:layout_marginStart="15dp"
        android:layout_marginBottom="15dp"
        />

    <Button
        android:id="@+id/check_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentEnd="true"
        android:text="Check params"
        android:layout_marginBottom="15dp"
        android:layout_marginEnd="15dp"
        />

</RelativeLayout>

Activity:

public class TestActivity extends MvpAppCompatActivity implements TestContract.View {


    TestAdapter testAdapter;

    @InjectPresenter
    public TestPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_layout);

        init();
    }

    private void init() {
        ExpandableView expandableView = findViewById(R.id.test_custom_view);
        expandableView.setAllowedExpand(true);

        Button add_btn = findViewById(R.id.add_btn);
        Button check_btn = findViewById(R.id.check_btn);




        add_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {


                presenter.addElem();

            }
        });

        check_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                expandableView.showParams();
            }
        });
    }

    @Override
    public void addElems(ArrayList<String> list) {

        testAdapter.notifyDataSetChanged();

    }

    @Override
    public void populateAdapter(ArrayList<String> list) {

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);

        RecyclerView recyclerView = findViewById(R.id.my_test_tv);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setNestedScrollingEnabled(false);
        recyclerView.setHasFixedSize(false);



        testAdapter = new TestAdapter(list);
        recyclerView.setAdapter(testAdapter);
    }
}

Adapter:

public class TestAdapter extends RecyclerView.Adapter<TestAdapter.TasksViewHolder> {
    private List<String> list;

    public TestAdapter(List<String> list) {
        this.list = list;


    }

    @NonNull
    @Override
    public TestAdapter.TasksViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.test_item, parent, false);

        TestAdapter.TasksViewHolder vh = new TestAdapter.TasksViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(@NonNull TestAdapter.TasksViewHolder holder, int position) {
        String text = list.get(position);


        holder.textView.setText(text);

    }


    public static class TasksViewHolder extends RecyclerView.ViewHolder {
        private TextView textView;


        public TasksViewHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.my_test_tv);

        }


    }

    @Override
    public int getItemCount() {
        return list.size();
    }
}

Upvotes: 1

Views: 1823

Answers (1)

Josef Adamcik
Josef Adamcik

Reputation: 5790

The ExpandableView contains some suspicious code in its onMeasure method.

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    if(mIsInit) {

        ((MarginLayoutParams)getChildAt(0).getLayoutParams()).bottomMargin=0;
        MarginLayoutParams marginLayoutParams = ((MarginLayoutParams)getChildAt(1).getLayoutParams());
        marginLayoutParams.bottomMargin=0;
        marginLayoutParams.topMargin=0;
        marginLayoutParams.height = 0;
        mExpandedViewHeight = getChildAt(1).getMeasuredHeight();
        defaultHeight = mExpandedViewHeight;

        mIsInit =false;
        mExpandState = ExpandState.CLOSED;
        View view = getChildAt(0);
        if (view != null){
            view.setOnClickListener(v -> toggle());
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

The mIsInit variable is set to true on creation and than to false when onMeasure is called for the first time. So the code in the condition runs only once.

But it stores value mExpandedViewHeight obtained as getChildAt(1).getMeasuredHeight() and that is current height of your relative layout, which contains (still empty) RecyclerView.

As far as I can see there's nothing in ExpadableView's code that would update this value, when you add an item to the Recylerview.

I am not sure, what would the correct/perfect implementation of the onMeasure method be (for your component). That would require some debugging and testing of the component, and perhaps some tweaks in other parts of the code.

If you wrote the component, you may want to invest more effort into debugging. If you didn't write the component, you should try to find other one, that is properly implemented. Custom components with custom measurements are an advanced topic.

If you really want to go with debugging and fixing your component, that first thing to do is to update the mExpandedViewHeight value on every measurement, but that might require updating other values that are derived from this value.

Upvotes: 2

Related Questions