Recyclerview加载数据后item-height变化

Recyclerview item-height changes after loading data

我在我的应用程序中使用带有 ImageSpan 和文本的文本跨度。文本是异步解析的,因此 ImageSpans 是 inserted/replaced。可能有一个或多个 ImageSpans 或 none。

如何预先计算包含 ImageSpans 的最终文本将占用的大小?

我遇到的问题是,当我最终更新 RecyclerView 项目中的 TextView 时,整个视图 "jumps"。你可以想象一下,如果有很多在不同时间设置的列表项,列表就会出现跳跃。

我想通过预先设置TextView的大小来消除"jump"当文本显示时,项目的大小不会改变并且列表不会跳转。

如有任何帮助或建议,我们将不胜感激。

由于我们不知道在完成之前加载的文本的大小,我们唯一的选择是为该文本保留一些区域

这可以通过将 TextView 或其父项之一(不是已知文本的父项)固定大小来轻松完成。



还推荐这些:

占位符可以是简单的虚拟文本或更优雅的内容,如下图所示。

你可以找到这个图书馆here

我最终通过创建自定义元素得到了这个来计算运行时 (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_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="2dp">

    <com.app.element.EmojiTextView
        android:id="@+id/lblMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:textSize="14sp"
        android:textColor="@color/black"
        app:emojiSize="25dp" />

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        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_width="wrap_content"
            android:layout_height="wrap_content"
            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 字符解析表情符号的示例,请查看此 link: https://github.com/ankushsachdeva/emojicon/blob/70bdd3731ebfc12a973d4125f5c5598015d96a62/lib/src/github/ankushsachdeva/emojicon/EmojiconHandler.java#L1396

小片段 in-case link 不再有效:

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