Android富文本開發

基礎概念目錄介紹

  • 01.業務需求簡單介紹
  • 02.實現的方案介紹
  • 03.異常狀態下保存狀態信息
  • 04.處理軟鍵盤迴刪按鈕邏輯
  • 05.在指定位置插入圖片
  • 06.在指定位置插入輸入文字
  • 07.若是對選中文字加粗
  • 08.利用Span對文字屬性處理
  • 09.如何設置插入多張圖片
  • 10.如何設置插入網絡圖片
  • 11.如何避免插入圖片OOM
  • 12.如何刪除圖片或者文字
  • 13.刪除和插入圖片添加動畫
  • 14.點擊圖片能夠查看大圖
  • 15.如何暴露設置文字屬性方法
  • 16.文字中間添加圖片注意事項
  • 17.鍵盤彈出和收縮優化
  • 18.先後臺切換編輯富文本優化
  • 19.生成html片斷上傳服務器
  • 20.生成json片斷上傳服務器
  • 21.圖片上傳策略問題思考
  • 22.一些細節問題的處理

00.該控件介紹

1.1 富文本介紹

  • 自定義文本控件,支持富文本,包含兩種狀態:編輯狀態和預覽狀態。編輯狀態中,能夠對插入本地或者網絡圖片,能夠同時插入多張有序圖片和刪除圖片,支持圖文混排,而且能夠對文字內容簡單操做加粗字體,設置字體下劃線,支持設置文字超連接(超連接支持跳轉),還能夠統計富文本中的字數,功能正在開發中和完善中……

1.2 富文本效果圖

image
image
image
image
image

1.3 富文本開源庫

01.業務需求簡單介紹

  • 富文本控件支持動態插入文字,圖片等圖文混排內容。圖片能夠支持本地圖片,也支持插入網絡連接圖片;
  • 富文本又兩種狀態:編輯狀態 + 預覽狀態 。兩種狀態能夠相互進行切換;
  • 富文本在編輯狀態,能夠同時選擇插入超過一張以上的多張圖片,而且能夠動態設置圖片之間的top間距;
  • 在編輯狀態,支持利用光標刪除文字內容,同時也支持用光標刪除圖片;
  • 在編輯狀態,插入圖片後,圖片的寬度填充滿手機屏幕的寬度,而後高度能夠動態設置,圖片是劇中裁剪顯示;
  • 在編輯狀態,插入圖片後,若是本地圖片過大,要求對圖片進行質量壓縮,大小壓縮;
  • 在編輯狀態,插入多張圖片時,添加插入過渡動畫,避免顯示圖片生硬。結束後,光標移到插入圖片中的最後一行顯示;
  • 編輯狀態中,圖片點擊暴露點擊事件接口,能夠在4個邊角位置動態設置一個刪除圖片的功能,點擊刪除按鈕則刪除圖片;
  • 連續插入多張圖片時,好比順序1,2,3,注意避免出現圖片插入順序混亂的問題(異步插入多張圖片可能出現順序錯亂問題);
  • 在編輯富文本狀態的時候,連續多張圖片之間插入輸入框,方便在圖片間輸入文本內容;
  • 在編輯狀態中,能夠設置文字大小和顏色,同時作好拓展需求,後期可能添加文本加粗,下劃線,插入超連接,對齊方式等功能;
  • 編輯狀態,連續插入多張圖片,若是想在圖片中間插入文字內容,則須要靠譜在圖片之間預留編輯文本控件,方便操做;
  • 支持對文字選中的內容進行設置加粗,添加下劃線,改變顏色,設置對齊方式等等;
  • 關於富文本字數統計,因爲富文本中包括文字和圖片,所以圖片和文字數量統計分開。參考易車是:共n個文字,共n個圖片顯示

02.實現的方案介紹

2.0 頁面構成分析

  • 整個界面的要求
    • 總體界面可滾動,能夠編輯,也能夠預覽
    • 內容可編輯能夠插入文字、圖片等。圖片提供按鈕操做
    • 軟鍵盤刪除鍵可刪除圖片,也能夠刪除文字內容
    • 文字能夠修改屬性,好比加粗,對齊,下劃線
  • 根據富文本做出如下分析
    • 使用原生控件,可插入圖片、文字界面不能用一個EditText來作,須要使用LinearLayout添加不一樣的控件,圖片部分用ImageView,界面可滑動最外層使用ScrollView。
    • 使用WebView+js+css方式,富文本格式用html方式展示,比較複雜,對標籤要很是熟悉才能夠嘗試使用
  • 使用原生控件多焦點問題分析
    • 界面是由多個輸入區域拼接而成,暫且把輸入區域稱爲EditText,圖片區域稱爲ImageView,外層是LinearLayout。
    • 若是一個富文本是:文字1+圖片1+文字2+文字3+圖片3+圖片4;那麼使用LinearLayout包含多個EditText實現的難點:
      • 如何處理記錄當前的焦點區域
      • 如何處理在文字區域的中間位置插入ImageView樣式的拆分和合並
      • 如何處理輸入區域的刪除鍵處理

2.2 第一種方案

  • 使用ScrollView做爲最外層,佈局包含LineaLayout,圖文混排內容,則是用TextView/EditText和ImageView去填充。
  • 富文本編輯狀態:ScrollView + LineaLayout + n個EditText+Span + n個ImageView
  • 富文本預覽狀態:ScrollView + LineaLayout + n個TextView+Span + n個ImageView
  • 刪除的時候,根據光標的位置,若是光標遇到是圖片,則能夠用光標刪除圖片;若是光標遇到是文字,則能夠用光標刪除文字
  • 當插入或者刪除圖片的時候,能夠添加一個過渡動畫效果,避免直接生硬的顯示。如何在ViewGroup中添加view,刪除view時給相應view和受影響的其餘view添加動畫,不太容易作。若是隻是對受到影響的view添加動畫,能夠經過設置view的高度使之顯示和隱藏,還能夠利用ScrollView經過滾動隱藏和顯示動畫,但其餘受影響的view則比較難處理,最終選擇佈局動畫LayoutTransition 就能夠很好地完成這個功能。

2.3 第二種方法

  • 使用WebView實現編輯器,支持n多格式,例如常見的html或者markdown格式。利用html標籤對富文本處理,這種方式就須要專門處理標籤的樣式。
  • 注意這種方法的實現,須要深刻研究js,css等,必須很是熟悉才能夠用到實際開發中,能夠看成學習一下。這種方式對於圖片的顯示和上傳,相比原生要麻煩一些。

2.4 富文本支持功能

  • 支持加粗、斜體、刪除線、下劃線行內樣式,一行代碼便可設置文本span屬性,十分方便
  • 支持添加單張或者多張圖片,而且插入過渡動畫友好,同時能夠保證插入圖片順序
  • 支持富文本編輯狀態和預覽狀態的切換,支持富文本內容轉化爲json內容輸出,轉化爲html內容輸出
  • 支持設置富文本的文字大小,行間距,圖片和文本間距,以及插入圖片的寬和高的屬性
  • 圖片支持點擊預覽,支持點擊叉號控件去除圖片,暴露給外部開發者調用。同時加載圖片的邏輯也是暴露給外部開發者,充分解耦
  • 關於富文本字數統計,因爲富文本中包括文字和圖片,所以圖片和文字數量統計分開。參考易車是:共n個文字,共n個圖片顯示

03.異常狀態下保存狀態信息

  • 對於自定義View,若是頁面出現異常致使自定義View異常退出,則固然但願保存一些重要的信息。自定義保存狀態類,繼承BaseSavedState,代碼以下所示
    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);
        }
    }
    複製代碼
  • 如何使用該保存狀態欄,自定義View中,有兩個特別的方法,分別是onSaveInstanceState和onRestoreInstanceState,具體邏輯以下所示
    /** * 保存重要信息 * @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();
    }
    複製代碼

04.處理軟鍵盤迴刪按鈕邏輯

  • 想了一下,當富文本處於編輯的狀態,利用光標能夠進行刪除插入點以前的字符。刪除的時候,根據光標的位置,若是光標遇到是圖片,則能夠用光標刪除圖片;若是光標遇到是文字,則能夠用光標刪除文字。
  • 更詳細的來講,監聽刪除鍵的點擊的邏輯須要注意,當光標在EditText 輸入中間,點擊刪除不進行處理正常刪除;當光標在EditText首端,判斷前一個控件,若是是圖片控件,刪除圖片控件,若是是輸入控件,刪除當前控件並將輸入區域合併成一個輸入區域。
  • 建立一個鍵盤退格監聽事件,代碼以下所示:
    // 初始化鍵盤退格監聽,主要用來處理點擊回刪按鈕時,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();
        }
    }
    複製代碼
  • 當EditText內容爲空時,發現手機根本沒法響應軟鍵盤的刪除監聽,這個是爲何呢?
    • 能夠看一下源碼,EditText繼承自TextView,翻看TextView的代碼,裏面有一個叫作InputConnection的東西,看起是什麼輸入鏈接的意思。若是想實現刪除的功能,須要自行實現重寫一個deleteSurroundingText()方法。
    /** * 刪除操做 * @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);
    }
    複製代碼

05.在指定位置插入圖片

  • 當點擊插入圖片的時候,須要思考兩個問題。第一個是在那個位置插入圖片,因此須要定位到這個位置;第二個是插入圖片後,何時折行操做。
  • 對於上面兩個問題,這個位置能夠取光標所在的位置,可是對於一個EditText輸入文本,插入圖片這個位置能夠分多種狀況:
    • 若是光標已經頂在了editText的最前面,則直接插入圖片,而且EditText下移便可
    • 若是光標已經頂在了editText的最末端,則須要添加新的imageView
    • 若是光標已經頂在了editText的最中間,則須要分割字符串,分割成兩個EditText,並在兩個EditText中間插入圖片
    • 若是當前獲取焦點的EditText爲空,直接在EditText下方插入圖片,而且插入空的EditText
  • 代碼思路以下所示
    /** * 插入一張圖片 * @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();
        }
    }
    複製代碼

06.在指定位置插入輸入文字

  • 前面已經提到了,若是一個富文本是:文字1+圖片1+文字2+文字3+圖片3+圖片4,那麼點擊文字1控件則在此輸入文字,點擊文字3控件則在此輸入文字。
  • 因此,這樣操做,肯定處理記錄當前的焦點區域位置十分重要。當前的編輯器已經添加了多個輸入文本EditText,如今的問題在於須要記錄當前編輯的EditText,在應用樣式的時候定位到輸入的控件,在編輯器中添加一個變量lastFocusEdit。具體能夠看代碼……
  • 既然能夠記錄最後焦點輸入文本,那麼如何監聽當前的輸入控件呢,這就用到了OnFocusChangeListener,這個又是在哪裏用到,具體以下面所示。要先setOnFocusChangeListener(focusListener) 再 requestFocus。
    /** * 全部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();
    	}
    }
    複製代碼

07.若是對選中文字加粗

  • Span 的分類介紹
    • 字符外觀,這種類型修改字符的外形可是不影響字符的測量,會觸發文本從新繪製可是不觸發從新佈局。
      • ForegroundColorSpan,BackgroundColorSpan,UnderlineSpan,StrikethrougnSpan
    • 字符大小布局,這種類型Span會更改文本的大小和佈局,會觸發文本的從新測量繪製
      • StyleSpan,RelativeSizeSpan,AbsoluteSizeSpan
    • 影響段落級別,這種類型Span 在段落級別起做用,更改文本塊在段落級別的外觀,修改對齊方式,邊距等。
      • AlignmentSpan,BulletSpan,QuoteSpan
  • 實現基礎樣式 粗體、 斜體、 下劃線 、中劃線 的設置和取消。舉個例子,對文本加粗,文字設置span樣式注意要點,這裏須要區分幾種狀況
  • 當前選中區域不存在 bold 樣式 這裏咱們選中BB。兩種狀況
    • 當前區域緊靠左側或者右側不存在粗體樣式: AABBCC 這時候直接設置 span便可
    • 當前區域緊靠左側或者右側存在粗體樣式如: AABBCC AABBCC AABBCC。這時候須要合併左右兩側的span,只剩下一個 span
  • 當前選中區域存在了Bold 樣式 選中 ABBC。四種狀況:
    • 選中樣式兩側不存在連續的bold樣式 AABBCC
    • 選中內部兩端存在連續的bold 樣式 AABBCC
    • 選中左側存在連續的bold 樣式 AABBCC
    • 選中右側存在連續的bold 樣式 AABBCC
    • 這時候須要合併左右兩側已經存在的span,只剩下一個 span
  • 接下來逐步分解,而後處理span的邏輯順序以下所示
    • 首先對選中文字內容樣式狀況判斷
    • 邊界判斷與設置
    • 取消Span(當咱們選中的區域在一段連續的 Bold 樣式裏面的時候,再次選擇Bold將會取消樣式)
  • 何時取消span呢,這個邏輯是比較複雜的,具體看看下面的舉例。
    • 當咱們選中的區域在一段連續的 Bold 樣式裏面的時候,再次選擇Bold將會取消樣式
    • 用戶能夠隨意的刪除文本,在刪除過程當中可能會出現以下的狀況:
      • 用戶輸入了 AABBCCDD
      • 用戶選擇了粗體樣式 AABBCCDD
      • 用戶刪除了CC而後顯示以下 : AABB DD
      • 這個時候選中其中的BD 此時,在該區域中 存在兩個span ,而且沒有一個 span 徹底包裹選中的 BD
      • 在這種狀況下 仍須要進行左右側邊界判斷進行刪除。這個具體能夠看代碼邏輯。

08.利用Span對文字屬性處理

  • 這裏僅僅是對字體加粗進行介紹,其實設置span能夠找到規律。多個span樣式,考慮到後期的拓展性,確定要進行封裝和抽象,具體該如何處理呢?
    • 設置文本選中內容加粗模式,代碼以下所示,能夠看到這裏只須要傳遞一個lastFocusEdit對象便可,這個對象是最近被聚焦的EditText。
    /** * 修改加粗樣式 */
    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);
    }
    複製代碼
    • 而後如何調用這個,在HyperTextEditor類中代碼以下所示。爲什麼要這樣寫,能夠把HyperTextEditor富文本類中設置span的邏輯放到SpanTextHelper類中處理,該類專門處理各類span屬性,這樣代碼結構更加清晰,也方便後期增長更多span屬性,避免一個類代碼太臃腫。
    /**
     * 修改加粗樣式
     */
    public void bold() {
        SpanTextHelper.getInstance().bold(lastFocusEdit);
    }
    複製代碼
  • 而後看一下new BoldStyle().applyStyle(editable, start, end)具體作了什麼?下面這段代碼邏輯,具體能夠看07.若是對選中文字加粗的分析思路。
    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);
            }
        }
    }
    複製代碼

09.如何設置插入多張圖片

  • 富文本固然支持插入多張圖片,那麼插入多張圖片是如何操做呢。插入1,2,3這三張圖片,如何保證它們的插入順序,從而避免插入錯位,帶着這幾個問題看一下插入多張圖片操做。
    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);
                }
            });
    複製代碼

10.如何設置插入網絡圖片

  • 插入圖片有兩種狀況,一種是本地圖片,一種是網絡圖片。因爲富文本中對插入圖片的寬高有限制,便可以動態設置圖片的高度,這就要求請求網絡圖片後,須要對圖片進行處理。
  • 首先看一下插入圖片的代碼,在HyperTextEditor類中,因爲封裝lib,不建議在lib中使用某個圖片加載庫加載圖片,而應該是暴露給外部開發者去加載圖片。
    /** * 在特定位置添加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();
        }
    }
    複製代碼
  • 那麼具體在那個地方去loadImage設置加載圖片呢?能夠發現這樣極大地提升了代碼的拓展性,緣由是你可能用glide,他可能用Picasso,還有的用ImageLoader,因此最好暴露給外部。
    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 { //若是是本地圖片
                
            }
        }
    });
    複製代碼

11.如何避免插入圖片OOM

  • 加載一個本地的大圖片或者網絡圖片,從加載到設置到View上,如何減下內存,避免加載圖片OOM。
    • 在展現高分辨率圖片的時候,最好先將圖片進行壓縮。壓縮後的圖片大小應該和用來展現它的控件大小相近,在一個很小的ImageView上顯示一張超大的圖片不會帶來任何視覺上的好處,但卻會佔用至關多寶貴的內存,並且在性能上還可能會帶來負面影響。
  • 加載圖片的內存都去哪裏呢?
    • 其實咱們的內存就是去bitmap裏了,BitmapFactory的每一個decode函數都會生成一個bitmap對象,用於存放解碼後的圖像,而後返回該引用。若是圖像數據較大就會形成bitmap對象申請的內存較多,若是圖像過多就會形成內存不夠用天然就會出現out of memory的現象。
  • 爲什麼容易OOM?
    • 經過BitmapFactory的decode的這些方法會嘗試爲已經構建的bitmap分配內存,這時就會很容易致使OOM出現。爲此每一種解析方法都提供了一個可選的BitmapFactory.Options參數,將這個參數的inJustDecodeBounds屬性設置爲true就可讓解析方法禁止爲bitmap分配內存,返回值也再也不是一個Bitmap對象,而是null。
  • 如何對圖片進行壓縮?
    • 1.解析圖片,獲取圖片資源的屬性
    • 2.計算圖片的縮放值
    • 3.最後對圖片進行質量壓縮
  • 具體設置圖片壓縮的代碼以下所示
    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;
    }
    複製代碼
  • 思考:inJustDecodeBounds這個參數是幹什麼的?
    • 若是設置爲true則表示decode函數不會生成bitmap對象,僅是將圖像相關的參數填充到option對象裏,這樣咱們就能夠在不生成bitmap而獲取到圖像的相關參數了。
  • 爲什麼設置兩次inJustDecodeBounds屬性?
    • 第一次:設置爲true則表示decode函數不會生成bitmap對象,僅是將圖像相關的參數填充到option對象裏,這樣咱們就能夠在不生成bitmap而獲取到圖像的相關參數。
    • 第二次:將inJustDecodeBounds設置爲false再次調用decode函數時就能生成bitmap了。而此時的bitmap已經壓縮減少不少了,因此加載到內存中並不會致使OOM。

12.如何刪除圖片或者文字

  • 當富文本處於編輯狀態時,點擊刪除圖片是能夠刪除圖片的,對於刪除的邏輯,封裝的lib能夠給開發者暴露一個刪除的監聽事件。注意刪除圖片有兩種操做:第一種是利用光標刪除,第二種是點擊觸發刪除。刪除圖片後,不只僅是要刪除圖片數據,並且還要刪除圖片ImageView控件。
    /** * 處理圖片上刪除的點擊事件 * 刪除類型 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();
        }
    }
    複製代碼

13.刪除和插入圖片添加動畫

  • 爲何要添加插入圖片的過渡動畫
    • 當向一個ViewGroup添加控件或者移除控件;這種場景雖然可以實現效果,並無一點過分效果,直來直去的添加或者移除,顯得有點生硬。有沒有辦法添加必定的過分效果,讓實現的效果顯得圓滑呢?
  • LayoutTransition簡單介紹
    • LayoutTransition類實際上Android系統中的一個實用工具類。使用LayoutTransition類在一個ViewGroup中對佈局更改進行動畫處理。
  • 如何運用到插入或者刪除圖片場景中
    • 向一個ViewGroup添加控件或者移除控件,這兩種效果的過程是應對應於控件的顯示、控件添加時其餘控件的位置移動、控件的消失、控件移除時其餘控件的位置移動等四種動畫效果。這些動畫效果在LayoutTransition中,由如下四個關鍵字作出了相關聲明:
      • APPEARING:元素在容器中顯現時須要動畫顯示。
      • CHANGE_APPEARING:因爲容器中要顯現一個新的元素,其它元素的變化須要動畫顯示。
      • DISAPPEARING:元素在容器中消失時須要動畫顯示。
      • CHANGE_DISAPPEARING:因爲容器中某個元素要消失,其它元素的變化須要動畫顯示。
    • 也就是說,ViewGroup中有多個ImageView對象,若是須要刪除其中一個ImageView對象的話,該ImageView對象能夠設置動畫(即DISAPPEARING 動畫形式),ViewGroup中的其它ImageView對象此時移動到新的位置的過程當中也能夠設置相關的動畫(即CHANGE_DISAPPEARING 動畫形式);
    • 若向ViewGroup中添加一個ImageView,ImageView對象能夠設置動畫(即APPEARING 動畫形式),ViewGroup中的其它ImageView對象此時移動到新的位置的過程當中也能夠設置相關的動畫(即CHANGE_APPEARING 動畫形式)。
    • 給ViewGroup設置動畫很簡單,只須要生成一個LayoutTransition實例,而後調用ViewGroup的setLayoutTransition(LayoutTransition)函數就能夠了。當設置了佈局動畫的ViewGroup添加或者刪除內部view時就會觸發動畫。
  • 具體初始化動畫的代碼以下所示:
    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);
        }
    }
    複製代碼
  • 動畫執行前後的順序
    • 分析源碼能夠知道,默認狀況下DISAPPEARING和CHANGE_APPEARING類型動畫會當即執行,其餘類型動畫則會有個延遲。也就是說若是刪除view,被刪除的view將先執行動畫消失,通過一些延遲受影響的view會進行動畫補上位置,若是添加view,受影響的view將會先給添加的view騰位置執行CHANGE_APPEARING動畫,通過一些時間的延遲纔會執行APPEARING動畫。這裏就不貼分析源碼的思路呢!

14.點擊圖片能夠查看大圖

  • 編輯狀態時,因爲圖片有空能比較大,在顯示在富文本的時候,會裁剪局中顯示,也就是圖片會顯示不全。那麼後期若是是想添加點擊圖片查看,則須要暴露給開發者監聽事件,須要考慮到後期拓展性,代碼以下所示:
    • 這樣作的目的是是暴露給外部開發者調用,點擊圖片的操做只須要傳遞view還有圖片便可。
    // 圖片處理
    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());
                }
            } 
        }
    };
    複製代碼

15.如何暴露設置文字屬性方法

  • 針對設置文字加粗,下劃線,刪除線等span屬性。同時設置span,有許多相似的地方,考慮到後期的添加和移除,如何封裝可以提升代碼的擴展性。
    /** * 修改加粗樣式 */
    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);
    }
    複製代碼
  • 上面實現了選中文本加粗的功能,斜體、 下劃線 、中劃線等樣式的設置和取消與粗體樣式一致,只是建立 span 的區別而已,能夠將代碼進行抽取。
    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) {
    
        }
    }
    複製代碼
  • 其餘的設置span的屬性代碼便是以下所示,能夠看到添加一種類型很容易,也容易看懂,便於拓展:
    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();
        }
    }
    複製代碼

16.文字中間添加圖片注意事項

  • 在文字中添加圖片比較特殊,所以這裏單獨拿出來講一下。在文字內容中間插入圖片,則須要分割字符串,分割成兩個EditText,並在兩個EditText中間插入圖片,那麼這個光標又定位在何處呢?
    • 對於光標前面的字符串保留,設置給當前得到焦點的EditText(此爲分割出來的第一個EditText)
    • 把光標後面的字符串放在新建立的EditText中(此爲分割出來的第二個EditText)
    • 在第二個EditText的位置插入一個空的EditText,以便連續插入多張圖片時,有空間寫文字,第二個EditText下移
    • 在空的EditText的位置插入圖片佈局,空的EditText下移。注意,這個過程添加動畫過渡一下插入的效果比較好,否則會比較生硬
    //獲取光標所在位置
    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);
    複製代碼

17.鍵盤彈出和收縮優化

  • 軟鍵盤彈出的時機
    • 若是不作任何處理,系統默認的是,進入頁面,第一個輸入框自動獲取焦點軟鍵盤自動彈出,這種用戶交互方式,每每不是產品想要的,每每會提出如下優化需求:
    • 需求1:editText獲取焦點,可是不彈出軟鍵盤(也就是說光標顯示第一個輸入框,不主動彈軟鍵盤)
      • 在第一個輸入框的最直接父佈局加入:android:focusable="true";android:focusableInTouchMode="true"
        • (效果:軟鍵盤不彈出,光標不顯示,其餘輸入框也不獲取焦點,ps非直接父佈局沒有效果)
      • android:windowSoftInputMode="stateAlwaysHidden"
        • (效果:軟鍵盤不彈出,光標顯示在第一個輸入框中)
    • 需求2:editText不獲取焦點,固然軟鍵盤不會主動彈出(光標也不顯示)
      • 在第一個輸入框的最直接父佈局加入:android:focusable="true";android:focusableInTouchMode="true"
        • (效果:軟鍵盤不彈出,光標不顯示,其餘輸入框也不獲取焦點,ps非直接父佈局沒有效果)
      • 在父佈局最頂部添加一個高度爲0的EditText,搶了焦點但不展現;
  • 軟鍵盤遮擋界面的問題
    • 當界面中有輸入框,須要彈起軟鍵盤輸入信息的時候,軟鍵盤可能遮擋部分佈局,更有甚者,當前輸入框若是在屏幕下方,軟鍵盤也會直接遮擋輸入框,這種狀況對用戶體驗是至關不友好的,因此要根據具體的狀況做出相應的處理。
    • android定義了一個屬性,名字爲windowSoftInputMode, 這個屬性用於設置Activity主窗口與軟鍵盤的交互模式,用於避免軟鍵盤遮擋內容的問題。咱們能夠在AndroidManifet.xml中對Activity進行設置。
    stateUnspecified-未指定狀態:軟件默認採用的交互方式,系統會根據當前界面自動調整軟鍵盤的顯示模式。
    stateUnchanged-不改變狀態:當前界面軟鍵盤狀態由上個界面軟鍵盤的狀態決定;
    stateHidden-隱藏狀態:進入頁面,不管是否有輸入需求,軟鍵盤是隱藏的,可是若是跳轉到下一個頁面軟鍵盤是展現的,回到這個頁面,軟鍵盤可能也是展現的,這個屬性區別下個屬性。
    stateAlwaysHidden-老是隱藏狀態:當設置該狀態時,軟鍵盤老是被隱藏,和stateHidden不一樣的是,當咱們跳轉到下個界面,若是下個頁面的軟鍵盤是顯示的,而咱們再次回來的時候,軟鍵盤就會隱藏起來。
    stateVisible-可見狀態:當設置爲這個狀態時,軟鍵盤老是可見的,即便在界面上沒有輸入框的狀況下也能夠強制彈出來出來。
    stateAlwaysVisible-老是顯示狀態:當設置爲這個狀態時,軟鍵盤老是可見的,和stateVisible不一樣的是,當咱們跳轉到下個界面,若是下個頁面軟鍵盤是隱藏的,而咱們再次回來的時候,軟鍵盤就會顯示出來。
    adjustUnspecified-未指定模式:設置軟鍵盤與軟件的顯示內容之間的顯示關係。當你跟咱們沒有設置這個值的時候,這個選項也是默認的設置模式。在這中狀況下,系統會根據界面選擇不一樣的模式。
    adjustResize-調整模式:當軟鍵盤顯示的時候,當前界面會自動重繪,會被壓縮,軟鍵盤消失以後,界面恢復正常(正常佈局,非scrollView父佈局);當父佈局是scrollView的時候,軟鍵盤彈出,會將佈局頂起(保證輸入框不被遮擋),不壓縮,並且能夠軟鍵盤不消失的狀況下,手動滑出被遮擋的佈局;
    adjustPan-默認模式:軟鍵盤彈出,軟鍵盤會遮擋屏幕下半部分佈局,當輸入框在屏幕下方佈局,軟鍵盤彈起,會自動將當前佈局頂起,保證,軟鍵盤不遮擋當前輸入框(正常佈局,非scrollView父佈局)。當父佈局是scrollView的時候,感受沒啥變化,仍是自定將佈局頂起,輸入框不被遮擋,不能夠手動滑出被遮擋的佈局(白瞎了scrollView);
    複製代碼
    • 看了上面的屬性,那麼該如何設置呢?具體效果能夠看demo案例。
    <activity android:name=".NewArticleActivity"
        android:windowSoftInputMode="adjustResize|stateHidden"/>
    複製代碼
  • 軟鍵盤及時退出的問題
    • 當用戶輸入完成以後,必須手動點擊軟鍵盤的收回鍵,軟鍵盤才收起。若是能經過代碼主動將軟鍵盤收起,這對於用戶體驗來講,是一個極大的提高,思前想後,參考網上的文檔,我的比較喜歡的實現方式是經過事件分發機制來解決這個問題。
  • 解決點擊EditText彈出收起鍵盤時出現的黑屏閃現現象
    View rootView = hte_content.getRootView();
    rootView.setBackgroundColor(Color.WHITE);
    複製代碼

18.先後臺切換編輯富文本優化

  • 因爲富文本中,用戶會輸入不少的內容,當關閉頁面時候,須要提醒用戶是否保存輸入內容。同時,切換到後臺的時候,須要注意保存輸入內容,避免長時間切換後臺進程內存吃緊,在回到前臺輸入的內容沒有呢,查閱了汽車之家,易車等app等手機上的富文本編輯器,都會有這個細節點的優化。

19.生成html片斷上傳服務器

19.1 提交富文本

  • 客戶端生成html片斷到服務器
    • 在客戶端提交帖子,文章。富文本包括圖片,文字內容,還有文字span樣式,同時會選擇一些文章,帖子的標籤。還有設置文章的類型,封面圖,做者等許多屬性。
    • 當點擊提交的時候,客戶端把這些數據,轉化成html,仍是轉化成json對象提交給服務器呢?思考一下,會有哪些問題……
  • 轉化成html
    • 對於將單個富文本轉化成html相對來講是比較容易的,由於富文本中之存在文字,圖片等。轉化成html細心就能夠。
    • 可是對於設置富文本的標籤,類型,做者,封面圖,日期,其餘關聯屬性怎麼合併到html中呢,這個相對麻煩。
  • 最後想說的是
    • 對於富文本寫帖子,文章,若是寫完富文本提交,則可使用轉化成html數據提交給服務器;
    • 對於富文本寫完帖子,文章,還有下一步,設置標籤,類型,封面圖,做者,時間,還有其餘屬性,則可使用轉化成json數據提交給服務器;

19.2 編輯富文本

  • 服務器返回html給客戶端加載
    • 涉及到富文本的加載,後臺管理端編輯器生成的一段html 代碼要渲染到移動端上面,一種方法是前端作成html頁面,放到服務器上,移動端這邊直接webView 加載url便可。
    • 還有一種後臺接口直接返回這段html富文本的,String類型的,移動端直接加載的;具體的需求按實際狀況而定。
  • 加載html文件流暢問題
    • webView直接加載url體驗上沒那麼流暢,相對的加載html文件會好點。可是對比原生,體驗上稍微弱點。
    • 若是不用WebView,使用TextView顯示html富文本,則會出現圖片不顯示,以及格式問題。
    • 若是不用WebView,使用自定義富文本RichText,則須要解析html顯示,若是對html標籤,js不熟悉,也不太好處理。

20.生成json片斷上傳服務器

  • 參考了易車發佈帖子,提交數據到服務器,針對富文本,是把它拼接成對象。將文字,圖片按照富文本的順序拼接成json片斷,而後提交給服務器。

20.1 提交富文本

  • 用原生ScrollView + LineaLayout + n個EditText+Span + n個ImageView來實現富文本。能夠先建立一個對象用來存儲數據,下面這個實體類比較簡單,開發中字段稍微多些。以下所示
    public class HyperEditData implements Serializable {
    
        /** * 富文本輸入文字內容 */
        private String inputStr;
        /** * 富文本輸入圖片地址 */
        private String imagePath;
        /** * 類型:1,表明文字;2,表明圖片 */
        private int type;
    
        //省略不少set,get方法
    }
    複製代碼
  • 而後怎麼去把富文本數據按照有序去放到集合中呢?以下所示,具體能夠看demo中的代碼……
    /** * 對外提供的接口, 生成編輯數據上傳 */
    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;
    }
    複製代碼
  • 最後將富文本數據轉化爲json提交到服務器,服務器拿到json後,結合富文本的後續信息,好比,做者,時間,類型,標籤等建立能夠用瀏覽器打開的h5頁面,這個須要跟服務器端配合。以下所示
    List<HyperEditData> editList = hte_content.buildEditData();
    //生成json
    Gson gson = new Gson();
    String content = gson.toJson(editList);
    //轉化成json字符串
    String string = HyperHtmlUtils.stringToJson(content);
    //提交服務器省略
    複製代碼

20.2 編輯富文本

  • 固然,提交了文章確定還有審覈功能,這個時候想去修改富文本怎麼辦。ok,須要服務器把以前傳遞給它的json返回給客戶端,而後解析填充到富文本中。這個就沒什麼好說的……

21.圖片上傳策略問題思考

  • 大多數開發者會採用的方式:
    • 先在編輯器裏顯示本地圖片,等待用戶編輯完成再上傳所有圖片,而後用上傳返回的url替換以前html中顯示本地圖片的位置。
  • 這樣會遇到不少問題:
    • 若是圖片不少,上傳的數據量會很大,手機的網絡狀態常常不穩定,很容易上傳失敗。另外等待時間會很長,體驗不好。
  • 解決辦法探討:
    • 選圖完成即上傳,獲得url以後直接插入,上傳是耗時操做,再加上圖片壓縮的時間,這樣編輯器顯示圖片會有可觀的延遲時間,實際項目中能夠加一個默認的佔位圖,另外加一個標記提醒用戶是否上傳完成,避免沒有上傳成功用戶即提交的問題。
  • 這種場景很容易想到:
    • 好比,在簡書,掘金上寫博客。寫文章時,插入本地圖片,即便你沒有提交文章,也會把圖片上傳到服務器,而後返回一個圖片連接給你,最後當你發表文章時,圖片只須要用連接替代便可。
  • 參考博客

22.一些細節問題的處理

  • 關於軟鍵盤的彈出和關閉,以及避免點擊EditText彈出收起鍵盤時出現的黑屏閃現現象,還有返回鍵判斷若是有軟鍵盤顯示則須要先關閉軟鍵盤。
  • 在獲取EditText控件內容字符串的時候,或者對字符串裁剪等等,建議最後都進行trim一下,避免字符串末尾處出現空格,增長嚴謹性。
  • 針對封裝庫中的一些工具類,或者不想被繼承的類,建議用finial修飾一下,這邊能夠避免反射修改屬性,或者經過繼承修改屬性,看了Rx,OkHttp等源碼,能夠發現不少類用了finial修飾。
  • 在控件銷燬的時候,建議移除一些監聽事件,同時保存一些比較重要的信息。針對設置span樣式,考慮後期添加更多,所以特別注意後期代碼的拆分和解藕操做。

富文本開源庫:github.com/yangchong21…

你的star是我開源的動力,謝謝!

相關文章
相關標籤/搜索