Android 自定義View 解決 TextView 自動換行排版不整齊
第一次寫東西,內心小緊張,又不知道怎麼寫,儘量把遇到的問題和解決思路說清楚,寫的不好請見諒。
需求
專案有一個需求,很簡單,就是一個recyclerview,item裡面是兩個textview。一個TextView顯示的字串包含圓角、半形和中、英文以及數字。
想起來簡單,但是一顯示就出問題了。右側的TextView因為自動換行的問題顯示錯亂,真不行。至於原因,網上有很多介紹,下面就講一下解決過程。
解決過程
1.有問題找度娘
和大多數人一樣,發現這個問題,立馬找百度看看有可以直接用的,大致分為兩種:
1)手動拆分字串:不管是自定義view還是在view預載入時,手動對要顯示的字串進行拆分,新增換行符“\n”後再顯示。
2)自定義view,挨個畫每個字元,如果排不下就換行
以這兩種方式,都找到了可以直接用程式碼。我還是比較謹慎,先寫了demo看了,效果還行,才往專案裡寫,心想這下總解決了哇,然而高興得太早了。demo只是給了個TextView,可是專案是RecyclerView,一執行,程式能跑起來的就不錯了,還有的直接OOM,於是心灰意冷。
2.求人不如求己
既然百度不到,就只有自己動手了。解決思路採用的是自定義view,挨個畫字元。原理很簡單,直接上程式碼:
public class MyTextView extends View { //內容填充畫筆 private Paint contentPaint; //標準的字型顏色 private String contentNormalColor = "#737373"; //有焦點的字型顏色 private String contentFocuedColor = "#333333"; //控制元件寬度 private int viewWidth = 0; //控制元件高度 private int viewHeight = 0; //標準的字的樣式 public static final int TEXT_TYPE_NORMAL = 1; //控制元件獲取焦點的時候進行的處理 public static final int TEXT_TYPE_FOCUED = 2; //預設是標準的樣式 private int currentTextType = TEXT_TYPE_NORMAL; //預設當前的顏色 private String textColor = "#333333"; //字型大小 private int textSize = 40; //內容 private String mText = "測試的文字資訊"; //最小view高度 private float minHeight = 0; //最小view寬度 private float minWidth = 0; //行間距 private float lineSpace; //最大行數 private int maxLines = 0; //文字測量工具 private Paint.FontMetricsInt textFm; ///真實的行數 private int realLines; //當前顯示的行數 private int line; //在末尾是否顯示省略號 private boolean showEllipsise; //文字顯示區的寬度 private int textWidth; //單行文字的高度 private int signleLineHeight; private int mPaddingLeft,mPaddingRight,mPaddingTop,mPaddingBottom; /** * 省略號 **/ private String ellipsis = "..."; public MyTextView(Context context) { this(context,null); } public MyTextView(Context context, AttributeSet attrs) { this(context, attrs,0); } public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initAttr(context,attrs); init(); } private boolean isFristload; /** * 初始化 */ private void init() { contentPaint = new Paint(); contentPaint.setTextSize(textSize); contentPaint.setAntiAlias(true); contentPaint.setStrokeWidth(2); contentPaint.setColor(Color.parseColor(textColor)); contentPaint.setTextAlign(Paint.Align.LEFT); textFm = contentPaint.getFontMetricsInt(); signleLineHeight=Math.abs(textFm.top-textFm.bottom); } /** * 初始化屬性 * @param context * @param attrs */ private void initAttr(Context context,AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTextView); mPaddingLeft = typedArray.getDimensionPixelSize(R.styleable.MyTextView_paddingLeft, 0); mPaddingRight = typedArray.getDimensionPixelSize(R.styleable.MyTextView_paddingRight, 0); mPaddingTop = typedArray.getDimensionPixelSize(R.styleable.MyTextView_paddingTop, 0); mPaddingBottom = typedArray.getDimensionPixelSize(R.styleable.MyTextView_paddingBottom, 0); mText = typedArray.getString(R.styleable.MyTextView_text); textColor = typedArray.getString(R.styleable.MyTextView_textColor); if(textColor==null){ textColor="#000000"; } textSize = typedArray.getDimensionPixelSize(R.styleable.MyTextView_textSize, 50); lineSpace = typedArray.getInteger(R.styleable.MyTextView_lineSpacing, 0); typedArray.recycle(); } public void setText(String ss){ this.mText=ss; invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec,heightMeasureSpec); viewWidth=getMeasuredWidth(); textWidth=viewWidth-mPaddingLeft-mPaddingRight; viewHeight= (int) getViewHeight(); setMeasuredDimension(viewWidth, viewHeight); } private float getViewHeight() { char[] textChars=mText.toCharArray(); float ww=0.0f; int count=0; StringBuilder sb=new StringBuilder(); for(int i=0;i<textChars.length;i++){ float v = contentPaint.measureText(textChars[i] + ""); if(ww+v<=textWidth){ sb.append(textChars[i]); ww+=v; } else{ count++; sb=new StringBuilder(); ww=0.0f; ww+=v; sb.append(textChars[i]); } } if(sb.toString().length()!=0){ count++; } return count*signleLineHeight+lineSpace*(count-1)+mPaddingBottom+mPaddingTop; } @Override protected void onDraw(Canvas canvas) { drawText(canvas); } /** * 迴圈遍歷畫文字 * @param canvas */ private void drawText(Canvas canvas) { char[] textChars=mText.toCharArray(); float ww=0.0f; float startL=0.0f; float startT=0.0f; startL+=mPaddingLeft; startT+=mPaddingTop+signleLineHeight/2+ (textFm.bottom-textFm.top)/2 - textFm.bottom; StringBuilder sb=new StringBuilder(); for(int i=0;i<textChars.length;i++){ float v = contentPaint.measureText(textChars[i] + ""); if(ww+v<=textWidth){ sb.append(textChars[i]); ww+=v; } else{ canvas.drawText(sb.toString(),startL,startT,contentPaint); startT+=signleLineHeight+lineSpace; sb=new StringBuilder(); ww=0.0f; ww+=v; sb.append(textChars[i]); } } if(sb.toString().length()>0){ canvas.drawText(sb.toString(),startL,startT,contentPaint); } } }
自定義屬性:
<declare-styleable name="MyTextView"> <attr name="paddingLeft" format="dimension" /> <attr name="paddingTop" format="dimension" /> <attr name="paddingRight" format="dimension" /> <attr name="paddingBottom" format="dimension" /> <attr name="textSize" format="dimension" /> <attr name="textColor" format="string" /> <attr name="text" format="string"/> <attr name="lineSpacing" format="integer" /> <attr name="maxLine" format="integer"/> <attr name="ellipsis" format="boolean" /> </declare-styleable>
有的屬性的邏輯沒新增進去,或者沒用上,後面再完善。下面上一個效果圖:
有個坑
view在測量尺寸上可能還是有點問題,用在recyclerview裡面,item的佈局用的包裹,因為item 的複用關係,該view的尺寸是對的,但是item的尺寸卻錯亂了,具體原因再找一下。遮蔽方法是:繼承recyclerview,重寫onMeasure():
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
int customSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthSpec, customSpec);
}
佈局上在recyclerview外套一層scrollview,就正常了。後頭有空再找找原因。
8.15補充:
上面這個問題,主要是recyclerview複用後,該自定義view的尺寸沒有重新繪製,呼叫一下requestLayout(),重繪一下就對了。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
其他的:
這個問題,好像有個比較好的解決方式,對textview.getText().toString()的字串,重新測量字元是否夠一排,如果夠的話就繪製一行,不夠的話換行。。