我在布局中有以下卡片:
<android.support.v7.widget.CardView
android:id="@+id/card1"
android:layout_width="match_parent"
android:layout_marginBottom="6dp">
<RelativeLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- [a RecyclerView as list with LinearLayout as header] -->
<include
android:id="@+id/table"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/total_label"
android:layout_below="@id/label"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp" />
<!-- [a LinearLayout with 2 TextViews] -->
<include
android:id="@+id/total_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
</RelativeLayout>
</android.support.v7.widget.CardView>
我尝试实现的是点击标签TextView
并隐藏卡中的其他内容(仅显示其标签组件)。再次,通过进一步单击标签,展开隐藏的内容。我可以这样做,如果我明确设置卡的高度,即400dp
,但是如果用作列表的包含RecyclerView
没有多少项(所有都是相同的高度和固定),那么很空的空间。卡片如何包装其内容? (当我在卡片的wrap_content
中使用layout_height
时,只有标签可见。)
这是卡片的点击处理程序:
card.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Resources r = getResources();
if (toggle) {
tableContainer.setVisibility(View.GONE);
totalLabel.setVisibility(View.GONE);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
float pxTopMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, getResources().getDimension(R.dimen.card_list_margin_top), r.getDisplayMetrics());
params.topMargin = (int) pxTopMargin;
card.setLayoutParams(params);
card.setVisibility(View.GONE);
card.setVisibility(View.VISIBLE);
} else {
tableContainer.setVisibility(View.VISIBLE);
totalLabel.setVisibility(View.VISIBLE);
float pxHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 400, r.getDisplayMetrics());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) pxHeight);
float pxTopMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, getResources().getDimension(R.dimen.card_list_margin_top), r.getDisplayMetrics());
params.topMargin = (int) pxTopMargin;
params.bottomMargin = (int) pxTopMargin;
card.setLayoutParams(params);
card.setVisibility(View.GONE);
card.setVisibility(View.VISIBLE);
}
toggle = !toggle;
}
});
确定问题实际上是使用具有属性的RecyclerView
创建列表:
要显示的特定行数(即15),如果低,则将其内容包装
我从other SO thread(由 se.solovyev 评论)发现了一个自定义线性布局并进行了一些编辑。结果令人满意(虽然我实际上并不知道在那里做了什么非常糟糕的事情,但有效)。
package com.trafficbroker.trafficbroker.views;
import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import java.lang.reflect.Field;
/**
* {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always
* wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters.
* <p/>
* Now it's impossible to run add/remove animations with child views which have arbitrary dimensions (height for
* VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions
* {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be.
* If animations are not used at all then a normal measuring procedure will run and child views will be measured during
* the measure pass.
*/
public class WrappingLinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {
private static boolean canMakeInsetsDirty = true;
private static Field insetsDirtyField = null;
private static final int CHILD_WIDTH = 0;
private static final int CHILD_HEIGHT = 1;
private static final int DEFAULT_CHILD_SIZE = 100;
private final int[] childDimensions = new int[2];
private final RecyclerView view;
private int childSize = DEFAULT_CHILD_SIZE;
private boolean hasChildSize;
private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;
private final Rect tmpRect = new Rect();
@SuppressWarnings("UnusedDeclaration")
public WrappingLinearLayoutManager(Context context) {
super(context);
this.view = null;
}
@SuppressWarnings("UnusedDeclaration")
public WrappingLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
this.view = null;
}
@SuppressWarnings("UnusedDeclaration")
public WrappingLinearLayoutManager(RecyclerView view) {
super(view.getContext());
this.view = view;
this.overScrollMode = ViewCompat.getOverScrollMode(view);
}
@SuppressWarnings("UnusedDeclaration")
public WrappingLinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) {
super(view.getContext(), orientation, reverseLayout);
this.view = view;
this.overScrollMode = ViewCompat.getOverScrollMode(view);
}
public void setOverScrollMode(int overScrollMode) {
if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER)
throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode);
if (this.view == null) throw new IllegalStateException("view == null");
this.overScrollMode = overScrollMode;
ViewCompat.setOverScrollMode(view, overScrollMode);
}
public static int makeUnspecifiedSpec() {
return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
}
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED;
final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED;
final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;
final int unspecified = makeUnspecifiedSpec();
if (exactWidth && exactHeight) {
// in case of exact calculations for both dimensions let's use default "onMeasure" implementation
super.onMeasure(recycler, state, widthSpec, heightSpec);
return;
}
final boolean vertical = getOrientation() == VERTICAL;
initChildDimensions(widthSize, heightSize, vertical);
int width = 0;
int height = 0;
// it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This
// happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the
// recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never
// called whiles scrolling)
recycler.clear();
final int stateItemCount = state.getItemCount();
final int adapterItemCount = (getItemCount() < 15 ? getItemCount() : 15);//getItemCount(); // MAX VISIBLE ROWS
// adapter always contains actual data while state might contain old data (f.e. data before the animation is
// done). As we want to measure the view with actual data we must use data from the adapter and not from the
// state
for (int i = 0; i < adapterItemCount; i++) {
if (vertical) {
if (!hasChildSize) {
if (i < stateItemCount) {
// we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
// we will use previously calculated dimensions
measureChild(recycler, i, widthSize, unspecified, childDimensions);
} else {
logMeasureWarning(i);
}
}
height += childDimensions[CHILD_HEIGHT];
if (i == 0) {
width = childDimensions[CHILD_WIDTH];
}
if (hasHeightSize && height >= heightSize) {
break;
}
} else {
if (!hasChildSize) {
if (i < stateItemCount) {
// we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
// we will use previously calculated dimensions
measureChild(recycler, i, unspecified, heightSize, childDimensions);
} else {
logMeasureWarning(i);
}
}
width += childDimensions[CHILD_WIDTH];
if (i == 0) {
height = childDimensions[CHILD_HEIGHT];
}
if (hasWidthSize && width >= widthSize) {
break;
}
}
}
if (exactWidth) {
width = widthSize;
} else {
width += getPaddingLeft() + getPaddingRight();
if (hasWidthSize) {
width = Math.min(width, widthSize);
}
}
if (exactHeight) {
height = heightSize;
} else {
height += getPaddingTop() + getPaddingBottom();
if (hasHeightSize) {
height = Math.min(height, heightSize);
}
}
setMeasuredDimension(width, height);
if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {
final boolean fit = (vertical && (!hasHeightSize || height < heightSize))
|| (!vertical && (!hasWidthSize || width < widthSize));
ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS);
}
}
private void logMeasureWarning(int child) {
if (BuildConfig.DEBUG) {
Log.w("LinearLayoutManager", "Can't measure child #" + child + ", previously used dimensions will be reused." +
"To remove this message either use #setChildSize() method or don't run RecyclerView animations");
}
}
private void initChildDimensions(int width, int height, boolean vertical) {
if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
// already initialized, skipping
return;
}
if (vertical) {
childDimensions[CHILD_WIDTH] = width;
childDimensions[CHILD_HEIGHT] = childSize;
} else {
childDimensions[CHILD_WIDTH] = childSize;
childDimensions[CHILD_HEIGHT] = height;
}
}
@Override
public void setOrientation(int orientation) {
// might be called before the constructor of this class is called
//noinspection ConstantConditions
if (childDimensions != null) {
if (getOrientation() != orientation) {
childDimensions[CHILD_WIDTH] = 0;
childDimensions[CHILD_HEIGHT] = 0;
}
}
super.setOrientation(orientation);
}
public void clearChildSize() {
hasChildSize = false;
setChildSize(DEFAULT_CHILD_SIZE);
}
public void setChildSize(int childSize) {
hasChildSize = true;
if (this.childSize != childSize) {
this.childSize = childSize;
requestLayout();
}
}
private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize, int[] dimensions) {
final View child;
try {
child = recycler.getViewForPosition(position);
} catch (IndexOutOfBoundsException e) {
if (BuildConfig.DEBUG) {
Log.w("LinearLayoutManager", "LinearLayoutManager doesn't work well with animations. Consider switching them off", e);
}
return;
}
final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();
final int hPadding = getPaddingLeft() + getPaddingRight();
final int vPadding = getPaddingTop() + getPaddingBottom();
final int hMargin = p.leftMargin + p.rightMargin;
final int vMargin = p.topMargin + p.bottomMargin;
// we must make insets dirty in order calculateItemDecorationsForChild to work
makeInsetsDirty(p);
// this method should be called before any getXxxDecorationXxx() methods
calculateItemDecorationsForChild(child, tmpRect);
final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);
final int childWidthSpec = getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
final int childHeightSpec = getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height, canScrollVertically());
child.measure(childWidthSpec, childHeightSpec);
dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;
// as view is recycled let's not keep old measured values
makeInsetsDirty(p);
recycler.recycleView(child);
}
private static void makeInsetsDirty(RecyclerView.LayoutParams p) {
if (!canMakeInsetsDirty) {
return;
}
try {
if (insetsDirtyField == null) {
insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty");
insetsDirtyField.setAccessible(true);
}
insetsDirtyField.set(p, true);
} catch (NoSuchFieldException e) {
onMakeInsertDirtyFailed();
} catch (IllegalAccessException e) {
onMakeInsertDirtyFailed();
}
}
private static void onMakeInsertDirtyFailed() {
canMakeInsetsDirty = false;
if (BuildConfig.DEBUG) {
Log.w("LinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
}
}
}
答案 0 :(得分:0)
我做了一个尽可能接近你的例子,但关键似乎是
card.requestLayout();
这就是我对CardView的看法
<android.support.v7.widget.CardView
android:id="@+id/card1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp">
<RelativeLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<!-- [a RecyclerView as list with LinearLayout as header] -->
<include
layout="@layout/table_container"
android:id="@+id/table"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/label"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp" />
<!-- [a LinearLayout with 2 TextViews] -->
<include
layout="@layout/total_label"
android:id="@+id/total_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/table" />
</RelativeLayout>
</android.support.v7.widget.CardView>
这是我为听众所做的事情
card.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (toggle) {
tableContainer.setVisibility(View.GONE);
totalLabel.setVisibility(View.GONE);
//ViewGroup.LayoutParams params = tableContainer.getLayoutParams();
//params.height = 1500;
//tableContainer.setLayoutParams(params);
} else {
tableContainer.setVisibility(View.VISIBLE);
totalLabel.setVisibility(View.VISIBLE);
//ViewGroup.LayoutParams params = tableContainer.getLayoutParams();
//params.height = 1500;
//tableContainer.setLayoutParams(params);
}
toggle = !toggle;
card.requestLayout();
}
});
以下是包含标记的布局之一的示例。
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent"
>
<TextView
android:id="@+id/total_labeltv"
android:layout_width="match_parent"
android:text="total label"
android:background="#C0c0c0"
android:layout_height="wrap_content">
</TextView></RelativeLayout>