Android 加载数据后,Recyclerview项目高度发生变化
在我的应用程序中,我将文本范围与ImageSpan和text一起使用。将异步解析文本,并相应地插入/替换ImageSpans。可能有一个或多个ImageSpans,或者根本没有 我如何事先计算包含ImageSpans的最终文本的大小 我遇到的问题是,当我最终更新RecyclerView项中的TextView时,整个视图“跳跃”。您可以想象,如果在不同的时间设置了许多列表项,那么列表就会出现跳跃 我想通过预先设置文本视图的大小来消除“跳转”,当文本显示时,项目的大小不会改变,列表也不会跳转Android 加载数据后,Recyclerview项目高度发生变化,android,layout,android-recyclerview,placeholder,Android,Layout,Android Recyclerview,Placeholder,在我的应用程序中,我将文本范围与ImageSpan和text一起使用。将异步解析文本,并相应地插入/替换ImageSpans。可能有一个或多个ImageSpans,或者根本没有 我如何事先计算包含ImageSpans的最终文本的大小 我遇到的问题是,当我最终更新RecyclerView项中的TextView时,整个视图“跳跃”。您可以想象,如果在不同的时间设置了许多列表项,那么列表就会出现跳跃 我想通过预先设置文本视图的大小来消除“跳转”,当文本显示时,项目的大小不会改变,列表也不会跳转 任何
任何帮助或建议都将不胜感激 因为我们不知道在完成之前加载的文本的大小,所以我们唯一的选择是为该文本保留一些区域 这可以通过使TextView或其父对象(不是已知文本的父对象)的大小固定而轻松实现
还建议:
- 制作文本
- 设置一个占位符
您可以找到这个库。我最终通过创建自定义元素,在运行时(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());
}
}
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);
}
}
}
<resources>
<!--Emojis-->
<attr name="emojiSize" format="dimension" />
<declare-styleable name="EmojiTextView">
<attr name="emojiSize" />
</declare-styleable>
</resources>
小片段,以防链接不再工作:
publicstaticvoidaddemojis(上下文上下文、可扩展文本、int表情大小、int索引、int长度){
int textLength=text.length();
int textLengthToProcessMax=textLength-index;
int textLengthToProcess=length<0 | | length>=textLengthToProcessMax?textLength:(长度+索引);
//删除所有文本中的跨距
EmojiconSpan[]oldSpans=text.getspan(0,textLength,EmojiconSpan.class);
对于(int i=0;i0xff){
icon=getEmojiResource(上下文,unicode);
}
如果(图标==0&&i+跳过0){
text.setSpan(新的EmojiconSpan(上下文、图标、表情大小)、i、i+skip、Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
谢谢,这实际上帮了我很多忙。我想测量预解析的文本,然后将TextView大小设置为测量值。解析的文本不应该比这个大很多。我很高兴我能帮上忙:-)
<resources>
<!--Emojis-->
<attr name="emojiSize" format="dimension" />
<declare-styleable name="EmojiTextView">
<attr name="emojiSize" />
</declare-styleable>
</resources>
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);
}
}
}