Android 自定义view-仿新浪微博#话题#插入EditText
Posted Ruffian-痞子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 自定义view-仿新浪微博#话题#插入EditText相关的知识,希望对你有一定的参考价值。
不小心打开新浪微博发微博页面有个可以插入话题#…#的功能,看着挺好玩的。就照着实现了一下。
如果不知道怎么样效果的可以打开微博看看。大概的功能是:
- 插入话题使用#特殊符号开头和结尾
- 话题文字高亮显示
- 删除话题选中整个话题文字一次性删除
- 可以插入多个话题
下面先看看我的实现效果图
怎么样,看起来是不是很吊的样子呢?又好像跟新浪微博的有些不一样哦,话题匹配符号不一样,没错,高仿微博但更胜于微博效果。
那么使用我的自定义控件你可以有哪些功能呢?
1.自定义每一个话题匹配符号 (# $ ^ & * 等)*
2.自定义话题高亮颜色,选中话题背景色
3.允许插入多个话题,并且可以获取对象列表,不仅仅是文本
4.点击话题光标自动移动至话题结束或开头
OK,那么这样一个自定控件到底有多难呢?其实很简单。下面来实现一下
强调一遍做事情思路一定要清晰,遇到困难不要紧,但是解决问题的思路一定要清晰,那么我们这个控件要实现哪些功能呢?
1.在光标处插入话题
这或许是最简单的问题了,我们直接继承原生的EditText,获取光标的位置,然后往EditText中insert文字。
这里需要注意的是:插入话题的时候不仅仅是文字,而是设置一个对象(很多需求是你获取输入框文字的时候还需要拿到话题的id等其他属性),同时需要将这个对象保存在集合中,最终提供给开发者使用。
那么我们自定一个类继承EditText,然后提供一个方法 setObject(RObject object)
/**
* 插入/设置话题
*
* @param object话题对象
*/
public void setObject(RObject object)
if (object == null)
return;
String objectRule = object.getObjectRule();
String objectText = object.getObjectText();
if (TextUtils.isEmpty(objectText) || TextUtils.isEmpty(objectRule))
return;
// 拼接字符# %s #,并保存
objectText = objectRule + objectText + objectRule;
object.setObjectText(objectText);
/**
* 添加话题<br/>
* 1.将话题内容添加到数据集合中<br/>
* 2.将话题内容添加到EditText中展示
*/
/**
* 1.添加话题内容到数据集合
*/
mRObjectsList.add(object);
/**
* 2.将话题内容添加到EditText中展示
*/
int selectionStart = getSelectionStart();// 光标位置
Editable editable = getText();// 原先内容
if (selectionStart >= 0)
editable.insert(selectionStart, objectText);// 在光标位置插入内容
editable.insert(getSelectionStart(), " ");// 话题后面插入空格,至关重要
setSelection(getSelectionStart());// 移动光标到添加的内容后面
RObject :话题对象,包含基本属性 话题文本,话题匹配字符#
public class RObject
private String objectRule = "#";// 匹配规则
private String objectText;// 话题文本
public String getObjectRule()
return objectRule;
public void setObjectRule(String objectRule)
this.objectRule = objectRule;
public String getObjectText()
return objectText;
public void setObjectText(String objectText)
this.objectText = objectText;
如果开发者的话题对象还需要其他的属性,那么继承 RObject 添加所需属性,例如
/**
* 测试使用开发者话题实体,必须继承RObject
*/
class MyTopic extends RObject
private String id;
// 其他属性...
public String getId()
return id;
public void setId(String id)
this.id = id;
第一段代码功能
1.将话题内容添加到数据集合中
2.将话题内容添加到EditText中展示
开发者传递一个RObject 对象过来,然后获取 objectRule ,objectText 根据匹配规则拼接字符串现在UI中,同时保存到数据集合。关键方法获取到当前光标位置然后调用insert方法插入文本
2.话题文本高亮颜色
第一步轻轻松松将内容插入到EditText,可以看到内容中已经有带 # 的话题内容内容出现了,看起来有点像了哦,那么现在就改变话题的文字颜色,关键方法
Editable editable = getText();
ForegroundColorSpan colorSpan = new ForegroundColorSpan(mForegroundColor);
editable.setSpan(colorSpan, findPosition, findPosition + objectText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
通过Editable 方法setSpan设置高亮颜色,这里需要获取到变色的 起始位置 和 结束位置 ,我们都知道在用户不断输入文字的时候起始位置和结束位置是在不停变化的,因此我们需要在EditText文字改变的时候刷新UI重新设置话题的高亮颜色。
监听文字变化,很简单直接实现EditText的监听接口
/**
* 输入框内容变化监听<br/>
* 1.当文字内容产生变化的时候实时更新UI
*/
this.addTextChangedListener(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)
// 文字改变刷新UI
refreshEditTextUI(s.toString());
);
刷新UI,重绘高亮文字文字方法
/**
* EditText内容修改之后刷新UI
*
* @param content输入框内容
*/
private void refreshEditTextUI(String content)
/**
* 内容变化时操作<br/>
* 1.查找匹配所有话题内容 <br/>
* 2.设置话题内容特殊颜色
*/
if (mRObjectsList.size() == 0)
return;
if (TextUtils.isEmpty(content))
mRObjectsList.clear();
return;
/**
* 重新设置span
*/
Editable editable = getText();
int findPosition = 0;
for (int i = 0; i < mRObjectsList.size(); i++)
final RObject object = mRObjectsList.get(i);
String objectText = object.getObjectText();// 文本
findPosition = content.indexOf(objectText);// 获取文本开始下标
if (findPosition != -1) // 设置话题内容前景色高亮
ForegroundColorSpan colorSpan = new ForegroundColorSpan(
mForegroundColor);
editable.setSpan(colorSpan, findPosition, findPosition
+ objectText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
方法要做的事情很明确
1.查找匹配所有话题内容,起始和结束位置
2.设置话题内容特殊颜色
这里得到改变后EditText的所有内容,使用 indexOf(“目标文本”)查找目标文本起始的位置,结束位置就简单啦,起始位置+目标文本的长度。由于这里支持多个话题插入,所以加多一个for循环遍历一遍就好。soEasy
得到起始和结束位置就好办了,使用Editable 的 setSpan() 方法设置高亮文本的起始和结束值。
到这里,已经实现了类似微博话题输入,并且可以高亮颜色展示的效果了。
3.实现话题选中删除效果
我们都知道,删除键是逐个删除文本的,那么我们需要做的就是,判断当光标处于话题内容中,或者在话题结束位置的时候 第一次点击删除实现选中整个文本效果,当用户再次点击删除键时再删除这个话题内容,而其他普通内容则逐个删除
我们通过监听输入板事件截取用户按下删除键时的事件,当光标处于话题内容中,或者在话题后面时,第一次按下删除键,实现选中文字效果,并且修改文本背景色,再次点击删除话题文本
但是要注意的是,我们不能利用 activity 里面的 onKeyDown() 和 onKeyUp() 两个回调,通过 log 发现文本变动和按键点击的回调顺序为 beforeTextChanged -> onTextChanged -> afterTextChanged -> onKeyDown -> onKeyUp .
这也说明了如果通过 拦截 onKeyDown() 和 onKeyUp() 两个回调时,文本是已经删除之后的文本,并不能有效的达到我们要实现的目的,那么有没有是文本改变之前就能截取到按键的方法呢?
其实我们可以通过监听 EditText 的 setOnKeyListener() 方法来监听按键( onKey -> beforeTextChanged -> onTextChanged -> afterTextChanged -> onKeyDown -> onKeyUp ):
/**
* 监听删除键 <br/>
* 1.光标在话题后面,将整个话题内容删除 <br/>
* 2.光标在普通文字后面,删除一个字符
*/
this.setOnKeyListener(new View.OnKeyListener()
@Override
public boolean onKey(View v, int keyCode, KeyEvent event)
if (keyCode == KeyEvent.KEYCODE_DEL
&& event.getAction() == KeyEvent.ACTION_DOWN)
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
/**
* 如果光标起始和结束不在同一位置,删除文本
*/
if (selectionStart != selectionEnd)
// 查询文本是否属于目标对象,若是移除列表数据
String tagetText = getText().toString().substring(
selectionStart, selectionEnd);
for (int i = 0; i < mRObjectsList.size(); i++)
RObject object = mRObjectsList.get(i);
if (tagetText.equals(object.getObjectText()))
mRObjectsList.remove(object);
return false;
int lastPos = 0;
Editable editable = getText();
// 遍历判断光标的位置
for (int i = 0; i < mRObjectsList.size(); i++)
String objectText = mRObjectsList.get(i)
.getObjectText();
lastPos = getText().toString().indexOf(objectText,
lastPos);
if (lastPos != -1)
if (selectionStart != 0
&& selectionStart >= lastPos
&& selectionStart <= (lastPos + objectText
.length()))
// 选中话题
setSelection(lastPos,
lastPos + objectText.length());
// 设置背景色
editable.setSpan(new BackgroundColorSpan(
mBackgroundColor), lastPos, lastPos
+ objectText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return true;
lastPos += objectText.length();
return false;
);
当满足第一次点击删除条件时设置光标 为 话题起始 到 话题结束
点击删除按键时判断光标起始位置和结束位置是否相等,如果不相等表示该文本已经选中,可以直接删除
到这里基本上完成了类似微博插入话题并且删除的效果,于是再仔细看看发现微博的效果中,光标无论如何不会出现在话题内容中,我们第一时间应该想到EditText有没有这类事件的监听呢,一查,果然有,那就太轻松了,这就是直接继承原生EditText的好处,很多方法直接用,我们只需要按照自己的需求稍作修改
我们重写onSelectionChanged(int selStart, int selEnd)
监听光标位置变化,如果光标位置处于话题内容中,直接将光标移动到话题最后。从而达到光标不会再话题内容中出现闪烁的效果,这样好看多了
/**
* 监听光标的位置,若光标处于话题内容中间则移动光标到话题结束位置
*/
@Override
protected void onSelectionChanged(int selStart, int selEnd)
super.onSelectionChanged(selStart, selEnd);
if (mRObjectsList == null || mRObjectsList.size() == 0)
return;
int startPosition = 0;
int endPosition = 0;
String objectText = "";
for (int i = 0; i < mRObjectsList.size(); i++)
objectText = mRObjectsList.get(i).getObjectText();// 文本
startPosition = getText().toString().indexOf(objectText);// 获取文本开始下标
endPosition = startPosition + objectText.length();
if (startPosition != -1 && selStart > startPosition
&& selStart <= endPosition) // 若光标处于话题内容中间则移动光标到话题结束位置
setSelection(endPosition);
好了到这里你就真的实现了高仿微博的实现效果了
但是我们知道实际开发中,我们不仅仅是在UI上展示话题文本高亮就可以了,在提交内容的时候我们希望可以拿到 id,text,以及其他的一些属性。因此我在一个设计时候就要求开发者传入一个RObject 对象,在需要提交到服务器的时候可以获取这些话题对象的集合,拿到想要的内容。
如果需要点击事件的时候也方便拿到话题对象的属性。(这里个人觉得在输入框中没有必要实现话题点击,就拿掉这部分功能了。如果要实现也很简单,在第二步设置话题颜色中ForegroundColorSpan
修改为ClickableSpan
顺便再添加一个回调函数响应点击事件,就可以了)
最后提供一个方法给开发者获取话题对象集合
/**
* 获取object列表数据
*/
public List<RObject> getObjects()
List<RObject> objectsList = new ArrayList<RObject>();
// 由于保存时候文本内容添加了匹配字符#,此处去除,还原数据
if (mRObjectsList != null && mRObjectsList.size() > 0)
for (int i = 0; i < mRObjectsList.size(); i++)
RObject object = mRObjectsList.get(i);
String objectText = object.getObjectText();
String objectRule = object.getObjectRule();
object.setObjectText(objectText.replace(objectRule, ""));// 将匹配规则字符替换
objectsList.add(object);
return objectsList;
这里需要注意的是,由于开发者的话题对象中文本是纯文字,匹配规则是另外的属性设置的,因此需要还原开发者本来的数据,再将整个话题对象返回给开发者
由于是分步骤讲解,因此可能代码不是很直观,那就贴一些整个自定义控件以及使用方法
1.自定义属性
我们需要自定义两个属性设置高亮文本颜色,和选中文本的背景色,我们在 /value/attrs.xml 中这么写:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- REditText -->
<declare-styleable name="REditText">
<attr name="object_foreground_color" format="color" />
<attr name="object_background_color" format="color" />
</declare-styleable>
</resources>
2.自定义REditText控件类
重要的方法以及实现思路在前面已经讲解,下面看整个类代码结构,结合注释,我相信聪明的你肯定不会看不懂,当然有问题可以留言
/**
* 仿微博话题输入控件
*
* @author Ruffian
*
*/
public class REditText extends EditText
// 默认,话题文本高亮颜色
private static final int FOREGROUND_COLOR = Color.parseColor("#FF8C00");
// 默认,话题背景高亮颜色
private static final int BACKGROUND_COLOR = Color.parseColor("#FFDEAD");
/**
* 开发者可设置内容
*/
private int mForegroundColor = FOREGROUND_COLOR;// 话题文本高亮颜色
private int mBackgroundColor = BACKGROUND_COLOR;// 话题背景高亮颜色
private List<RObject> mRObjectsList = new ArrayList<RObject>();// object集合
public REditText(Context context)
this(context, null);
public REditText(Context context, AttributeSet attrs)
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.REditText);
mBackgroundColor = a
.getColor(R.styleable.REditText_object_background_color,
BACKGROUND_COLOR);
mForegroundColor = a
.getColor(R.styleable.REditText_object_foreground_color,
FOREGROUND_COLOR);
a.recycle();
// 初始化设置
initView();
/**
* 监听光标的位置,若光标处于话题内容中间则移动光标到话题结束位置
*/
@Override
protected void onSelectionChanged(int selStart, int selEnd)
super.onSelectionChanged(selStart, selEnd);
if (mRObjectsList == null || mRObjectsList.size() == 0)
return;
int startPosition = 0;
int endPosition = 0;
String objectText = "";
for (int i = 0; i < mRObjectsList.size(); i++)
objectText = mRObjectsList.get(i).getObjectText();// 文本
startPosition = getText().toString().indexOf(objectText);// 获取文本开始下标
endPosition = startPosition + objectText.length();
if (startPosition != -1 && selStart > startPosition
&& selStart <= endPosition) // 若光标处于话题内容中间则移动光标到话题结束位置
setSelection(endPosition);
/**
* 初始化控件,一些监听
*/
private void initView()
/**
* 输入框内容变化监听<br/>
* 1.当文字内容产生变化的时候实时更新UI
*/
this.addTextChangedListener(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)
// 文字改变刷新UI
refreshEditTextUI(s.toString());
);
/**
* 监听删除键 <br/>
* 1.光标在话题后面,将整个话题内容删除 <br/>
* 2.光标在普通文字后面,删除一个字符
*/
this.setOnKeyListener(new View.OnKeyListener()
@Override
public boolean onKey(View v, int keyCode, KeyEvent event)
if (keyCode == KeyEvent.KEYCODE_DEL
&& event.getAction() == KeyEvent.ACTION_DOWN)
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
/**
* 如果光标起始和结束不在同一位置,删除文本
*/
if (selectionStart != selectionEnd)
// 查询文本是否属于目标对象,若是移除列表数据
String tagetText = getText().toString().substring(
selectionStart, selectionEnd);
for (int i = 0; i < mRObjectsList.size(); i++)
RObject object = mRObjectsList.get(i);
if (tagetText.equals(object.getObjectText()))
mRObjectsList.remove(object);
return false;
int lastPos = 0;
Editable editable = getText();
// 遍历判断光标的位置
for (int i = 0; i < mRObjectsList.size(); i++)
String objectText = mRObjectsList.get(i)
.getObjectText();
lastPos = getText().toString().indexOf(objectText,
lastPos);
if (lastPos != -1)
if (selectionStart != 0
&& selectionStart >= lastPos
&& selectionStart <= (lastPos + objectText
.length()))
// 选中话题
setSelection(lastPos,
lastPos + objectText.length());
// 设置背景色
editable.setSpan(new BackgroundColorSpan(
mBackgroundColor), lastPos, lastPos
+ objectText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return true;
lastPos += objectText.length();
return false;
);
/**
* EditText内容修改之后刷新UI
*
* @param content输入框内容
*/
private void refreshEditTextUI(String content)
/**
* 内容变化时操作<br/>
* 1.查找匹配所有话题内容 <br/>
* 2.设置话题内容特殊颜色
*/
if (mRObjectsList.size() == 0)
return;
if (TextUtils.isEmpty(content))
mRObjectsList.clear();
return;
/**
* 重新设置span
*/
Editable editable = getText();
int findPosition = 0;
for (int i = 0; i < mRObjectsList.size(); i++)
final RObject object = mRObjectsList.get(i);
String objectText = object.getObjectText();// 文本
findPosition = content.indexOf(objectText);// 获取文本开始下标
if (findPosition != -1) // 设置话题内容前景色高亮
ForegroundColorSpan colorSpan = new ForegroundColorSpan(
mForegroundColor);
editable.setSpan(colorSpan, findPosition, findPosition
+ objectText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
/**
* 插入/设置话题
*
* @param object话题对象
*/
public void setObject(RObject object)
if (object == null)
return;
String objectRule = object.getObjectRule();
String objectText = object.getObjectText();
if (TextUtils.isEmpty(objectText) || TextUtils.isEmpty(objectRule))
return;
// 拼接字符# %s #,并保存
objectText = objectRule + objectText + objectRule;
object.setObjectText(objectText);
/**
* 添加话题<br/>
* 1.将话题内容添加到数据集合中<br/>
* 2.将话题内容添加到EditText中展示
*/
/**
* 1.添加话题内容到数据集合
*/
mRObjectsList.add(object);
/**
* 2.将话题内容添加到EditText中展示
*/
int selectionStart = getSelectionStart();// 光标位置
Editable editable = getText();// 原先内容
if (selectionStart >= 0)
editable.insert(selectionStart, objectText);// 在光标位置插入内容
editable.insert(getSelectionStart(), " ");// 话题后面插入空格,至关重要
setSelection(getSelectionStart());// 移动光标到添加的内容后面
/**
* 获取object列表数据
*/
public List<RObject> getObjects()
List<RObject> objectsList = new ArrayList<RObject>();
// 由于保存时候文本内容添加了匹配字符#,此处去除,还原数据
if (mRObjectsList != null && mRObjectsList.size() > 0)
for (int i = 0; i < mRObjectsList.size(); i++)
RObject object = mRObjectsList.get(i);
String objectText = object.getObjectText();
String objectRule = object.getObjectRule();
object.setObjectText(objectText.replace(objectRule, ""));// 将匹配规则字符替换
objectsList.add(object);
return objectsList;
3.控件使用
我们在xml文件中调用
<cn.r.topic.edittext.android.widget.REditText
xmlns:view="http://schemas.android.com/apk/res-auto"
android:id="@+id/edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minLines="5"
view:object_background_color="#FFD0AAF2"
view:object_foreground_color="#ab86cc" />
在Avtivity中调用。有两个方法
1.mREditText.setObject(RObject object);// 设置话题
2.List<RObject> list = mREditText.getObjects();// 获取话题对象集合
开发者可以直接使用,可以继承此类拓展所需属性
public class RObject
private String objectRule = "#";// 匹配规则
private String objectText;// 高亮文本
public String getObjectRule()
return objectRule;
public void setObjectRule(String objectRule)
this.objectRule = objectRule;
public String getObjectText()
return objectText;
public void setObjectText(String objectText)
this.objectText = objectText;
public class MainActivity extends Activity implements OnClickListener
private REditText mREditText;
private TextView mResult;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
public void initViews()
mREditText = (REditText) findViewById(R.id.edittext);
mResult = (TextView) findViewById(R.id.result);
findViewById(R.id.topic_iv).setOnClickListener(this);
findViewById(R.id.send).setOnClickListener(this);
@Override
public void onClick(View view)
switch (view.getId())
case R.id.topic_iv:
// 设置话题
setTopic();
break;
case R.id.send:
// 展示话题对象内容
getTopicsData();
break;
/**
* 添加设置话题
*
* @author Ruffian
*/
private void setTopic()
MyTopic topic = new MyTopic();
int id = (int) (Math.random() * 100);
topic.setId("No." + id);
topic.setObjectText("双" + id + "狂欢");// 必须设置
switch (id % 3)
case 0:
topic.setObjectRule("*");// 开发者设置,默认#
break;
case 1:
topic.setObjectRule("$");// 开发者设置,默认#
break;
case 2:
topic.setObjectRule("#");// 开发者设置,默认#
break;
mREditText.setObject(topic);// 设置话题
/**
* 获取话题列表数据
*
* @author Ruffian
*/
private void getTopicsData()
/**
* 获取话题对象集合,遍历
*/
List<RObject> list = mREditText.getObjects();// 获取话题对象集合
if (list == null || list.size() == 0)
mResult.setText("no data");
return;
MyTopic myTopic;
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < list.size(); i++)
myTopic = (MyTopic) list.get(i);// 强制转化为开发者topic类型
stringBuffer.append("id= " + myTopic.getId() + " text= "
+ myTopic.getObjectText() + "\\n");
mResult.setText(stringBuffer.toString());
/**
* 测试使用开发者话题实体,必须继承RObject
*/
class MyTopic extends RObject
private String id;
// 其他属性...
public String getId()
return id;
public void setId(String id)
this.id = id;
这里我点击按钮随机产生一个话题对象,插入,点击发送,展示话题对象列表内容
好了思路和代码都在这里了,各位看客完全可以在这些基础上定制更完善的控件。
源码下载
以上是关于Android 自定义view-仿新浪微博#话题#插入EditText的主要内容,如果未能解决你的问题,请参考以下文章
监听文本框输入开发仿新浪微博限制输入字数的textarea插件