天天看点

android 原生加载html图文输入框

最近在做一个比较坑的需求,在原生里面需要实现图文混排并且需要在指定位置输入内容,做完型填空。百度google发现没有这种实现。心中一万句草泥马在翻腾。各种曲折,终于撸出来了,如果你也有这个需求,希望能够帮到你!

以上功能如下:

1.在TextView中加载带图文的html代码

2.在指定位置插入EditText控件

3.获取每一个EditText输入内容*

4.viewpager中实现单页EdirText内容获取*

话不多说,先上图.

android 原生加载html图文输入框
android 原生加载html图文输入框

示例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