我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();
}
}
答案 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
值,但这可能需要更新从该值派生的其他值。