android中根據控件寬度,實現展示文本內容,解決中英文自動換行

近來天氣漸冷啊!晚上加班加很晚回來的路上有點扛不住啊!好在桂花開了。還挺香的!

  • 需求原型
    最近有個需求看圖:
    這裏寫圖片描述
    要求還行,不怎麼奇怪。其中如果顯示一行的話我們完全可以用view 自帶的ellipsize屬性配合singleLine來實現。比如:
 android:ellipsize="start"
 android:singleLine="true"
 android:ellipsize="middle"
 android:singleLine="true"
 android:ellipsize="end"
 android:singleLine="true"
 android:ellipsize="marquee"
 android:singleLine="true"

可以看到elilipsize一共有4個可選值。看名稱其實很容易理解,開始,中間,結尾和無限。實現的效果分別是如果文本內容超過textview 的寬度,那麼分別顯示…在開頭中間,結尾和循環。注意一定要配合singleLine=true來使用,不然會失效。當然marquee這個屬性再配合一些其他屬性我們就可以實現跑馬燈效果了。如下:

 android:marqueeRepeatLimit="marquee_forever"
 android:focusableInTouchMode="true"
 android:ellipsize="marquee"
 android:singleLine="true"

好了,那麼現在我們要求是2行,那麼singleline這個屬性不能用。那麼我們上面的屬性當然也不能實現了。我們該怎麼辦呢。想了很多辦法,其實挺難的。最終我的理解爲:第一行顯示最前面,中間…第二行顯示最後的內容就行了。所以網上查找了一番,發現控件上能顯示多少文字還是比較簡單的。具體代碼是如下:

TextView textView = (TextView) findViewById(R.id.test);
textView.setText(text);

int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
textView.measure(spec, spec);

// getMeasuredWidth 獲得控件寬度
int measuredWidth = textView.getMeasuredWidth();
// textView getPaint measureText 獲得控件的TextView的對象
TextPaint textPaint = textView.getPaint();
// 獲得輸入的text 的寬度
float textPaintWidth = textPaint.measureText(text);

根據網上這些基礎的提示我的腦子就有了想法。既然控件寬度能獲取到,對應的文本內容的寬度也能拿到,那麼一行能顯示多少文本就很容易得到了。

//獲取我們需要展示的文本內容
String content=textView.getText().toString();
// getMeasuredWidth 獲得控件寬度
int measuredWidth = textView.getMeasuredWidth();

// textView getPaint measureText 獲得控件的TextView的對象
TextPaint textPaint = textView.getPaint();
// 獲得輸入的text 的寬度
float textPaintWidth = textPaint.measureText(content);
//先判斷文本是否超過2行
if(textPaintWidth<measuredWidth*2 ){
    return content;//能顯示完全我們直接返回就行了。無需操作

}

    //當前的textview 的textSize爲15sp 其實很明顯文字大小不同,每個字符佔用的長度也是不同的,這裏假設爲15。我通過日誌知道:".",0,"a","A","好",“ ” 等。這些分別佔用的數值爲:8,10,16,17,30,30。所以說其實挺麻煩的,因爲區別很大。這裏明顯中文的顯示是最大的爲30。所以我們長度給一個最低範圍-30。
    // 首先計算一共能顯示多少個字符:
        num = (measuredWidth -30) ;
//統計文本總長度
        int sumLenth=0;
        //定義第一行應該顯示的sb
        StringBuilder sbfor = new StringBuilder();
        //通過循環從content中找出第一行能顯示的所有文本
        for(int i=0;i<content.length();i++){
            if(sumLenth<num-16){//繼續縮小化一點
            //獲取每個位置的文本
                String str=content.substring(i,i+1);
                //計算長度並且統計
    sumLenth=sumLenth+(int)textPaint.measureText(str);                          
                if(sumLenth<num-16){
                //當第一行還能顯示我們就拼接
                    sbfor.append(str);
                }
            }else{
            //超出長度我們就退出循環並且拿到內容:sbfor
                i=content.length();
            }
        }
        //接下來計算第二行 的文本內容。也就是文本最後的內容。很好理解這次我們從後往前遍歷截取。
        num=measuredWidth-8;
        sumLenth=0;
        String sb="";
        for(int i=content.length()-1;i>0;i--){
            if(sumLenth<num){
                String str="";
                if(i==content.length()-1){//拿最後一位
                    str=content.substring(i);
                }else{
                    str=content.substring(i,i+1);
                }
    sumLenth=sumLenth+(int) textPaint.measureText(str);
                if(sumLenth<num){
                    sb=str+sb;
                }
            }else{
            //退出循環
                i=0;
            }
        }
//好了這裏我們既獲得了第一行文本以及第二行文本,那我們就可以拼接得到最終內容了.
 return sbfor.toString()+" "+"..."+sb;

最終顯示效果如下:
這裏寫圖片描述
這裏寫圖片描述

大家可以看到上圖中已經實現了我們想要實現的效果。其實不然,我當時做完上面操作之後發現還有問題,可以看到上圖被我圈紅色的地方,很多做過小說app的朋友肯定知道,漢字和字符的編碼方式不同,導致如果像上圖中“天翼-V2.1.0-1310-9-7(2016092110934).apk”其中前面天翼是漢字用的utf-8編碼,而後面的V以及一串數字編碼採用的是unicode編碼,所以,如果後面的一串數字如果一行不能完全顯示,並且前面存在漢字,那麼它會在漢字後面直接換行,選擇和後面的靠在一起。不信大家可以試試。也就是會這麼顯示: 天翼-
V2.1.0-1310-9-7(2016092110934).apk這種方式,這明顯不是我們想要的效果。所以得解決這個問題。網上搜了很多,有說都轉成統一編碼方式,或者選擇自定義控件。自定義控件其實實現的效果是把“天翼-”這個兩個字的間距拉寬,充滿整行,讓你看的不是那麼生硬。其實這種並不能完全達到效果。後來我自己想了想,解決這個辦法最好的方式就是在一行的最後加上一個空格,就能解決上面的問題。所以其實上面那個效果能實現,是因爲我在
“天翼-V2.1.0-1310-9-7(201609211 “ 加了空格”0934).apk”
所以代碼如下:

//計算一行顯示數量
float num = (measuredWidth -30);
        if(textPaintWidth <(2*measuredWidth -30)){
        //對於大於一行,小於2行的文本
            int sumLenth=0;
            StringBuilder sbfor = new StringBuilder();
            //依然遍歷
            for(int i=0;i<content.length();i++){
                if(sumLenth<num-16){
                    String str=content.substring(i,i+1);
        sumLenth=sumLenth+(int) textPaint.measureText(str);
                    if(sumLenth<num-16){
                        sbfor.append(str);
                    }
                }else{
                //找到一行的位置,並且加上空格,並且再加上後面的文本
        sbfor.append(" ").append(content.substring(i));
                    return sbfor.toString();

                }
            }
        }

好了到達這裏我們就解決問題了。其實這個問題目前很少有人去解決,最然我這個暫時解決了,但是效果不能說百分百。大家可以看一下小米自帶的本地文件應用,也沒有對這個進行解決。如圖:
這裏寫圖片描述
很明顯大家看到。黑筆圈的地方。自動換行了。並且小米系統並沒有做處理。所以說目前這個技術實現的人還是比較少的所以拿出來分享一下。

補充說明一下。如何正確獲取控件的寬度:

TextPaint paint = mTvProgramName.getPaint();
paint.setTextSize(mTvProgramName.getTextSize());
// paint.measureText(mTvProgramName.getText().toString()); //這個方法能把文本所佔寬度衡量出來.
Log.i(TAG, "getFocus paint.measureText(mTvProgramName.getText().toString())="
           + paint.measureText(mTvProgramName.getText().toString()));
mTvProgramName.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                @Override
    public void onLayoutChange(final View v, final int left, final int top, final int right, final int bottom, final int oldLeft, final int oldTop, final int oldRight,
                        final int oldBottom) {
                        //在此,得到TextView控件的寬度
   Log.i(TAG, "onLayoutChange  mTvProgramName.getWidth()=" + v.getWidth());

                }
            });

我是這麼獲取的。

//獲取屏幕寬度,高度dpi,分辨率
Map<String,Float> map=Constants.getScreenWidth(activity);
//由於明確知道textview 之外的寬度爲110dp,那麼通過dip和dp的轉換可獲得textview的實際dip
//屏幕寬度減去佔用的dpi
measuredWidth =map.get("width")-map.get("density")*110;
發佈了34 篇原創文章 · 獲贊 88 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章