Android 基于TextView实现填空题

  • 阅读本文大概需要 5 min

关键字

填空题SpanStringHtmlTextView自定义ReplacementSpan猿题库

前言

关于填空题,每人都有自己实现的特点,比如这篇文章这种基于PopupWindow或者是Dialog中转来把文字填入,当然也有选词填入的,但个人觉得这都不是真正意义上的填空题。真正意义上的填空题应该可以能在文本下划线里输入文字,而且能满足比较好的扩展。这一点我觉得猿题库做的比较好,所以这篇文章可以说是模仿猿题库填空题的效果,但是也有不同之处,效果的实现思路很大程度上借鉴了z56402344AnswerDemo,在此对作者表示感谢。

高清无码图

***的无码图.gif

怎么样,效果还是还可以吧,当然,你要选中加其它各种效果还是很容易扩展的,下面我将一步一步讲解实现的思路,心急的童鞋想直接看代码可以戳这里
👉特殊通道

臆想

如果你的设计师要求你实现这种效果,你大概想采用TextView +EditText的组合来实现,然后根据文本出现的下划线拆分,然后一个一个拼接,咦,怎么布局呢?突然流畅的思路被卡住了!又水平又垂直的。可能你会想到了流式布局,阴郁的天气瞬间晴朗了起来!BUT,光性能先不说,比如我以后要加一个试题的复制功能,就不好扩展了,代码整洁性以及扩展性是所不能够忍受的!

那怎么搞?

怎么搞.jpg

下面将从两方面来讲

  • 需要实现的效果
  • 实现思路

PS:需要具备Spanned系列Api使用的基础,具体学习可以网上搜索博客


Spanned继承关系图

需要的效果

  1. 自动识别特定带填空题的标识,并能够自定义样式
  2. 填空题部分能够相应点击效果
  3. 填空题部分能够输入文字
  4. 输入完成之后可以把文本显示到下划线上面

需求写完一时爽,程序猿实现起来苦叫天。没办法,毕竟拿人钱财替人消灾,不行也得行,硬着头皮上!

实现思路

有点Android基础的都晓得TextView可以展示富文本信息,其实这些富文本信息的实现都是由Spanned系列拼接成的的SpannableStringBuilder,然后通过TextView.setText(CharSequence text)方法设置进去(说到富文本展示,还有一个方式是Html.fromHtml(String source)及其重载方法,翻看源码,本质上的实现还是和Spanned系列拼接而成的,我这里采用的就是这种方式),这里我把填空题设置为自定义的<edit>标签,然后在Html. fromHtml(String source, int flags, Html.ImageGetter imageGetter, Html.TagHandler tagHandler)重载方法的第三个参数Html.TagHandler来处理我的自定义标签<edit>,替换成我想要的内容,这里替换成我的自定义ReplacementSpan,这里可能很多人没有听说过这个类,本篇文章讲解实现思路,所以不打算深入讲解这个类的使用,这里简单介绍一下它的类的继承关系

ReplacementSpan继承关系

可以看到ReplacementSpan类继承自MetricAffectingSpan,而MetricAffectingSpan又继承自CharacterStyle,而我们熟知的许多下划线UnderlineSpan 背景 BackgroundColorSpan 都是继承该类。

CharacterStyle继承关系

这里我自定义ReplacementSpan,就可以实现下划线以及在下划线上显示用户输入文字的效果(这里在思路上已经实现了第1个和第4个效果)

TextView有个点击触摸事件方法setMovementMethod(MovementMethod movement),这里我们可以自定义MovementMethod来实现特定字符的点击效果(这里我们就实现了第2个效果),在点击的时候计算好填空题的位置和长宽,然后把EditText放置上去就可以输入文字了(这里实现第3个效果)
大体上解决思路就讲完了,具体到代码可能需要很多细节。

咦?搞什么,没有代码??没有贴代码吹啥牛逼!

吹牛逼

这里我放一下关键类自定义ReplacementSpan,更多的细节我的代码已经上传gitHub,前面已经给出链接。
Show you the code

/**
 * Created by tangminglong on 17/10/19.
 * 自定义Span,用来绘制填空题
 */

public class ReplaceSpan extends ReplacementSpan {

    private final Context context;
    public String mText = "";//保存的String

    private final Paint mPaint;

    public Object mObject;//回调中的任意对象
    private int textWidth = 80;//单词的宽度

    public OnClickListener mOnClick;
    public int id = 0;//回调中的对应Span的ID


    public ReplaceSpan(Context context,Paint paint) {
        this.context = context;
        mPaint = paint;
        textWidth = DensityUtils.dp2px(context,textWidth);

    }

    public void setDrawTextColor(int res){
        mPaint.setColor(context.getResources().getColor(res));
    }


    @Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        //返回自定义Span宽度

        return textWidth;
    }

    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {

        float bottom1 =  paint.getFontMetrics().bottom;
        float y1 = y+bottom1;

        CharSequence ellipsize = TextUtils.ellipsize(mText, (TextPaint) paint, textWidth, TextUtils.TruncateAt.END);
        int width = (int)paint.measureText(ellipsize,0,ellipsize.length());

        width = (textWidth-width)/2;

        canvas.drawText(ellipsize,0,ellipsize.length(),  x+width, (float) y,mPaint);

        //需要填写的单词下方画线
        //这里bottom-1,是为解决有时候下划线超出canvas
        Paint linePaint = new Paint();

        linePaint.setColor(mPaint.getColor());
        linePaint.setStrokeWidth(2);
        canvas.drawLine(x, y1, x + textWidth, y1, linePaint);
    }

    public void onClick(TextView v, Spannable buffer, boolean isDown, int x, int y, int line, int off){
        if (mOnClick != null){
            mOnClick.OnClick(v,id,this);
        }
    }

    public  interface OnClickListener{
        void OnClick(TextView v, int id, ReplaceSpan span);
    }
    
}

最后

有时间的话我会整理出其他题型的适配,欢迎关注。
最近学习自定义View的进阶,扔物线的系列教程很不错,好东西和大家一起分享。
👉特殊通道

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,612评论 4 59
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,567评论 25 707
  • 前段时间在开发群里看到有人问android的TextView该如何自定义超链接的跳转,如:有字符串“使用该软件,即...
    zhangjinhuang阅读 7,093评论 1 36
  • 一、饮食结构 运动和节食都不是减肥和保持过程中的重中之重,重要的是“习惯”,是形成了瘦人的生活习惯,三餐规律,并且...
    修一云阅读 164评论 0 0
  • 世界上有那么一种人,星座对于他们来说就如同神谕。今天该不该上街,要穿什么颜色衣服,那个男人/女人和自己是否般配,此...
    大狗说阅读 4,908评论 0 1