前提
前一段时间做了一个程序,提词器APP,结合greendao保存数据。最近新增了一个需求,实现部分文字富文本的展现。师傅找了一个网上的SDK,但是在集成的时候总是出问题,我又不想把项目挪进来,感觉很麻烦,自己搞了一个比较low的。过程一步一步走下来,最后效果很Low。
效果图
查询了下Android中富文本的实现,主要是SpannableStringBuilder类,我的想法是获取到选中的文字,然后直接设置富文本的格式,展示出来。
设计思路
第一步:
EditText中选中文本后菜单的设置,用到了setCustomSelectionActionModeCallback
代码如下:
//选择文本的菜单
text.setCustomSelectionActionModeCallback(new ActionMode.Callback2() {
//Callback2()接口要求SDK>23 android6以上的设备 也可以实现Callback()接口
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater menuInflater = mode.getMenuInflater();
menuInflater.inflate(R.menu.selection_menu, menu);
return true;//返回false不会显示弹窗
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
selectionStart = text.getSelectionStart();
selectionEnd = text.getSelectionEnd();
text.requestFocus();
if (selectionStart != selectionEnd) {
//选择的字体
charSequence = text.getText().subSequence(selectionStart, selectionEnd);
}
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_add:
//设置字体增大
break;
case R.id.menu_dec:
//设置字体减小
break;
case R.id.menu_red:
//红色字体
break;
case R.id.menu_blue:
//蓝色字体
break;
case R.id.menu_black:
//黑色字体
break;
}
return false;//返回true系统默认的菜单选项会不见
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
});
第二步
设置富文本样式
//创建富文本对象
SpannableStringBuilder str=new SpannableStringBuilder(textAll);
//将要设置的整体文本加载进来
str = textAll = text.getText().toString();
//设置字体的大小 start是起始角标 end是结束角标
str.setSpan(new AbsoluteSizeSpan(font), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置字体颜色
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(color);
str.setSpan(foregroundColorSpan, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
//应用字体
text.setText(str, TextView.BufferType.SPANNABLE);
本以为这就可以了,但是这样就导致我每次更新,上一次的富文本样式就会被刷没,然后我就使用greendao来保存富文本属性。一个数据表,里面对应了几个属性:page页面、起始坐标、终止坐标、文字大小、文字颜色。这样每设置一个,就把他作为一个对象存储起来,这个对象与page绑定,在点击进入不同页面的时候,就会根据page去查询该页有几个属性对象,以此设置进str中,然后setText出来。
第三步
富文本样式与greendao绑定,主要代码是这样的
greendao管理类
public class DetailManger {
private static final String TAG = "DetailManger";
private static final String dbName = "detail.db";
private final Context context;
private DetailBeanDao dao;
private DaoMaster.DevOpenHelper openHelper;
public static DaoSession daoSession;
private static DetailManger mInstance;
private DetailManger(Context context){
this.context = context;
openHelper = new DaoMaster.DevOpenHelper(context, dbName, null);
Database db = openHelper.getWritableDb();
daoSession = new DaoMaster(db).newSession();
DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
DaoSession daoSession = daoMaster.newSession();
dao = daoSession.getDetailBeanDao();
}
//单例
public static DetailManger getInstance(Context context){
if (mInstance==null){
synchronized (DetailManger.class){
if (mInstance==null){
mInstance=new DetailManger(context);
}
}
}
return mInstance;
}
//可读数据库
private SQLiteDatabase getReadableDatabase() {
if (openHelper == null) {
openHelper = new DaoMaster.DevOpenHelper(context, dbName, null);
}
SQLiteDatabase db = openHelper.getReadableDatabase();
return db;
}
//可写数据库
private SQLiteDatabase getWritableDatabase() {
if (openHelper == null) {
openHelper = new DaoMaster.DevOpenHelper(context, dbName, null);
}
SQLiteDatabase db = openHelper.getWritableDatabase();
return db;
}
//插入
public void insert(DetailBean detailBean) {
dao.insert(detailBean);
}
//删除数据
public void delete(DetailBean detailBean) {
dao.delete(detailBean);
}
//更改
public void update(DetailBean detailBean) {
dao.update(detailBean);
}
//查询
public ArrayList<DetailBean> query() {
QueryBuilder<DetailBean> qb = dao.queryBuilder();
ArrayList<DetailBean> list = (ArrayList<DetailBean>) qb.list();
// for (int i = 0; i < list.size(); i++) {
// list.get(i).setIsFlush(false);
// }
return list;
}
public ArrayList<DetailBean> queryByPage(int page){
return (ArrayList<DetailBean>) dao.queryBuilder().where(DetailBeanDao.Properties.Page.eq(page)).list();
}
}
在每次点击后都将新设置的样式存入,重新读取一下,把所有的富文本刷新出来
case R.id.menu_add:
//设置字体增大
if (charSequence != null) {
saveState(page, selectionStart, selectionEnd, size*2, Color.BLACK);//保存数据
readState(page);
}
break;
//保存富文本样式
private void saveState(int page, int start, int end, int font, int color) {
SpannableStringBuilder str;
textAll = text.getText().toString();
DetailBean bean = new DetailBean();
bean.setPage(page);
bean.setStart(start);
bean.setEnd(end);
bean.setFont(font);
bean.setColor(color);
DetailManger.getInstance(MainActivity.this).insert(bean);
Log.d(TAG, "saveState: " + bean.getPage() + " " + bean.getStart() + " " + bean.getEnd() + " " + bean.getFont() + " " + bean.getColor());
str = new SpannableStringBuilder(textAll);
str.setSpan(new AbsoluteSizeSpan(font), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(color);
str.setSpan(foregroundColorSpan, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
readState(page);
}
//读取显示富文本样式
private void readState(int page) {
SpannableStringBuilder str;
textAll = text.getText().toString();
ArrayList<DetailBean> detailBeans = DetailManger.getInstance(MainActivity.this).queryByPage(page);
str = new SpannableStringBuilder(textAll);
for (int j = 0; j < detailBeans.size(); j++) {
DetailBean bean = detailBeans.get(j);
int start = bean.getStart();
int end = bean.getEnd();
int font = bean.getFont();
int color = bean.getColor();
if (selectionStart == bean.getStart() && selectionEnd == bean.getEnd()) {
Log.d(TAG, "readState: 1111111");
}
if (str!=null){
str.setSpan(new AbsoluteSizeSpan(font), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(color);
str.setSpan(foregroundColorSpan, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
text.setText(str, TextView.BufferType.SPANNABLE);
}
并且在每次刚进入APP的时候、点击不同页面的时候、以及修改菜单选项后都调用一个readState()方法就可以了。
现阶段觉得设计很low,大佬们有什么好的方法吗!!!
富文本的设置总结
//创建一个SpannableString对象
sStr = new SpannableString("文本文本文本文本文本文本文本文本文本文本文本文本");
//设置字体(default,default-bold,monospace,serif,sans-serif)
sStr.setSpan(new TypefaceSpan("default"), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sStr.setSpan(new TypefaceSpan("default-bold"), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sStr.setSpan(new TypefaceSpan("monospace"), 4, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sStr.setSpan(new TypefaceSpan("serif"), 6, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sStr.setSpan(new TypefaceSpan("sans-serif"), 8, 10, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置字体大小(绝对值,单位:像素),第二个参数boolean dip,如果为true,表示前面的字体大小单位为dip,否则为像素
sStr.setSpan(new AbsoluteSizeSpan(20), 10, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sStr.setSpan(new AbsoluteSizeSpan(20, true), 12, 14, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置字体大小(相对值,单位:像素) 参数表示为默认字体大小的多少倍 ,0.5表示一半
sStr.setSpan(new RelativeSizeSpan(0.5f), 14, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置字体前景色
sStr.setSpan(new ForegroundColorSpan(Color.RED), 16, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置字体背景色
sStr.setSpan(new BackgroundColorSpan(Color.CYAN), 18, 20, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置字体样式: NORMAL正常,BOLD粗体,ITALIC斜体,BOLD_ITALIC粗斜体
sStr.setSpan(new StyleSpan(android.graphics.Typeface.NORMAL), 20, 21, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sStr.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 21, 22, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sStr.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 22, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sStr.setSpan(new StyleSpan(android.graphics.Typeface.BOLD_ITALIC), 23, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置下划线
sStr.setSpan(new UnderlineSpan(), 24, 26, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置删除线
sStr.setSpan(new StrikethroughSpan(), 26, 28, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置上下标
sStr.setSpan(new SubscriptSpan(), 28, 30, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sStr.setSpan(new SuperscriptSpan(), 30, 32, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置字体大小(相对值,单位:像素) 参数表示为默认字体宽度的多少倍 ,2.0f表示默认字体宽度的两倍,即X轴方向放大为默认字体的两倍,而高度不变
sStr.setSpan(new ScaleXSpan(2.0f), 32, 34, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置项目符号
sStr.setSpan(new BulletSpan(android.text.style.BulletSpan.STANDARD_GAP_WIDTH,Color.GREEN), 0 ,sStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //第一个参数表示项目符号占用的宽度,第二个参数为项目符号的颜色
//设置图片
Drawable drawable = getResources().getDrawable(R.drawable.ic_launcher);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
sStr.setSpan(new ImageSpan(drawable), 24, 26, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(sStr);
tv.setMovementMethod(LinkMovementMethod.getInstance());
sStr2 = new SpannableString("电话邮件百度一下短信彩信进入地图");
//超级链接(需要添加setMovementMethod方法附加响应)
sStr2.setSpan(new URLSpan("tel:8008820"), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //电话
sStr2.setSpan(new URLSpan(" mailto:kejunlu@qq.comsStr2.setSpan(new URLSpan("mailto:kejunlu@qq.com"), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //邮件
sStr2.setSpan(new URLSpan(" http://www.baidu.com"), 4, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //网络
sStr2.setSpan(new URLSpan("sms:10086"), 8, 10, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //短信 使用sms:或者smsto:
sStr2.setSpan(new URLSpan("mms:10086"), 10, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//彩信 使用mms:或者mmsto:
sStr2.setSpan(new URLSpan("geo:32.123456,-17.123456"), 12, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //地图