Android 富文本开发,移动混合开发
} else if (editStr1.length() == 0) {//如果光标已经顶在了 editText 的最前面,则直接插入图片,并且 EditText 下移即可} else if (editStr2.length() == 0) {// 如果光标已经顶在了 editText 的最末端,则需要添加新的 imageView 和 EditText} else {//如果光标已经顶在了 editText 的最中间,则需要分割字符串,分割成两个 EditText,并在两个 EditText 中间插入图片}hideKeyBoard();} catch (Exception e) {e.printStackTrace();}}
06.在指定位置插入输入文字
前面已经提到了,如果一个富文本是:文字 1+图片 1+文字 2+文字 3+图片 3+图片 4,那么点击文字 1 控件则在此输入文字,点击文字 3 控件则在此输入文字。
所以,这样操作,确定处理记录当前的焦点区域位置十分重要。当前的编辑器已经添加了多个输入文本 EditText,现在的问题在于需要记录当前编辑的 EditText,在应用样式的时候定位到输入的控件,在编辑器中添加一个变量 lastFocusEdit。具体可以看代码……
/**
所有 EditText 的焦点监听 listener*/private OnFocusChangeListener focusListener;
focusListener = new OnFocusChangeListener() {@Overridepublic void onFocusChange(View v, boolean hasFocus) {if (hasFocus) {lastFocusEdit = (EditText) v;HyperLogUtils.d("HyperTextEditor---onFocusChange--"+lastFocusEdit);}}};
/**
在特定位置插入 EditText
@param index 位置
@param editStr EditText 显示的文字*/public void addEditTextAtIndex(final int index, CharSequence editStr) {//省略部分代码 try {EditText editText = createEditText("插入文字", EDIT_PADDING);editText.setOnFocusChangeListener(focusListener);layout.addView(editText, index);//插入新的 EditText 之后,修改 lastFocusEdit 的指向 lastFocusEdit = editText;//获取焦点 lastFocusEdit.requestFocus();//将光标移至文字指定索引处 lastFocusEdit.setSelection(editStr.length(), editStr.length());} catch (Exception e) {e.printStackTrace();}}
07.如果对选中文字加粗
Span 的分类介绍
字符外观,这种类型修改字符的外形但是不影响字符的测量,会触发文本重新绘制但是不触发重新布局。
ForegroundColorSpan,BackgroundColorSpan,UnderlineSpan,StrikethrougnSpan
字符大小布局,这种类型 Span 会更改文本的大小和布局,会触发文本的重新测量绘制
StyleSpan,RelativeSizeSpan,AbsoluteSizeSpan
影响段落级别,这种类型 Span 在段落级别起作用,更改文本块在段落级别的外观,修改对齐方式,边距等。
AlignmentSpan,BulletSpan,QuoteSpan
实现基础样式 粗体、 斜体、 下划线 、中划线 的设置和取消。举个例子,对文本加粗,文字设置 span 样式注意要点,这里需要区分几种情况
当前选中区域不存在 bold 样式 这里我们选中 BB。两种情况
当前区域紧靠左侧或者右侧不存在粗体样式: AABBCC 这时候直接设置 span 即可
当前区域紧靠左侧或者右侧存在粗体样式如: AABBCC AABBCC AABBCC。这时候需要合并左右两侧的 span,只剩下一个 span
当前选中区域存在了 Bold 样式 选中 ABBC。四种情况:
选中样式两侧不存在连续的 bold 样式 AABBCC
选中内部两端存在连续的 bold 样式 AABBCC
选中左侧存在连续的 bold 样式 AABBCC
选中右侧存在连续的 bold 样式 AABBCC
这时候需要合并左右两侧已经存在的 span,只剩下一个 span
接下来逐步分解,然后处理 span 的逻辑顺序如下所示
首先对选中文字内容样式情况判断
边界判断与设置
取消 Span(当我们选中的区域在一段连续的 Bold 样式里面的时候,再次选择 Bold 将会取消样式)
什么时候取消 span 呢,这个逻辑是比较复杂的,具体看看下面的举例。
当我们选中的区域在一段连续的 Bold 样式里面的时候,再次选择 Bold 将会取消样式
用户可以随意的删除文本,在删除过程中可能会出现如下的情况:
用户输入了 AABBCCDD
用户选择了粗体样式 AABBCCDD
用户删除了 CC 然后显示如下 : AABB DD
这个时候选中其中的 BD 此时,在该区域中 存在两个 span ,并且没有一个 span 完全包裹选中的 BD
在这种情况下 仍需要进行左右侧边界判断进行删除。这个具体可以看代码逻辑。
08.利用 Span 对文字属性处理
这里仅仅是对字体加粗进行介绍,其实设置 span 可以找到规律。多个 span 样式,考虑到后期的拓展性,肯定要进行封装和抽象,具体该如何处理呢?
设置文本选中内容加粗模式,代码如下所示,可以看到这里只需要传递一个 lastFocusEdit 对象即可,这个对象是最近被聚焦的 EditText。
/**
修改加粗样式*/public void bold(EditText lastFocusEdit) {//获取 editable 对象 Editable editable = lastFocusEdit.getEditableText();//获取当前选中的起始位置 int start = lastFocusEdit.getSelectionStart();//获取当前选中的末尾位置 int end = lastFocusEdit.getSelectionEnd();HyperLogUtils.i("bold select Start:" + start + " end: " + end);if (checkNormalStyle(start, end)) {return;}new BoldStyle().applyStyle(editable, start, end);}
然后如何调用这个,在 HyperTextEditor 类中代码如下所示。为何要这样写,可以把 HyperTextEditor 富文本类中设置 span 的逻辑放到 SpanTextHelper 类中处理,该类专门处理各种 span 属性,这样代码结构更加清晰,也方便后期增加更多 span 属性,避免一个类代码太臃肿。
/**
修改加粗样式*/public void bold() {SpanTextHelper.getInstance().bold(lastFocusEdit);}
public void applyStyle(Editable editable, int start, int end) {//获取 从 start 到 end 位置上所有的指定 class 类型的 Span 数组 E[] spans = editable.getSpans(start, end, clazzE);E existingSpan = null;if (spans.length > 0) {existingSpan = spans[0];}if (existingSpan == null) {//当前选中内部无此样式,开始设置 span 样式 checkAndMergeSpan(editable, start, end, clazzE);} else {//获取 一个 span 的起始位置 int existingSpanStart = editable.getSpanStart(existingSpan);//获取一个 span 的结束位置 int existingSpanEnd = editable.getSpanEnd(existingSpan);if (existingSpanStart <= start && existingSpanEnd >= end) {//在一个 完整的 span 中//删除 样式//removeStyle(editable, start, end, clazzE, true);} else {//当前选中区域存在了某某样式,需要合并样式 checkAndMergeSpan(editable, start, end, clazzE);}}}
09.如何设置插入多张图片
Observable.create(new ObservableOnSubscribe<String>() {@Overridepublic void subscribe(ObservableEmitter<String> emitter) {try{hte_content.measure(0, 0);List<Uri> mSelected = Matisse.obtainResult(data);// 可以同时插入多张图片 for (Uri imageUri : mSelected) {String imagePath = HyperLibUtils.getFilePathFromUri(NewActivity.this, imageUri);Bitmap bitmap = HyperLibUtils.getSmallBitmap(imagePath, screenWidth, screenHeight);//压缩图片 imagePath = SDCardUtil.saveToSdCard(bitmap);emitter.onNext(imagePath);}emitter.onComplete();}catch (Exception e){e.printStackTrace();emitter.onError(e);}}}).subscribeOn(Schedulers.io())//生产事件在 io.observeOn(AndroidSchedulers.mainThread())//消费事件在 UI 线程.subscribe(new Observer<String>() {@Overridepublic void onComplete() {ToastUtils.showRoundRectToast("图片插入成功");}
@Overridepublic void onError(Throwable e) {ToastUtils.showRoundRectToast("图片插入失败:"+e.getMessage());}
@Overridepublic void onSubscribe(Disposable d) {
}
@Overridepublic void onNext(String imagePath) {//插入图片 hte_content.insertImage(imagePath);}});
10.如何设置插入网络图片
插入图片有两种情况,一种是本地图片,一种是网络图片。由于富文本中对插入图片的宽高有限制,即可以动态设置图片的高度,这就要求请求网络图片后,需要对图片进行处理。
/**
在特定位置添加 ImageView*/public void addImageViewAtIndex(final int index, final String imagePath) {if (TextUtils.isEmpty(imagePath)){return;}try {imagePaths.add(imagePath);final RelativeLayout imageLayout = createImageLayout();HyperImageView imageView = imageLayout.findViewById(R.id.edit_imageView);imageView.setAbsolutePath(imagePath);HyperManager.getInstance().loadImage(imagePath, imageView, rtImageHeight);layout.addView(imageLayout, index);} catch (Exception e) {e.printStackTrace();}}
HyperManager.getInstance().setImageLoader(new ImageLoader() {@Overridepublic void loadImage(final String imagePath, final ImageView imageView, final int imageHeight) {Log.e("---", "imageHeight: "+imageHeight);//如果是网络图片 if (imagePath.startsWith("http://") || imagePath.startsWith("https://")){//直接用图片加载框架加载图片即可} else { //如果是本地图片
}}});
11.如何避免插入图片 OOM
加载一个本地的大图片或者网络图片,从加载到设置到 View 上,如何减下内存,避免加载图片 OOM。
在展示高分辨率图片的时候,最好先将图片进行压缩。压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的 ImageView 上显示一张超大的图片不会带来任何视觉上的好处,但却会占用相当多宝贵的内存,而且在性能上还可能会带来负面影响。
加载图片的内存都去哪里呢?
其实我们的内存就是去 bitmap 里了,BitmapFactory 的每个 decode 函数都会生成一个 bitmap 对象,用于存放解码后的图像,然后返回该引用。如果图像数据较大就会造成 bitmap 对象申请的内存较多,如果图像过多就会造成内存不够用自然就会出现 out of memory 的现象。
为何容易 OOM?
通过 BitmapFactory 的 decode 的这些方法会尝试为已经构建的 bitmap 分配内存,这时就会很容易导致 OOM 出现。为此每一种解析方法都提供了一个可选的 BitmapFactory.Options 参数,将这个参数的 inJustDecodeBounds 属性设置为 true 就可以让解析方法禁止为 bitmap 分配内存,返回值也不再是一个 Bitmap 对象,而是 null。
如何对图片进行压缩?
1.解析图片,获取图片资源的属性
2.计算图片的缩放值
3.最后对图片进行质量压缩
public static Bitmap getSmallBitmap(String filePath, int newWidth, int newHeight) {final BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFile(filePath, options);// Calculate inSampleSize// 计算图片的缩放值 options.inSampleSize = calculateInSampleSize(options, newWidth, newHeight);// Decode bitmap with inSampleSize setoptions.inJustDecodeBounds = false;Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);// 质量压缩 Bitmap newBitmap = compressImage(bitmap, 500);if (bitmap != null){//手动释放资源 bitmap.recycle();}return newBitmap;}
思考:inJustDecodeBounds 这个参数是干什么的?
如果设置为 true 则表示 decode 函数不会生成 bitmap 对象,仅是将图像相关的参数填充到 option 对象里,这样我们就可以在不生成 bitmap 而获取到图像的相关参数了。
为何设置两次 inJustDecodeBounds 属性?
第一次:设置为 true 则表示 decode 函数不会生成 bitmap 对象,仅是将图像相关的参数填充到 option 对象里,这样我们就可以在不生成 bitmap 而获取到图像的相关参数。
第二次:将 inJustDecodeBounds 设置为 false 再次调用 decode 函数时就能生成 bitmap 了。而此时的 bitmap 已经压缩减小很多了,所以加载到内存中并不会导致 OOM。
12.如何删除图片或者文字
/**
处理图片上删除的点击事件
删除类型 0 代表 backspace 删除 1 代表按红叉按钮删除
@param view 整个 image 对应的 relativeLayout view*/private void onImageCloseClick(View view) {try {//判断过渡动画是否结束,只能等到结束才可以操作 if (!mTransition.isRunning()) {disappearingImageIndex = layout.indexOfChild(view);//删除文件夹里的图片 List<HyperEditData> dataList = buildEditData();HyperEditData editData = dataList.get(disappearingImageIndex);if (editData.getImagePath() != null){if (onHyperListener != null){onHyperListener.onRtImageDelete(editData.getImagePath());}//SDCardUtil.deleteFile(editData.imagePath);//从图片集合中移除图片链接 imagePaths.remove(editData.getImagePath());}//然后移除当前 viewlayout.removeView(view);//合并上下 EditText 内容 mergeEditText();}} catch (Exception e) {e.printStackTrace();}}
13.删除和插入图片添加动画
为什么要添加插入图片的过渡动画
当向一个 ViewGroup 添加控件或者移除控件;这种场景虽然能够实现效果,并没有一点过度效果,直来直去的添加或者移除,显得有点生硬。有没有办法添加一定的过度效果,让实现的效果显得圆滑呢?
LayoutTransition 简单介绍
LayoutTransition 类实际上 Android 系统中的一个实用工具类。使用 LayoutTransition 类在一个 ViewGroup 中对布局更改进行动画处理。
如何运用到插入或者删除图片场景中
向一个 ViewGroup 添加控件或者移除控件,这两种效果的过程是应对应于控件的显示、控件添加时其他控件的位置移动、控件的消失、控件移除时其他控件的位置移动等四种动画效果。这些动画效果在 LayoutTransition 中,由以下四个关键字做出了相关声明:
APPEARING:元素在容器中显现时需要动画显示。
CHANGE_APPEARING:由于容器中要显现一个新的元素,其它元素的变化需要动画显示。
DISAPPEARING:元素在容器中消失时需要动画显示。
CHANGE_DISAPPEARING:由于容器中某个元素要消失,其它元素的变化需要动画显示。
也就是说,ViewGroup 中有多个 ImageView 对象,如果需要删除其中一个 ImageView 对象的话,该 ImageView 对象可以设置动画(即 DISAPPEARING 动画形式),ViewGroup 中的其它 ImageView 对象此时移动到新的位置的过程中也可以设置相关的动画(即 CHANGE_DISAPPEARING 动画形式);
若向 ViewGroup 中添加一个 ImageView,ImageView 对象可以设置动画(即 APPEARING 动画形式),ViewGroup 中的其它 ImageView 对象此时移动到新的位置的过程中也可以设置相关的动画(即 CHANGE_APPEARING 动画形式)。
给 ViewGroup 设置动画很简单,只需要生成一个 LayoutTransition 实例,然后调用 ViewGroup 的 setLayoutTransition(LayoutTransition)函数就可以了。当设置了布局动画的 ViewGroup 添加或者删除内部 view 时就会触发动画。
mTransition = new LayoutTransition();mTransition.addTransitionListener(new LayoutTransition.TransitionListener() {
@Overridepublic void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {}
@Overridepublic void endTransition(LayoutTransition transition,ViewGroup container, View view, int transitionType) {if (!transition.isRunning() && transitionType == LayoutTransition.CHANGE_DISAPPEARING) {// transition 动画结束,合并 EditTextmergeEditText();}}});mTransition.enableTransitionType(LayoutTransition.APPEARING);mTransition.setDuration(300);layout.setLayoutTransition(mTransition);
@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();if (mTransition!=null){//移除 Layout 变化监听 mTransition.removeTransitionListener(transitionListener);}}
动画执行先后的顺序
分析源码可以知道,默认情况下 DISAPPEARING 和 CHANGE_APPEARING 类型动画会立即执行,其他类型动画则会有个延迟。也就是说如果删除 view,被删除的 view 将先执行动画消失,经过一些延迟受影响的 view 会进行动画补上位置,如果添加 view,受影响的 view 将会先给添加的 view 腾位置执行 CHANGE_APPEARING 动画,经过一些时间的延迟才会执行 APPEARING 动画。这里就不贴分析源码的思路呢!
14.点击图片可以查看大图
编辑状态时,由于图片有空能比较大,在显示在富文本的时候,会裁剪局中显示,也就是图片会显示不全。那么后期如果是想添加点击图片查看,则需要暴露给开发者监听事件,需要考虑到后期拓展性,代码如下所示:
这样做的目的是是暴露给外部开发者调用,点击图片的操作只需要传递 view 还有图片即可。
// 图片处理 btnListener = new OnClickListener() {@Overridepublic voi
《Android 学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
d onClick(View v) {if (v instanceof HyperImageView){HyperImageView imageView = (HyperImageView)v;// 开放图片点击接口 if (onHyperListener != null){onHyperListener.onImageClick(imageView, imageView.getAbsolutePath());}}}};
15.如何暴露设置文字属性方法
/**
修改加粗样式*/public void bold() {SpanTextHelper.getInstance().bold(lastFocusEdit);}
/**
修改斜体样式*/public void italic() {SpanTextHelper.getInstance().italic(lastFocusEdit);}
/**
修改删除线样式*/public void strikeThrough() {SpanTextHelper.getInstance().strikeThrough(lastFocusEdit);}
/**
修改下划线样式*/public void underline() {SpanTextHelper.getInstance().underline(lastFocusEdit);}
public abstract class NormalStyle<E> {
private Class<E> clazzE;
public NormalStyle() {//利用反射 clazzE = (Class<E>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];}
/**
样式情况判断
@param editable editable
@param start start
@param end end*/public void applyStyle(Editable editable, int start, int end) {
}}
public class ItalicStyle extends NormalStyle<ItalicStyleSpan> {@Overrideprotected ItalicStyleSpan newSpan() {return new ItalicStyleSpan();}}
public class UnderlineStyle extends NormalStyle<UnderLineSpan> {@Overrideprotected UnderLineSpan newSpan() {return new UnderLineSpan();}}
16.文字中间添加图片注意事项
在文字中添加图片比较特殊,因此这里单独拿出来说一下。在文字内容中间插入图片,则需要分割字符串,分割成两个 EditText,并在两个 EditText 中间插入图片,那么这个光标又定位在何处呢?
对于光标前面的字符串保留,设置给当前获得焦点的 EditText(此为分割出来的第一个 EditText)
把光标后面的字符串放在新创建的 EditText 中(此为分割出来的第二个 EditText)
在第二个 EditText 的位置插入一个空的 EditText,以便连续插入多张图片时,有空间写文字,第二个 EditText 下移
在空的 EditText 的位置插入图片布局,空的 EditText 下移。注意,这个过程添加动画过渡一下插入的效果比较好,不然会比较生硬
//获取光标所在位置 int cursorIndex = lastFocusEdit.getSelectionStart();//获取光标前面的字符串 String editStr1 = lastEditStr.substring(0, cursorIndex).trim();//获取光标后的字符串 String editStr2 = lastEditStr.substring(cursorIndex).trim();
lastFocusEdit.setText(editStr1);addEditTextAtIndex(lastEditIndex + 1, editStr2);addEditTextAtIndex(lastEditIndex + 1, "");addImageViewAtIndex(lastEditIndex + 1, imagePath);
17.键盘弹出和收缩优化
软键盘弹出的时机
如果不做任何处理,系统默认的是,进入页面,第一个输入框自动获取焦点软键盘自动弹出,这种用户交互方式,往往不是产品想要的,往往会提出以下优化需求:
需求 1:editText 获取焦点,但是不弹出软键盘(也就是说光标显示第一个输入框,不主动弹软键盘)
在第一个输入框的最直接父布局加入:android:focusable="true";android:focusableInTouchMode="true"
(效果:软键盘不弹出,光标不显示,其他输入框也不获取焦点,ps 非直接父布局没有效果)
android:windowSoftInputMode="stateAlwaysHidden"
(效果:软键盘不弹出,光标显示在第一个输入框中)
需求 2:editText 不获取焦点,当然软键盘不会主动弹出(光标也不显示)
在第一个输入框的最直接父布局加入:android:focusable="true";android:focusableInTouchMode="true"
(效果:软键盘不弹出,光标不显示,其他输入框也不获取焦点,ps 非直接父布局没有效果)
在父布局最顶部添加一个高度为 0 的 EditText,抢了焦点但不展示;
软键盘遮挡界面的问题
当界面中有输入框,需要弹起软键盘输入信息的时候,软键盘可能遮挡部分布局,更有甚者,当前输入框如果在屏幕下方,软键盘也会直接遮挡输入框,这种情况对用户体验是相当不友好的,所以要根据具体的情况作出相应的处理。
android 定义了一个属性,名字为 windowSoftInputMode, 这个属性用于设置 Activity 主窗口与软键盘的交互模式,用于避免软键盘遮挡内容的问题。我们可以在 AndroidManifet.xml 中对 Activity 进行设置。
stateUnspecified-未指定状态:软件默认采用的交互方式,系统会根据当前界面自动调整软键盘的显示模式。stateUnchanged-不改变状态:当前界面软键盘状态由上个界面软键盘的状态决定;stateHidden-隐藏状态:进入页面,无论是否有输入需求,软键盘是隐藏的,但是如果跳转到下一个页面软键盘是展示的,回到这个页面,软键盘可能也是展示的,这个属性区别下个属性。stateAlwaysHidden-总是隐藏状态:当设置该状态时,软键盘总是被隐藏,和 stateHidden 不同的是,当我们跳转到下个界面,如果下个页面的软键盘是显示的,而我们再次回来的时候,软键盘就会隐藏起来。stateVisible-可见状态:当设置为这个状态时,软键盘总是可见的,即使在界面上没有输入框的情况下也可以强制弹出来出来。stateAlwaysVisible-总是显示状态:当设置为这个状态时,软键盘总是可见的,和 stateVisible 不同的是,当我们跳转到下个界面,如果下个页面软键盘是隐藏的,而我们再次回来的时候,软键盘就会显示出来。adjustUnspecified-未指定模式:设置软键盘与软件的显示内容之间的显示关系。当你跟我们没有设置这个值的时候,这个选项也是默认的设置模式。在这中情况下,系统会根据界面选择不同的模式。adjustResize-调整模式:当软键盘显示的时候,当前界面会自动重绘,会被压缩,软键盘消失之后,界面恢复正常(正常布局,非 scrollView 父布局);当父布局是 scrollView 的时候,软键盘弹出,会将布局顶起(保证输入框不被遮挡),不压缩,而且可以软键盘不消失的情况下,手动滑出被遮挡的布局;adjustPan-默认模式:软键盘弹出,软键盘会遮挡屏幕下半部分布局,当输入框在屏幕下方布局,软键盘弹起,会自动将当前布局顶起,保证,软键盘不遮挡当前输入框(正常布局,非 scrollView 父布局)。当父布局是 scrollView 的时候,感觉没啥变化,还是自定将布局顶起,输入框不被遮挡,不可以手动滑出被遮挡的布局(白瞎了 scrollView);
看了上面的属性,那么该如何设置呢?具体效果可以看 demo 案例。
<activity android:name=".NewArticleActivity"android:windowSoftInputMode="adjustResize|stateHidden"/>
软键盘及时退出的问题
当用户输入完成之后,必须手动点击软键盘的收回键,软键盘才收起。如果能通过代码主动将软键盘收起,这对于用户体验来说,是一个极大的提升,思前想后,参考网上的文档,个人比较喜欢的实现方式是通过事件分发机制来解决这个问题。
View rootView = hte_content.getRootView();rootView.setBackgroundColor(Color.WHITE);
18.前后台切换编辑富文本优化
由于富文本中,用户会输入很多的内容,当关闭页面时候,需要提醒用户是否保存输入内容。同时,切换到后台的时候,需要注意保存输入内容,避免长时间切换后台进程内存吃紧,在回到前台输入的内容没有呢,查阅了汽车之家,易车等 app 等手机上的富文本编辑器,都会有这个细节点的优化。
19.生成 html 片段上传服务器
19.1 提交富文本
客户端生成 html 片段到服务器
在客户端提交帖子,文章。富文本包括图片,文字内容,还有文字 span 样式,同时会选择一些文章,帖子的标签。还有设置文章的类型,封面图,作者等许多属性。
当点击提交的时候,客户端把这些数据,转化成 html,还是转化成 json 对象提交给服务器呢?思考一下,会有哪些问题……
转化成 html
对于将单个富文本转化成 html 相对来说是比较容易的,因为富文本中之存在文字,图片等。转化成 html 细心就可以。
但是对于设置富文本的标签,类型,作者,封面图,日期,其他关联属性怎么合并到 html 中呢,这个相对麻烦。
最后想说的是
对于富文本写帖子,文章,如果写完富文本提交,则可以使用转化成 html 数据提交给服务器;
对于富文本写完帖子,文章,还有下一步,设置标签,类型,封面图,作者,时间,还有其他属性,则可以使用转化成 json 数据提交给服务器;
19.2 编辑富文本
服务器返回 html 给客户端加载
涉及到富文本的加载,后台管理端编辑器生成的一段 html 代码要渲染到移动端上面,一种方法是前端做成 html 页面,放到服务器上,移动端这边直接 webView 加载 url 即可。
还有一种后台接口直接返回这段 html 富文本的,String 类型的,移动端直接加载的;具体的需求按实际情况而定。
加载 html 文件流畅问题
webView 直接加载 url 体验上没那么流畅,相对的加载 html 文件会好点。但是对比原生,体验上稍微弱点。
如果不用 WebView,使用 TextView 显示 html 富文本,则会出现图片不显示,以及格式问题。
如果不用 WebView,使用自定义富文本 RichText,则需要解析 html 显示,如果对 html 标签,js 不熟悉,也不太好处理。
20.生成 json 片段上传服务器
参考了易车发布帖子,提交数据到服务器,针对富文本,是把它拼接成对象。将文字,图片按照富文本的顺序拼接成 json 片段,然后提交给服务器。
20.1 提交富文本
public class HyperEditData implements Serializable {
/**
富文本输入文字内容/private String inputStr;/*
富文本输入图片地址/private String imagePath;/*
类型:1,代表文字;2,代表图片*/private int type;
//省略很多 set,get 方法}
/**
对外提供的接口, 生成编辑数据上传*/public List<HyperEditData> buildEditData() {List<HyperEditData> dataList = new ArrayList<>();try {int num = layout.getChildCount();for (int index = 0; index < num; index++) {View itemView = layout.getChildAt(index);HyperEditData hyperEditData = new HyperEditData();if (itemView instanceof EditText) {//文本 EditText item = (EditText) itemView;hyperEditData.setInputStr(item.getText().toString());hyperEditData.setType(2);} else if (itemView instanceof RelativeLayout) {//图片 HyperImageView item = itemView.findViewById(R.id.edit_imageView);hyperEditData.setImagePath(item.getAbsolutePath());hyperEditData.setType(1);}dataList.add(hyperEditData);}} catch (Exception e) {e.printStackTrace();}HyperLogUtils.d("HyperTextEditor----buildEditData------dataList---"+dataList.size());return dataList;}
List<HyperEditData> editList = hte_content.buildEditData();//生成 jsonGson gson = new Gson();String content = gson.toJson(editList);//转化成 json 字符串 String string = HyperHtmlUtils.stringToJson(content);//提交服务器省略
20.2 编辑富文本
当然,提交了文章肯定还有审核功能,这个时候想去修改富文本怎么办。ok,需要服务器把之前传递给它的 json 返回给客户端,然后解析填充到富文本中。这个就没什么好说的……
21.图片上传策略问题思考
大多数开发者会采用的方式:
先在编辑器里显示本地图片,等待用户编辑完成再上传全部图片,然后用上传返回的 url 替换之前 html 中显示本地图片的位置。
这样会遇到很多问题:
如果图片很多,上传的数据量会很大,手机的网络状态经常不稳定,很容易上传失败。另外等待时间会很长,体验很差。
解决办法探讨:
选图完成即上传,得到 url 之后直接插入,上传是耗时操作,再加上图片压缩的时间,这样编辑器显示图片会有可观的延迟时间,实际项目中可以加一个默认的占位图,另外加一个标记提醒用户是否上传完成,避免没有上传成功用户即提交的问题。
这种场景很容易想到:
比如,在简书,掘金上写博客。写文章时,插入本地图片,即使你没有提交文章,也会把图片上传到服务器,然后返回一个图片链接给你,最后当你发表文章时,图片只需要用链接替代即可。
参考博客
Android 富文本编辑器(四):HTML 文本转换:www.jianshu.com/p/578085fb0…
Android 端 (图文混排)富文本编辑器的开发(一):www.jianshu.com/p/155aa1e9f…
图文混排富文本文章编辑器实现详解:blog.csdn.net/ljzdyh/arti…
22.一些细节问题的处理
关于软键盘的弹出和关闭,以及避免点击 EditText 弹出收起键盘时出现的黑屏闪现现象,还有返回键判断如果有软键盘显示则需要先关闭软键盘。
分享读者
作者 2013 年 java 转到 Android 开发,在小厂待过,也去过华为,OPPO 等大厂待过,18 年四月份进了阿里一直到现在。
被人面试过,也面试过很多人。深知大多数初中级 Android 工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!
我们整理了一份阿里 P7 级别的 Android 架构师全套学习资料,特别适合有 3-5 年以上经验的小伙伴深入学习提升。
主要包括腾讯,以及字节跳动,阿里,华为,小米,等一线互联网公司主流架构技术。

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!
我们致力打造一个平等,高质量的 Android 交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
35 岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35 岁后的你只会比周围的人更值钱。
评论