最近在做一个比较坑的需求,在原生里面需要实现图文混排并且需要在指定位置输入内容,做完型填空。百度google发现没有这种实现。心中一万句草泥马在翻腾。各种曲折,终于撸出来了,如果你也有这个需求,希望能够帮到你!
以上功能如下:
1.在TextView中加载带图文的html代码
2.在指定位置插入EditText控件
3.获取每一个EditText输入内容*
4.viewpager中实现单页EdirText内容获取*
话不多说,先上图.
示例html如下
private static final String str = "<p><span style=\"font-size: 14px;font-family: 宋体\">过点[__Fill.Replace__]<img src=\"http://www.ennnjoy.cn/ueditor/jsp/upload/image/20170831/1504153950101096441.png\" title=\"1504153950101096441.png\" alt=\"image.png\"/></span><span style=\"font-size: 14px;font-family: 宋体\">直线</span><span style=\"font-size: 14px;font-family: inherit, serif\"><img src=\"http://www.ennnjoy.cn/ueditor/jsp/upload/image/20170831/1504153959369086949.png\" title=\"1504153959369086949.png\" alt=\"image.png\"/></span><span style=\"font-size: 14px;font-family: 宋体\">[__Fill.Replace__]与线段</span><span style=\"font-size: 14px;font-family: inherit, serif\"><img src=\"http://www.ennnjoy.cn/ueditor/jsp/upload/image/20170831/1504153965505061192.png\" title=\"1504153965505061192.png\" alt=\"image.png\"/></span><span style=\"font-size: 14px;font-family: 宋体\">相交</span><span style=\"font-size: 14px;font-family: inherit, serif\">,</span><span style=\"font-size: 14px;font-family: 宋体\">若</span><span style=\"font-size: 14px;font-family: inherit, serif\"><img src=\"http://www.ennnjoy.cn/ueditor/jsp/upload/image/20170831/1504153971057037078.png\" title=\"1504153971057037078.png\" alt=\"image.png\"/></span><span style=\"font-size: 14px;font-family: 宋体\">和<img src=\"http://www.ennnjoy.cn/ueditor/jsp/upload/image/20170831/1504153977221037497.png\" title=\"1504153977221037497.png\" alt=\"image.png\"/></span><span style=\"font-size: 14px;font-family: inherit, serif\">,</span><span style=\"font-size: 14px;font-family: 宋体\">则直线</span><span style=\"font-size: 14px;font-family: inherit, serif\"><img width=\"5\" height=\"13\" src=\"/plugins/utf8-jsp/themes/default/images/spacer.gif\" alt=\"www.xiangpi.com\"/></span><span style=\"font-size: 14px;font-family: 宋体\">的斜率</span><span style=\"font-size: 14px;font-family: inherit, serif\"><img width=\"9\" height=\"13\" src=\"/plugins/utf8-jsp/themes/default/images/spacer.gif\" alt=\"http://img.xiangpi.com/Formula/E1D10FB60667175EBFC750A466A1448A.gif\"/></span><span style=\"font-size: 14px;font-family: 宋体\">的取值范围是</span><span style=\"font-size: 14px;font-family: inherit, serif\">[__Fill.Replace__].</span></p>";
一、图文混排
系统的TextView通过ImageGetter就可以原生实现对文本img标签的支持,从而达到图文混排的效果。
Html.fromHtml(text, imageGetter, null);
具体时间参照api。
这里需要一个ImageGetter对象。
public class URLImageGetter implements Html.ImageGetter {
private String shopDeString;
private TextView textView;
Context context;
public URLImageGetter(String shopDeString, Context context, TextView textView) {
this.shopDeString = shopDeString;
this.context = context;
this.textView = textView;
}
@Override
public Drawable getDrawable(final String source) {
final URLDrawable urlDrawable = new URLDrawable();
//Glide下载图片
Glide.with(context).load(source).asBitmap().into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap loadedImage, GlideAnimation<? super Bitmap> glideAnimation) {
// 取得想要缩放的matrix參數
int width = loadedImage.getWidth();
int height = loadedImage.getHeight();
float f = ;
if (height < )
f = (float) ( / height);
else
f = f;
Matrix matrix = new Matrix();
matrix.postScale(f, f);
// 得到新的圖片
Bitmap newbm = Bitmap.createBitmap(loadedImage, , , width, height, matrix, true);
urlDrawable.bitmap = newbm;
urlDrawable.setBounds(, , newbm.getWidth(), newbm.getHeight());
textView.invalidate();
textView.setText(textView.getText());
}
});
return urlDrawable;
}
}
需要导入Glide库
当然你还可以用其他的下载库或者使用AsyncTask异步下载
以上就能实现图文混排需要,如果只是简单的需要实现图文混排功能,推荐
Github上的一个库https://github.com/zzhoujay/RichText
二、在指定位置插入EditText
原理:通过和后台约定好具体字段标示在指定为Edittext输入框。
Spanned android.text.Html.fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler)
Source: 需处理的html文本
imageGetter :对图片处理(处理html中的图片标签)
tagHandler :对标签进行处理(相当于自定义的标签处理,在这里面可以处理自定义的标签)
具体实现如下:
/**
* Some parts of this code are based on android.text.Html
*/
public class HtmlTagHandler implements Html.TagHandler {
private static final boolean DEBUG = true;
private static final String TAG = "HtmlTagHandler";
private int mListItemCount = ;
private final Vector<String> mListParents = new Vector<String>();
public List<Integer> indexs=new ArrayList<>();//填空标识下标集合
public ArrayList<ClickSpan> mSpans = new ArrayList<ClickSpan>();
private TextView tv;
private Context mContext;
private RelativeLayout rl;
private ClickController clickController;
private String mReplaceText;//替换的文本
public HtmlTagHandler(TextView tv, RelativeLayout rl, Context context, ClickController clickController, String replaceText){
this.tv = tv;
this.mContext = context;
this.rl = rl;
this.clickController = clickController;
this.mReplaceText = replaceText;
}
int idCount=;
@Override
public void handleTag(final boolean opening, final String tag, Editable output,
final XMLReader xmlReader) {
if (opening) { //加载html body 标签
// opening tag
if (DEBUG) {
Log.d(TAG, "opening, output: " + output.toString());
}
} else {//加载主体部分
// closing tag
if (DEBUG) {
int index = -;
int lastIndex =;
String line = "__________";
outputSpan(output, line, lastIndex);
}
}
}
private int outputSpan(Editable output, String line, int lastIndex) {
int index = -;
while (- != (index = output.toString().indexOf(mReplaceText, lastIndex))) {
lastIndex = index;
indexs.add(lastIndex);//将下标添加到集合
//将下标替换
output.replace(lastIndex, lastIndex + mReplaceText.length(), line);
//创建ClickableSpan 实现点击
ClickSpan clickSpan = new ClickSpan(output.toString(), mContext, tv, rl, idCount, clickController);
idCount++;
clickController.addContent("");
mSpans.add(clickSpan);
output.setSpan(clickSpan, lastIndex, lastIndex + line.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return lastIndex;
}
/**
* Mark the opening tag by using private classes
*
* @param output
* @param mark
*/
private void start(Editable output, Object mark) {
int len = output.length();
output.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
if (DEBUG) {
Log.d(TAG, "len: " + len);
}
}
private void end(Editable output, Class kind, Object repl, boolean paragraphStyle) {
Object obj = getLast(output, kind);
// start of the tag
int where = output.getSpanStart(obj);
// end of the tag
int len = output.length();
output.removeSpan(obj);
if (where != len) {
// paragraph styles like AlignmentSpan need to end with a new line!
if (paragraphStyle) {
output.append("\n");
len++;
}
output.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (DEBUG) {
Log.d(TAG, "where: " + where);
Log.d(TAG, "len: " + len);
}
}
/**
* Get last marked position of a specific tag kind (private class)
*
* @param text
* @param kind
* @return
*/
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(, text.length(), kind);
if (objs.length == ) {
return null;
} else {
for (int i = objs.length; i > ; i--) {
if (text.getSpanFlags(objs[i - ]) == Spannable.SPAN_MARK_MARK) {
return objs[i - ];
}
}
return null;
}
}
private void handleListTag(Editable output) {
if (mListParents.lastElement().equals("ul")) {
output.append("\n");
String[] split = output.toString().split("\n");
int lastIndex = split.length - ;
int start = output.length() - split[lastIndex].length() - ;
output.setSpan(new BulletSpan( * mListParents.size()), start, output.length(), );
} else if (mListParents.lastElement().equals("ol")) {
mListItemCount++;
output.append("\n");
String[] split = output.toString().split("\n");
int lastIndex = split.length - ;
int start = output.length() - split[lastIndex].length() - ;
output.insert(start, mListItemCount + ". ");
output.setSpan(new LeadingMarginSpan.Standard( * mListParents.size()), start,
output.length(), );
}
}
}
关键地方,重写handleTag方法,通过debug我想你就能够清楚的知道他的流程是怎么走的了。这里就不过多的解释了。
三、获取每一个EditText输入内容
这个参考的AnswerDemo中的代码,github地址如下
https://github.com/z56402344/AnswerDemo
这块如果需要深入研究的话可以看下Spannable的一些api
我这边需要获取到点击的输入框的位置。通过ClickableSpan 可以做到监听点击每个内容。
/**
* Created by hzp on 2017/9/13.
*/
public class ClickSpan extends ClickableSpan {
private String txt;
private int mFontT; // 字体top
private int mFontB;// 字体bottom
public static int mOldSpan = -;
private String mStr;
public String mWidthStr="__________";;
public int id = ;//回调中的对应Span的ID
protected ImmFocus mFocus = new ImmFocus();
private TextView textView;
private RectF mRf;
private Context mContext;
private RelativeLayout rl;
private ClickController mClickController;
public ClickSpan(String txt, Context context, TextView textView, RelativeLayout rl, int id, ClickController mClickController) {
this.txt = txt;
this.id = id;
this.mContext = context;
this.textView = textView;
this.rl =rl;
this.mClickController = mClickController;
}
@Override
public void onClick(View widget) {
Log.d("点击了==","ClickSpan");
Toast.makeText(mContext,""+id, Toast.LENGTH_LONG).show();
mOldSpan = id;
String s = mClickController.getmInputList().get(id);
mClickController.setData(s,null,mOldSpan);
// //如果当前span身上有值,先赋值给et身上
// mEditText.setText(TextUtils.isEmpty(mInputList.get(id))?"":mInputList.get(id));
setEtXY(textView, drawSpanRect(textView, this));
}
//获取出对应Span的RectF数据
public RectF drawSpanRect(TextView v, ClickSpan s) {
Layout layout = v.getLayout();
Spannable buffer = (Spannable) v.getText();
int l = buffer.getSpanStart(s);
int r = buffer.getSpanEnd(s);
int line = layout.getLineForOffset(l);
int l2 = layout.getLineForOffset(r);
if (mRf == null) {
mRf = new RectF();
Rect rt = new Rect();
v.getPaint().getTextBounds("TgQyYjJ", , , rt);
mFontT = rt.top;
mFontB = rt.bottom;
}
mRf.left = layout.getPrimaryHorizontal(l);
mRf.right = layout.getSecondaryHorizontal(r);
// 通过基线去校准
line = layout.getLineBaseline(line);
mRf.top = line + mFontT;
mRf.bottom = line + mFontB;
return mRf;
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + f);
}
//添加edittext到指定位置
public void setEtXY(TextView tv, RectF rf) {
EditText et = new EditText(mContext);
et.setId(id);
et.setSingleLine(true );
et.setBackgroundColor();
et.setTextColor(Color.parseColor("#000000"));
//设置et w,h的值
// RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) et.getLayoutParams();
RelativeLayout.LayoutParams lp=new RelativeLayout.LayoutParams((int)(rf.right - rf.left),dip2px(mContext,mClickController.getEditHeight()));
// lp.width = (int) (rf.right - rf.left);
// lp.height = (int) (rf.bottom - rf.top);
//设置et 相对于tv x,y的相对位置
lp.leftMargin = (int) (tv.getLeft() + rf.left);
lp.topMargin = (int) (tv.getTop() + rf.top)-dip2px(mContext,);
et.setLayoutParams(lp);
rl.addView(et);
//获取焦点,弹出软键盘
et.setFocusable(true);
et.requestFocus();
et.setSelection(et.getText().length());
showImm(true, et);
et.addTextChangedListener(mWatcher);
et.setFilters(new InputFilter[]{new InputFilter.LengthFilter(mWidthStr.length())});
}
//输入填空的监听
private TextWatcher mWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
try{
mClickController.setData(s.toString(),null,id);
// for (int i=0;i<mClickController.getmInputList().size();i++){
// Log.d("输入集合----",mClickController.getmInputList().get(i));
// }
CallBackListenerImp.mCallbackLisntener.callBack(mClickController.getmInputList());
}catch (Exception e){
e.printStackTrace();
}
}
};
public void showImm(boolean bOn, View focus) {
try {
if (bOn) {
if (focus != null) {
ImmFocus.show(true, focus);
} else {
mFocus.setFocus(focus);
}
} else {
ImmFocus.show(false, null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void updateDrawState(TextPaint ds) {
//根据自己的需求定制文本的样式
// ds.setColor(ds.linkColor);
ds.setUnderlineText(false);
}
}
如果你不想看这么多东西直接调用方法:
ClickController mClickController = new ClickController(mTv);
String mReplaceText= "[__Fill.Replace__]";
//添加链接
mTv.setMovementMethod(LinkMovementMethod
.getInstance());
mTv.setText(Html.fromHtml(source, new URLImageGetter(source, this, mTv), new HtmlTagHandler(mTv, rl, this,mClickController,mReplaceText)));
source为html代码
mTv为显示的textView
rl为包裹textview的RelativeLayout布局
项目已上传github
https://github.com/huang8023wei/HtmlLoadEditText-master