From 6ce311e8351de30e67ee724749c92901b4757ce0 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Wed, 9 Mar 2022 17:53:51 +0600 Subject: [PATCH] 5022-Fix_mailListItem_flicker_issue issue: https://gitlab.e.foundation/e/backlog/-/issues/5022 Custom `EllipsizingTextView` is executing expensive string operations (concat & substring) on every messageListItem's onDraw call. Everytime `notifyDataSetChanged` is called, this expensive opertion excuted & user can experience the flickering. the operation cannot be transfer to any other place (because it is adding the ellipsize for multiline textView & this is must done in onDraw method), replacing `EllipsizingTextView` with `TextView` resolve this issue. --- .../fsck/k9/fragment/MessageListAdapter.kt | 22 +-- .../com/fsck/k9/view/EllipsizingTextView.kt | 184 ------------------ .../src/main/res/layout/message_list_item.xml | 4 +- 3 files changed, 4 insertions(+), 206 deletions(-) delete mode 100644 app/ui/legacy/src/main/java/com/fsck/k9/view/EllipsizingTextView.kt diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListAdapter.kt b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListAdapter.kt index c56149f6c5..0c026017b3 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListAdapter.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListAdapter.kt @@ -7,9 +7,7 @@ import android.graphics.Color import android.graphics.Typeface import android.graphics.drawable.Drawable import android.text.Spannable -import android.text.SpannableStringBuilder import android.text.style.AbsoluteSizeSpan -import android.text.style.ForegroundColorSpan import android.text.style.StyleSpan import android.view.LayoutInflater import android.view.View @@ -47,7 +45,6 @@ class MessageListAdapter internal constructor( private val forwardedIcon: Drawable = theme.resolveDrawableAttribute(R.attr.messageListForwarded) private val answeredIcon: Drawable = theme.resolveDrawableAttribute(R.attr.messageListAnswered) private val forwardedAnsweredIcon: Drawable = theme.resolveDrawableAttribute(R.attr.messageListAnsweredForwarded) - private val previewTextColor: Int = theme.resolveColorAttribute(R.attr.messageListPreviewTextColor) private val activeItemBackgroundColor: Int = theme.resolveColorAttribute(R.attr.messageListActiveItemBackgroundColor) private val selectedItemBackgroundColor: Int = theme.resolveColorAttribute(R.attr.messageListSelectedBackgroundColor) private val readItemBackgroundColor: Int = theme.resolveColorAttribute(R.attr.messageListReadItemBackgroundColor) @@ -140,9 +137,9 @@ class MessageListAdapter internal constructor( val holder = view.tag as MessageViewHolder if (appearance.showContactPicture) { - if(isInSelectionMode) { + if (isInSelectionMode) { holder.contactPicture.isVisible = false - if(isSelected) { + if (isSelected) { holder.selected.setImageResource(R.drawable.ic_check_circle_large) } else { holder.selected.setImageResource(R.drawable.ic_non_check_circle_large) @@ -183,7 +180,6 @@ class MessageListAdapter internal constructor( if (appearance.previewLines > 0) { val preview = getPreview(isMessageEncrypted, previewText) holder.preview.setText(preview, TextView.BufferType.SPANNABLE) - formatPreviewText(holder.preview) } else { holder.preview.visibility = GONE } @@ -216,20 +212,6 @@ class MessageListAdapter internal constructor( addBeforePreviewSpan(displayText, displayText.length, messageRead) } - private fun formatPreviewText( - preview: TextView - ) { - val previewText = preview.text as Spannable - - // Set span (color) for preview message - previewText.setSpan( - ForegroundColorSpan(previewTextColor), - 0, - previewText.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - } - private fun addBeforePreviewSpan(text: Spannable, length: Int, messageRead: Boolean) { val fontSize = if (appearance.senderAboveSubject) { appearance.fontSizes.messageListSubject diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/view/EllipsizingTextView.kt b/app/ui/legacy/src/main/java/com/fsck/k9/view/EllipsizingTextView.kt deleted file mode 100644 index 7184338116..0000000000 --- a/app/ui/legacy/src/main/java/com/fsck/k9/view/EllipsizingTextView.kt +++ /dev/null @@ -1,184 +0,0 @@ -package com.fsck.k9.view - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.Canvas -import android.text.Layout -import android.text.StaticLayout -import android.text.TextUtils.TruncateAt -import android.util.AttributeSet -import android.widget.TextView -import androidx.appcompat.widget.AppCompatTextView - -class EllipsizingTextView : AppCompatTextView { - interface EllipsizeListener { - fun ellipsizeStateChanged(ellipsized: Boolean) - } - - private val ellipsizeListeners: MutableList = ArrayList() - var isEllipsized = false - private set - private var isStale = true - private var programmaticChange = false - private var fullText: String? = null - private var customLineSpacingMultiplier = 1.0f - private var lineAdditionalVerticalPadding = 0.0f - - constructor(context: Context?) : super(context!!) {} - constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) {} - constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super( - context!!, attrs, defStyle - ) { - } - - fun addEllipsizeListener(listener: EllipsizeListener?) { - if (listener == null) { - throw NullPointerException() - } - ellipsizeListeners.add(listener) - } - - fun removeEllipsizeListener(listener: EllipsizeListener) { - ellipsizeListeners.remove(listener) - } - - override fun setLineSpacing(add: Float, mult: Float) { - lineAdditionalVerticalPadding = add - customLineSpacingMultiplier = mult - super.setLineSpacing(add, mult) - } - - override fun onTextChanged(text: CharSequence, start: Int, before: Int, after: Int) { - super.onTextChanged(text, start, before, after) - if (!programmaticChange) { - fullText = text.toString() - isStale = true - } - } - - override fun onDraw(canvas: Canvas) { - if (isStale) { - resetText() - } - super.onDraw(canvas) - } - - @SuppressLint("DiscouragedPrivateApi") - override fun getMaxLines(): Int { - val textViewClassInstance = TextView::class.java - try { - val MaxMode = textViewClassInstance.getDeclaredField("mMaxMode") - MaxMode.isAccessible = true - val mMaxMode = MaxMode.getInt(this) - val Maximum = textViewClassInstance.getDeclaredField("mMaximum") - Maximum.isAccessible = true - val mMaximum = Maximum.getInt(this) - val LINES = textViewClassInstance.getDeclaredField("LINES") - LINES.isAccessible = true - val mLINES = LINES.getInt(this) - return if (mMaxMode == mLINES) mMaximum else -1 - } catch (e: NoSuchFieldException) { - e.printStackTrace() - } catch (e: IllegalAccessException) { - e.printStackTrace() - } catch (e: IllegalArgumentException) { - e.printStackTrace() - } - return -1 - } - - private fun resetText() { - val maxLines = maxLines - var workingText = fullText - var ellipsized = false - if (maxLines != -1) { - val layout = createWorkingLayout(workingText) - val originalLineCount = layout.lineCount - if (originalLineCount > maxLines) { - if (this.ellipsize == TruncateAt.START) { - workingText = - fullText!!.substring(layout.getLineStart(originalLineCount - maxLines - 1)).trim { it <= ' ' } - while (createWorkingLayout(ELLIPSIS + workingText).lineCount > maxLines) { - val firstSpace = workingText!!.indexOf(' ') - workingText = if (firstSpace == -1) { - workingText.substring(1) - } else { - workingText.substring(firstSpace + 1) - } - } - workingText = ELLIPSIS + workingText - } else if (this.ellipsize == TruncateAt.END) { - workingText = fullText!!.substring(0, layout.getLineEnd(maxLines - 1)).trim { it <= ' ' } - while (createWorkingLayout(workingText + ELLIPSIS).lineCount > maxLines) { - val lastSpace = workingText!!.lastIndexOf(' ') - workingText = if (lastSpace == -1) { - workingText.substring(0, workingText.length - 1) - } else { - workingText.substring(0, lastSpace) - } - } - workingText = workingText + ELLIPSIS - } else if (this.ellipsize == TruncateAt.MIDDLE) { - var shrinkLeft = false - val firstOffset = layout.getLineEnd(maxLines / 2) - val secondOffset = layout.getLineEnd(originalLineCount - 1) - firstOffset + 1 - var firstWorkingText = fullText!!.substring(0, firstOffset).trim { it <= ' ' } - var secondWorkingText = fullText!!.substring(secondOffset).trim { it <= ' ' } - while (createWorkingLayout(firstWorkingText + ELLIPSIS + secondWorkingText).lineCount > maxLines) { - if (shrinkLeft) { - shrinkLeft = false - val lastSpace = firstWorkingText.lastIndexOf(' ') - firstWorkingText = if (lastSpace == -1) { - firstWorkingText.substring( - 0, firstWorkingText.length - 1 - ) - } else { - firstWorkingText.substring( - 0, lastSpace - ) - } - } else { - shrinkLeft = true - val firstSpace = secondWorkingText.indexOf(' ') - secondWorkingText = if (firstSpace == -1) { - secondWorkingText - .substring(1) - } else { - secondWorkingText - .substring(firstSpace + 1) - } - } - } - workingText = firstWorkingText + ELLIPSIS + secondWorkingText - } - ellipsized = true - } - } - if (workingText != text) { - programmaticChange = true - text = try { - workingText - } finally { - programmaticChange = false - } - } - isStale = false - if (ellipsized != isEllipsized) { - isEllipsized = ellipsized - for (listener in ellipsizeListeners) { - listener.ellipsizeStateChanged(ellipsized) - } - } - } - - private fun createWorkingLayout(workingText: String?): Layout { - return StaticLayout( - workingText, paint, width - paddingLeft - paddingRight, - Layout.Alignment.ALIGN_NORMAL, customLineSpacingMultiplier, lineAdditionalVerticalPadding, false - ) - } - - companion object { - private const val ELLIPSIS = "..." - } -} \ No newline at end of file diff --git a/app/ui/legacy/src/main/res/layout/message_list_item.xml b/app/ui/legacy/src/main/res/layout/message_list_item.xml index 8c79449b35..7e43c87f61 100644 --- a/app/ui/legacy/src/main/res/layout/message_list_item.xml +++ b/app/ui/legacy/src/main/res/layout/message_list_item.xml @@ -115,7 +115,7 @@ android:textColor="@color/color_default_primary_text" tools:text="Message preview" /> -