写点什么

Android 富文本开发,移动混合开发

作者:嘟嘟侠客
  • 2021 年 11 月 27 日
  • 本文字数:12032 字

    阅读完需:约 39 分钟

} 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 岁后的你只会比周围的人更值钱。


本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

用户头像

嘟嘟侠客

关注

还未添加个人签名 2021.03.19 加入

还未添加个人简介

评论

发布
暂无评论
Android富文本开发,移动混合开发