在安卓开发中,如果你们的项目需要展示文本,然后文本里面嵌套着图片,并且展示的文字有些字需要标记成不同的颜色,文字还需要有点击事件。如果让你按之前的思路去实现这样的一个效果,你会这样来设计实现思路:
1)文字用TextView展示,图片用ImageView展示,然后文字需要被截取,根据后台返回的文字索引脚标。
2)截取的文字会和图片链接进行组合布局进行展示,这样的问题就会是文字和图片的布局是不固定的,会根据后台返回的文字和图片不同而展示不同。
3)文字如果需要添加点击事件,需要重新设计布局添加TextView控件
从上面的实现思路我们可以发现,如果按之前的TextView和ImageView组合的方式进行展示的话,就会非常麻烦。
那么我们就可以使用SpannableStringBuilder这个对象类来实现上面的需求,其原理就是把文本转成Html网页来进行展示。
从上图我们可以发现这样设置没有一点的问题,选中的文字会展示不同的背景颜色,但是再点击颜色的时候会绿色的文字间距背景颜色。之前一直觉得是文本转成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 analysisBeans, List 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 markSpans) {
setMarkPenBgColor(markSpans);
//增加马克笔点击事件
setTextClickable(markSpans);
}
/**
* 设置马克笔的背景色
* @param markSpans 马克笔列表
*/
private void setMarkPenBgColor(List 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 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 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 imageSpans) {
try {
if(ObjectUtils.isNotEmpty(imageSpans)) {
final List 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 imageUrls, int position);
/**
* 文字被点击后的回调方法
* @param textStrs
* @param position
*/
void onTextClick(List textStrs, int position);
/**
* 文字被点击后的回调方法
* @param textStrs
* @param position
*/
void onTextClickMark(List textStrs, int position);
void onTextClickAnalysis(List 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;
}
}