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);
//提交服務器省略
複製代碼