最近需要实现个需求,感觉还挺常用的,并且挺有意思,所以记录一下,要求是显示一段文字,文字中间有填空的地方,用户点击填空的下划线,可以输入内容,输入完成后的内容替换到填空上,这段文字的长度自动变化。

如图:

模拟器效果略卡,接下来说说怎么实现的吧。

1.准备工作:

我们需要先了解SpannableString这个对象类型的使用方法。先来点简单的:

  • 使用ForegroundColorSpan为一段文字设置不同的字体颜色
String str = "本人持有____国学生签证,在____国院校就读;";
ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.RED);
SpannableString ss = new SpannableString(str);
ss.setSpan(colorSpan, 0, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
content.setText(ss);

效果如下:

这里注意一下setSpan的flag有4中取值:
SPAN_INCLUSIVE_EXCLUSIVE表示插入start前的内容和[start,end)左闭右开区间内的内容受到span的影响;
SPAN_INCLUSIVE_INCLUSIVE表示插入start前的内容,插入end后的内容和[start,end)左闭右开区间内的内容受到span的影响;
SPAN_EXCLUSIVE_EXCLUSIVE表示只有[start,end)左闭右开区间内的内容受到span的影响;
SPAN_EXCLUSIVE_INCLUSIVE表示插入end后的内容和[start,end)左闭右开区间内的内容受到span的影响。
值得一说的是网上查到的大部分资料都是像上面这么介绍这四个flag的影响的,
但是这四个flag的影响起作用只在可编辑控件中起作用在不可编辑控件中这四个flag不起任何作用,
例如textview,因为不可编辑控件根本没办法在span生效的内容前或者后插入新的内容,
所以在不可编辑的控件中使用4个flag中的任意一个都可以。

  • 为下划线部分设置可点击事件:
String str = "本人持有____国学生签证,在____国院校就读;";
ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.RED);
SpannableString ss = new SpannableString(str);
ss.setSpan(colorSpan, 4, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ClickableSpan clickableSpan = new ClickableSpan() {@Override
    public void onClick(View widget) {Toast.makeText(MainActivity.this, "我被点击了", Toast.LENGTH_SHORT).show();
    }
};
ss.setSpan(clickableSpan, 4, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//注意这句必须写,否则点击事件不生效
content.setMovementMethod(LinkMovementMethod.getInstance());
content.setText(ss);

效果如下:

好了了解两种就好,剩下的SpannableString可以实现的效果大家可以自己再去学习,用法都大同小异。

2.实现需求效果思路分析:

  • 把字符串转换SpannableStringBuilder类型的对象(SpannableStringBuilder和SpannableString的关系类似于StringBuilder和String)。
  • 为字符串的对应位置设置点击效果(因为一段文字中可能有多处需要设置的,所以位置信息用列表来存储)。
  • 在ClickableSpan对应的Onclick方法中弹出输入框+确定按钮+软键盘。
  • 在确定按钮的点击事件中设置把输入框内容替换对应位置的下划线,并且更新SpannableStringBuilder中对应的start和end的位置。

3.根据思路介绍主要代码:

  • 变量解释:
/**存放Span影响内容的边界信息对象列表*/
private ArrayList<RangBean> ranges;
/**可变长度的可分组字符串*/
private SpannableStringBuilder ssb;
/**存放答案的列表*/
private ArrayList<String> answers;
/**用来输入答案的可编辑文本框控件*/
private EditText input;
/**用来确定答案的按钮控件*/
private Button sure;
/**用来展示输入答案的可编辑文本框和确定按钮界面的popupwindow*/
private PopupWindow popupWindow;
  • 将字符串转换成SpannableStringBuilder对象:
ssb.clear();
ssb.append(str);
  • 为字符串中的对应位置设置颜色+点击+下划线效果:
for (int i=0; i<ranges.size(); i++) {RangBean bean = ranges.get(i);

    //设置文字颜色
    //这里注意一下setSpan的flag有4中取值(start的取值范围从0开始):
    //SPAN_INCLUSIVE_EXCLUSIVE表示插入start前的内容和[start,end)左闭右开区间内的内容受到span的影响;
    //SPAN_INCLUSIVE_INCLUSIVE表示插入start前的内容,插入end后的内容和[start,end)左闭右开区间内的内容受到span的影响;
    //SPAN_EXCLUSIVE_EXCLUSIVE表示只有[start,end)左闭右开区间内的内容受到span的影响;
    //SPAN_EXCLUSIVE_INCLUSIVE表示插入end后的内容和[start,end)左闭右开区间内的内容受到span的影响。
    //值得一说的是网上查到的大部分资料都是像上面这么介绍这四个flag的影响的,
    //但是这四个flag的影响起作用只在可编辑控件中起作用
    //例如editext中可以,如果想体验可以把我MainActivty注释的edittext
    // 和activity_main里的相关代码解除注释试一试,在不可编辑控件中这四个flag不起任何
    //作用,例如textview,因为不可编辑控件根本没办法在span生效的内容前或者后插入新的内容,
    // 所以在不可编辑的控件中使用4个flag中的任意一个都可以,你可以试着把下面的flag修改试试效果
    ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.parseColor("#4DB6AC"));
    ssb.setSpan(colorSpan, bean.getStart(), bean.getEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

    //设置可点击
    ClickableSpan clickableSpan = new MyClickableSpan(i);
    ssb.setSpan(clickableSpan, bean.getStart(), bean.getEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

    //设置下划线
    UnderlineSpan underlineSpan = new UnderlineSpan();
    ssb.setSpan(underlineSpan, bean.getStart(), bean.getEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

    answers.add("");
}//注意这里必须为文本框设置这个属性,clickablespan才生效
setMovementMethod(LinkMovementMethod.getInstance());
//将可变长度可分组字符串设置到文本框里
setText(ssb)
  • 在ClickableSpan对应的OnClick方法中弹出输入框+确定按钮+软键盘;
  • 在确定按钮对应的OnClick方法中将输入框中的内容替换下划线,更新位置信息:
private class MyClickableSpan extends ClickableSpan {private int index;

    /**
     * 构造方法
     * @param index 点击位置在边界列表中对应的index
     */
    public MyClickableSpan(int index) {this.index = index;
    }@Override
    public void onClick(View widget) {//将答案展示在输入框中
        input.setText(answers.get(index));
        //设置光标移动到答案最后
        input.setSelection(input.length());
        //显示popupwindow
        popupWindow.showAtLocation(EditableTextView.this, Gravity.BOTTOM, 0, 0);
        //下面两行是弹出软键盘
        InputMethodManager innputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        innputMethodManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);

        sure.setOnClickListener(new OnClickListener() {@Override
            public void onClick(View v) {String answer = input.getText().toString();
                //将输入框中的答案根据位置添加到答案列表中
                answers.set(index, answer);
                //如果输入框中的答案为空,则用下划线代替
                if (TextUtils.isEmpty(answer)) {answers.set(index, "");
                    answer = "____";
                }//获取当前位置的边界信息
                RangBean bean = ranges.get(index);
                //将可变长度可分组字符串中边界信息对应位置替换成答案
                ssb.replace(bean.getStart(), bean.getEnd(), answer);
                //计算边界信息中的答案长度与输入的答案长度的差值
                int change = bean.getEnd() - bean.getStart() - answer.length();
                //更新当前位置及之后的边界信息
                for (int i=index; i<ranges.size(); i++) {RangBean rb = ranges.get(i);
                    //当前位置的起始位置信息不变,之后的所有位置的起始边界信息根据差值移动
                    if (i != index) {rb.setStart(rb.getStart() - change);
                    }//当前位置以及之后所有位置的结束边界信息根据差值移动
                    rb.setEnd(rb.getEnd() - change);
                }//将文本框中的内容更新
                setText(ssb);
                //隐藏popupwindow
                popupWindow.dismiss();
            }});
    }
}

4.总结和提示:

主要的实现思路和实现代码就是上面这些了,我为了方便使用将这些封装在了一个继承自TextView的自定义EditableTextView中,这样使用的时候只用使用EditableTextView并且将文字和位置信息列表设置好就行,很简单的。提示就是上面这么写完你会发现如果你的软键盘上如果有隐藏按钮或者你直接按物理返回键的时候,软键盘隐藏了,但是Popupwindow却没有隐藏,反正我觉得有点尴尬,所以我又写了个方法监听软键盘的打开和隐藏,如果监听到软键盘隐藏了就让popupwindow也隐藏就好了。废话就不说了,附上demo下载链接:http://download.csdn.net/download/wozuihaole/10266870

5.完整代码:

public class EditableTextView extends android.support.v7.widget.AppCompatTextView {/**存放Span影响内容的边界信息对象列表*/
    private ArrayList<RangBean> ranges;
    /**可变长度的可分组字符串*/
    private SpannableStringBuilder ssb;
    /**存放答案的列表*/
    private ArrayList<String> answers;
    /**用来输入答案的可编辑文本框控件*/
    private EditText input;
    /**用来确定答案的按钮控件*/
    private Button sure;
    /**用来展示输入答案的可编辑文本框和确定按钮界面的popupwindow*/
    private PopupWindow popupWindow;
    /**软键盘的高度,我们可以认为屏幕高度的三分之一就是软键盘打开时的高度*/
    private int softInputHeight;

    public EditableTextView(Context context) {super(context);
        init();
    }public EditableTextView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);
        init();
    }public EditableTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);
        init();
    }/**初始化*/
    private void init() {answers = new ArrayList<>();
        ranges = new ArrayList<>();
        ssb = new SpannableStringBuilder();

        //计算屏幕高度的三分之一,赋值给软键盘高度
        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        softInputHeight = dm.heightPixels/3;

        //加载一个输入框和确定按钮的布局
        View view = LayoutInflater.from(getContext()).inflate(R.layout.input, null);
        input = (EditText) view.findViewById(R.id.editable_textview_input);
        sure = (Button) view.findViewById(R.id.editable_textview_sure);

        //初始化popupwindow
        popupWindow = new PopupWindow(view, ViewGroup.LayoutParams.MATCH_PARENT, dp2px(40));
        popupWindow.setFocusable(true);
        //设置popupwindow会被软键盘顶上去,而不是覆盖掉
        popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
        //下面的两个方法一起使用,设置点击popupwindow边界外部时使得popupwindow消失
        popupWindow.setBackgroundDrawable(new ColorDrawable(0x00000000));
        popupWindow.setOutsideTouchable(true);

        initSoftListener();
    }/**
     * 设置显示内容和内容中受span影响的边界信息列表,
     * 使得文本框中的显示内容的下划线部分的字体颜色改变,并且可以点击,
     * 点击后弹出输入框,输入内容确定后替换下划线部分的内容
     * @param str 显示的内容
     * @param rangBeans 内容中受span影响的边界信息列表
     * 例如:
     *     str = "本人持有____国学生签证,在____国院校就读;"
     *     RangBean ranges = new ArrayList<>();
     *     ranges.add(new RangBean(4, 8));
     *     ranges.add(new RangBean(15, 19));
     *     setData(str, ranges);
     */
    public void setData(String str, ArrayList<RangBean> rangBeans) {if (TextUtils.isEmpty(str) || rangBeans == null || rangBeans.size() <= 0) {Toast.makeText(getContext(), "参数不能为空", Toast.LENGTH_SHORT).show();
            return;
        }ssb.clear();
        ssb.append(str);
        ranges.clear();
        ranges.addAll(rangBeans);

        for (int i=0; i<ranges.size(); i++) {RangBean bean = ranges.get(i);

            //设置文字颜色
            //这里注意一下setSpan的flag有4中取值(start的取值范围从0开始):
            //SPAN_INCLUSIVE_EXCLUSIVE表示插入start前的内容和[start,end)左闭右开区间内的内容受到span的影响;
            //SPAN_INCLUSIVE_INCLUSIVE表示插入start前的内容,插入end后的内容和[start,end)左闭右开区间内的内容受到span的影响;
            //SPAN_EXCLUSIVE_EXCLUSIVE表示只有[start,end)左闭右开区间内的内容受到span的影响;
            //SPAN_EXCLUSIVE_INCLUSIVE表示插入end后的内容和[start,end)左闭右开区间内的内容受到span的影响。
            //值得一说的是网上查到的大部分资料都是像上面这么介绍这四个flag的影响的,
            //但是这四个flag的影响起作用只在可编辑控件中起作用
            //例如editext中可以,如果想体验可以把我MainActivty注释的edittext
            // 和activity_main里的相关代码解除注释试一试,在不可编辑控件中这四个flag不起任何
            //作用,例如textview,因为不可编辑控件根本没办法在span生效的内容前或者后插入新的内容,
            // 所以在不可编辑的控件中使用4个flag中的任意一个都可以,你可以试着把下面的flag修改试试效果
            ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.parseColor("#4DB6AC"));
            ssb.setSpan(colorSpan, bean.getStart(), bean.getEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            //设置可点击
            ClickableSpan clickableSpan = new MyClickableSpan(i);
            ssb.setSpan(clickableSpan, bean.getStart(), bean.getEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            //设置下划线
            UnderlineSpan underlineSpan = new UnderlineSpan();
            ssb.setSpan(underlineSpan, bean.getStart(), bean.getEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            answers.add("");
        }//注意这里必须为文本框设置这个属性,clickablespan才生效
        setMovementMethod(LinkMovementMethod.getInstance());
        //将可变长度可分组字符串设置到文本框里
        setText(ssb);
    }private class MyClickableSpan extends ClickableSpan {private int index;

        /**
         * 构造方法
         * @param index 点击位置在边界列表中对应的index
         */
        public MyClickableSpan(int index) {this.index = index;
        }@Override
        public void onClick(View widget) {//将答案展示在输入框中
            input.setText(answers.get(index));
            //设置光标移动到答案最后
            input.setSelection(input.length());
            //显示popupwindow
            popupWindow.showAtLocation(EditableTextView.this, Gravity.BOTTOM, 0, 0);
            //下面两行是弹出软键盘
            InputMethodManager innputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            innputMethodManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);

            sure.setOnClickListener(new OnClickListener() {@Override
                public void onClick(View v) {String answer = input.getText().toString();
                    //将输入框中的答案根据位置添加到答案列表中
                    answers.set(index, answer);
                    //如果输入框中的答案为空,则用下划线代替
                    if (TextUtils.isEmpty(answer)) {answers.set(index, "");
                        answer = "____";
                    }//获取当前位置的边界信息
                    RangBean bean = ranges.get(index);
                    //将可变长度可分组字符串中边界信息对应位置替换成答案
                    ssb.replace(bean.getStart(), bean.getEnd(), answer);
                    //计算边界信息中的答案长度与输入的答案长度的差值
                    int change = bean.getEnd() - bean.getStart() - answer.length();
                    //更新当前位置及之后的边界信息
                    for (int i=index; i<ranges.size(); i++) {RangBean rb = ranges.get(i);
                        //当前位置的起始位置信息不变,之后的所有位置的起始边界信息根据差值移动
                        if (i != index) {rb.setStart(rb.getStart() - change);
                        }//当前位置以及之后所有位置的结束边界信息根据差值移动
                        rb.setEnd(rb.getEnd() - change);
                    }//将文本框中的内容更新
                    setText(ssb);
                    //隐藏popupwindow
                    popupWindow.dismiss();
                }});
        }}private int dp2px(float dp) {return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
                getResources().getDisplayMetrics());
    }/**设置软键盘的关闭和打开监听,要使用这个方法必须手机api在11或以上,
     * 并且activity在清单配置文件中设置android:windowSoftInputMode="adjustResize"*/
    private void initSoftListener(){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {((Activity)getContext()).findViewById(android.R.id.content).addOnLayoutChangeListener(new View.OnLayoutChangeListener() {@Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom,
                                           int oldLeft, int oldTop, int oldRight, int oldBottom) {//现在认为只要控件将Activity向上推的高度超过了1/3屏幕高,就认为软键盘弹起
                    if(oldBottom != 0 && bottom != 0 &&((oldBottom - bottom) > softInputHeight)){onOpenSoftInput();

                    }else if(oldBottom != 0 && bottom != 0 &&((bottom - oldBottom) >  softInputHeight)){onCloseSoftInput();
                    }}});
        }}/**
     * 当软键盘打开时回调
     */
    private void onOpenSoftInput(){Log.i("zhangdi","软件盘打开");
    }/**
     * 当软键盘关闭时回调
     */
    private void onCloseSoftInput(){Log.i("zhangdi","软件盘关闭");
        if (popupWindow != null && popupWindow.isShowing()) {popupWindow.dismiss();
        }}
}

android实现填空模式相关推荐

  1. Android沉浸式模式状态栏(二)

    其实说到沉浸式状态栏这个名字,真不知道这种叫法是谁先发起的.因为Android官方从来没有给出过沉浸式状态栏这样的命名,只有沉浸式模式(Immersive Mode)这种说法.而有些人在没有完全了解清 ...

  2. Android Activity启动模式,回退栈管理!

    (一): Activity被回收了怎么办?当系统内存不足的时候Activity是有可能被回收的,打个比方用户在A Activity启动了B Activity那么A就处于停止状态,由于内存不足A会被回收 ...

  3. android四中启动模式

    android四中启动模式分别为 转载于:https://www.cnblogs.com/goneone/p/3490111.html

  4. Android的启动模式(上)

    Android的启动模式(上) 1. 基本介绍 大家平时只要懂一点Android知识的话,都一定会知道,一个应用的组成,往往包含了许多的activity组件,每个activity都应该围绕用户的特定动 ...

  5. android分屏模式_Android分屏模式开发注意事项

    Android分屏模式开发注意到主页面在分屏模式下,页面所占比例在增大或者减小的时候都会调用oncreat方法,所以查资料总结一下分屏对页面的生命周期的影响以及开发时的注意事项: App页面从全屏模式 ...

  6. Android分屏模式代码实现

    文章目录 生命周期 开发者相关 相关模块和主要类 `ActivityManager` `WindowManager` `Framework API` `SystemUI` 多窗口的功能实现 两个系统服 ...

  7. android recovery分区内刷镜像,Android手机Recovery模式取证方法研究.pdf

    Android手机Recovery模式取证方法研究 2015 年第 9 期 信息通信 2015 (总第 153 期) INFORMATION & COMMUNICATIONS (Sum. No ...

  8. Android之MVP模式

    今天来看看Android的MVP模式,使用框架开发,开发速度以及代码的目录结构会别有一番风格. Google的demo:https://github.com/googlesamples/android ...

  9. Android护眼模式功能小记

    最近自己在做一个小说阅读器,看到某阅有护眼模式功能,别人都有,我怎么能没有? 现在这功能已经不稀奇了,很多手机都带有这个功能. 实现起来不难,用一个蒙版遮在界面上面就行. 至于蒙版,可以用Window ...

  10. 安卓访客模式_如何设置Android访客模式以及为什么要这么做 | MOS86

    Android访客模式是一个选项,可让您隐藏自己的所有东西,但仍保持手机正常运行. 当您切换到访客模式时,您将隐藏所有应用程序,历史记录,图片,消息等,同时允许其他人使用您的手机. 使用Android ...

最新文章

  1. 面试前必知必会的二分查找及其变种
  2. echarts legend文字配置多个颜色(转)
  3. 常用开源工具、框架收藏
  4. Spring 环境与profile(一)——超简用例
  5. 安卓BLE开发教程(一) BLE基础
  6. c语言写入文件后换行,关于文件操作,碰到空格就换行
  7. 删除以x为根节点的子树并释放☆
  8. 栈Stack的相关操作(java)
  9. 流体力学有限元法(二)
  10. 迈达斯GTS-NX网格模型(FPN)导入Flac3D 6.0
  11. 新科LoRa网关和LoRa节点
  12. plc用c语言编程实例,化学反应生产过程的PLC控制编程实例
  13. latex插图编号_latex中插图心得
  14. 怎么压缩html的文件,css如何压缩?
  15. ASUS 論壇,有官方槍手,想要刪除抗議 Windows 7 的帖子,並合理化,並罵消費者白目
  16. layer 在标题中加点击监听事件
  17. Kubernetes CKA认证运维工程师笔记-Kubernetes故障排查
  18. 使用VLC组播测试及VLC收不到UDP组播数据
  19. 总结图扑软件可实现的可视化效果案例分享
  20. 小米一代扫地机器人磨损家具_为了以后的众测 篇二:无差评居家神器——Mi 小米 扫地机器人...

热门文章

  1. mysql innodb 幻读_MySQL InnoDB四个事务级别 与 脏读、不重复读、幻读
  2. nacos需要mysql吗_nacos无法连接mysql的解决方法
  3. mysql学籍表设计_MySQL基础-学生管理系统数据库设计
  4. python自动化运维平台下载_5、python自动化运维——集中化管理平台Ansible
  5. Django:报错 unsupported format character ‘Y‘ (0x59) at index 70
  6. Django:MySQL查询结果为datetime.date无法转换为JSON
  7. c语言如何输出字符问号,C语言入门5-键盘的输入和屏幕输出
  8. Java实现的FTP协议断点续传功能(上传/下载)通用类
  9. Eigen编译_Eigen向量化_内存对齐 EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  10. C语言union类型和C语言 uchar类型的个人见解