主要實(shí)現(xiàn)圖、文、視頻、音頻混排輸入的效果。以下以圖文混排舉例,視頻音頻混排原理是一樣的,效果如圖:

device-2018-01-29-183104.png
實(shí)現(xiàn):
1.自定義ImageView,里面包含媒體文件的本地path、遠(yuǎn)程url等信息,方便后期使用時(shí)的賦值與取值。DataImageView.java:
/**
* 這只是一個(gè)簡單的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.創(chuàng)建資源文件,在資源文件里引入剛剛自定義的ImageView,并添加上用于刪除媒體文件的按鈕。edit_imageview.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp">
<com.nongji.ah.custom.DataImageView
android:id="@+id/edit_imageView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@mipmap/ic_default"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/image_close"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:paddingBottom="20dp"
android:paddingLeft="20dp"
android:paddingRight="10dp"
android:paddingTop="10dp"
android:scaleType="fitXY"
android:src="@mipmap/icon_ypdel" />
</RelativeLayout>
3.創(chuàng)建富文本的編輯器自定義的ScrollView(RichTextEditor),并給外部提供insertImage接口方便外部進(jìn)行插入圖片的操作,添加的圖片跟當(dāng)前光標(biāo)所在位置有關(guān)。主要變量:
private static final int EDIT_PADDING = 10; // edittext常規(guī)padding是10dp
private static final int EDIT_FIRST_PADDING_TOP = 10; // 第一個(gè)EditText的 paddingTop值
private int viewTagIndex = 1; // 新生的view都會打一個(gè)tag,對每個(gè)view來說,這個(gè)tag是唯一的。
private LinearLayout allLayout; // 這個(gè)是所有子view的容器,scrollView內(nèi)部的唯一一個(gè)ViewGroup
private OnKeyListener keyListener; // 所有EditText的軟鍵盤監(jiān)聽器
private OnClickListener btnListener; // 圖片右上角紅叉按鈕監(jiān)聽器
private OnFocusChangeListener focusListener; // 所有EditText的焦點(diǎn)監(jiān)聽listener
private EditText lastFocusEdit; // 最近被聚焦的EditText
private LayoutTransition mTransitioner; // 只在圖片View添加或remove時(shí),觸發(fā)transition動畫
private int editNormalPadding = 0; //EditText的初始間距
private int disappearingImageIndex = 0;//被刪除的媒體文件的索引
private OnClickListener picListener;//圖片點(diǎn)擊事件
初始化工作:
// 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;
監(jiān)聽操作:
private void initListener(){
// 初始化鍵盤退格監(jiān)聽,主要用來處理點(diǎn)擊回刪按鈕時(shí),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);
}
};
// 圖片點(diǎn)擊事件
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("====> 被點(diǎn)擊圖片的路徑 = ",view.getAbsolutePath());
}
};
// 所有EditText的焦點(diǎn)監(jiān)聽listener
focusListener = new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
lastFocusEdit = (EditText) v;
}
}
};
}
具體實(shí)現(xiàn)方法:
- 處理軟鍵盤backSpace回退事件
/**
* 處理軟鍵盤backSpace回退事件
* @param editTxt 光標(biāo)所在的文本輸入框
*/
private void onBackspacePress(EditText editTxt) {
int startSelection = editTxt.getSelectionStart();
// 只有在光標(biāo)已經(jīng)頂?shù)轿谋据斎肟虻淖钋胺剑谂卸ㄊ欠駝h除之前的圖片,或兩個(gè)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) {
// 光標(biāo)EditText的上一個(gè)view對應(yīng)的是圖片
onImageCloseClick(preView);
} else if (preView instanceof EditText) {
// 光標(biāo)EditText的上一個(gè)view對應(yīng)的還是文本框EditText
String str1 = editTxt.getText().toString();
EditText preEdit = (EditText) preView;
String str2 = preEdit.getText().toString();
// 合并文本view時(shí),不需要transition動畫
allLayout.setLayoutTransition(null);
allLayout.removeView(editTxt);
allLayout.setLayoutTransition(mTransitioner); // 恢復(fù)transition動畫
// 文本合并
preEdit.setText(str2 + str1);
preEdit.requestFocus();
preEdit.setSelection(str2.length(), str2.length());
lastFocusEdit = preEdit;
}
}
}
}
- 處理圖片叉掉的點(diǎn)擊事件
/**
* 處理圖片叉掉的點(diǎn)擊事件
* @param view 整個(gè)image對應(yīng)的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;
}
- 根據(jù)圖片的絕對路徑添加view
/**
* 根據(jù)絕對路徑添加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為空,或者光標(biāo)已經(jīng)頂在了editText的最前面,則直接插入圖片,并且EditText下移即可
addImageViewAtIndex(lastEditIndex, bitmap, imagePath, url);
} else {
// 如果EditText非空且光標(biāo)不在最頂端,則需要添加新的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之后恢復(fù)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");
// 調(diào)整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無法觸發(fā)動畫,此處post處理
allLayout.postDelayed(new Runnable() {
@Override
public void run() {
allLayout.addView(imageLayout, index);
}
}, 200);
}
- 根據(jù)view的寬度,動態(tài)縮放bitmap尺寸
/**
* 根據(jù)view的寬度,動態(tài)縮放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動畫結(jié)束,合并EditText
// mergeEditText();
}
}
});
mTransitioner.setDuration(300);
}
- 對外提供接口,生成編輯數(shù)據(jù)上傳
public List<EditData> buildEditData() {
List<EditData> dataList = new ArrayList<EditData>();
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;//媒體文件的遠(yuǎn)程路徑
public String type;//區(qū)分媒體文件類型 video-視頻 audio-音頻 pic-圖片
}
4.使用:
- 布局文件引入自定義的ScrollView
<com.nongji.ah.custom.RichTextEditor
android:id="@+id/edit_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/dp_15"
android:background="@color/white"></com.nongji.ah.custom.RichTextEditor>
- Activity中引用
@Bind(R.id.edit_content)
RichTextEditor editor;
/***
* 圖片上傳處理(略)本地圖片路徑 picPath,圖片上傳成功后返回的路徑picUrl
*/
editor.insertImage(path, picUrl);
- 處理結(jié)果
/**
* 獲取富文本編輯框里的內(nèi)容
* 最終結(jié)果以html格式上傳
*/
List<RichTextEditor.EditData> data = editor.buildEditData();
for (int i = 0; i < data.size(); i++) {
String imagUrl = data.get(i).url;//媒體文件的遠(yuǎn)程路徑
String inputStr = data.get(i).inputStr;//輸入的文本
String type = data.get(i).type;//判斷媒體文件的類型
if (null != inputStr && !"".equals(inputStr))
content = content + "<p>" + inputStr + "</p>";
if (null != imagUrl && !"".equals(imagUrl)) {
if (type.equals("pic")) {
String html = "<p><img src = " + "\""+ imagUrl +"\""+ "/></p>";
content = content + html;
} else if (type.equals("video")) {
String html = "<p><video controls=\"controls\" poster = "+"\""+frames+"\""+"><source src = "+"\""+ imagUrl +"\""+" type = \"video/mp4\"/></video></p>";
content = content + html;
} else if (type.equals("audio")) {
String html = "<p><audio src = " +"\""+ imagUrl +"\"" + " id=\"audio\"></audio></p>";
content = content + html;
}
}
}