富文本編輯器RichTextEditor

主要實(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;
                }
            }
        }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容