近來天氣漸冷啊!晚上加班加很晚回來的路上有點扛不住啊!好在桂花開了。還挺香的!
- 需求原型
最近有個需求看圖:
要求還行,不怎麼奇怪。其中如果顯示一行的話我們完全可以用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;