Android富文本开发,三年Android开发【附源码】_top级移动开发

             int editIndex = layout.indexOfChild(editTxt);             // 如果editIndex-1<0,             View preView = layout.getChildAt(editIndex - 1);             if (null != preView) {                 if (preView instanceof RelativeLayout) {                     // 光标EditText的上一个view对应的是图片,删除图片操作                     onImageCloseClick(preView);                 } else if (preView instanceof EditText) {                     // 光标EditText的上一个view对应的还是文本框EditText                 }             }         }     } catch (Exception e) {         e.printStackTrace();     } }  ```
  • 当EditText内容为空时,发现手机根本无法响应软键盘的删除监听,这个是为什么呢?

    • 可以看一下源码,EditText继承自TextView,翻看TextView的代码,里面有一个叫做InputConnection的东西,看起是什么输入连接的意思。如果想实现删除的功能,需要自行实现重写一个deleteSurroundingText()方法。
    /**  * 删除操作  * @param beforeLength                      beforeLength  * @param afterLength                       afterLength  * @return  */ @Override public boolean deleteSurroundingText(int beforeLength, int afterLength) {     HyperLogUtils.d("DeletableEditText---deleteSurroundingText--"+beforeLength+"----"+afterLength);     if (beforeLength == 1 && afterLength == 0) {         return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))                 && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));     }     return super.deleteSurroundingText(beforeLength, afterLength); } 

05.在指定位置插入图片

  • 当点击插入图片的时候,需要思考两个问题。第一个是在那个位置插入图片,所以需要定位到这个位置;第二个是插入图片后,什么时候折行操作。
  • 对于上面两个问题,这个位置可以取光标所在的位置,但是对于一个EditText输入文本,插入图片这个位置可以分多种情况:
    • 如果光标已经顶在了editText的最前面,则直接插入图片,并且EditText下移即可
    • 如果光标已经顶在了editText的最末端,则需要添加新的imageView
    • 如果光标已经顶在了editText的最中间,则需要分割字符串,分割成两个EditText,并在两个EditText中间插入图片
    • 如果当前获取焦点的EditText为空,直接在EditText下方插入图片,并且插入空的EditText
  • 代码思路如下所示“`
    /**
    • 插入一张图片
    • @param imagePath 图片路径地址
      */
      public void insertImage(String imagePath) {
      if (TextUtils.isEmpty(imagePath)){
      return;
      }
      try {
      //lastFocusEdit获取焦点的EditText
      String lastEditStr = lastFocusEdit.getText().toString();
      //获取光标所在位置
      int cursorIndex = lastFocusEdit.getSelectionStart();
      //获取光标前面的字符串
      String editStr1 = lastEditStr.substring(0, cursorIndex).trim();
      //获取光标后的字符串
      String editStr2 = lastEditStr.substring(cursorIndex).trim();
      //获取焦点的EditText所在位置
      int lastEditIndex = layout.indexOfChild(lastFocusEdit);
      if (lastEditStr.length() == 0) {
      //如果当前获取焦点的EditText为空,直接在EditText下方插入图片,并且插入空的EditText
      } 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。具体可以看代码……
  • 既然可以记录最后焦点输入文本,那么如何监听当前的输入控件呢,这就用到了OnFocusChangeListener,这个又是在哪里用到,具体如下面所示。要先setOnFocusChangeListener(focusListener) 再 requestFocus。“`
    /**

    • 所有EditText的焦点监听listener
      */
      private OnFocusChangeListener focusListener;

    focusListener = new OnFocusChangeListener() {br/>@Override
    public 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); } 
  • 然后看一下new BoldStyle().applyStyle(editable, start, end)具体做了什么?下面这段代码逻辑,具体可以看07.如果对选中文字加粗的分析思路。“`
    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.如何设置插入多张图片

10.如何设置插入网络图片

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 set
    options.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.如何删除图片或者文字

  • 当富文本处于编辑状态时,点击删除图片是可以删除图片的,对于删除的逻辑,封装的lib可以给开发者暴露一个删除的监听事件。注意删除图片有两种操作:第一种是利用光标删除,第二种是点击触发删除。删除图片后,不仅仅是要删除图片数据,而且还要删除图片ImageView控件。“`
    /**
    • 处理图片上删除的点击事件
    • 删除类型 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());
      }
      //然后移除当前view
      layout.removeView(view);
      //合并上下EditText内容
      mergeEditText();
      }
      } catch (Exception e) {
      e.printStackTrace();
      }
      }

13.删除和插入图片添加动画

14.点击图片可以查看大图

  • 编辑状态时,由于图片有空能比较大,在显示在富文本的时候,会裁剪局中显示,也就是图片会显示不全。那么后期如果是想添加点击图片查看,则需要暴露给开发者监听事件,需要考虑到后期拓展性,代码如下所示:

    • 这样做的目的是是暴露给外部开发者调用,点击图片的操作只需要传递view还有图片即可。
    // 图片处理 btnListener = new OnClickListener() {     @Override     public void onClick(View v) {         if (v instanceof HyperImageView){             HyperImageView imageView = (HyperImageView)v;             // 开放图片点击接口             if (onHyperListener != null){                 onHyperListener.onImageClick(imageView, imageView.getAbsolutePath());             }         }      } }; 

15.如何暴露设置文字属性方法

Android进阶资料

以下的资料是近年来,我和一些朋友面试收集整理了很多大厂的面试真题和资料,还有来自如阿里、小米、爱奇艺等一线大厂的大牛整理的架构进阶资料。希望可以帮助到大家。

CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》

Android进阶核心笔记

Android富文本开发,三年Android开发【附源码】_top级移动开发

百万年薪必刷面试题

Android富文本开发,三年Android开发【附源码】_top级移动开发

最全Android进阶学习视频

本站由小牛团队全力维护,小牛十年了,大家已经步入中年 。本站源码全部经过团队成员测试并调试,价格可能比其它网站略贵几元钱,不解释!
小牛资源 » Android富文本开发,三年Android开发【附源码】_top级移动开发

发表评论

全站资源亲测可用,价格略高几元,不解释

立即查看 了解详情