帶插入圖片功能的EditText


效果圖:

image

我們來看看怎么實(shí)現(xiàn)蹂析,首先我們肯定是在EditText上進(jìn)行擴(kuò)展肯定是繼承于EditText纺酸。我們用到的最主要的兩個(gè)類是SpannableString和ImageSpan充易,通過這兩個(gè)類儡炼,我們就能實(shí)現(xiàn)圖片和文字共存,一般適用的場(chǎng)景是論壇或者帖子的發(fā)表或者提交

具體實(shí)現(xiàn):

我們首先要用SpannableString來編輯要插入的圖片內(nèi)容

/**
    * 編輯插入的內(nèi)容
    *
    * @param picPath
    * @return
    */
   private CharSequence getDrawableStr(String picPath) {
       String str = "<img src=\"" + picPath + "\"/>";
       Bitmap bm = createImageThumbnail(picPath);
       final SpannableString ss = new SpannableString(str);
       // 定義插入圖片
       Drawable drawable = new BitmapDrawable(bm);

       float scenewidth = Util.getScene(Util.SCENE_WIDTH) / 3;
       float width = drawable.getIntrinsicWidth();
       float height = drawable.getIntrinsicHeight();
       if (width > scenewidth) {
           width = width - 20;
           height = height - 20;
       } else {
           float scale = (scenewidth) / width;
           width *= scale;
           height *= scale;
       }

       //設(shè)置圖片的寬高
       drawable.setBounds(2, 0, (int) width, (int) height);
       //ALIGN_BOTTOM 調(diào)整圖片距離字有一定的間隙
       VerticalCenterImageSpan span = new VerticalCenterImageSpan(drawable, 1);
       //SPAN_INCLUSIVE_EXCLUSIVE 會(huì)導(dǎo)致刪除后面的文字消失
       ss.setSpan(span, 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
       /*
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范圍的前面和后面插入新字符都不會(huì)應(yīng)用新樣式
       Spannable.SPAN_EXCLUSIVE_INCLUSIVE:前面不包括员咽,后面包括根暑。即僅在范圍字符的后面插入新字符時(shí)會(huì)應(yīng)用新樣式
       Spannable.SPAN_INCLUSIVE_EXCLUSIVE:前面包括力试,后面不包括。
       Spannable.SPAN_INCLUSIVE_INCLUSIVE:前后都包括排嫌。
        */
       return ss;
   }

其中需要插入創(chuàng)建的圖片畸裳,我把創(chuàng)建圖片的代碼單獨(dú)拉出來了

/**
  * 創(chuàng)建圖片
  * @param filePath
  * @return
  */
 public static Bitmap createImageThumbnail(String filePath) {
     Bitmap bitmap = null;
     BitmapFactory.Options opts = new BitmapFactory.Options();
     opts.inTempStorage = new byte[100 * 1024];
     // 默認(rèn)是Bitmap.Config.ARGB_8888
     opts.inPreferredConfig = Bitmap.Config.RGB_565;
     opts.inSampleSize = 2;
     try {
         bitmap = BitmapFactory.decodeFile(filePath, opts);
     } catch (Exception e) {
     }
     return bitmap;
 }

然后使用自定義的ImageSpan來調(diào)整圖片的位置

public class VerticalCenterImageSpan extends ImageSpan {

      public VerticalCenterImageSpan(Drawable d, int verticalAlignment) {
          super(d, verticalAlignment);
      }

      @Override
      public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
          Drawable b = getDrawable();
          canvas.save();

          int transY = bottom - b.getBounds().bottom;
          if (mVerticalAlignment == ALIGN_BASELINE) {
              transY -= paint.getFontMetricsInt().descent;
          } else if (mVerticalAlignment == ALIGN_BOTTOM) {

          } else {
              transY += paint.getFontMetricsInt().descent * 2;
          }

          canvas.translate(x, transY);
          b.draw(canvas);
          canvas.restore();
      }
  }

最后通過EditText的Editable的insert方法把編輯好的圖片插入到內(nèi)容當(dāng)中

Editable editable = getText();
CharSequence sequence = getDrawableStr(picPath);
editable.insert(getSelectionStart(), sequence);

這樣就能把圖片插入到內(nèi)容當(dāng)中了,實(shí)現(xiàn)圖片和文字共存

獲取插入的圖片集合

調(diào)用getImage()方法就能獲取到插入的圖片的集合


image

具體的實(shí)現(xiàn)就是把插入進(jìn)來的圖片存到一個(gè)集合當(dāng)中淳地,然后監(jiān)聽刪除和插入的內(nèi)容是否是圖片怖糊,然后做對(duì)應(yīng)的操作,具體的實(shí)現(xiàn)可以看最下面的全部代碼颇象。

全部代碼

package com.example.yinshuai.imageeditext.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.widget.EditText;
import android.widget.Toast;

import com.example.yinshuai.imageeditext.util.Util;

import java.util.ArrayList;
import java.util.List;

/**
* Created by yinshuai on 2017/8/14.
*
* @author yinshuai
*/
public class EditTextPlus extends EditText {
  /**
   * 最大輸入字符
   */
  public static final int MAXLENGTH = 2000;

  /**
   * 一張圖片所占的字符長(zhǎng)度
   */
  public static final int IMAGELENGTH = 2;
  /**
   * 占位符
   */
  private String placeholder = "&";

  /**
   * 最大添加圖片數(shù)量
   */
  private int maxImage = 8;
  private Context mContext;

  private String submitCon = "";
  private boolean insertImage = false;

  private OnInsertionImageListener onInsertionImageListener;
  private OnDeleteConteneListener onDeleteConteneListener;

  private float startY;
  private float startX;
  private float selectionStart;

  private List<String> image = new ArrayList<>();

  public EditTextPlus(Context context) {
      super(context);
      mContext = context;
      init();
  }

  public EditTextPlus(Context context, AttributeSet attrs) {
      super(context, attrs);
      mContext = context;
      init();
  }

  public void init() {
      setGravity(Gravity.TOP);
      addTextChangedListener(watcher);
  }

  public interface OnInsertionImageListener {
      /**
       * 插入圖片時(shí)的監(jiān)聽
       */
      void insertion();
  }

  public void setOnInsertionImageListener(OnInsertionImageListener onInsertionImageListener) {
      this.onInsertionImageListener = onInsertionImageListener;
  }

  public interface OnDeleteConteneListener {
      /**
       * 刪除圖片的監(jiān)聽
       */
      void delete();
  }

  public void setOnDeleteConteneListener(OnDeleteConteneListener onDeleteConteneListener) {
      this.onDeleteConteneListener = onDeleteConteneListener;
  }


  /**
   * 添加圖片集合
   *
   * @param list
   */
  public void addImage(List<String> list) {
      if (getTextContent().length() + IMAGELENGTH > MAXLENGTH) {
          Toast.makeText(mContext, "輸入的內(nèi)容超過最大限制", Toast.LENGTH_SHORT).show();
          return;
      }
      Editable editable = getText();
      for (int i = 0; i < list.size(); i++) {
          if (getImage().size() >= maxImage) {
              Toast.makeText(mContext, "圖片超過最大數(shù)量", Toast.LENGTH_SHORT).show();
              return;
          }
          if (list.get(i) != null && !TextUtils.isEmpty(list.get(i))) {
              if (!TextUtils.isEmpty(getText().toString()) && !insertImage) {
                  //如果第一張就是圖片不用換行
                  editable.insert(getSelectionStart(), "\n");
              } else if (getSelectionStart() < getText().length()) {
                  //當(dāng)從中間插入時(shí)
                  editable.insert(getSelectionStart(), "\n");
              }
              CharSequence sequence = getDrawableStr(list.get(i));
              if (sequence != null) {
                  image.add(list.get(i));
                  editable.insert(getSelectionStart(), sequence);
                  editable.insert(getSelectionStart(), "\n");
                  insertImage = true;
              }
          } else {
              Toast.makeText(mContext, "圖片路徑為空", Toast.LENGTH_SHORT).show();
          }
      }
      //讓光標(biāo)始終在最后
      this.setSelection(getText().toString().length());
      if (onInsertionImageListener != null) {
          onInsertionImageListener.insertion();
      }
  }


  /**
   * 獲取插入的圖片列表
   *
   * @return
   */
  public List<String> getImage() {
      List<String> picPaths = new ArrayList<>();
      String content = this.getText().toString();
      for (int i = 0; i < image.size(); i++) {
          if (content.indexOf(image.get(i)) != -1) {
              picPaths.add(image.get(i));
          }
      }
      return picPaths;
  }

  /**
   * 判斷傳進(jìn)來的字符串是否是一個(gè)圖片地址
   * @param content
   * @return
   */
  public boolean isImage(String content) {
      for (int i = 0; i < image.size(); i++) {
          if (content.indexOf(image.get(i)) != -1) {
              return true;
          }
      }
      return false;
  }

  /**
   * 獲取去除image后的文字內(nèi)容
   *
   * @return
   */
  public String getTextContent() {
      return submitCon;
  }


  /**
   * 這個(gè)TextWatcher用來監(jiān)聽刪除和輸入的內(nèi)容如果是圖片的話 要相應(yīng)把list集合中的圖片也要移除 不然最后獲取到的圖片集合是錯(cuò)誤的
   */
  private String tempString;
  private TextWatcher watcher = new TextWatcher() {
      @Override
      public void onTextChanged(CharSequence s, int start, int before, int count) {
          insertImage = false;
          //如果小于就是刪除操作
          if (s.length() < tempString.length()) {
              String deletString = tempString.substring(start, start + before);
              if (image != null && image.size() > 0) {
                  for (int i = 0; i < image.size(); i++) {
                      //如果刪除的內(nèi)容中包含這張圖片 那么就把圖片集合中的對(duì)應(yīng)的圖片刪除
                      if (deletString.toString().indexOf(image.get(i)) != -1) {
                          image.remove(i);
                          if (onDeleteConteneListener != null) {
                              onDeleteConteneListener.delete();
                          }
                      }
                  }
              }
          }
      }

      @Override
      public void beforeTextChanged(CharSequence s, int start, int count, int after) {
          tempString = s.toString();
      }

      @Override
      public void afterTextChanged(Editable s) {
          invalidate();
          requestLayout();

          StringBuffer stringBuffer = new StringBuffer(getText().toString());
          for (int i = 0; i < image.size(); i++) {
              if (stringBuffer.indexOf(image.get(i)) != -1) {
                  int index = stringBuffer.indexOf(image.get(i));
                  stringBuffer.delete(index - 10, index + image.get(i).length() + 3);
                  stringBuffer.insert(index - 10, placeholder);
              }
          }

          if (stringBuffer.toString().indexOf(placeholder) == 0) {
              stringBuffer.insert(0, " ");
          }
          submitCon = stringBuffer.toString();
      }


  };

  /**
   * 編輯插入的內(nèi)容
   *
   * @param picPath
   * @return
   */
  private CharSequence getDrawableStr(String picPath) {
      String str = "<img src=\"" + picPath + "\"/>";
      Bitmap bm = createImageThumbnail(picPath);
      final SpannableString ss = new SpannableString(str);
      // 定義插入圖片
      Drawable drawable = new BitmapDrawable(bm);

      float scenewidth = Util.getScene(Util.SCENE_WIDTH) / 3;
      float width = drawable.getIntrinsicWidth();
      float height = drawable.getIntrinsicHeight();
      if (width > scenewidth) {
          width = width - 20;
          height = height - 20;
      } else {
          float scale = (scenewidth) / width;
          width *= scale;
          height *= scale;
      }

      //設(shè)置圖片的寬高
      drawable.setBounds(2, 0, (int) width, (int) height);
      //ALIGN_BOTTOM 調(diào)整圖片距離字有一定的間隙
      VerticalCenterImageSpan span = new VerticalCenterImageSpan(drawable, 1);
      //SPAN_INCLUSIVE_EXCLUSIVE 會(huì)導(dǎo)致刪除后面的文字消失
      ss.setSpan(span, 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
      /*
      Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括伍伤,即在指定范圍的前面和后面插入新字符都不會(huì)應(yīng)用新樣式
      Spannable.SPAN_EXCLUSIVE_INCLUSIVE:前面不包括,后面包括遣钳。即僅在范圍字符的后面插入新字符時(shí)會(huì)應(yīng)用新樣式
      Spannable.SPAN_INCLUSIVE_EXCLUSIVE:前面包括扰魂,后面不包括。
      Spannable.SPAN_INCLUSIVE_INCLUSIVE:前后都包括蕴茴。
       */
      return ss;
  }


  /**
   * 自定義ImageSpan 來調(diào)整圖片的位置
   */
  public class VerticalCenterImageSpan extends ImageSpan {

      public VerticalCenterImageSpan(Drawable d, int verticalAlignment) {
          super(d, verticalAlignment);
      }

      @Override
      public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
          Drawable b = getDrawable();
          canvas.save();

          int transY = bottom - b.getBounds().bottom;
          if (mVerticalAlignment == ALIGN_BASELINE) {
              transY -= paint.getFontMetricsInt().descent;
          } else if (mVerticalAlignment == ALIGN_BOTTOM) {

          } else {
              transY += paint.getFontMetricsInt().descent * 2;
          }

          canvas.translate(x, transY);
          b.draw(canvas);
          canvas.restore();
      }
  }


  /**
   * 創(chuàng)建圖片
   * @param filePath
   * @return
   */
  public static Bitmap createImageThumbnail(String filePath) {
      Bitmap bitmap = null;
      BitmapFactory.Options opts = new BitmapFactory.Options();
      opts.inTempStorage = new byte[100 * 1024];
      // 默認(rèn)是Bitmap.Config.ARGB_8888
      opts.inPreferredConfig = Bitmap.Config.RGB_565;
      opts.inSampleSize = 2;
      try {
          bitmap = BitmapFactory.decodeFile(filePath, opts);
      } catch (Exception e) {
      }
      return bitmap;
  }


  /**
   * 重寫dispatchTouchEvent是為了解決上下滑動(dòng)時(shí)光標(biāo)跳躍的問題
   *
   * @param event
   * @return
   */
  @Override
  public boolean dispatchTouchEvent(MotionEvent event) {
      switch (event.getAction()) {
          case MotionEvent.ACTION_DOWN:
              startY = event.getRawY();
              startX = event.getRawX();
              selectionStart = getSelectionStart();
              break;
          case MotionEvent.ACTION_MOVE:
              break;
          case MotionEvent.ACTION_UP:
              float endY = event.getRawY();
              float endX = event.getRawX();

              if (Math.abs(endY - startY) > 10 || Math.abs(endX - startX) > 10) {
                  return true;
              }
              break;
          default:
              break;
      }
      return super.dispatchTouchEvent(event);
  }
}

Github項(xiàng)目地址:ImageEditText
個(gè)人博客:小白的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末劝评,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子荐开,更是在濱河造成了極大的恐慌付翁,老刑警劉巖简肴,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晃听,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡砰识,警方通過查閱死者的電腦和手機(jī)能扒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辫狼,“玉大人初斑,你說我怎么就攤上這事∨虼Γ” “怎么了见秤?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵砂竖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我鹃答,道長(zhǎng)乎澄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任测摔,我火速辦了婚禮置济,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锋八。我一直安慰自己浙于,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布挟纱。 她就那樣靜靜地躺著羞酗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪樊销。 梳的紋絲不亂的頭發(fā)上整慎,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音围苫,去河邊找鬼裤园。 笑死,一個(gè)胖子當(dāng)著我的面吹牛剂府,可吹牛的內(nèi)容都是我干的拧揽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼腺占,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼淤袜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衰伯,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤铡羡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后意鲸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烦周,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年怎顾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了读慎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡槐雾,死狀恐怖夭委,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情募强,我是刑警寧澤株灸,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布崇摄,位于F島的核電站,受9級(jí)特大地震影響慌烧,放射性物質(zhì)發(fā)生泄漏配猫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一杏死、第九天 我趴在偏房一處隱蔽的房頂上張望泵肄。 院中可真熱鬧,春花似錦淑翼、人聲如沸腐巢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冯丙。三九已至,卻和暖如春遭京,著一層夾襖步出監(jiān)牢的瞬間胃惜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工哪雕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留船殉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓斯嚎,卻偏偏與公主長(zhǎng)得像利虫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子堡僻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容