效果圖:
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è)人博客:小白的博客