public class TextEditorState extends View.BaseSavedState {
public int rtImageHeight;
public static final Creator<TextEditorState> CREATOR = new Creator<TextEditorState>() {
@Override
public TextEditorState createFromParcel(Parcel in) {
return new TextEditorState(in);
}
@Override
public TextEditorState[] newArray(int size) {
return new TextEditorState[size];
}
};
public TextEditorState(Parcelable superState) {
super(superState);
}
public TextEditorState(Parcel source) {
super(source);
rtImageHeight = source.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(rtImageHeight);
}
}
复制代码
/** * 保存重要信息 * @return */
@Nullable
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
TextEditorState viewState = new TextEditorState(superState);
viewState.rtImageHeight = rtImageHeight;
return viewState;
}
/** * 复现 * @param state state */
@Override
protected void onRestoreInstanceState(Parcelable state) {
TextEditorState viewState = (TextEditorState) state;
rtImageHeight = viewState.rtImageHeight;
super.onRestoreInstanceState(viewState.getSuperState());
requestLayout();
}
复制代码
// 初始化键盘退格监听,主要用来处理点击回删按钮时,view的一些列合并操做
keyListener = new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
//KeyEvent.KEYCODE_DEL 删除插入点以前的字符
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
EditText edit = (EditText) v;
//处于退格删除的逻辑
onBackspacePress(edit);
}
return false;
}
};
复制代码
/** * 处理软键盘backSpace回退事件 * @param editTxt 光标所在的文本输入框 */
private void onBackspacePress(EditText editTxt) {
try {
int startSelection = editTxt.getSelectionStart();
// 只有在光标已经顶到文本输入框的最前方,在断定是否删除以前的图片,或两个View合并
if (startSelection == 0) {
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();
}
}
复制代码
/** * 删除操做 * @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);
}
复制代码
/** * 插入一张图片 * @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();
}
}
复制代码
/** * 全部EditText的焦点监听listener */
private OnFocusChangeListener focusListener;
focusListener = new OnFocusChangeListener() {
@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();
}
}
复制代码
/** * 修改加粗样式 */
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);
}
复制代码
/**
* 修改加粗样式
*/
public void bold() {
SpanTextHelper.getInstance().bold(lastFocusEdit);
}
复制代码
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);
}
}
}
复制代码
Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) {
try{
hte_content.measure(0, 0);
List<Uri> mSelected = Matisse.obtainResult(data);
// 能够同时插入多张图片
for (Uri imageUri : mSelected) {
String imagePath = HyperLibUtils.getFilePathFromUri(NewActivity.this, imageUri);
Bitmap bitmap = HyperLibUtils.getSmallBitmap(imagePath, screenWidth, screenHeight);
//压缩图片
imagePath = SDCardUtil.saveToSdCard(bitmap);
emitter.onNext(imagePath);
}
emitter.onComplete();
}catch (Exception e){
e.printStackTrace();
emitter.onError(e);
}
}
})
.subscribeOn(Schedulers.io())//生产事件在io
.observeOn(AndroidSchedulers.mainThread())//消费事件在UI线程
.subscribe(new Observer<String>() {
@Override
public void onComplete() {
ToastUtils.showRoundRectToast("图片插入成功");
}
@Override
public void onError(Throwable e) {
ToastUtils.showRoundRectToast("图片插入失败:"+e.getMessage());
}
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String imagePath) {
//插入图片
hte_content.insertImage(imagePath);
}
});
复制代码
/** * 在特定位置添加ImageView */
public void addImageViewAtIndex(final int index, final String imagePath) {
if (TextUtils.isEmpty(imagePath)){
return;
}
try {
imagePaths.add(imagePath);
final RelativeLayout imageLayout = createImageLayout();
HyperImageView imageView = imageLayout.findViewById(R.id.edit_imageView);
imageView.setAbsolutePath(imagePath);
HyperManager.getInstance().loadImage(imagePath, imageView, rtImageHeight);
layout.addView(imageLayout, index);
} catch (Exception e) {
e.printStackTrace();
}
}
复制代码
HyperManager.getInstance().setImageLoader(new ImageLoader() {
@Override
public void loadImage(final String imagePath, final ImageView imageView, final int imageHeight) {
Log.e("---", "imageHeight: "+imageHeight);
//若是是网络图片
if (imagePath.startsWith("http://") || imagePath.startsWith("https://")){
//直接用图片加载框架加载图片便可
} else { //若是是本地图片
}
}
});
复制代码
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;
}
复制代码
/** * 处理图片上删除的点击事件 * 删除类型 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();
}
}
复制代码
mTransition = new LayoutTransition();
mTransition.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();
}
}
});
mTransition.enableTransitionType(LayoutTransition.APPEARING);
mTransition.setDuration(300);
layout.setLayoutTransition(mTransition);
复制代码
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mTransition!=null){
//移除Layout变化监听
mTransition.removeTransitionListener(transitionListener);
}
}
复制代码
// 图片处理
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());
}
}
}
};
复制代码
/** * 修改加粗样式 */
public void bold() {
SpanTextHelper.getInstance().bold(lastFocusEdit);
}
/** * 修改斜体样式 */
public void italic() {
SpanTextHelper.getInstance().italic(lastFocusEdit);
}
/** * 修改删除线样式 */
public void strikeThrough() {
SpanTextHelper.getInstance().strikeThrough(lastFocusEdit);
}
/** * 修改下划线样式 */
public void underline() {
SpanTextHelper.getInstance().underline(lastFocusEdit);
}
复制代码
public abstract class NormalStyle<E> {
private Class<E> clazzE;
public NormalStyle() {
//利用反射
clazzE = (Class<E>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
/** * 样式状况判断 * @param editable editable * @param start start * @param end end */
public void applyStyle(Editable editable, int start, int end) {
}
}
复制代码
public class ItalicStyle extends NormalStyle<ItalicStyleSpan> {
@Override
protected ItalicStyleSpan newSpan() {
return new ItalicStyleSpan();
}
}
public class UnderlineStyle extends NormalStyle<UnderLineSpan> {
@Override
protected UnderLineSpan newSpan() {
return new UnderLineSpan();
}
}
复制代码
//获取光标所在位置
int cursorIndex = lastFocusEdit.getSelectionStart();
//获取光标前面的字符串
String editStr1 = lastEditStr.substring(0, cursorIndex).trim();
//获取光标后的字符串
String editStr2 = lastEditStr.substring(cursorIndex).trim();
lastFocusEdit.setText(editStr1);
addEditTextAtIndex(lastEditIndex + 1, editStr2);
addEditTextAtIndex(lastEditIndex + 1, "");
addImageViewAtIndex(lastEditIndex + 1, imagePath);
复制代码
stateUnspecified-未指定状态:软件默认采用的交互方式,系统会根据当前界面自动调整软键盘的显示模式。
stateUnchanged-不改变状态:当前界面软键盘状态由上个界面软键盘的状态决定;
stateHidden-隐藏状态:进入页面,不管是否有输入需求,软键盘是隐藏的,可是若是跳转到下一个页面软键盘是展现的,回到这个页面,软键盘可能也是展现的,这个属性区别下个属性。
stateAlwaysHidden-老是隐藏状态:当设置该状态时,软键盘老是被隐藏,和stateHidden不一样的是,当咱们跳转到下个界面,若是下个页面的软键盘是显示的,而咱们再次回来的时候,软键盘就会隐藏起来。
stateVisible-可见状态:当设置为这个状态时,软键盘老是可见的,即便在界面上没有输入框的状况下也能够强制弹出来出来。
stateAlwaysVisible-老是显示状态:当设置为这个状态时,软键盘老是可见的,和stateVisible不一样的是,当咱们跳转到下个界面,若是下个页面软键盘是隐藏的,而咱们再次回来的时候,软键盘就会显示出来。
adjustUnspecified-未指定模式:设置软键盘与软件的显示内容之间的显示关系。当你跟咱们没有设置这个值的时候,这个选项也是默认的设置模式。在这中状况下,系统会根据界面选择不一样的模式。
adjustResize-调整模式:当软键盘显示的时候,当前界面会自动重绘,会被压缩,软键盘消失以后,界面恢复正常(正常布局,非scrollView父布局);当父布局是scrollView的时候,软键盘弹出,会将布局顶起(保证输入框不被遮挡),不压缩,并且能够软键盘不消失的状况下,手动滑出被遮挡的布局;
adjustPan-默认模式:软键盘弹出,软键盘会遮挡屏幕下半部分布局,当输入框在屏幕下方布局,软键盘弹起,会自动将当前布局顶起,保证,软键盘不遮挡当前输入框(正常布局,非scrollView父布局)。当父布局是scrollView的时候,感受没啥变化,仍是自定将布局顶起,输入框不被遮挡,不能够手动滑出被遮挡的布局(白瞎了scrollView);
复制代码
<activity android:name=".NewArticleActivity"
android:windowSoftInputMode="adjustResize|stateHidden"/>
复制代码
View rootView = hte_content.getRootView();
rootView.setBackgroundColor(Color.WHITE);
复制代码
public class HyperEditData implements Serializable {
/** * 富文本输入文字内容 */
private String inputStr;
/** * 富文本输入图片地址 */
private String imagePath;
/** * 类型:1,表明文字;2,表明图片 */
private int type;
//省略不少set,get方法
}
复制代码
/** * 对外提供的接口, 生成编辑数据上传 */
public List<HyperEditData> buildEditData() {
List<HyperEditData> dataList = new ArrayList<>();
try {
int num = layout.getChildCount();
for (int index = 0; index < num; index++) {
View itemView = layout.getChildAt(index);
HyperEditData hyperEditData = new HyperEditData();
if (itemView instanceof EditText) {
//文本
EditText item = (EditText) itemView;
hyperEditData.setInputStr(item.getText().toString());
hyperEditData.setType(2);
} else if (itemView instanceof RelativeLayout) {
//图片
HyperImageView item = itemView.findViewById(R.id.edit_imageView);
hyperEditData.setImagePath(item.getAbsolutePath());
hyperEditData.setType(1);
}
dataList.add(hyperEditData);
}
} catch (Exception e) {
e.printStackTrace();
}
HyperLogUtils.d("HyperTextEditor----buildEditData------dataList---"+dataList.size());
return dataList;
}
复制代码
List<HyperEditData> editList = hte_content.buildEditData();
//生成json
Gson gson = new Gson();
String content = gson.toJson(editList);
//转化成json字符串
String string = HyperHtmlUtils.stringToJson(content);
//提交服务器省略
复制代码