diff --git a/app/src/main/java/com/gh/common/util/Extensions.kt b/app/src/main/java/com/gh/common/util/Extensions.kt
index 2b1afa872c..c1c3e39ecd 100644
--- a/app/src/main/java/com/gh/common/util/Extensions.kt
+++ b/app/src/main/java/com/gh/common/util/Extensions.kt
@@ -27,6 +27,7 @@ import com.gh.common.constant.Config
import com.gh.common.constant.Constants
import com.gh.common.view.CenterImageSpan
import com.gh.common.view.CustomLinkMovementMethod
+import com.gh.common.view.ExpandTextView
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.R
import com.gh.gamecenter.WebActivity
@@ -199,6 +200,18 @@ fun String.insert(index: Int, string: String): String {
return this.substring(0, index) + string + this.substring(index, this.length)
}
+/**
+ * TextView 内部处理 ul li ol 得跟 Android 版本走,这里换成专属的标签手动处理
+ */
+fun String.replaceUnsupportedHtmlTag(): String {
+ return this.replace("
", "")
+ .replace("- ", "")
+ .replace("
", "")
+}
+
/**
* 在限定 interval 里只触发一次 action
*/
@@ -459,21 +472,33 @@ fun TextView.setTextChangedListener(action: (s: CharSequence, start: Int, before
/**
* 拦截 TextView 中的 Url Span,用应用内页面的形式打开链接
- * @param text 文字
- * @param handleDefaultHighlighter 是否转换使用 ### ### 格式的包裹高亮文字
- * @param copyHighlighterOnClick 点击时是否复制使用 ### ### 格式的包裹高亮文字
- *
+ * @param shrankText 未展开时的文字
+ * @param expandedText 展开后的文字
*/
-fun TextView.setTextWithInterceptingInternalUrl(text: CharSequence,
- handleDefaultHighlighter: Boolean = false,
- copyHighlighterOnClick: Boolean = false) {
- var ssb = SpannableStringBuilder.valueOf(text).apply {
+fun ExpandTextView.setTextWithInterceptingInternalUrl(shrankText: CharSequence, expandedText: CharSequence) {
+ var shrankSsb = shrankText.interceptUrlSpanAndRoundImageSpan()
+ var expandedSsb = expandedText.interceptUrlSpanAndRoundImageSpan()
+
+ // 去掉多余的 p 换行
+ if (expandedSsb.endsWith("\n", true)) {
+ expandedSsb = SpannableStringBuilder((expandedSsb.subSequence(0, expandedSsb.length - 2)))
+ }
+
+ movementMethod = CustomLinkMovementMethod.getInstance()
+
+ shrankSsb = TextHelper.updateSpannableStringWithHighlightedSpan(context, shrankSsb, Constants.DEFAULT_TEXT_WRAPPER, R.color.theme_font)
+ expandedSsb = TextHelper.updateSpannableStringWithHighlightedSpan(context, expandedSsb, Constants.DEFAULT_TEXT_WRAPPER, R.color.theme_font)
+ setShrankTextAndExpandedText(shrankSsb, expandedSsb)
+}
+
+fun CharSequence.interceptUrlSpanAndRoundImageSpan(): SpannableStringBuilder {
+ return SpannableStringBuilder.valueOf(this).apply {
getSpans(0, length, URLSpan::class.java).forEach {
setSpan(
object : ClickableSpan() {
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
- ds.color = ContextCompat.getColor(context, R.color.theme_font)
+ ds.color = ContextCompat.getColor(HaloApp.getInstance().application, R.color.theme_font)
ds.isUnderlineText = false
}
@@ -500,25 +525,6 @@ fun TextView.setTextWithInterceptingInternalUrl(text: CharSequence,
removeSpan(it)
}
}
-
- movementMethod = CustomLinkMovementMethod.getInstance()
- if (handleDefaultHighlighter) {
- ssb = TextHelper.updateSpannableStringWithHighlightedSpan(
- context,
- ssb,
- Constants.DEFAULT_TEXT_WRAPPER,
- R.color.theme_font,
- object : SimpleCallback {
- override fun onCallback(arg: String) {
- if (copyHighlighterOnClick) {
- arg.copyTextAndToast("已复制:$arg")
- }
- }
- })
- setText(ssb)
- } else {
- setText(ssb)
- }
}
fun Int.toColor(): Int {
diff --git a/app/src/main/java/com/gh/common/util/ExtraTagHandler.kt b/app/src/main/java/com/gh/common/util/ExtraTagHandler.kt
new file mode 100644
index 0000000000..9221da1856
--- /dev/null
+++ b/app/src/main/java/com/gh/common/util/ExtraTagHandler.kt
@@ -0,0 +1,183 @@
+package com.gh.common.util
+
+import android.text.Editable
+import android.text.Html.TagHandler
+import android.text.Spanned
+import android.text.style.BulletSpan
+import android.text.style.LeadingMarginSpan
+import android.util.Log
+import org.xml.sax.XMLReader
+import java.util.*
+
+/**
+ * Implements support for ordered (``) and unordered (``) lists in to Android TextView.
+ *
+ *
+ * This can be used as follows:
+ * `textView.setText(Html.fromHtml("", null, new HtmlListTagHandler()));`
+ *
+ *
+ * Implementation based on code by Juha Kuitunen (https://bitbucket.org/Kuitsi/android-textview-html-list),
+ * released under Apache License v2.0. Refactored & improved by Matthias Stevens (InThePocket.mobi).
+ *
+ *
+ * **Known issues:**
+ * * The indentation on nested ``s isn't quite right (TODO fix this)
+ * * the `start` attribute of `` is not supported. Doing so is tricky because
+ * [Html.TagHandler.handleTag] does not expose tag attributes.
+ * The only way to do it would be to use reflection to access the attribute information kept by the XMLReader
+ * (see: http://stackoverflow.com/a/24534689/1084488).
+ *
+ * https://bitbucket.org/Kuitsi/android-textview-html-list/src/master/app/src/main/java/fi/iki/kuitsi/listtest/MyTagHandler.java
+ *
+ */
+class ExtraTagHandler : TagHandler {
+ /**
+ * Keeps track of lists (ol, ul). On bottom of Stack is the outermost list
+ * and on top of Stack is the most nested list
+ */
+ private val lists = Stack()
+
+ /**
+ * @see android.text.Html.TagHandler.handleTag
+ */
+ override fun handleTag(opening: Boolean, tag: String, output: Editable, xmlReader: XMLReader) {
+ if (UL_TAG.equals(tag, ignoreCase = true)) {
+ if (opening) { // handle
+ lists.push(Ul())
+ } else { // handle
+ lists.pop()
+ }
+ } else if (OL_TAG.equals(tag, ignoreCase = true)) {
+ if (opening) { // handle
+ lists.push(Ol()) // use default start index of 1
+ } else { // handle
+ lists.pop()
+ }
+ } else if (LI_TAG.equals(tag, ignoreCase = true)) {
+ if (opening) { // handle -
+ lists.peek().openItem(output)
+ } else { // handle
+ lists.peek().closeItem(output, lists.size)
+ }
+ } else {
+ Log.d("TagHandler", "Found an unsupported tag $tag")
+ }
+ }
+
+ /**
+ * Abstract super class for [Ul] and [Ol].
+ */
+ private abstract class ListTag {
+ /**
+ * Opens a new list item.
+ *
+ * @param text
+ */
+ open fun openItem(text: Editable) {
+ if (text.length > 0 && text[text.length - 1] != '\n') {
+ text.append("\n")
+ }
+ val len = text.length
+ text.setSpan(this, len, len, Spanned.SPAN_MARK_MARK)
+ }
+
+ /**
+ * Closes a list item.
+ *
+ * @param text
+ * @param indentation
+ */
+ fun closeItem(text: Editable, indentation: Int) {
+ if (text.length > 0 && text[text.length - 1] != '\n') {
+ text.append("\n")
+ }
+ val replaces = getReplaces(text, indentation)
+ val len = text.length
+ val listTag = getLast(text)
+ val where = text.getSpanStart(listTag)
+ text.removeSpan(listTag)
+ if (where != len) {
+ for (replace in replaces) {
+ text.setSpan(replace, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ }
+ }
+ }
+
+ protected abstract fun getReplaces(text: Editable?, indentation: Int): Array
+
+ /**
+ * Note: This knows that the last returned object from getSpans() will be the most recently added.
+ *
+ * @see Html
+ */
+ private fun getLast(text: Spanned): ListTag? {
+ val listTags = text.getSpans(0, text.length, ListTag::class.java)
+ return if (listTags.size == 0) {
+ null
+ } else listTags[listTags.size - 1]
+ }
+ }
+
+ /**
+ * Class representing the unordered list (``) HTML tag.
+ */
+ private class Ul : ListTag() {
+ override fun getReplaces(text: Editable?, indentation: Int): Array {
+ // Nested BulletSpans increases distance between BULLET_SPAN and text, so we must prevent it.
+ var bulletMargin = INDENT_PX
+ if (indentation > 1) {
+ bulletMargin = INDENT_PX - BULLET_SPAN.getLeadingMargin(true)
+ if (indentation > 2) {
+ // This get's more complicated when we add a LeadingMarginSpan into the same line:
+ // we have also counter it's effect to BulletSpan
+ bulletMargin -= (indentation - 2) * LIST_ITEM_INDENT_PX
+ }
+ }
+ return arrayOf(
+ LeadingMarginSpan.Standard(LIST_ITEM_INDENT_PX * (indentation - 1)),
+ BulletSpan(bulletMargin)
+ )
+ }
+ }
+
+ /**
+ * Class representing the ordered list (``) HTML tag.
+ */
+ private class Ol
+ /**
+ * Creates a new `` with start index of 1.
+ */ @JvmOverloads constructor(private var nextIdx: Int = 1) : ListTag() {
+ override fun openItem(text: Editable) {
+ super.openItem(text)
+ text.append(Integer.toString(nextIdx++)).append(". ")
+ }
+
+ override fun getReplaces(text: Editable?, indentation: Int): Array {
+ var numberMargin = LIST_ITEM_INDENT_PX * (indentation - 1)
+ if (indentation > 2) {
+ // Same as in ordered lists: counter the effect of nested Spans
+ numberMargin -= (indentation - 2) * LIST_ITEM_INDENT_PX
+ }
+ return arrayOf(LeadingMarginSpan.Standard(numberMargin))
+ }
+ /**
+ * Creates a new `` with given start index.
+ *
+ * @param nextIdx
+ */
+ }
+
+ companion object {
+ private const val OL_TAG = "hol"
+ private const val UL_TAG = "hul"
+ private const val LI_TAG = "hli"
+
+ /**
+ * List indentation in pixels. Nested lists use multiple of this.
+ */
+ private const val INDENT_PX = 10
+ private const val LIST_ITEM_INDENT_PX = INDENT_PX * 2
+ private val BULLET_SPAN = BulletSpan(INDENT_PX)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/common/view/ExpandTextView.java b/app/src/main/java/com/gh/common/view/ExpandTextView.java
index 791ad05143..ed5fc678e5 100644
--- a/app/src/main/java/com/gh/common/view/ExpandTextView.java
+++ b/app/src/main/java/com/gh/common/view/ExpandTextView.java
@@ -8,6 +8,7 @@ import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
+import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.util.AttributeSet;
import android.view.View;
@@ -23,9 +24,11 @@ public class ExpandTextView extends AppCompatTextView {
private CharSequence mSnapshotText;
private String mEndText = "...";
+ private CharSequence mShrankText = "";
private String mExpandText = mEndText + "全文";
+ private CharSequence mExpandedText = "";
private boolean mUseGradientAlphaEndText = false;
- private boolean mShrinkOnAnchor = false; // 以锚点所在的位置进行收起 (即 maxLines = 锚点所在的行)
+ private boolean mShowExpandTextRegardlessOfMaxLines = false;
private int mMaxLines = 3; // 由于sdk版本限制(getMaxLines) 这里设置默认值
@@ -39,7 +42,6 @@ public class ExpandTextView extends AppCompatTextView {
private Rect mLastActualLineRect;
private static int DEFAULT_ADDITIONAL_END_TEXT_COUNT = 2;
- public static final String SHRINK_ANCHOR = "☼";
public ExpandTextView(Context context) {
super(context);
@@ -55,45 +57,33 @@ public class ExpandTextView extends AppCompatTextView {
mLastActualLineRect = new Rect();
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ExpandTextView);
- mShrinkOnAnchor = ta.getBoolean(R.styleable.ExpandTextView_shrinkOnAnchor, false);
mUseGradientAlphaEndText = ta.getBoolean(R.styleable.ExpandTextView_useGradientAlphaEndText, false);
mEndText = ta.getString(R.styleable.ExpandTextView_endText) == null ? mEndText : ta.getString(R.styleable.ExpandTextView_endText);
- mExpandText = ta.getString(R.styleable.ExpandTextView_expandText) == null ? mExpandText : ta
- .getString(R.styleable.ExpandTextView_expandText);
+ mExpandText = ta.getString(R.styleable.ExpandTextView_expandText) == null ? mExpandText : ta.getString(R.styleable.ExpandTextView_expandText);
ta.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mShrinkOnAnchor && getText().toString().contains(SHRINK_ANCHOR)) {
- updateMaxLinesByShrinkAnchor();
+ // TODO 复用有问题
+ if (mShowExpandTextRegardlessOfMaxLines && !mIsExpanded) {
+ updateMaxLines();
}
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() - getExtraBottomPadding());
}
- private void updateMaxLinesByShrinkAnchor() {
- int lineCount = getLineCount();
- for (int line = 0; line < lineCount; line++) {
- CharSequence substring = getText().subSequence(getLayout().getLineStart(line), getLayout().getLineEnd(line));
- if (substring.toString().contains(SHRINK_ANCHOR)) {
- if (mMaxLines != line + 1) {
- if (!mIsExpanded) {
- setExpandMaxLines(line + 1);
- mMaxLinesCalculatedCallback.onMaxLinesCalculated(line + 1);
- }
- setText(findAndDeleteAnchor(getText()));
- break;
- }
- }
- }
+ private void updateMaxLines() {
+ mMaxLines = getLineCount() - 1;
+ mMaxLinesCalculatedCallback.onMaxLinesCalculated(getLineCount() - 1);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- if (mInitLayout && !mIsExpanded && getLineCount() > mMaxLines && mMaxLines > 0) {
- mSnapshotText = findAndDeleteAnchor(getText());
+ if ((mShowExpandTextRegardlessOfMaxLines && !mIsExpanded)
+ || (mInitLayout && !mIsExpanded && getLineCount() > mMaxLines && mMaxLines > 0)) {
+ mSnapshotText = getText();
mInitLayout = false;
showExpandButton();
}
@@ -107,6 +97,18 @@ public class ExpandTextView extends AppCompatTextView {
this.mExpandCallback = callback;
}
+ public void setShrankTextAndExpandedText(CharSequence shrankText, CharSequence expandedText) {
+ mShrankText = shrankText;
+ mExpandedText = expandedText;
+ mShowExpandTextRegardlessOfMaxLines = !TextUtils.isEmpty(shrankText);
+
+ if (!mIsExpanded && mShowExpandTextRegardlessOfMaxLines) {
+ setText(mShrankText);
+ } else {
+ setText(mExpandedText);
+ }
+ }
+
@Override
public void setText(CharSequence text, BufferType type) {
mInitLayout = true;
@@ -139,7 +141,7 @@ public class ExpandTextView extends AppCompatTextView {
if (viewWidth - lastLineRight > expandTextWidth) {
if (mUseGradientAlphaEndText) {
finalEndText = content.toString()
- .substring(content.length() - additionalEndTextCount, content.length()) + mEndText;
+ .substring(content.length() - additionalEndTextCount, content.length()) + mEndText;
finalEndText = finalEndText.replace("\n", "");
content = content.subSequence(0, content.length() - additionalEndTextCount) + finalEndText + mExpandText;
@@ -198,7 +200,11 @@ public class ExpandTextView extends AppCompatTextView {
public void onClick(@NonNull View widget) {
mIsExpanded = true;
setMaxLines(Integer.MAX_VALUE);
- setText(mSnapshotText);
+ if (mShowExpandTextRegardlessOfMaxLines) {
+ setText(mExpandedText);
+ } else {
+ setText(mSnapshotText);
+ }
if (mExpandCallback != null) {
mExpandCallback.onExpand();
@@ -229,7 +235,7 @@ public class ExpandTextView extends AppCompatTextView {
if (getMeasuredHeight() == getLayout().getHeight() - heightBetweenLastVisibleLineRectAndLastActualLineRect) {
result = mLastVisibleLineRect.bottom - (lastVisibleLineBaseline + layout.getPaint()
- .getFontMetricsInt().descent + getPaddingBottom());
+ .getFontMetricsInt().descent + getPaddingBottom());
if (getLineSpacingExtra() > result) {
result = 0;
} else {
@@ -240,18 +246,6 @@ public class ExpandTextView extends AppCompatTextView {
return result;
}
- // 去掉锚点
- private CharSequence findAndDeleteAnchor(CharSequence charSequence) {
- int anchorIndex = charSequence.toString().indexOf(SHRINK_ANCHOR);
- if (anchorIndex >= 0) {
- SpannableStringBuilder sb = new SpannableStringBuilder(getText());
- sb.delete(anchorIndex, anchorIndex + 1);
- return sb;
- } else {
- return charSequence;
- }
- }
-
/**
* 此方法仅更改标记,不做实际展开的操作
*/
diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/desc/DescAdapter.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/desc/DescAdapter.kt
index c152fce060..7bfd33fc81 100644
--- a/app/src/main/java/com/gh/gamecenter/gamedetail/desc/DescAdapter.kt
+++ b/app/src/main/java/com/gh/gamecenter/gamedetail/desc/DescAdapter.kt
@@ -636,8 +636,9 @@ class DescAdapter(context: Context,
titleHintTv.paint?.isUnderlineText = true
contentTv.setExpandMaxLines(maxDesLines)
contentTv.setIsExpanded(Int.MAX_VALUE == maxDesLines)
- val spanned = HtmlCompat.fromHtml(customColumn.des ?: "", HtmlCompat.FROM_HTML_MODE_LEGACY, PicassoImageGetter(contentTv), null)
- contentTv.setTextWithInterceptingInternalUrl(text = spanned, handleDefaultHighlighter = true, copyHighlighterOnClick = false)
+ val shrankSpanned = HtmlCompat.fromHtml(customColumn.desBrief ?: "", HtmlCompat.FROM_HTML_MODE_COMPACT, PicassoImageGetter(contentTv), ExtraTagHandler())
+ val expandedSpanned = HtmlCompat.fromHtml(customColumn.des ?: "", HtmlCompat.FROM_HTML_MODE_COMPACT, PicassoImageGetter(contentTv), ExtraTagHandler())
+ contentTv.setTextWithInterceptingInternalUrl(shrankText= shrankSpanned, expandedText = expandedSpanned)
contentTv.setSelfCalculateMaxLinesCallback { mExpandableTextViewMaxLinesSparseIntArray.put(position, it) }
recyclerView.isNestedScrollingEnabled = false
recyclerView.layoutManager = layoutManager
diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/desc/DescViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/desc/DescViewModel.kt
index 8cb10ea82c..b3a32e0dc9 100644
--- a/app/src/main/java/com/gh/gamecenter/gamedetail/desc/DescViewModel.kt
+++ b/app/src/main/java/com/gh/gamecenter/gamedetail/desc/DescViewModel.kt
@@ -12,7 +12,6 @@ import com.facebook.common.util.UriUtil
import com.gh.common.constant.Constants
import com.gh.common.repository.RemenkapaiRepository
import com.gh.common.util.*
-import com.gh.common.view.ExpandTextView
import com.gh.gamecenter.R
import com.gh.gamecenter.entity.ErrorEntity
import com.gh.gamecenter.entity.GameEntity
@@ -270,14 +269,8 @@ class DescViewModel(application: Application,
// 手动为自定义栏目的说明添加展开分隔符
if (!TextUtils.isEmpty(rawItem.customColumn?.desBrief)) {
- val index = rawItem.customColumn?.desBrief?.length ?: 0
- if (index >= 0) {
- rawItem.customColumn?.desFull = rawItem.customColumn?.desFull?.insert(index, ExpandTextView.SHRINK_ANCHOR)
- }
- }
-
- if (rawItem.customColumn?.name != "游戏简介") {
- rawItem.customColumn?.showDesRowNum = Int.MAX_VALUE - 1
+ rawItem.customColumn?.desFull = rawItem.customColumn?.desFull?.replaceUnsupportedHtmlTag()
+ rawItem.customColumn?.desBrief = rawItem.customColumn?.desBrief?.replaceUnsupportedHtmlTag()
}
rawItem.customColumn?.des = rawItem.customColumn?.desFull ?: rawItem.customColumn?.des
diff --git a/app/src/main/res/layout/gamedetail_item_custom_column.xml b/app/src/main/res/layout/gamedetail_item_custom_column.xml
index 885f5a5c16..6677d79d3f 100644
--- a/app/src/main/res/layout/gamedetail_item_custom_column.xml
+++ b/app/src/main/res/layout/gamedetail_item_custom_column.xml
@@ -232,7 +232,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/contentHintContainer"
app:layout_goneMarginTop="10dp"
- app:shrinkOnAnchor="true"
app:useGradientAlphaEndText="true"
tools:text="公告文章、权重大于0的自定义栏目和介绍文案,这三类版块内容可组合拼接为一个整体部分,即拼接内容可为其中两种(如公告文章+介绍文案),也可为全部三种(包括多个自定义栏目内容),其中公告文章和自定义栏目之间、公告文章和介绍文案之间、自定义栏目和介绍文案之间加上分割线隔开"
tools:visibility="visible" />
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 58ffb40ae7..5f9f29b281 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -106,7 +106,6 @@
-