【问题标题】:Recyclerview item-height changes after loading data加载数据后 Recyclerview 项目高度更改
【发布时间】:2018-12-31 17:11:20
【问题描述】:

我在我的应用程序中使用带有 ImageSpan 和文本的文本跨度。文本被异步解析并相应地插入/替换 ImageSpan。可能有一个或多个 ImageSpan,或者根本没有。

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

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

我想通过预先设置TextView的大小来消除“跳转”,当文本显示时,item的大小不改变,列表不跳转。

任何帮助或建议将不胜感激。

【问题讨论】:

    标签: android layout android-recyclerview placeholder


    【解决方案1】:

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

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



    这些也是推荐的:

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

    你可以找到这个库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_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 字符中解析表情符号的示例,请查看此链接: 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);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-24
      相关资源
      最近更新 更多