如何以编程方式重新测量视图?

时间:2018-05-08 08:19:49

标签: android android-custom-view

CustomView只有2 RelativeLayouts。每个RecyclerViews都有RelativeLayout个。当我向RecyclerView添加新元素时,它不会改变高度。

如果我改变了屏幕方向,那么android会很好地测量它。所以我的问题是如何以编程方式告诉android他需要重新测量子元素和父元素。

requestlayout()invalidate()无效

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());
    }
}

布局:

<?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>

活动:

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

适配器:

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();
    }
}

1 个答案:

答案 0 :(得分:1)

ExpandableView在其onMeasure方法中包含一些可疑代码。

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

mIsInit变量在创建时设置为true,而在第一次调用false时设置为onMeasure。所以条件中的代码只运行一次。

但它存储了mExpandedViewHeight获得的值getChildAt(1).getMeasuredHeight(),它是您的相对布局的当前高度,其中包含(仍为空)RecyclerView。

据我所知,当您向Recylerview添加项目时,ExpadableView的代码中没有任何内容会更新此值。

我不确定,onMeasure方法的正确/完美实现是什么(对于您的组件)。这需要对组件进行一些调试和测试,并且可能需要对代码的其他部分进行一些调整。

如果您编写了该组件,您可能需要投入更多精力进行调试。如果你没有编写组件,你应该尝试找到另一个正确实现的组件。具有自定义测量的自定义组件是一个高级主题。

如果您真的想要调试和修复组件,首先要做的是更新每个测量的mExpandedViewHeight值,但这可能需要更新从该值派生的其他值。