背景:在android开发中,经常会遇到icon 和 textview 在同一行。这种情况下产品和UI设计师会告诉你,要求icon图标和textview垂直方向上中心线对齐,即在同一行中 icon距离上下的距离是相等的,textview亦然。或者还有更常见的情况,当我们android程序猿根据UI设计师的设计图,一通疯狂操作之后,自信满满的给到UI设计师的时候,当我们正在洋洋得意的认为小case的时候,UI会找到你,说控件之间的上下间距不对,和UI设计稿上的距离对不上。What! 仔细看看代码,对的呀,设计稿上是10dp,代码里面写的也是10dp,为什么不对呢。这就是这篇文章要解决的问题了。
根据上面的背景描述,我相信每一个android程序猿都遇到过这个问题,因为这是原生的TextView与生俱来的小问题,UI设计师给到的设计稿上的控件之间的间距,都是贴着文本内容测量出来的距离,但是android TextView 会在文本的上下分别加上一点空白区域。这就会导致我们的实际效果和UI设计稿有了出入。要解决这个问题,怎么办呢!下面根据我自己的经验和网上查找的资料,列出大概的几种方案。如果有不错误的地方请大家斧正。
解决方案:
1、这个问题不解决,每次有问题,通过和UI设计师不断的上下微调(不推荐)
这个方案比较费时,而且不一定在每个机型上都能调到正合适。
2、android:includeFontPadding=“false”
这个是网上看到的最多的,百度出来的基本都是说设置这个属性,这个属性确实能减少一点上下的留白,但是做不到完全去除内容以外的上下留白。
3、自定义View,在onDraw()方法中使用drawText()重新绘制,使其不留上下空白
这个方案看起来可以解决文字上下留白的问题,但是当你使用之后会发现,这种办法自定义的View无法使用TextView的属性和方法,让你没有办法像使用原生TextView一样使用。功能比较单一,只是纯粹的只能基本的文字处理,如果想做一些其他的处理,需要挨个在onDraw()里面一一实现,事倍功半,而且比较容易出错。
4、我的方案:能不能不破坏TextView的原生用法,在原有TextView的基础上消除掉上下留白,让我可以像使用原生TextView一样使用
网上找了很多资料,我们先看一张图,帮助我们理解,然后我们看看通过什么办法可以做的消除上下留白。
为了简单这个图是我从网上copy过来的这样懒得再去作图了😃,这种图网上很多,这里我做个简单的介绍吧。
top: baseline到顶部的最大距离。
ascent: 上坡度,baseline到顶部的推荐距离。
baseline: 基线。
descent: 下坡度,baseline到底部的推荐距离。
bottom: baseline到底部的最大距离。
我的做法思路是:我们要消除的是:
1.Top到文字顶部的距离。
2.bottom到文字底部的距离。
这两个距离如果消除了,就不存在上下留白的问题了。
有了这个思路之后,我们知道 xml里面可以使用 paddingTop = "-10dp"这种办法可以让文字向TextView的顶部移动对应的像素,那么我们就可以在代码里面给它加上一个向上的 负的 paddingTop 让文字紧贴着TextView的顶部消除上面的留白。那底部的留白是不是可以用同样的办法设置一个负数 的PaddingBottom来解决呢?答案是否定的,我们总不能在两个相反的方向上同时让文字向上向下吧,就想拔河一样,只能走向一边。所以,底部的留白我们就换一种思路,我们顶部的留白已经消除了,那么我们只需要能够计算出的TextView的高度是不是就解决了呢。答案是肯定的,我们在得到TextView的测量高度后,再减去bottom到文字底部的距离剩下的就是整个内容的高度了,并且没有了上下留白。
好了有了思路,就可以付诸实践敲敲代码试试看了。
/**
* @author luowang8
* @date 2020-01-17 16:36
*/
@SuppressLint("AppCompatCustomView")
public class NoSpaceTextView extends TextView {
/**
* 控制measure()方法 刷新测量
*/
private boolean refreshMeasure =false;
public NoSpaceTextView(Context context) {
super(context);
}
public NoSpaceTextView(Context context,@Nullable AttributeSet attrs) {
super(context,attrs);
}
public NoSpaceTextView(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {
super(context,attrs,defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
removeSpace(widthMeasureSpec,heightMeasureSpec);
}
@Override
public void setText(CharSequence text,BufferType type) {
super.setText(text,type);
// 每次文本内容改变时,需要测量两次,确保计算的高度没有问题
refreshMeasure =true;
}
/**
* 这里处理文本的上下留白问题
*/
private void removeSpace(int widthspc,int heightspc) {
int paddingTop =0;
String[] linesText =getLinesText();
TextPaint paint =getPaint();
Rect rect =new Rect();
String text = linesText[0];
paint.getTextBounds(text,0, text.length(), rect);
Paint.FontMetricsInt fontMetricsInt =new Paint.FontMetricsInt();
paint.getFontMetricsInt(fontMetricsInt);
paddingTop = (fontMetricsInt.top - rect.top);
// 设置TextView向上的padding (小于0, 即把TextView文本内容向上移动)
setPadding(getLeftPaddingOffset()
, paddingTop +getTopPaddingOffset()
,getRightPaddingOffset()
,getBottomPaddingOffset());
String endText = linesText[linesText.length -1];
paint.getTextBounds(endText,0, endText.length(), rect);
// 再减去最后一行文本的底部空白,得到的就是TextView内容上线贴边的的高度,到达消除文本上下留白的问题
setMeasuredDimension(getMeasuredWidth()
,getMeasuredHeight() - (fontMetricsInt.bottom - rect.bottom));
if (refreshMeasure) {
refreshMeasure =false;
measure(widthspc,heightspc);
}
}
/**
* 获取每一行的文本内容
*/
private String[]getLinesText() {
int start =0;
int end =0;
String[] texts =new String[getLineCount()];
String text =getText().toString();
Layout layout =getLayout();
for (int i =0; i
end = layout.getLineEnd(i);
String line = text.substring(start, end);//指定行的内容
start = end;
texts[i] = line;
}
return texts;
}
}
因为文字可能有多行,所以需要获取第一行的文字内容来做计算,fontMetricsInt.top 即我们上面说的最顶部的位置,rect.top即是文字的顶部,这两的差值就是顶部的留白部分,然后我们设置一个顶部的padding 就可以消除掉顶部留白了。
同理文字有多行,底部的留白,我们需要以最后一行的文字为准,先得到原始的TextView高度,然后再减去 最后一行底部的留白,剩下的就是我们的实际要的高度。
到此我们就基本完成了一个没有上下留白的TextView了,并且这个TextView和以前的用法一模一样,我们也不用担心,TextView的其他功能用不了。机智的童鞋可能要问了,我们可以设置负的paddingTop 这种方式来达到我们的目的,那我们可不可以使用marginTop呢?其实这两种方式均是可以实现消除上下留白的,原理其实都一样,我这里就提供padding这一种代码方案,margin的原理是一样的,有兴趣的童鞋不妨自己写写试试。
效果图: