您好,登錄后才能下訂單哦!
這篇文章主要介紹“Android中怎么仿instagram實現文字自動排版功能”,在日常操作中,相信很多人在Android中怎么仿instagram實現文字自動排版功能問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Android中怎么仿instagram實現文字自動排版功能”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
玩過ins的朋友應該知道ins里面有一個編輯文字自動排版的功能,應用會根據用戶輸入的每行文字自動進行排版,以達到一個緊湊美觀的效果。
效果圖如下:
因為網上找不到什么相關的資料,所以就直接通過玩ins猜測大概的實現思路,我整理下自己一開始的一些疑問。
當輸入的文字越來越多,字體越來越小時怎么保證每行最多能夠顯示的文本不變?
正常情況下,當你字體越來越小而輸入框寬度不變時,那么你每行可輸入的文字就會變多,但是你發現ins無論字體多大,每行最多能容納的文本是不變。我猜測可能是輸入框會隨著字體的變化而改變。
通過打開開發者選項的應用布局邊界,可以看到確實ins的輸入框的寬度是動態變化的。
下面是打開應用布局邊界后的效果圖:
當然,這里可能會引入一個新的問題,那就是輸入框的寬度是怎么動態改變的?
好讓它剛好能夠在字體大小變化的過程中最多可容納的文本數不變。
這個問題會在下面說,這里先不展開。
每行字體大小是怎么確定的,又是怎樣聯動變化的?
這個問題一開始想了很久,我覺得如果把這個問題搞明白基本就已經成功一半了。大部分人一開始可能都會很容易陷入局部思維,包括我也一樣,一直在糾結每行字體是怎么變化的,但其實應該要從整體考慮,從整體考慮一切都會變得很簡單,代碼實現上也會變得更加容易,不需要處理各種特殊情況。
具體思路:
遍歷每行文本,以適應最大文本寬度算出每行的字體大小,然后以每行的字體大小算出每行行高度,把每行行高度累加得到文本總高度,然后判斷文本總高度是否大于最大文本高度,如果大于則按比例縮小每行的字體大小,以縮小每行的行高度,得到新的文本總高度,直到文本總高度小于最大文本高度。
上面的這么大段文字總結起來其實就4個步驟:
拆行
按匹配最大寬度計算每行字體大小
按匹配最大高度計算每行字體大小
重新調整EditText寬度
在動手之前我們需要知道幾個相關知識點:
span
span可以使TextView分段顯示不同樣式的文字。在自動排版中因為每行文字字體大小不一樣,所以我們需要為每行文字設置不同的span。
Layout
Layout是一個用于各種文本計算的輔助類,TextView的文字排版布局都是依賴于Layout實現的。因為Layout是完全跟TextView解耦的,所以我們可以構建合適的Layout來幫助我們計算字體大小。
下面是Layout的官方定義:
A base class that manages text layout in visual elements on the screen.
For text that will be edited, use a DynamicLayout, which will be updated as the text changes. For text that will not change, use a StaticLayout.
Layout有幾個子類,其中較常用的是DynamicLayout和StaticLayout,按照官方的說法,當你的文本是可編輯的則使用的是DynamicLayout,當你的文本不可編輯那么就使用StaticLayout。
所以說EditText的文本計算工作應該都是交給了DynamicLayout實現。
首先我們需要監聽文字的輸入變化,當文本變化時去計算每行的字體大小,最終渲染到屏幕。監聽文本變化的代碼如下:
addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { refresh(); } @Override public void afterTextChanged(Editable s) { } });
一.拆行
監聽到文本變化后需要對文本進行拆行,得到每行的文字。我們可以通過Layout實現,代碼如下:
String text = layout.getText().toString(); int lineCount = layout.getLineCount(); for (int i = 0; i < lineCount; i++) { int start = layout.getLineStart(i); int end = layout.getLineEnd(i); String rowStr = text.substring(start,end); }
但是,但是,但是,這里有一個點需要特別注意,不能通過EditText自帶的Layout來計算每行文本,不然拿到的每行文本是錯誤的。
為什么呢?
舉個例子:
如下圖所示,當你在第二行將要輸入“好”時,因為你輸入"好"后該行文本寬度已經大于此時EditText的寬度了,所以“好”字會被認為是重啟一行,這樣你得到的每行文本就是錯的了,因為“好”應該顯示在第二行才對。
這就涉及到我在思路探究中提到的第一個問題,無論我字體怎么縮小放大,如何保證每行最多可顯示的文本都是一樣的?
那么如何保證呢?
其實很簡單,因為影響到文字自動換行的因素主要就是字體大小和最大文本寬度,那么只要保證這兩個因素不變,無論你輸入什么文本,都能準確一致的拆分出每一行的文字。
因為EditText的每行字體在變,而且寬度也在變,所以通過EditText自帶的Layout算出的每行文本肯定是錯誤的。
所以,思路應該是這樣的,你需要構建一個用于計算的Layout,這個Layout的字體大小和寬度必須是固定不變的,這樣它就能夠保證每行最多可容納的文本始終是一樣的,這樣我就能夠準確拆分出每行文本。
前面已經說過EditText的計算工作都是交給DynamicLayout,所以我們需要創建的是DynamicLayout。代碼如下
protected Layout buildCalculateLayout(CharSequence text,TextView host){ TextPaint paint = new TextPaint(host.getPaint()); paint.setTextSize(mDefFontSize); return new DynamicLayout(text,paint, mDefMaxTextWidth,host.getLayout().getAlignment(),host.getLayout().getSpacingMultiplier(),host.getLayout().getSpacingAdd(),host.getIncludeFontPadding()); }
需要注意的是,這里除了字體大小和寬度,其他的參數都需要跟EditText的參數一樣。
其中mDefFontSize是一開始定義的一個默認字體大小,mDefMaxTextWidth是EditText在沒動態調整寬度前的寬度(需要減去padding)。
這樣子每次都是通過自構建的Layout去計算每行的文本,就不需要考慮EditText的字體和寬度的動態變化。
二.按匹配最大寬度計算每行字體大小
搞定了第一步拆行后,其實已經離成功不遠了,接下來就是如何確定每行字體大小了。
確定字體大小說簡單簡單,說難也難,關鍵是看你有沒有想到那個點。比如一開始我一直糾結于每行文字是怎么隨著輸入文字個數和行數變化動態改變的,陷入了局部細節,搞得自己暈頭轉向,如果按照這個方向思考我感覺估計是怎么做都搞不定的。
后來,想了兩天后還是沒搞明白,我就試著換個思維方式,從整體來考慮,接下來就有種恍然大悟的感覺,原來其實沒那么難。
首先,有一個規律是很顯然的:
每行文字越多,它的字體就越小,文字越少,字體就越大。
那么我就想一開始時你把每行文字的寬度放大到最大文本寬度,算出匹配這個寬度的字體應該多大,這樣文字越少的行,字體就越大,文字越多的行,字體就越小,這個不就是符合那個規律嗎。
計算文本寬度的代碼如下:
float width = paint.measureText(text);
因為需要通過不斷更改字體大小,去算出匹配最大寬度的字體,所以為了減少計算量,一開始可以做一個初始字體大小的換算。
當字體大小是mDefFontSize時對應的文本寬度是mDefMaxTextWidth,那么當文本寬度是x時,對應的字體大小是y,因為字體大小和寬度成反比(寬度越小,字體越大),所以y的計算公式就是:
y = mDefMaxTextWidth \ x * mDefFontSizex = paint.measureText(text);
這樣我們就可以得到一個比較接近目標值的字體大小,這時候再去判斷此時文本寬度是否匹配最大文本寬度,不等于的話再去改變字體大小,直到文本寬度匹配最大文本寬度為止。
代碼如下:
public float calculateMatchWidthSize(Paint paint,String text,int maxWidth){ float textSize = paint.getTextSize(); float width = paint.measureText(text); if(maxWidth >= width && maxWidth - width <= text.length()){ return textSize; } if(width > maxWidth){ textSize = getNarrowFitTextSize(paint,text,maxWidth,1); }else{ textSize = getZoomFitTextSize(paint,text,maxWidth,1); } return textSize; } private float getNarrowFitTextSize(Paint paint,String text,int maxWidth,float rate){ float textSize = paint.getTextSize(); textSize -= 1 * rate; paint.setTextSize(textSize); float width = paint.measureText(text); if(maxWidth >= width && maxWidth - width <= text.length()){ return textSize; } //結束條件 if(width < maxWidth){ return getZoomFitTextSize(paint,text,maxWidth,rate); }else{ return getNarrowFitTextSize(paint,text,maxWidth,rate); } } private float getZoomFitTextSize(Paint paint,String text,int maxWidth,float rate){ float textSize = paint.getTextSize(); textSize += 1 * rate; paint.setTextSize(textSize); float width = paint.measureText(text); if(maxWidth >= width && maxWidth - width <= text.length()){ return textSize; } //結束條件 if(width < maxWidth){ return getZoomFitTextSize(paint,text,maxWidth,rate); }else{ return getNarrowFitTextSize(paint,text,maxWidth,rate); } }
三.按匹配最大高度計算每行字體大小
按照匹配最大寬度計算出來的字體會很大,導致文本高度很高,這時候就需要再動態調整每行字體大小,直到文本高度匹配最大高度為止。
動態調整字體大小時,每行文字的字體大小需要按比例調整,比如每行字體都調整為原來的0.9倍大小。
計算每行文本高度的代碼:
int height = paint.getFontMetricsInt(null);
為了讓每行文本高度的累加值等于文本實際總高度,需要設置EditText的邊距為0并且去掉文字上下的空白部分。代碼如下:
//去掉文本上下空白區域 mHost.setIncludeFontPadding(false); //不設置行間距 mHost.setLineSpacing(0,1);
為了提高計算速度,采用二分法來動態調整字體大小,代碼如下:
/** * 二分法查找合適的字體大小,字體大小按比例調整 * @return */ private void calculateMatchHeightSizeByRate(float lowRate,float highRate,int minHeight,int maxHeight){ if(highRate - lowRate <= RATE_SCALE_ERROR_VALUE){ return; } float middleRate= (lowRate+highRate)/2; scaleFontSizeByRate(middleRate); int height = getTextHeight(); if(height > maxHeight){ //縮小字體后文字高度大于最大值,需要繼續縮小字體 highRate = middleRate; calculateMatchHeightSizeByRate(lowRate,highRate,minHeight,maxHeight); } else if(height < minHeight){ //縮小字體后文字高度小于最小值,需要放大字體 lowRate = middleRate; calculateMatchHeightSizeByRate(lowRate,highRate,minHeight,maxHeight); } } private int getTextHeight(){ int totalHeight = 0; for(CustomSpanData customSpanData: mCustomTextSpanDataList){ int lineHeight = getSingleLineHeight(customSpanData.getTextSize()); totalHeight += lineHeight; } return totalHeight; } private int getSingleLineHeight(float fontSize){ Paint paint = new Paint(mHost.getPaint()); paint.setTextSize(fontSize); return paint.getFontMetricsInt(null); } private void scaleFontSizeByRate(float rate){ for(int i=0;i<mOriFontSizePxList.size();i++){ float fontSize = mOriFontSizePxList.get(i) * rate; mCustomTextSpanDataList.get(i).setTextSize(UNIT_PX,fontSize); } }
四.重新調整EditText的寬度
前面有提到一個問題那就是輸入框的寬度是怎么動態改變的?
閱讀了前三個步驟后是不是已經有了答案,首先通過自構建的Layout確定每行需要顯示什么文本,然后動態調整每行字體大小以適應輸入框的寬高,這時候可能每行的字體已經很小了,如果不調整EditText的寬度必然會導致不同行的文字頂到同一行顯示。
到此,關于“Android中怎么仿instagram實現文字自動排版功能”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。