主要实现图、文、视频、音频混排输入的效果。以下以图文混排举例,视频音频混排原理是一样的,效果如图:
device-2018-01-29-183104.png
实现:
1.自定义ImageView,里面包含媒体文件的本地path、远程url等信息,方便后期使用时的赋值与取值。DataImageView.java:
/**
* 这只是一个简单的ImageView,可以存放Bitmap和Path等信息
* @author wangtt
*/
public class DataImageView extends ImageView {
private String absolutePath;
private Bitmap bitmap;
private String type;//video-视频 audio-音频 pic-图片
public DataImageView(Context context) {
this(context, null);
}
public DataImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DataImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public String getAbsolutePath() {
return absolutePath;
}
public void setAbsolutePath(String absolutePath) {
this.absolutePath = absolutePath;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
2.创建资源文件,在资源文件里引入刚刚自定义的ImageView,并添加上用于删除媒体文件的按钮。edit_imageview.xml:
3.创建富文本的编辑器自定义的ScrollView(RichTextEditor),并给外部提供insertImage接口方便外部进行插入图片的操作,添加的图片跟当前光标所在位置有关。主要变量:
private static final int EDIT_PADDING = 10; // edittext常规padding是10dp
private static final int EDIT_FIRST_PADDING_TOP = 10; // 第一个EditText的 paddingTop值
private int viewTagIndex = 1; // 新生的view都会打一个tag,对每个view来说,这个tag是唯一的。
private LinearLayout allLayout; // 这个是所有子view的容器,scrollView内部的唯一一个ViewGroup
private OnKeyListener keyListener; // 所有EditText的软键盘监听器
private OnClickListener btnListener; // 图片右上角红叉按钮监听器
private OnFocusChangeListener focusListener; // 所有EditText的焦点监听listener
private EditText lastFocusEdit; // 最近被聚焦的EditText
private LayoutTransition mTransitioner; // 只在图片View添加或remove时,触发transition动画
private int editNormalPadding = 0; //EditText的初始间距
private int disappearingImageIndex = 0;//被删除的媒体文件的索引
private OnClickListener picListener;//图片点击事件
初始化工作:
// 1. 初始化allLayout
allLayout = new LinearLayout(context);
allLayout.setOrientation(LinearLayout.VERTICAL);
allLayout.setBackgroundColor(Color.WHITE);
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT);
layoutParams.topMargin = 15;
layoutParams.leftMargin = 15;
layoutParams.rightMargin = 15;
addView(allLayout, layoutParams);
setupLayoutTransitions();
initListener();
LinearLayout.LayoutParams firstEditParam = new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
editNormalPadding = dip2px(EDIT_PADDING);
EditText firstEdit = createEditText("input here",
dip2px(EDIT_FIRST_PADDING_TOP));
allLayout.addView(firstEdit, firstEditParam);
lastFocusEdit = firstEdit;
监听操作:
private void initListener(){
// 初始化键盘退格监听,主要用来处理点击回删按钮时,view的一些列合并操作
keyListener = new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
EditText edit = (EditText) v;
onBackspacePress(edit);
}
return false;
}
};
// 图片叉掉处理
btnListener = new OnClickListener() {
@Override
public void onClick(View v) {
RelativeLayout parentView = (RelativeLayout) v.getParent();
onImageCloseClick(parentView);
}
};
// 图片点击事件
picListener = new OnClickListener() {
@Override
public void onClick(View v) {
RelativeLayout parentView = (RelativeLayout) v.getParent();
DataImageView view = (DataImageView) parentView.findViewById(R.id.edit_imageView);
Log.e("====> 被点击图片的路径 = ",view.getAbsolutePath());
}
};
// 所有EditText的焦点监听listener
focusListener = new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
lastFocusEdit = (EditText) v;
}
}
};
}
具体实现方法:
- 处理软键盘backSpace回退事件
/**
* 处理软键盘backSpace回退事件
* @param editTxt 光标所在的文本输入框
*/
private void onBackspacePress(EditText editTxt) {
int startSelection = editTxt.getSelectionStart();
// 只有在光标已经顶到文本输入框的最前方,在判定是否删除之前的图片,或两个View合并
if (startSelection == 0) {
int editIndex = allLayout.indexOfChild(editTxt);
View preView = allLayout.getChildAt(editIndex - 1); // 如果editIndex-1<0,
// 则返回的是null
if (null != preView) {
if (preView instanceof RelativeLayout) {
// 光标EditText的上一个view对应的是图片
onImageCloseClick(preView);
} else if (preView instanceof EditText) {
// 光标EditText的上一个view对应的还是文本框EditText
String str1 = editTxt.getText().toString();
EditText preEdit = (EditText) preView;
String str2 = preEdit.getText().toString();
// 合并文本view时,不需要transition动画
allLayout.setLayoutTransition(null);
allLayout.removeView(editTxt);
allLayout.setLayoutTransition(mTransitioner); // 恢复transition动画
// 文本合并
preEdit.setText(str2 + str1);
preEdit.requestFocus();
preEdit.setSelection(str2.length(), str2.length());
lastFocusEdit = preEdit;
}
}
}
}
- 处理图片叉掉的点击事件
/**
* 处理图片叉掉的点击事件
* @param view 整个image对应的relativeLayout view
* @type 删除类型 0代表backspace删除 1代表按红叉按钮删除
*/
private void onImageCloseClick(View view) {
if (!mTransitioner.isRunning()) {
disappearingImageIndex = allLayout.indexOfChild(view);
allLayout.removeView(view);
}
}
- 生成文本输入框
/**
* 生成文本输入框
*/
private EditText createEditText(String hint, int paddingTop) {
EditText editText = (EditText) inflater.inflate(R.layout.edit_item1,
null);
editText.setOnKeyListener(keyListener);
editText.setTag(viewTagIndex++);
editText.setPadding(editNormalPadding, paddingTop, editNormalPadding, 0);
editText.setHint(hint);
editText.setOnFocusChangeListener(focusListener);
return editText;
}
- 生成图片view
/**
* 生成图片View
*/
private RelativeLayout createImageLayout() {
RelativeLayout layout = (RelativeLayout) inflater.inflate(
R.layout.edit_imageview, null);
layout.setTag(viewTagIndex++);
View pic = layout.findViewById(R.id.edit_imageView);
pic.setTag(layout.getTag());
View closeView = layout.findViewById(R.id.image_close);
closeView.setTag(layout.getTag());
closeView.setOnClickListener(btnListener);
pic.setOnClickListener(picListener);
return layout;
}
- 根据图片的绝对路径添加view
/**
* 根据绝对路径添加view
* @param imagePath
*/
public void insertImage(String imagePath, String url) {
Bitmap bmp = getScaledBitmap(imagePath, getWidth());
insertImage(bmp, imagePath, url);
}
/**
* 插入一张图片
*/
private void insertImage(Bitmap bitmap, String imagePath, String url) {
String lastEditStr = lastFocusEdit.getText().toString();
int cursorIndex = lastFocusEdit.getSelectionStart();
String editStr1 = lastEditStr.substring(0, cursorIndex).trim();
int lastEditIndex = allLayout.indexOfChild(lastFocusEdit);
if (lastEditStr.length() == 0 || editStr1.length() == 0) {
// 如果EditText为空,或者光标已经顶在了editText的最前面,则直接插入图片,并且EditText下移即可
addImageViewAtIndex(lastEditIndex, bitmap, imagePath, url);
} else {
// 如果EditText非空且光标不在最顶端,则需要添加新的imageView和EditText
lastFocusEdit.setText(editStr1);
String editStr2 = lastEditStr.substring(cursorIndex).trim(); //subString() 是截取字符串 将剩余字符串赋值给editStr2
if (allLayout.getChildCount() - 1 == lastEditIndex
|| editStr2.length() > 0) {
addEditTextAtIndex(lastEditIndex + 1, editStr2);
}
addImageViewAtIndex(lastEditIndex + 1, bitmap, imagePath, url);
lastFocusEdit.requestFocus();
lastFocusEdit.setSelection(editStr1.length(), editStr1.length());
}
hideKeyBoard();
}
- 在特定的位置插入EditText
/**
* 在特定位置插入EditText
* @param index 位置
* @param editStr EditText显示的文字
*/
private void addEditTextAtIndex(final int index, String editStr) {
EditText editText2 = createEditText("", getResources()
.getDimensionPixelSize(R.dimen.dp_0));
editText2.setText(editStr);
// 请注意此处,EditText添加、或删除不触动Transition动画
allLayout.setLayoutTransition(null);
allLayout.addView(editText2, index);
allLayout.setLayoutTransition(mTransitioner); // remove之后恢复transition动画
}
- 在特定位置添加ImageView
private void addImageViewAtIndex(final int index, Bitmap bmp,
String imagePath, String url) {
final RelativeLayout imageLayout = createImageLayout();
DataImageView imageView = (DataImageView) imageLayout
.findViewById(R.id.edit_imageView);
imageView.setImageBitmap(bmp);
imageView.setBitmap(bmp);
imageView.setAbsolutePath(imagePath);
imageView.setUrl(url);
imageView.setType("pic");
// 调整imageView的高度
int imageHeight = getWidth() * bmp.getHeight() / bmp.getWidth();
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT, imageHeight);
lp.leftMargin = 15;
lp.rightMargin = 15;
lp.bottomMargin = 15;
lp.topMargin = 15;
imageView.setLayoutParams(lp);
// onActivityResult无法触发动画,此处post处理
allLayout.postDelayed(new Runnable() {
@Override
public void run() {
allLayout.addView(imageLayout, index);
}
}, 200);
}
- 根据view的宽度,动态缩放bitmap尺寸
/**
* 根据view的宽度,动态缩放bitmap尺寸
* @param width view的宽度
*/
private Bitmap getScaledBitmap(String filePath, int width) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
int sampleSize = options.outWidth > width ? options.outWidth / width
+ 1 : 1;
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
return BitmapFactory.decodeFile(filePath, options);
}
- 初始化transition动画
private void setupLayoutTransitions() {
mTransitioner = new LayoutTransition();
allLayout.setLayoutTransition(mTransitioner);
mTransitioner.addTransitionListener(new LayoutTransition.TransitionListener() {
@Override
public void startTransition(LayoutTransition transition,
ViewGroup container, View view, int transitionType) {
}
@Override
public void endTransition(LayoutTransition transition,
ViewGroup container, View view, int transitionType) {
if (!transition.isRunning()
&& transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
// transition动画结束,合并EditText
// mergeEditText();
}
}
});
mTransitioner.setDuration(300);
}
- 对外提供接口,生成编辑数据上传
public List buildEditData() {
List dataList = new ArrayList();
int num = allLayout.getChildCount();
for (int index = 0; index < num; index++) {
View itemView = allLayout.getChildAt(index);
EditData itemData = new EditData();
if (itemView instanceof EditText) {
EditText item = (EditText) itemView;
itemData.inputStr = item.getText().toString();
} else if (itemView instanceof RelativeLayout) {
DataImageView item = (DataImageView) itemView
.findViewById(R.id.edit_imageView);
itemData.imagePath = item.getAbsolutePath();
itemData.type = item.getType();
itemData.url = item.getUrl();
}
dataList.add(itemData);
}
return dataList;
}
public class EditData {
public String inputStr;//输入的字符串
public String imagePath;//媒体文件的本地路径
public String url;//媒体文件的远程路径
public String type;//区分媒体文件类型 video-视频 audio-音频 pic-图片
}
4.使用:
- 布局文件引入自定义的ScrollView
- Activity中引用
@Bind(R.id.edit_content)
RichTextEditor editor;
/***
* 图片上传处理(略)本地图片路径 picPath,图片上传成功后返回的路径picUrl
*/
editor.insertImage(path, picUrl);
- 处理结果
/**
* 获取富文本编辑框里的内容
* 最终结果以html格式上传
*/
List data = editor.buildEditData();
for (int i = 0; i < data.size(); i++) {
String imagUrl = data.get(i).url;//媒体文件的远程路径
String inputStr = data.get(i).inputStr;//输入的文本
String type = data.get(i).type;//判断媒体文件的类型
if (null != inputStr && !"".equals(inputStr))
content = content + "" + inputStr + "
";
if (null != imagUrl && !"".equals(imagUrl)) {
if (type.equals("pic")) {
String html = "![]( + )
";
content = content + html;
} else if (type.equals("video")) {
String html = "";
content = content + html;
} else if (type.equals("audio")) {
String html = "";
content = content + html;
}
}
}