在安卓開發中,如果你們的項目需要展示文本,然後文本裡面嵌套着圖檔,并且展示的文字有些字需要标記成不同的顔色,文字還需要有點選事件。如果讓你按之前的思路去實作這樣的一個效果,你會這樣來設計實作思路:
1)文字用TextView展示,圖檔用ImageView展示,然後文字需要被截取,根據背景傳回的文字索引腳标。
2)截取的文字會和圖檔連結進行組合布局進行展示,這樣的問題就會是文字和圖檔的布局是不固定的,會根據背景傳回的文字和圖檔不同而展示不同。
3)文字如果需要添加點選事件,需要重新設計布局添加TextView控件
從上面的實作思路我們可以發現,如果按之前的TextView和ImageView組合的方式進行展示的話,就會非常麻煩。
那麼我們就可以使用SpannableStringBuilder這個對象類來實作上面的需求,其原理就是把文本轉成Html網頁來進行展示。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL9UERNhXTq1ENNRVT3V1MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZlBnauEDN1UjN0UTMzAzNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpeg)
從上圖我們可以發現這樣設定沒有一點的問題,選中的文字會展示不同的背景顔色,但是再點選顔色的時候會綠色的文字間距背景顔色。之前一直覺得是文本轉成Html之後導緻的Html文本自帶背景顔色導緻的,後面才發現文本轉成Html文本:
Html.fromHtml(content,Html.FROM_HTML_MODE_LEGACY, mURLImageGetter, null);
傳回一個Spanned對象,
富文本轉成Html網頁顯示之後,預設點選事件是需要高亮顯示的,但是這是TextView裡面設定的方法:
/**
* Sets the color used to display the selection highlight.
*
* @attr ref android.R.styleable#TextView_textColorHighlight
*/
@android.view.RemotableViewMethod
public void setHighlightColor(@ColorInt int color) {
if (mHighlightColor != color) {
mHighlightColor = color;
invalidate();
}
}
是以,我們想要去掉我們選中的Html文本的點選背景顔色,就需要設定這個高亮顯示的顔色設定成白色就行了。
setHighlightColor(Color.parseColor("#00000000"));
富文本經常還需要設定選中文字的功能,下面就推薦一個選中文字改變背景顔色的開源架構。
下面是安卓開發,把文本直接轉成富文本的工具類:
/**
* 文章詳情-富文本控件
* @author guotianhui
*/
public class FenJRichTextView extends AppCompatTextView{
private Spannable mSpannable;
private ClickableSpan clickableSpan;
private URLImageGetter mURLImageGetter;//加載圖檔使用的,處理html中![在這裡插入圖檔描述]()的處理器,生成Drawable對象并傳回
private ColorUnderlineSpan mUnderlineSpan;
private ForegroundColorSpan mFCTxtContentSpan; //文字顔色
private FenJRichTextViewHelper mFenJRichTextViewHelper;
private SpannableStringBuilder mSpannableStringBuilder;
private OnRichContentClickListener mRichContentClickListener;//圖檔點選回調
public FenJRichTextView(Context context) {
this(context, null);
}
public FenJRichTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FenJRichTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mURLImageGetter = new URLImageGetter(this);
mFenJRichTextViewHelper = new FenJRichTextViewHelper();
}
/**
* 設定富文本内容
* @param content 文章内容(包含img标簽)
* @param markSpans 馬克筆清單
*/
public void setRichContent(String content, List<LevelAnalysisBean> analysisBeans, List<MarkerPenBean> markSpans) {
try {
//顯示帶圖檔的html的處理方法,第二個參數imageGetter是加載圖檔使用的,第三個參數是過濾标簽使用的
Spanned spanned = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
spanned = Html.fromHtml(content,Html.FROM_HTML_MODE_LEGACY, mURLImageGetter, null);
}else {
spanned = Html.fromHtml(content,mURLImageGetter, null);
}
if (spanned instanceof SpannableStringBuilder) {
mSpannableStringBuilder = (SpannableStringBuilder) spanned;
} else {
mSpannableStringBuilder = new SpannableStringBuilder(spanned);
}
mSpannable = null;
setMarkPen(markSpans);
setImageClickable();
setHighlightColor(Color.parseColor("#00000000"));
setTextClickableAnalysis(analysisBeans);
setMovementMethod(LinkMovementMethod.getInstance());
super.setText(mSpannableStringBuilder);
}catch (Exception e){
LogUtils.e(">>>>>>>>>>>>","設定文章内容出現異常:"+e);
}
}
/**
* 設定馬克筆
* @param markSpans
*/
public void setMarkPen(List<MarkerPenBean> markSpans) {
setMarkPenBgColor(markSpans);
//增加馬克筆點選事件
setTextClickable(markSpans);
}
/**
* 設定馬克筆的背景色
* @param markSpans 馬克筆清單
*/
private void setMarkPenBgColor(List<MarkerPenBean> markSpans) {
try {
if (ObjectUtils.isNotEmpty(markSpans)) {
for (MarkerPenBean articleDetailMark : markSpans) {
int color;
if (TextUtils.isEmpty(articleDetailMark.getMarkColor())) {
//容錯,顔色傳回空使用預設顔色
color = Color.parseColor("#fbfab7");
} else {
color = Color.parseColor(articleDetailMark.getMarkColor());
}
int startPosition = articleDetailMark.getMarkStartPosition();
int endPosition = articleDetailMark.getMarkEndPosition();
int lg = mSpannableStringBuilder.length();
if (endPosition > 0 && lg >= endPosition) {
updateTxtBgColor(color, startPosition, endPosition);
}
}
}
}catch (Exception e){
LogUtils.e(">>>>>>>>>>>>>>>>","設定馬克筆背景色異常:"+e);
}
}
/**
* 設定文字背景色
* @param color
* @param startPosition
* @param endPosition
*/
private void updateTxtBgColor(int color, int startPosition, int endPosition) {
try {
int contentLength = mSpannableStringBuilder.length();
if (endPosition > contentLength) {
endPosition = contentLength;
}
Log.e(">>>>>>>>>>>>>>>>","設定文字背景色color:"+color);
mSpannableStringBuilder.setSpan(new BackgroundColorSpan(color), startPosition, endPosition,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}catch (Exception e){
LogUtils.e(">>>>>>>>>>>>","設定文字背景色數組腳标越界:"+e);
}
}
/**
* 設定文字顔色
* @param color 顔色
* @param startPosition 開始位置(包含)
* @param endPosition 結束位置(不包含)
*/
public void updateTxtColor(int color, int startPosition, int endPosition) {
try {
if (mSpannable == null) {
super.setText(getText(), BufferType.SPANNABLE);
CharSequence charSequence = getText();
if (charSequence instanceof Spannable) {
mSpannable = (Spannable) charSequence;
}
}
if (mFCTxtContentSpan == null) {
mFCTxtContentSpan = new ForegroundColorSpan(color);
}
Log.e(">>>>>>>>>>>>>>>>","設定文字顔色color:"+color);
int articleL = getText().toString().length();
if (endPosition > articleL) {
endPosition = articleL;
}
mSpannable.setSpan(mFCTxtContentSpan, startPosition, endPosition,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
} catch (Exception e){
LogUtils.e(">>>>>>>>>>>>更改文字顔色報錯:"+e.getMessage());
}
}
/**
* 某段文字添加下劃線
* @param color
* @param startPosition 下劃線開始位置
* @param endPosition 下劃線結束位置
*/
public void addTxtUnderline(int color, int startPosition, int endPosition) {
try {
if (mSpannable == null) {
super.setText(getText(), BufferType.SPANNABLE);
CharSequence charSequence = getText();
if (charSequence instanceof Spannable) {
mSpannable = (Spannable) charSequence;
}
}
if (mUnderlineSpan == null) {
mUnderlineSpan = new ColorUnderlineSpan(color);
}
int contentL = getText().toString().length();
if (endPosition < contentL) {
mSpannable.setSpan(mUnderlineSpan, startPosition, endPosition, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
} else if (contentL > startPosition) {
mSpannable.setSpan(mUnderlineSpan, startPosition, contentL, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
} else {
}
}catch (Exception e){
LogUtils.e(">>>>>>>>>>>","文字添加下劃線報錯:"+e);
}
}
/**
* 移除文字顔色
*/
public void removeTxtColor() {
if (mSpannable != null && mFCTxtContentSpan != null) {
mSpannable.removeSpan(mFCTxtContentSpan);
mFCTxtContentSpan = null;
}
}
/**
* 移除文字下滑線
*/
public void removeTxtUnLine() {
if (mSpannable != null && mUnderlineSpan != null) {
mSpannable.removeSpan(mUnderlineSpan);
mUnderlineSpan = null;
}
}
/**
* 移除點選事件
*/
public void removeContentClickableSpan() {
if (mSpannable != null && clickableSpan != null) {
mSpannable.removeSpan(clickableSpan);
clickableSpan = null;
}
}
/**
* 設定文字背景色
* @param content
* @param color
*/
public void setRichTxtBgColor(String content, int color) {
if (TextUtils.isEmpty(content)) {
return;
}
//顯示帶圖檔的html的處理方法
Spanned spanned = Html.fromHtml(content, mURLImageGetter, null);
if (spanned instanceof SpannableStringBuilder) {
mSpannableStringBuilder = (SpannableStringBuilder) spanned;
} else {
mSpannableStringBuilder = new SpannableStringBuilder(spanned);
}
mSpannable = null;
Log.e(">>>>>>>>>>>>>>>"," 設定文字背景色color:"+color);
updateTxtBgColor(color, 0, mSpannableStringBuilder.length());
setMovementMethod(LinkMovementMethod.getInstance());
super.setText(spanned);
}
/**
* 設定富本文内容
* @param content 文章内容(包含img标簽)
*/
public void setRichContent(String content) {
if (TextUtils.isEmpty(content)) {
return;
}
//顯示帶圖檔的html的處理方法
Spanned spanned = Html.fromHtml(content, mURLImageGetter, null);
if (spanned instanceof SpannableStringBuilder) {
mSpannableStringBuilder = (SpannableStringBuilder) spanned;
} else {
mSpannableStringBuilder = new SpannableStringBuilder(spanned);
}
mSpannable = null;
// setMovementMethod(LinkMovementMethod.getInstance());
super.setText(spanned);
}
/**
* 處理圖檔的點選事件
*/
private void setImageClickable() {
try {
ImageSpan[] imageSpans = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ImageSpan.class);
final ArrayList<PictureItem> imageUrls = new ArrayList<>();
if (ObjectUtils.isNotEmpty(imageSpans)) {
for (int i = 0, size = imageSpans.length; i < size; i++) {
ImageSpan imageSpan = imageSpans[i];
String imageUrl = imageSpan.getSource();
int start = mSpannableStringBuilder.getSpanStart(imageSpan);
int end = mSpannableStringBuilder.getSpanEnd(imageSpan);
PictureItem pictureItem = new PictureItem();
pictureItem.setLevelPictureUrl(imageUrl);
imageUrls.add(pictureItem);
final int position = i;
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
if (mRichContentClickListener != null) {
mRichContentClickListener.onImageClick(imageUrls, position);
}
}
};
//增加事件
mSpannableStringBuilder.setSpan(clickableSpan, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}catch (Exception e){
LogUtils.e(">>>>>>>>>>>>>>>>", "文章圖檔點選報錯:"+e);
}
}
/**
*設定可以點選的解析卡文字
* @param analysisBeans
*/
public void setTextClickableAnalysis(final List<LevelAnalysisBean> analysisBeans) {
try {
if(ObjectUtils.isNotEmpty(analysisBeans)) {
for (int i = 0, size = analysisBeans.size(); i < size; i++) {
LevelAnalysisBean imageSpan = analysisBeans.get(i);
int start = imageSpan.getLocation().getIndexClickable();
if (start < 0) {
continue;
}
int end = start + imageSpan.getLocation().getLengthClickable();
final int position = i;
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
if (mRichContentClickListener != null) {
mRichContentClickListener.onTextClickAnalysis(analysisBeans, position);
}
}
//去除超連結下劃線
@Override
public void updateDrawState(TextPaint ds) {
/**set textColor**/
// ds.setColor(ds.linkColor);
/**Remove the underline**/
ds.setUnderlineText(false);
}
};
//超文本點選事件
if (end > 0 && mSpannableStringBuilder.length() >= end) {
mSpannableStringBuilder.setSpan(clickableSpan, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}
}catch (Exception e){
LogUtils.e(">>>>>>>>>>>>>>>","設定文章的解析卡報錯:"+e);
}
}
/**
* 設定文字點選事件
*/
private void setTextClickable(List<MarkerPenBean> imageSpans) {
try {
if(ObjectUtils.isNotEmpty(imageSpans)) {
final List<MarkerPenBean> markList = new ArrayList<>();
for (int i = 0, size = imageSpans.size(); i < size; i++) {
MarkerPenBean imageSpan = imageSpans.get(i);
int start = imageSpan.getMarkStartPosition();
int end = imageSpan.getMarkEndPosition();
markList.add(imageSpan);
final int position = i;
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
if (mRichContentClickListener != null) {
mRichContentClickListener.onTextClickMark(markList, position);
}
}
//去除超連結下劃線
@Override
public void updateDrawState(TextPaint ds) {
/**set textColor**/
// ds.setColor(ds.linkColor);
/**Remove the underline**/
ds.setUnderlineText(false);
}
};
//超文本點選事件
if (end > 0 && mSpannableStringBuilder.length() >= end) {
mSpannableStringBuilder.setSpan(clickableSpan, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}
}catch (Exception e){
LogUtils.e(">>>>>>>>>>>>>>>>","設定文字的點選事件:"+e);
}
}
/**
* 修改下劃線顔色
*/
public void updateUnlineColor(final int color, int startPosition, int endPosition) {
if (clickableSpan == null) {
clickableSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
}
@Override
public void updateDrawState(TextPaint ds) {
/**set textColor**/
ds.setColor(getColor_(color));
/**Remove the underline**/
ds.setUnderlineText(true);
}
};
}
if (mSpannable == null) {
super.setText(getText(), BufferType.SPANNABLE);
CharSequence charSequence = getText();
if (charSequence instanceof Spannable) {
mSpannable = (Spannable) charSequence;
}
}
//增加超文本點選事件
mSpannable.setSpan(clickableSpan, startPosition, endPosition, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
public void removeClickableSpan(int start, int end) {
ClickableSpan[] clickableSpans = mSpannableStringBuilder.getSpans(start, end, ClickableSpan.class);
if (clickableSpans != null && clickableSpans.length != 0) {
for (ClickableSpan cs : clickableSpans) {
mSpannableStringBuilder.removeSpan(cs);
}
}
//super.setTag(getText());
}
public void onDestroy() {
if (mURLImageGetter != null) {
mURLImageGetter.clear();
mURLImageGetter = null;
}
this.setText(null);
}
/**
* 設定圖檔監聽事件
*/
public void setRichContentClickListener(OnRichContentClickListener mRichContentClickListener) {
this.mRichContentClickListener = mRichContentClickListener;
}
public interface OnRichContentClickListener {
/**
* 圖檔被點選後的回調方法
* @param imageUrls 本篇富文本内容裡的全部圖檔
* @param position 點選處圖檔在imageUrls中的位置
*/
void onImageClick(ArrayList<PictureItem> imageUrls, int position);
/**
* 文字被點選後的回調方法
* @param textStrs
* @param position
*/
void onTextClick(List<String> textStrs, int position);
/**
* 文字被點選後的回調方法
* @param textStrs
* @param position
*/
void onTextClickMark(List<MarkerPenBean> textStrs, int position);
void onTextClickAnalysis(List<LevelAnalysisBean> textStrs, int position);
}
/**
* 圖檔标簽
* @param imgUrl
* @return
*/
public String getImgLabel(String imgUrl) {
return FenJRichTextViewHelper.getImgLabel(imgUrl);
}
/**
* 馬克筆标簽
* @param marker 文字背景顔色
* @return
*/
public String getTextMarker(String color, String marker) {
return mFenJRichTextViewHelper.getTextMarker(color, marker);
}
/**
* 封裝擷取顔色
*/
public int getColor_(int colorId) {
return ContextCompat.getColor(getContext(), colorId);
}
private class ColorUnderlineSpan extends UnderlineSpan {
private int underlineColor;
public ColorUnderlineSpan(int underlineColor) {
super();
this.underlineColor = underlineColor;
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
// ds.setColor(underlineColor);
// ds.linkColor = underlineColor;
}
}
/**
* @param content
* @param resId
* @param vipW
* @param vipH
*/
public void setIconText(String content, int resId, int vipW, int vipH) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);
Bitmap localB = zoomImg(bitmap,dip2px(getContext(), bitmap.getWidth()),dip2px(getContext(), bitmap.getHeight()));
CenterAlignImageSpan imgSpan = new CenterAlignImageSpan(localB);
SpannableString spanString = new SpannableString("V");
spanString.setSpan(imgSpan, 0, "V".length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
super.setText(spanString);
append(content);
}
private Drawable getDrawable(int resId) {
return ContextCompat.getDrawable(getContext(), resId);
}
public Html.ImageGetter getImageGetterInstance(final CharSequence content) {
Html.ImageGetter imgGetter = new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
int id = Integer.parseInt(source);
int hdp = dip2px(getContext(), 19);
int wdp = dip2px(getContext(), 42);
Drawable drawable = new TextDrawable(getContext(), source,hdp,10);
// int hdp = drawable.getIntrinsicWidth();
drawable.setBounds(0, 0, wdp, hdp);
return drawable;
}
};
return imgGetter;
}
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
public void clearAllMarkerPen() {
if (mSpannableStringBuilder != null) {
mSpannableStringBuilder.clearSpans();
}
}
public void clear() {
clearAllMarkerPen();
if (mSpannableStringBuilder != null) {
mSpannableStringBuilder.clear();
}
}
public class CenterAlignImageSpan extends ImageSpan {
public CenterAlignImageSpan(Drawable drawable) {
super(drawable);
}
public CenterAlignImageSpan(Bitmap b) {
super(b);
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
Drawable b = getDrawable();
Paint.FontMetricsInt fm = paint.getFontMetricsInt();
int transY = (y + fm.descent + y + fm.ascent) / 2 - b.getBounds().bottom / 2;//計算y方向的位移
canvas.save();
canvas.translate(x, transY);//繪制圖檔位移一段距離
b.draw(canvas);
canvas.restore();
}
}
/**
* 放大圖檔
* @param bm
* @param newWidth
* @param newHeight
* @return
*/
public Bitmap zoomImg(Bitmap bm, int newWidth ,int newHeight){
// 獲得圖檔的寬高
int width = bm.getWidth();
int height = bm.getHeight();
// 計算縮放比例
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 取得想要縮放的matrix參數
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
// 得到新的圖檔 www.2cto.com
Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
return newbm;
}
}