当前位置:   article > 正文

Android 自定义View 解决 TextView 自动换行排版不整齐_textview 自动换行 整齐排版

textview 自动换行 整齐排版

第一次写东西,内心小紧张,又不知道怎么写,尽量把遇到的问题和解决思路说清楚,写的不好请见谅。

需求

项目有一个需求,很简单,就是一个recyclerview,item里面是两个textview。一个TextView显示的字符串包含圆角、半角和中、英文以及数字。

想起来简单,但是一显示就出问题了。右侧的TextView因为自动换行的问题显示错乱,真不行。至于原因,网上有很多介绍,下面就讲一下解决过程。

解决过程

1.有问题找度娘

和大多数人一样,发现这个问题,立马找百度看看有可以直接用的,大致分为两种:

1)手动拆分字符串:不管是自定义view还是在view预加载时,手动对要显示的字符串进行拆分,添加换行符“\n”后再显示。

2)自定义view,挨个画每个字符,如果排不下就换行

 

以这两种方式,都找到了可以直接用代码。我还是比较谨慎,先写了demo看了,效果还行,才往项目里写,心想这下总解决了哇,然而高兴得太早了。demo只是给了个TextView,可是项目是RecyclerView,一运行,程序能跑起来的就不错了,还有的直接OOM,于是心灰意冷。

2.求人不如求己

既然百度不到,就只有自己动手了。解决思路采用的是自定义view,挨个画字符。原理很简单,直接上代码:

  1. public class MyTextView extends View {
  2. //内容填充画笔
  3. private Paint contentPaint;
  4. //标准的字体颜色
  5. private String contentNormalColor = "#737373";
  6. //有焦点的字体颜色
  7. private String contentFocuedColor = "#333333";
  8. //控件宽度
  9. private int viewWidth = 0;
  10. //控件高度
  11. private int viewHeight = 0;
  12. //标准的字的样式
  13. public static final int TEXT_TYPE_NORMAL = 1;
  14. //控件获取焦点的时候进行的处理
  15. public static final int TEXT_TYPE_FOCUED = 2;
  16. //默认是标准的样式
  17. private int currentTextType = TEXT_TYPE_NORMAL;
  18. //默认当前的颜色
  19. private String textColor = "#333333";
  20. //字体大小
  21. private int textSize = 40;
  22. //内容
  23. private String mText = "测试的文字信息";
  24. //最小view高度
  25. private float minHeight = 0;
  26. //最小view宽度
  27. private float minWidth = 0;
  28. //行间距
  29. private float lineSpace;
  30. //最大行数
  31. private int maxLines = 0;
  32. //文字测量工具
  33. private Paint.FontMetricsInt textFm;
  34. ///真实的行数
  35. private int realLines;
  36. //当前显示的行数
  37. private int line;
  38. //在末尾是否显示省略号
  39. private boolean showEllipsise;
  40. //文字显示区的宽度
  41. private int textWidth;
  42. //单行文字的高度
  43. private int signleLineHeight;
  44. private int mPaddingLeft,mPaddingRight,mPaddingTop,mPaddingBottom;
  45. /**
  46. * 省略号
  47. **/
  48. private String ellipsis = "...";
  49. public MyTextView(Context context) {
  50. this(context,null);
  51. }
  52. public MyTextView(Context context, AttributeSet attrs) {
  53. this(context, attrs,0);
  54. }
  55. public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
  56. super(context, attrs, defStyleAttr);
  57. initAttr(context,attrs);
  58. init();
  59. }
  60. private boolean isFristload;
  61. /**
  62. * 初始化
  63. */
  64. private void init() {
  65. contentPaint = new Paint();
  66. contentPaint.setTextSize(textSize);
  67. contentPaint.setAntiAlias(true);
  68. contentPaint.setStrokeWidth(2);
  69. contentPaint.setColor(Color.parseColor(textColor));
  70. contentPaint.setTextAlign(Paint.Align.LEFT);
  71. textFm = contentPaint.getFontMetricsInt();
  72. signleLineHeight=Math.abs(textFm.top-textFm.bottom);
  73. }
  74. /**
  75. * 初始化属性
  76. * @param context
  77. * @param attrs
  78. */
  79. private void initAttr(Context context,AttributeSet attrs) {
  80. TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
  81. mPaddingLeft = typedArray.getDimensionPixelSize(R.styleable.MyTextView_paddingLeft, 0);
  82. mPaddingRight = typedArray.getDimensionPixelSize(R.styleable.MyTextView_paddingRight, 0);
  83. mPaddingTop = typedArray.getDimensionPixelSize(R.styleable.MyTextView_paddingTop, 0);
  84. mPaddingBottom = typedArray.getDimensionPixelSize(R.styleable.MyTextView_paddingBottom, 0);
  85. mText = typedArray.getString(R.styleable.MyTextView_text);
  86. textColor = typedArray.getString(R.styleable.MyTextView_textColor);
  87. if(textColor==null){
  88. textColor="#000000";
  89. }
  90. textSize = typedArray.getDimensionPixelSize(R.styleable.MyTextView_textSize, 50);
  91. lineSpace = typedArray.getInteger(R.styleable.MyTextView_lineSpacing, 0);
  92. typedArray.recycle();
  93. }
  94. public void setText(String ss){
  95. this.mText=ss;
  96. invalidate();
  97. }
  98. @Override
  99. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  100. super.onMeasure(widthMeasureSpec,heightMeasureSpec);
  101. viewWidth=getMeasuredWidth();
  102. textWidth=viewWidth-mPaddingLeft-mPaddingRight;
  103. viewHeight= (int) getViewHeight();
  104. setMeasuredDimension(viewWidth, viewHeight);
  105. }
  106. private float getViewHeight() {
  107. char[] textChars=mText.toCharArray();
  108. float ww=0.0f;
  109. int count=0;
  110. StringBuilder sb=new StringBuilder();
  111. for(int i=0;i<textChars.length;i++){
  112. float v = contentPaint.measureText(textChars[i] + "");
  113. if(ww+v<=textWidth){
  114. sb.append(textChars[i]);
  115. ww+=v;
  116. }
  117. else{
  118. count++;
  119. sb=new StringBuilder();
  120. ww=0.0f;
  121. ww+=v;
  122. sb.append(textChars[i]);
  123. }
  124. }
  125. if(sb.toString().length()!=0){
  126. count++;
  127. }
  128. return count*signleLineHeight+lineSpace*(count-1)+mPaddingBottom+mPaddingTop;
  129. }
  130. @Override
  131. protected void onDraw(Canvas canvas) {
  132. drawText(canvas);
  133. }
  134. /**
  135. * 循环遍历画文字
  136. * @param canvas
  137. */
  138. private void drawText(Canvas canvas) {
  139. char[] textChars=mText.toCharArray();
  140. float ww=0.0f;
  141. float startL=0.0f;
  142. float startT=0.0f;
  143. startL+=mPaddingLeft;
  144. startT+=mPaddingTop+signleLineHeight/2+ (textFm.bottom-textFm.top)/2 - textFm.bottom;
  145. StringBuilder sb=new StringBuilder();
  146. for(int i=0;i<textChars.length;i++){
  147. float v = contentPaint.measureText(textChars[i] + "");
  148. if(ww+v<=textWidth){
  149. sb.append(textChars[i]);
  150. ww+=v;
  151. }
  152. else{
  153. canvas.drawText(sb.toString(),startL,startT,contentPaint);
  154. startT+=signleLineHeight+lineSpace;
  155. sb=new StringBuilder();
  156. ww=0.0f;
  157. ww+=v;
  158. sb.append(textChars[i]);
  159. }
  160. }
  161. if(sb.toString().length()>0){
  162. canvas.drawText(sb.toString(),startL,startT,contentPaint);
  163. }
  164. }
  165. }

自定义属性:

  1. <declare-styleable name="MyTextView">
  2. <attr name="paddingLeft" format="dimension" />
  3. <attr name="paddingTop" format="dimension" />
  4. <attr name="paddingRight" format="dimension" />
  5. <attr name="paddingBottom" format="dimension" />
  6. <attr name="textSize" format="dimension" />
  7. <attr name="textColor" format="string" />
  8. <attr name="text" format="string"/>
  9. <attr name="lineSpacing" format="integer" />
  10. <attr name="maxLine" format="integer"/>
  11. <attr name="ellipsis" format="boolean" />
  12. </declare-styleable>

有的属性的逻辑没添加进去,或者没用上,后面再完善。下面上一个效果图:

 

有个坑

view在测量尺寸上可能还是有点问题,用在recyclerview里面,item的布局用的包裹,因为item 的复用关系,该view的尺寸是对的,但是item的尺寸却错乱了,具体原因再找一下。屏蔽方法是:继承recyclerview,重写onMeasure():

  1. @Override
  2. protected void onMeasure(int widthSpec, int heightSpec) {
  3. int customSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
  4. super.onMeasure(widthSpec, customSpec);
  5. }

布局上在recyclerview外套一层scrollview,就正常了。后头有空再找找原因。

8.15补充:

上面这个问题,主要是recyclerview复用后,该自定义view的尺寸没有重新绘制,调用一下requestLayout(),重绘一下就对了。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

其他的:

这个问题,好像有个比较好的解决方式,对textview.getText().toString()的字符串,重新测量字符是否够一排,如果够的话就绘制一行,不够的话换行。。
 

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号