国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

如何簡單的實現一個富文本,圖文混排編輯器

2019-11-09 17:11:10
字體:
來源:轉載
供稿:網友

如何簡單的實現一個android圖文混排,據我所知,android有很多種現成的方式可以實現圖文混排

WebView + javaScript EditText + Spanscrollview + view

上面幾種方法是比較常見的實現圖文混排+富文本的辦法。


WebView + Javascript

在使用webview實現富文本真是太簡單了,也就是html+CSS+js嘛,想怎么搞就怎么搞,不過這種的難點就是在手機客戶端中的編輯問題,畢竟是webview和android view的轉化問題,實現起來還是很多坑,不符合我的需求,略過

EditText + Span 這個雖然可以很好的實現簡單富文本的編輯,但是在圖文混排,以及各種主要自定義的組件面前就顯得捉襟見肘,顧忽略

scrollview + view 這才是我想介紹的實現方式,這個的優點是可以實現各種各樣的view,想什么組件自定義就行,而且實現比較簡單,簡單幾句就可以實現文本插入編輯。


scrollview + view:

先上一個簡單的效果圖

這里寫圖片描述

首先,我先定義一個組件的接口

//富文本組件都要實現該接口 public interface IEditView { //下面的方法根據具體的組件自己增加刪除 //上傳文件返回的id String getUploadId(); /** * 獲取view類型 */ Enum getViewType(); /** * 獲取文件本地路徑 * @return */ String getFilePath(); /** * 獲取具體實現的view * @return */ View getView(); /** * 設置點擊組件下面的空白回調事件 * @param listener */ void setOnClickViewListener(IClickCallBack listener); /** * 獲取顯示的文本 * @return */ String getContent(); Holder getHolder(); //這里定個了多個組件類型 enum Type{ IMAGE,FILE,VOICE,LOCATION,CONTENT,TITLE,UNKOWN } class Holder implements Serializable{ public String uploadId; public String filePath; public String fileName; public Enum viewType; public String content; @Override public String toString() { return "ViewHolder{" + "uploadId='" + uploadId + '/'' + ", filePath='" + filePath + '/'' + ", fileName='" + fileName + '/'' + ", viewType=" + viewType + ", content='" + content + '/'' + '}'; } }

還有一個組件的點擊接口,可根據自己的組件自己選擇實現的方法

public interface IClickCallBack { /** * 點擊view下面的空白處回調事件,可在此實現插入edittext,在組件下面留一條空白又好看又可以點擊 * @param v 點擊的view * @param widget 當前的組件 */ void onBlankViewClick(View v, View widget); /** * 點擊view里面的刪除圖標回調事件,部分類型的view里面沒有刪除圖標 * @param v 點擊的view * @param widget 當前的組件 */ void onDeleteIconClick(View v, View widget); /** * 組件的點擊事件 * @param v * @param widget */ void onContentClick(View v, View widget);}

然后定義兩個簡單的組件 RichEditText 和RichImageView

//實現一個簡單的文本框組件 public class RichEditText extends FrameLayout implements IEditView { PRivate LayoutInflater mInflater; private Context mContext; private EditText mEditText; private IClickCallBack clickCallBack; public Holder holder; @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { return new DeleteInputConnection(super.onCreateInputConnection(outAttrs), true); } //處理軟鍵盤回刪按鈕backSpace時回調OnKeyListener private class DeleteInputConnection extends InputConnectionWrapper { public DeleteInputConnection(InputConnection target, boolean mutable) { super(target, mutable); } @Override public boolean sendKeyEvent(KeyEvent event) { return super.sendKeyEvent(event); } @Override public boolean deleteSurroundingText(int beforeLength, int 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); } } public RichEditText(Context context) { this(context, null); } public RichEditText(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RichEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; mInflater = LayoutInflater.from(context); mInflater.inflate(R.layout.item_rich_edit,this); holder = new Holder(); holder.viewType = Type.CONTENT; init(); } private void init() { mEditText = (EditText) findViewById(R.id.et_rich); findViewById(R.id.blank_view).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if(clickCallBack != null) clickCallBack.onBlankViewClick(v, RichEditText.this); } }); mEditText.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if(clickCallBack != null) clickCallBack.onContentClick(v, RichEditText.this); return false; } }); } public void setContent(String content){ mEditText.setText(content); } public EditText getEditText(){ return mEditText; } public int getSelectionStart(){ return mEditText.getSelectionStart(); } public void setText(String text){ mEditText.setText(text); } public void setSelection(int start,int stop){ mEditText.setSelection(start,stop); } public void reqFocus(){ mEditText.requestFocus(); } @Override public String getUploadId() { return null; } @Override public Enum getViewType() { return Type.CONTENT; } @Override public String getFilePath() { return null; } @Override public View getView() { return this; } @Override public void setOnClickViewListener(IClickCallBack listener) { this.clickCallBack = listener; } @Override public String getContent() { String s = mEditText.getText().toString(); holder.content = s; return s; } @Override public Holder getHolder() { return holder; } }

實現一個簡單的圖片組件

public class RichImageView extends FrameLayout implements IEditView { private LayoutInflater mInflater; private Context mContext; private ImageView mEditImageView; private ImageView mImageClose; private View mBlankView; private IClickCallBack clickCallBack; private Holder holder; private int SCREEN_WIDTH; public RichImageView(Context context) { this(context, null); } public RichImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RichImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; mInflater = LayoutInflater.from(context); mInflater.inflate(R.layout.item_edit_imageview, this); holder = new Holder(); holder.viewType = Type.IMAGE; DisplayMetrics dm = new DisplayMetrics(); ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(dm); SCREEN_WIDTH = dm.widthPixels; init(); } private void init() { mEditImageView = (ImageView) findViewById(R.id.edit_imageView); mImageClose = (ImageView) findViewById(R.id.image_close); mBlankView = findViewById(R.id.blank_view); //圖片組件下面留一條空白為了和下面的組件有間隔,也可以點擊空白時候插入一個文本框 mBlankView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (clickCallBack != null) { clickCallBack.onBlankViewClick(v, RichImageView.this); } } }); //圖片組件右上角有一個刪除按鈕 mImageClose.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (clickCallBack != null) { clickCallBack.onDeleteIconClick(v, RichImageView.this); } } }); //圖片組件點擊,調用組件點擊事件 mEditImageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (clickCallBack != null) { clickCallBack.onContentClick(v, RichImageView.this); } } }); } //設置圖片路徑,我這里隨便寫死了 public void setEditImageView(final String imagePath) {// if (TextUtils.isEmpty(imagePath))// return; holder.filePath = imagePath; mEditImageView.getLayoutParams().width= SCREEN_WIDTH; BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; BitmapFactory.decodeFile(imagePath, opts); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(SCREEN_WIDTH, SCREEN_WIDTH); mEditImageView.setLayoutParams(layoutParams); mEditImageView.setBackgroundResource(R.drawable.ceshi); } @Override public String getUploadId() { return holder.uploadId; } @Override public Enum getViewType() { return Type.IMAGE; } @Override public String getFilePath() { return holder.filePath; } @Override public View getView() { return this; } @Override public void setOnClickViewListener(IClickCallBack listener) { this.clickCallBack = listener; } @Override public String getContent() { return null; } @Override public Holder getHolder() { return holder; } }

定義了兩個簡單的組件之后,接下來就是最后的組件管理器RichSrcollView,對組件的增刪其實也是最基本的addview和removeview. 管理器實現了組件的點擊事件,鍵盤的回退刪除,組件的插入方法等待。

/** * 富文本內容編輯組件 * 文本編輯內容組件每次都會自動添加,你只需要添加各種其他組件就行了 */ public class RichSrcollView extends ScrollView { public static final String KEY_TITLE = "title"; public static final String KEY_CONTENT = "content"; private LinearLayout allLayout; // 這個是所有子view的容器,scrollView內部的唯一一個ViewGroup private OnKeyListener keyListener; // 所有EditText的軟鍵盤監聽器 private OnFocusChangeListener focusListener; // 所有EditText的焦點監聽listener public RichEditText lastFocusView; // 最近被聚焦的view private LayoutTransition mTransitioner; // 只在圖片View添加或remove時,觸發transition動畫 private Context mContext; private boolean hasTitle = false; public RichSrcollView(Context context) { this(context, null); } public RichSrcollView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RichSrcollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; // 初始化allLayout,用來存放所有富文本組件 allLayout = new LinearLayout(context); allLayout.setOrientation(LinearLayout.VERTICAL); allLayout.setBackgroundColor(Color.WHITE); setupLayoutTransitions(); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); addView(allLayout, layoutParams); // 鍵盤退格監聽 // 主要用來處理點擊回刪按鈕時,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) { RichEditText richEditText = (RichEditText) v.getParent().getParent(); onBackspacePress(richEditText); } return false; } }; //定一個焦點改變監聽器,用來知道最后的焦點在哪個組件,這樣插入新組件的話就會插入到那個組件的后面 focusListener = new OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { lastFocusView = (RichEditText) v.getParent().getParent(); } } }; //初始化生成一個編輯文本框 LinearLayout.LayoutParams firstEditParam = new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); RichEditText view = createEditText(); allLayout.addView(view, firstEditParam); lastFocusView = view; } public void removeAllIEditView() { if (allLayout != null) { allLayout.removeAllViews(); } } /** * 處理軟鍵盤backSpace回退事件 * 回退時是否在文本上回退,在文本上時是否還有數據,有就刪除數據,沒有就上次上一個組件,當前焦點還是在這個文本框,這樣才有一種富文本編輯器的感覺 * * @param */ private void onBackspacePress(RichEditText curView) { int startSelection = curView.getEditText().getSelectionStart(); // 只有在光標已經頂到文本輸入框的最前方,在判定是否刪除之前的組件,或兩個View合并 if (startSelection == 0) { //表示一個文本框,這種情況回退不能刪除組件 if (allLayout.getChildCount() <= 1) { return; } int editIndex = allLayout.indexOfChild(curView); View preView = allLayout.getChildAt(editIndex - 1); // 則返回的是null if (null != preView) { if (preView instanceof RichEditText) { // 光標EditText的上一個view對應的還是文本框EditText String str1 = curView.getEditText().getText().toString(); EditText preEdit = ((RichEditText) preView).getEditText(); String str2 = preEdit.getText().toString(); // 合并文本view時,不需要transition動畫 allLayout.setLayoutTransition(null); allLayout.removeView(curView); allLayout.setLayoutTransition(mTransitioner); // 恢復transition動畫 // 文本合并 preEdit.setText(str2 + str1); preEdit.requestFocus(); preEdit.setSelection(str2.length(), str2.length()); lastFocusView = (RichEditText) preView; } else if (preView instanceof IEditView) { // 光標EditText的上一個view對應的是組件 onEditViewCloseClick(preView); } } } } /** * 處理組件關閉圖標的點擊事件 * * @param view 整個image對應的relativeLayout view */ private void onEditViewCloseClick(View view) { if (!mTransitioner.isRunning()) { allLayout.removeView(view); } } /** * 生成文本輸入框 */ private RichEditText createEditText() { RichEditText richEditText = new RichEditText(mContext); richEditText.getEditText().setOnKeyListener(keyListener); if (haveEditText()) richEditText.getEditText().setHint(""); richEditText.getEditText().setOnFocusChangeListener(focusListener); return richEditText; } private boolean haveEditText() { int childCount = allLayout.getChildCount(); for (int i = 0; i < childCount; i++) { IEditView iEditView = (IEditView) allLayout.getChildAt(i); if (iEditView.getViewType().ordinal() == IEditView.Type.CONTENT.ordinal()) { return true; } } return false; } private void setEditViewListener(IEditView editView) { //刪除按鈕設置監聽器 editView.setOnClickViewListener(new IClickCallBack() { @Override public void onBlankViewClick(View v, View widget) { //點擊組件下面的空白,如果當前組件和上下組件都不是文本框,則創建一個文本框 int childCount = allLayout.getChildCount(); for (int i = 0; i < childCount; i++) { if (allLayout.getChildAt(i) == widget) { View curView = allLayout.getChildAt(i); View nextView = allLayout.getChildAt(i + 1); if (!(curView instanceof RichEditText) && (nextView == null || !(nextView instanceof RichEditText))) { addEditTextAtIndex(i + 1, ""); break; } } } } @Override public void onDeleteIconClick(View v, View widget) { // Toast.makeText(mContext,"點擊刪除",Toast.LENGTH_SHORT).show(); onEditViewCloseClick(widget); if (lastFocusView != null) lastFocusView.reqFocus(); } @Override public void onContentClick(View v, View widget) { } }); } /** * 在特定位置插入EditText * * @param index 位置 * @param editStr EditText顯示的文字 */ private void addEditTextAtIndex(final int index, String editStr) { RichEditText view = createEditText(); EditText editText2 = (EditText) view.findViewById(R.id.et_rich); editText2.setText(editStr); lastFocusView = view; view.reqFocus(); // 請注意此處,EditText添加、或刪除不觸動Transition動畫 allLayout.setLayoutTransition(null); allLayout.addView(view, index); allLayout.setLayoutTransition(mTransitioner); // remove之后恢復transition動畫 } /** * 在特定位置添加一個編輯組件 */ private void addEditViewAtIndexAnimation(final int index, final IEditView editView) { postDelayed(new Runnable() { @Override public void run() { allLayout.addView(editView.getView(), index); } }, 200); } private void srollToBottom() { postDelayed(new Runnable() { @Override public void run() { if (lastFocusView != null) lastFocusView.reqFocus(); fullScroll(ScrollView.FOCUS_DOWN); } }, 1000); } /** * 立即插入一個編輯組件,適用于編輯話題,有延時會導致順序錯亂 * 代價是沒有動畫 * * @param index 顯示位置 * @param editView 組件 */ private void addEditViewAtIndexImmediate(final int index, final IEditView editView) { allLayout.addView(editView.getView(), index); postDelayed(new Runnable() { @Override public void run() { if (lastFocusView != null) lastFocusView.reqFocus(); fullScroll(ScrollView.FOCUS_DOWN); } }, 1000); } /** * 初始化transition動畫 */ private void setupLayoutTransitions() { mTransitioner = new LayoutTransition(); allLayout.setLayoutTransition(mTransitioner); mTransitioner.setDuration(300); } /** * 獲取當前焦點的Edittext * * @return */ public EditText getCurFousEditText() { if (lastFocusView != null) return lastFocusView.getEditText(); return null; } public void setLastEditTextFocus() { int childCount = allLayout.getChildCount(); for (int i = childCount - 1; i >= 0; i--) { View childAt = allLayout.getChildAt(i); if (childAt instanceof RichEditText) { ((RichEditText) childAt).reqFocus(); showKeyBoard(((RichEditText) childAt).getEditText()); return; } } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getY() > allLayout.getBottom()) { setLastEditTextFocus(); return true; } return super.dispatchTouchEvent(ev); } /** * 隱藏小鍵盤 */ public void hideKeyBoard() { InputMethodManager imm = (InputMethodManager) getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(lastFocusView.getWindowToken(), 0); } public void showKeyBoard(EditText view) { InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); view.setSelection(0); view.setFocusable(true); view.setFocusableInTouchMode(true); view.requestFocus(); imm.showSoftInput(view, 0); } /** * 插入一個編輯組件,根據焦點的不同而位置不同 */ public void insertEditView(IEditView editView) { setEditViewListener(editView); String lastEditStr = lastFocusView.getContent(); lastFocusView.reqFocus(); int cursorIndex = lastFocusView.getSelectionStart(); int lastEditIndex = allLayout.indexOfChild(lastFocusView); if (cursorIndex >= 0) { String editStr1 = lastEditStr.substring(0, cursorIndex).trim(); if (lastEditStr.length() == 0 || editStr1.length() == 0) { // 如果EditText為空,或者光標已經頂在了editText的最前面,則直接插入組件,并且EditText下移即可 addEditViewAtIndexAnimation(lastEditIndex, editView); } else { // 如果EditText非空且光標不在最頂端,則需要添加新的imageView和EditText lastFocusView.setText(editStr1); String editStr2 = lastEditStr.substring(cursorIndex).trim(); if (allLayout.getChildCount() - 1 == lastEditIndex || editStr2.length() > 0) { addEditTextAtIndex(lastEditIndex + 1, editStr2); } addEditViewAtIndexAnimation(lastEditIndex + 1, editView); lastFocusView.reqFocus(); lastFocusView.setSelection(lastFocusView.getContent().length(), lastFocusView.getContent().length()); } if (allLayout.indexOfChild(lastFocusView) >= allLayout.getChildCount() - 1) { srollToBottom(); } } else { //出現失去焦點的情況,默認添加到最后面 addEditViewAtIndexAnimation(allLayout.getChildCount() - 1, editView); srollToBottom(); } hideKeyBoard(); } /** * 獲取全部數據集合 */ public List<IEditView> buildData() { List<IEditView> dataList = new ArrayList<IEditView>(); int num = allLayout.getChildCount(); for (int index = 0; index < num; index++) { IEditView itemView = (IEditView) allLayout.getChildAt(index); dataList.add(itemView); } return dataList; } }

大體的注釋都有,而具體的引用很簡單,我這里點擊按鈕的時候就新建一個圖片組件,而文本框組件可以點擊組件下面的空白條插入。

Button button = (Button) findViewById(R.id.button); final RichSrcollView richSrcollVIew = (RichSrcollView) findViewById(R.id.scrollview); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { RichImageView richImageView = new RichImageView(MainActivity.this); //設置圖片路徑 richImageView.setEditImageView(""); //插入組件 richSrcollVIew.insertEditView(richImageView); } });

只需要在scrollview實現一些view的添加和刪除,以及組件間的拼接,就可以實現一個很簡單的可定制的富文本編輯器。

然而有一個缺點就是,畢竟是scrollview,不像listview recycleview那樣可以資源回收,這個插入太多圖片有可能導致oom

代碼查看 https://github.com/JadynChan/RichTextDemo


上一篇:星星評分

下一篇:百度百科算法集合

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 田阳县| 南澳县| 富源县| 弥勒县| 新蔡县| 九台市| 同心县| 海城市| 敖汉旗| 铜鼓县| 黄龙县| 蒲城县| 沁源县| 洱源县| 新兴县| 武陟县| 桦川县| 平利县| 江源县| 锦屏县| 南和县| 蒙山县| 克拉玛依市| 台安县| 城市| 襄城县| 铜陵市| 阳江市| 福贡县| 寿宁县| 漳平市| 双城市| 无极县| 黄石市| 大石桥市| 庆城县| 浑源县| 铁力市| 特克斯县| 靖远县| 大厂|