私はDialogFragment
(カードのリスト)を含むRecyclerView
を持っています。
このRecyclerView
内には、任意の高さを持つことができる1つ以上のCardViews
があります。
に含まれるDialogFragment
に基づいて、このCardViews
を正しい高さにしたいです。
通常これは簡単です、私はこのようにRecyclerView
にwrap_content
を設定します。
<Android.support.v7.widget.RecyclerView ...
xmlns:tools="http://schemas.Android.com/tools"
Android:id="@+id/recycler_view"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:clickable="true"
Android:scrollbars="vertical" >
</Android.support.v7.widget.RecyclerView>
私はRecyclerView
を使っているのでこれはうまくいきません:
https://issuetracker.google.com/issues/37001674
そして
入れ子になったRecyclerのビューの高さがその内容を折り返さない
どちらのページでも、LinearLayoutManager
を拡張してonMeasure()
をオーバーライドすることを提案しています。
私は最初に誰かが最初のリンクで提供したLayoutManagerを使いました:
public static class WrappingLayoutManager extends LinearLayoutManager {
public WrappingLayoutManager(Context context) {
super(context);
}
private int[] mMeasuredDimension = new int[2];
@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);
measureScrapChild(recycler, 0,
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
int width = mMeasuredDimension[0];
int height = mMeasuredDimension[1];
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
case View.MeasureSpec.AT_MOST:
width = widthSize;
break;
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
case View.MeasureSpec.AT_MOST:
height = heightSize;
break;
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth();
measuredDimension[1] = view.getMeasuredHeight();
recycler.recycleView(view);
}
}
}
しかしこれはうまくいきませんでした
heightSize = View.MeasureSpec.getSize(heightSpec);
match_parent
に関連していると思われる非常に大きな値を返します。
height = heightSize;
(2番目のスイッチの場合)をコメントすることによって、私は高さをうまく働かせることができましたが、TextView
の中のCardView
の子がそれ自身のテキスト(長い文章)を折り返さない場合だけです。
そのTextView
がそれ自身のテキストをラップするとすぐに高さは増すべきですが、そうではありません。その長い文の高さは、折り返された行ではなく1行として計算されました(2行以上)。
私のLayoutManager
がWRAP_CONTENT
と連携するように、このRecyclerView
をどのように改善すべきかについて何かアドバイスはありますか?
編集:このレイアウトマネージャはたいていの人のために働くかもしれません、しかしそれはスクロールとテキストビューの折り返しの高さを計算することにまだ問題があります
public class MyLinearLayoutManager extends LinearLayoutManager {
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
@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);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}
Android Support Library 23.2.1 updateから、すべてのWRAP_CONTENTが正しく機能するはずです。
さらにgradle
ファイルまたはでライブラリのバージョンを更新してください。
compile 'com.Android.support:recyclerview-v7:23.2.1'
のようないくつかの問題を解決しました。さまざまなメジャースペックメソッドに関連するバグを修正しました
http://developer.Android.com/tools/support-library/features.html#v7-recyclerview を確認してください。
これはうまくいっているようで、他の解決策が持っている問題を欠いているクラスの改良版です:
package org.solovyev.Android.views.llm;
import Android.content.Context;
import Android.support.v7.widget.RecyclerView;
import Android.util.Log;
import Android.view.View;
/**
* {@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.
*
* 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 LinearLayoutManager extends Android.support.v7.widget.LinearLayoutManager {
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 int childSize = DEFAULT_CHILD_SIZE;
private boolean hasChildSize;
@SuppressWarnings("UnusedDeclaration")
public LinearLayoutManager(Context context) {
super(context);
}
@SuppressWarnings("UnusedDeclaration")
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
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 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();
// 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, widthSpec, unspecified, childDimensions);
} else {
logMeasureWarning(i);
}
}
height += childDimensions[CHILD_HEIGHT];
if (i == 0) {
width = childDimensions[CHILD_WIDTH];
}
if (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, heightSpec, childDimensions);
} else {
logMeasureWarning(i);
}
}
width += childDimensions[CHILD_WIDTH];
if (i == 0) {
height = childDimensions[CHILD_HEIGHT];
}
if (width >= widthSize) {
break;
}
}
}
if ((vertical && height < heightSize) || (!vertical && width < widthSize)) {
// we really should wrap the contents of the view, let's do it
if (exactWidth) {
width = widthSize;
} else {
width += getPaddingLeft() + getPaddingRight();
}
if (exactHeight) {
height = heightSize;
} else {
height += getPaddingTop() + getPaddingBottom();
}
setMeasuredDimension(width, height);
} else {
// if calculated height/width exceeds requested height/width let's use default "onMeasure" implementation
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
}
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 widthSpec, int heightSpec, int[] dimensions) {
final View child = recycler.getViewForPosition(position);
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;
final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);
final int childWidthSpec = getChildMeasureSpec(widthSpec, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
final int childHeightSpec = getChildMeasureSpec(heightSpec, 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;
recycler.recycleView(child);
}
}
これは ライブラリ としても利用可能です。関連クラスへのリンク。
最後にこの問題の解決策を見つけました。
あなたがする必要があるのはRecyclerView
をRelativeLayout
でラップすることです。多分他にも動作する可能性のあるビューがあります。
<RelativeLayout
Android:layout_width="match_parent"
Android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
Android:id="@+id/list"
Android:layout_width="match_parent"
Android:layout_height="wrap_content" />
</RelativeLayout>
UPDATE
Android Support Library 23.2アップデートでは、すべてのWRAP_CONTENTが正しく機能するはずです。
Gradle fileでライブラリのバージョンを更新してください。
compile 'com.Android.support:recyclerview-v7:23.2.0'
元の答え
他の質問で答えたように、あなたのリサイクル業者のビューの高さがスクリーンの高さより大きいとき、あなたはオリジナルのonMeasure()メソッドを使う必要があります。このレイアウトマネージャはItemDecorationを計算し、さらにスクロールすることができます。
public class MyLinearLayoutManager extends LinearLayoutManager {
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
@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);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
// If child view is more than screen size, there is no need to make it wrap content. We can use original onMeasure() so we can scroll view.
if (height < heightSize && width < widthSize) {
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
} else {
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
// For adding Item Decor Insets to view
super.measureChildWithMargins(view, 0, 0);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom() + getPaddingBottom() + getDecoratedBottom(view) , p.height);
view.measure(childWidthSpec, childHeightSpec);
// Get decorated measurements
measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}
これがモノラルAndroid用のc#バージョンです
/*
* Ported by Jagadeesh Govindaraj (@jaganjan)
*Copyright 2015 serso aka se.solovyev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.Apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Contact details
*
* Email: se.solovyev @gmail.com
* Site: http://se.solovyev.org
*/
using Android.Content;
using Android.Graphics;
using Android.Support.V4.View;
using Android.Support.V7.Widget;
using Android.Util;
using Android.Views;
using Java.Lang;
using Java.Lang.Reflect;
using System;
using Math = Java.Lang.Math;
namespace Droid.Helper
{
public class WrapLayoutManager : LinearLayoutManager
{
private const int DefaultChildSize = 100;
private static readonly Rect TmpRect = new Rect();
private int _childSize = DefaultChildSize;
private static bool _canMakeInsetsDirty = true;
private static readonly int[] ChildDimensions = new int[2];
private const int ChildHeight = 1;
private const int ChildWidth = 0;
private static bool _hasChildSize;
private static Field InsetsDirtyField = null;
private static int _overScrollMode = ViewCompat.OverScrollAlways;
private static RecyclerView _view;
public WrapLayoutManager(Context context, int orientation, bool reverseLayout)
: base(context, orientation, reverseLayout)
{
_view = null;
}
public WrapLayoutManager(Context context) : base(context)
{
_view = null;
}
public WrapLayoutManager(RecyclerView view) : base(view.Context)
{
_view = view;
_overScrollMode = ViewCompat.GetOverScrollMode(view);
}
public WrapLayoutManager(RecyclerView view, int orientation, bool reverseLayout)
: base(view.Context, orientation, reverseLayout)
{
_view = view;
_overScrollMode = ViewCompat.GetOverScrollMode(view);
}
public void SetOverScrollMode(int overScrollMode)
{
if (overScrollMode < ViewCompat.OverScrollAlways || overScrollMode > ViewCompat.OverScrollNever)
throw new ArgumentException("Unknown overscroll mode: " + overScrollMode);
if (_view == null) throw new ArgumentNullException(nameof(_view));
_overScrollMode = overScrollMode;
ViewCompat.SetOverScrollMode(_view, overScrollMode);
}
public static int MakeUnspecifiedSpec()
{
return View.MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified);
}
public override void OnMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec,
int heightSpec)
{
var widthMode = View.MeasureSpec.GetMode(widthSpec);
var heightMode = View.MeasureSpec.GetMode(heightSpec);
var widthSize = View.MeasureSpec.GetSize(widthSpec);
var heightSize = View.MeasureSpec.GetSize(heightSpec);
var hasWidthSize = widthMode != MeasureSpecMode.Unspecified;
var hasHeightSize = heightMode != MeasureSpecMode.Unspecified;
var exactWidth = widthMode == MeasureSpecMode.Exactly;
var exactHeight = heightMode == MeasureSpecMode.Exactly;
var unspecified = MakeUnspecifiedSpec();
if (exactWidth && exactHeight)
{
// in case of exact calculations for both dimensions let's use default "onMeasure" implementation
base.OnMeasure(recycler, state, widthSpec, heightSpec);
return;
}
var vertical = Orientation == Vertical;
InitChildDimensions(widthSize, heightSize, vertical);
var width = 0;
var 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();
var stateItemCount = state.ItemCount;
var adapterItemCount = ItemCount;
// 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 (var 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[ChildHeight];
if (i == 0)
{
width = ChildDimensions[ChildWidth];
}
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[ChildWidth];
if (i == 0)
{
height = ChildDimensions[ChildHeight];
}
if (hasWidthSize && width >= widthSize)
{
break;
}
}
}
if (exactWidth)
{
width = widthSize;
}
else
{
width += PaddingLeft + PaddingRight;
if (hasWidthSize)
{
width = Math.Min(width, widthSize);
}
}
if (exactHeight)
{
height = heightSize;
}
else
{
height += PaddingTop + PaddingBottom;
if (hasHeightSize)
{
height = Math.Min(height, heightSize);
}
}
SetMeasuredDimension(width, height);
if (_view == null || _overScrollMode != ViewCompat.OverScrollIfContentScrolls) return;
var fit = (vertical && (!hasHeightSize || height < heightSize))
|| (!vertical && (!hasWidthSize || width < widthSize));
ViewCompat.SetOverScrollMode(_view, fit ? ViewCompat.OverScrollNever : ViewCompat.OverScrollAlways);
}
private void LogMeasureWarning(int child)
{
#if DEBUG
Log.WriteLine(LogPriority.Warn, "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");
#endif
}
private void InitChildDimensions(int width, int height, bool vertical)
{
if (ChildDimensions[ChildWidth] != 0 || ChildDimensions[ChildHeight] != 0)
{
// already initialized, skipping
return;
}
if (vertical)
{
ChildDimensions[ChildWidth] = width;
ChildDimensions[ChildHeight] = _childSize;
}
else
{
ChildDimensions[ChildWidth] = _childSize;
ChildDimensions[ChildHeight] = height;
}
}
public void ClearChildSize()
{
_hasChildSize = false;
SetChildSize(DefaultChildSize);
}
public void SetChildSize(int size)
{
_hasChildSize = true;
if (_childSize == size) return;
_childSize = size;
RequestLayout();
}
private void MeasureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize,
int[] dimensions)
{
View child = null;
try
{
child = recycler.GetViewForPosition(position);
}
catch (IndexOutOfRangeException e)
{
Log.WriteLine(LogPriority.Warn, "LinearLayoutManager",
"LinearLayoutManager doesn't work well with animations. Consider switching them off", e);
}
if (child != null)
{
var p = child.LayoutParameters.JavaCast<RecyclerView.LayoutParams>()
var hPadding = PaddingLeft + PaddingRight;
var vPadding = PaddingTop + PaddingBottom;
var hMargin = p.LeftMargin + p.RightMargin;
var 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);
var hDecoration = GetRightDecorationWidth(child) + GetLeftDecorationWidth(child);
var vDecoration = GetTopDecorationHeight(child) + GetBottomDecorationHeight(child);
var childWidthSpec = GetChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.Width,
CanScrollHorizontally());
var childHeightSpec = GetChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.Height,
CanScrollVertically());
child.Measure(childWidthSpec, childHeightSpec);
dimensions[ChildWidth] = GetDecoratedMeasuredWidth(child) + p.LeftMargin + p.RightMargin;
dimensions[ChildHeight] = 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)
{
var klass = Java.Lang.Class.FromType (typeof (RecyclerView.LayoutParams));
InsetsDirtyField = klass.GetDeclaredField("mInsetsDirty");
InsetsDirtyField.Accessible = true;
}
InsetsDirtyField.Set(p, true);
}
catch (NoSuchFieldException e)
{
OnMakeInsertDirtyFailed();
}
catch (IllegalAccessException e)
{
OnMakeInsertDirtyFailed();
}
}
private static void OnMakeInsertDirtyFailed()
{
_canMakeInsetsDirty = false;
#if DEBUG
Log.Warn("LinearLayoutManager",
"Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
#endif
}
}
}
RecyclerView
はwrap_content
で23.2.0
のサポートを追加しました。これはバグがあり、23.2.1は安定していたので、次のように使用できます。
compile 'com.Android.support:recyclerview-v7:24.2.0'
改訂履歴はこちらで確認できます。
https://developer.Android.com/topic/libraries/support-library/revisions.html
注:
また、サポートライブラリを更新した後、RecyclerView
はwrap_content
だけでなくmatch_parent
も尊重するので、RecyclerView
のItem Viewがmatch_parent
として設定されている場合、単一のビューは画面全体を埋めます。
スクロールとテキストの折り返しの問題は、このコードが幅と高さの両方がwrap_content
に設定されていると仮定していることです。しかし、LayoutManager
は水平幅が制限されていることを知る必要があります。そのため、それぞれの子ビューに対して独自のwidthSpec
を作成する代わりに、元のwidthSpec
を使用するだけです。
@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);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
widthSpec,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(widthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
この ポスト に記載されているように、これはバージョン23.2でリリースしたときに動作します。公式ブログポストの引用
このリリースはLayoutManager APIにエキサイティングな新機能をもたらします:自動測定!これにより、RecyclerViewはその内容のサイズに基づいて自分自身のサイズを変更できます。これは、RecyclerViewのディメンションにWRAP_CONTENTを使用するなど、以前は利用できなかったシナリオが可能になったことを意味します。内蔵のLayoutManagerはすべて自動測定をサポートしています。
これを試してください(これは厄介な解決策ですが、うまくいくかもしれません):あなたのonCreate
のActivity
メソッド、またはあなたのフラグメントのonViewCreated
メソッドの中で。次のように、RecyclerView
が最初にレンダリングされたときに呼び出されるようにコールバックを設定します。
vRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
calculeRecyclerViewFullHeight();
}
});
calculeRecyclerViewFullHeight
では、その子の身長に基づいてRecyclerView
フルハイトを計算します。
protected void calculateSwipeRefreshFullHeight() {
int height = 0;
for (int idx = 0; idx < getRecyclerView().getChildCount(); idx++ ) {
View v = getRecyclerView().getChildAt(idx);
height += v.getHeight();
}
SwipeRefreshLayout.LayoutParams params = getSwipeRefresh().getLayoutParams();
params.height = height;
getSwipeRefresh().setLayoutParams(params);
}
私の場合、私のRecyclerView
はSwipeRefreshLayout
に含まれていますが、SwipeRefreshView
ではなくRecyclerView
に設定していますが、SwipeRefreshView
がない場合は代わりにRecyclerView
に設定できます。
これがあなたを助けたかどうか私に知らせてください。
このコメントは私を助けてくれました。Put the recyclerview in any other layout (Relative layout is preferable). Then change recyclerview's height/width as match parent to that layout and set the parent layout's height/width as wrap content.
上記のソリューションのいくつかを使用しましたが、width
で機能していましたが、height
で機能していました。
compileSdkVersion
が2より大きい場合、2のように、リサイクラービューのそれぞれのサポートライブラリで提供されているRecyclerViewを直接使用できます。 = 'com.Android.support:recyclerview-v7:23.2.1'
になります。これらはサポートライブラリ幅と高さの両方でwrap_content
の属性をサポートします。依存関係に追加する必要があります
compile 'com.Android.support:recyclerview-v7:23.2.1'
compileSdkVersion
が2より小さい場合、以下の解決策を使用できます。私はこれを見つけました Googleスレッド この問題に関して。このスレッドには、 LinearLayoutManager の実装につながる貢献が1つあります。
heightとwidthの両方でテストしましたが、どちらの場合でも問題なく動作しました。
/*
* Copyright 2015 serso aka se.solovyev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.Apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Contact details
*
* Email: [email protected]
* Site: http://se.solovyev.org
*/
package org.solovyev.Android.views.llm;
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 LinearLayoutManager 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 LinearLayoutManager(Context context) {
super(context);
this.view = null;
}
@SuppressWarnings("UnusedDeclaration")
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
this.view = null;
}
@SuppressWarnings("UnusedDeclaration")
public LinearLayoutManager(RecyclerView view) {
super(view.getContext());
this.view = view;
this.overScrollMode = ViewCompat.getOverScrollMode(view);
}
@SuppressWarnings("UnusedDeclaration")
public LinearLayoutManager(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();
// 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");
}
}
}
ライブラリを使用する代わりに、新しいバージョンが出るまでの最も簡単な解決策は、単に b.Android.com/74772 を開くことです。これまでに知られている最良の解決策を簡単に見つけることができます。
シモンズ: b.Android.com/74772#c50 私のために働いた
アダプタビューホルダのonCreateViewHolderメソッドで、親ビューグループの代わりにnull値でビューを更新します。
@Override
public AdapterItemSku.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflator.inflate(R.layout.layout_item, null, false);
return new MyViewHolder(view);
}
ご使用のRecyclerViewをNestedScrollView内に配置するだけです。完全に動作します
<Android.support.v4.widget.NestedScrollView
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:layout_marginTop="10dp"
Android:layout_marginBottom="25dp">
<Android.support.v7.widget.RecyclerView
Android:id="@+id/kliste"
Android:layout_width="match_parent"
Android:layout_height="match_parent" />
</Android.support.v4.widget.NestedScrollView>
このレイアウトをRecyclerView をGridViewレイアウトとして使用しない方
GridLayoutManagerをこのように使うだけです。
RecyclerView.LayoutManager mRecyclerGrid=new GridLayoutManager(this,3,LinearLayoutManager.VERTICAL,false);
mRecyclerView.setLayoutManager(mRecyclerGrid);
1行に表示する項目数を設定できます(3を置き換えます)。
コードに従うようにmeasureScrapChild
を置き換えます。
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension)
{
View view = recycler.GetViewForPosition(position);
if (view != null)
{
MeasureChildWithMargins(view, widthSpec, heightSpec);
measuredDimension[0] = view.MeasuredWidth;
measuredDimension[1] = view.MeasuredHeight;
recycler.RecycleView(view);
}
}
私はxamarinを使っているので、これはc#コードです。これは簡単にJavaに「翻訳」できると思います。
他のレイアウトにリサイクルビューを配置することをお勧めします(相対レイアウトが望ましい)。次に、そのレイアウトの親と一致するようにrecyclerviewの高さ/幅を変更し、親レイアウトの高さ/幅を折り返しの内容として設定します。わたしにはできる
あなたはメインビューとしてFrameLayoutを入れて、それからScrollViewと少なくともあなたのRecyclerViewでRelativeLayoutの中に入れなければなりません、それは私のために働きます。
ここでの本当のトリックはRelativeLayoutです...
お力になれて、嬉しいです。
これが新しい初心者に役立つことを願っています、私は公開されているgithub Gistリンクに触れました
https://Gist.github.com/Abhishekgarg727/72d29bb4996809b2e097037c8839249
<script src="https://Gist.github.com/Abhishekgarg727/72d29bb4996809b2e097037c88392493.js"></script>