加载数据后 Recyclerview 项目高度更改
Posted
技术标签:
【中文标题】加载数据后 Recyclerview 项目高度更改【英文标题】:Recyclerview item-height changes after loading data 【发布时间】:2018-12-31 17:11:20 【问题描述】:我在我的应用程序中使用带有 ImageSpan 和文本的文本跨度。文本被异步解析并相应地插入/替换 ImageSpan。可能有一个或多个 ImageSpan,或者根本没有。
如何预先计算包含 ImageSpan 的最终文本将占用的大小?
我遇到的问题是,当我最终更新 RecyclerView 项中的 TextView 时,整个视图“跳跃”。你可以想象有很多在不同时间设置的列表项,列表会出现跳跃。
我想通过预先设置TextView的大小来消除“跳转”,当文本显示时,item的大小不改变,列表不跳转。
任何帮助或建议将不胜感激。
【问题讨论】:
【参考方案1】:由于在加载完成之前我们不知道要加载的文本的大小,因此我们唯一的选择是为该文本保留一些区域。
这可以通过将 TextView 或其父级之一(不是已知文本的父级)固定大小来轻松完成。
这些也是推荐的:
将文字设为ellipsized。 设置占位符。占位符可以是简单的虚拟文本或更优雅的内容,如下图所示。
你可以找到这个库here。
【讨论】:
谢谢,这对我帮助很大。我有一个想法来测量预解析的文本,然后将 TextView 大小设置为测量值。解析后的文本不应该比这个大很多。 很高兴能帮上忙 :-)【参考方案2】:我最终通过创建自定义元素来计算运行时(onLayout)中子视图的宽度和高度。
此视图是文本和日期布局,它计算文本部分并防止文本与右下角的日期重叠:
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class TextDateLayout extends FrameLayout
private EmojiTextView lblMessage;
private LinearLayout llDateWrapper;
private LayoutParams lblMessageLayoutParams;
private int lblMessageWidth;
private int lblMessageHeight;
private LayoutParams llDateWrapperLayoutParams;
private int llDateWrapperWidth;
private int llDateWrapperHeight;
public TextDateLayout(@NonNull Context context)
super(context);
public TextDateLayout(@NonNull Context context, @Nullable AttributeSet attrs)
super(context, attrs);
public TextDateLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
try
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
lblMessage = (EmojiTextView) getChildAt(0);
llDateWrapper = (LinearLayout) getChildAt(1);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int pWidthSize = widthSize;
if (lblMessage == null || llDateWrapper == null || widthSize <= 0) return;
int availableWidth = widthSize - getPaddingLeft() - getPaddingRight();
lblMessageLayoutParams = (LayoutParams) lblMessage.getLayoutParams();
lblMessageWidth = lblMessage.getMeasuredWidth() + lblMessageLayoutParams.leftMargin + lblMessageLayoutParams.rightMargin;
lblMessageHeight = lblMessage.getMeasuredHeight() + lblMessageLayoutParams.topMargin + lblMessageLayoutParams.bottomMargin;
llDateWrapperLayoutParams = (LayoutParams) llDateWrapper.getLayoutParams();
llDateWrapperWidth = llDateWrapper.getMeasuredWidth() + llDateWrapperLayoutParams.leftMargin + llDateWrapperLayoutParams.rightMargin;
llDateWrapperHeight = llDateWrapper.getMeasuredHeight() + llDateWrapperLayoutParams.topMargin + llDateWrapperLayoutParams.bottomMargin;
int lblMessageLineCount = lblMessage.getLineCount();
float lblMessageLastLineWidth = lblMessageLineCount > 0 ? lblMessage.getLayout().getLineWidth(lblMessageLineCount - 1) : 0;
widthSize = getPaddingLeft() + getPaddingRight();
int heightSize = getPaddingTop() + getPaddingBottom();
if (lblMessageLineCount > 1 && lblMessageLastLineWidth + llDateWrapperWidth < lblMessage.getMeasuredWidth())
widthSize += lblMessageWidth;
heightSize += lblMessageHeight;
else if (lblMessageLineCount > 1 && lblMessageLastLineWidth + llDateWrapperWidth > availableWidth)
widthSize += lblMessageWidth;
heightSize += lblMessageHeight + llDateWrapperHeight;
else if (lblMessageLineCount == 1 && lblMessageWidth + llDateWrapperWidth > pWidthSize)
widthSize = pWidthSize;
heightSize += lblMessageHeight + llDateWrapperHeight;
else if (lblMessageLineCount == 1 && lblMessageWidth + llDateWrapperWidth < getMeasuredWidth())
widthSize = getMeasuredWidth();
heightSize += lblMessageHeight;
else
widthSize += lblMessageWidth + llDateWrapperWidth;
heightSize += lblMessageHeight;
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
catch (Exception ex)
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
super.onLayout(changed, left, top, right, bottom);
if (lblMessage == null || llDateWrapper == null) return;
lblMessage.layout(getPaddingLeft(),
getPaddingTop(),
lblMessage.getWidth() + getPaddingLeft(),
lblMessage.getHeight() + getPaddingTop());
使用它会是这样的:
<com.app.element.TextDateLayout
android:layout_
android:layout_
android:layout_margin="2dp">
<com.app.element.EmojiTextView
android:id="@+id/lblMessage"
android:layout_
android:layout_
android:layout_gravity="left"
android:textSize="14sp"
android:textColor="@color/black"
app:emojiSize="25dp" />
<LinearLayout
android:orientation="horizontal"
android:layout_
android:layout_
android:layout_marginTop="10dp"
android:layout_gravity="bottom|right"
android:paddingLeft="10dp"
android:gravity="center_vertical">
<com.app.element.TextView
android:id="@+id/lblTimestamp"
android:layout_
android:layout_
android:textSize="9sp"
android:textColor="@color/black" />
</LinearLayout>
</com.app.element.TextDateLayout>
EmojiTextView
:
import android.content.Context;
import android.content.res.TypedArray;
import android.text.SpannableStringBuilder;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatTextView;
import com.app.R;
import com.app.helpers.EmojiHelper;
public class EmojiTextView extends AppCompatTextView
private int emojiSize;
public EmojiTextView(Context context)
super(context);
init(null);
public EmojiTextView(Context context, AttributeSet attrs)
super(context, attrs);
init(attrs);
public EmojiTextView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
init(attrs);
private void init(AttributeSet attrs)
if (attrs != null)
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.EmojiTextView);
emojiSize = (int) a.getDimension(R.styleable.EmojiTextView_emojiSize, getTextSize());
a.recycle();
else
emojiSize = (int) getTextSize();
setText(getText());
@Override
public void setText(CharSequence text, BufferType type)
if (text != null && text.length() > 0)
SpannableStringBuilder builder = new SpannableStringBuilder(text);
EmojiHelper.getInstance().parseForEmojis(builder, emojiSize); //This takes the string and parses the Emojis, replacing the text with ImageSpans
super.setText(builder, type);
else
super.setText(text, type);
EmojiTextView
的属性:
<resources>
<!--Emojis-->
<attr name="emojiSize" format="dimension" />
<declare-styleable name="EmojiTextView">
<attr name="emojiSize" />
</declare-styleable>
</resources>
有关从 Unicode 字符中解析表情符号的示例,请查看此链接: https://github.com/ankushsachdeva/emojicon/blob/70bdd3731ebfc12a973d4125f5c5598015d96a62/lib/src/github/ankushsachdeva/emojicon/EmojiconHandler.java#L1396
小片段以防链接失效:
public static void addEmojis(Context context, Spannable text, int emojiSize, int index, int length)
int textLength = text.length();
int textLengthToProcessMax = textLength - index;
int textLengthToProcess = length < 0 || length >= textLengthToProcessMax ? textLength : (length+index);
// remove spans throughout all text
EmojiconSpan[] oldSpans = text.getSpans(0, textLength, EmojiconSpan.class);
for (int i = 0; i < oldSpans.length; i++)
text.removeSpan(oldSpans[i]);
int skip;
for (int i = index; i < textLengthToProcess; i += skip)
skip = 0;
int icon = 0;
char c = text.charAt(i);
if (isSoftBankEmoji(c))
icon = getSoftbankEmojiResource(c);
skip = icon == 0 ? 0 : 1;
if (icon == 0)
int unicode = Character.codePointAt(text, i);
skip = Character.charCount(unicode);
if (unicode > 0xff)
icon = getEmojiResource(context, unicode);
if (icon == 0 && i + skip < textLengthToProcess)
int followUnicode = Character.codePointAt(text, i + skip);
if (followUnicode == 0x20e3)
int followSkip = Character.charCount(followUnicode);
switch (unicode)
//...
skip += followSkip;
else
int followSkip = Character.charCount(followUnicode);
switch (unicode)
//...
skip += followSkip;
if (icon > 0)
text.setSpan(new EmojiconSpan(context, icon, emojiSize), i, i + skip, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
【讨论】:
以上是关于加载数据后 Recyclerview 项目高度更改的主要内容,如果未能解决你的问题,请参考以下文章