Android开发过程中可能很多人都遇到过这样的问题,那就是TextView上下内边距的问题。使用TextView控件的时候由于其内边距导致与UI效果相差甚远。很是让不少程序猿难受,也包括我自己因为这个原因也没少和UI设计师打嘴仗。于是最近写了一个自定义的NoBroderTextView去除了TextView上下内边距。
通过查阅Google官网的Android开发文档发现TextView中有android:includeFontPadding属性去除内边距。但是当设置该数据行为false的时候发现TextView还有一点点内边距存在,并不能让TextView控件的上下边距与文本严丝合缝。而这一点点的间距是Android框架中为TextView预留的字体边际。无法通过System API设置及修改。感兴趣的同学可以查看源码发现。无奈只能自定义实现TextView实现了,话不多少直接上代码。
package com.liming.nobroder.nopaddingtextview.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.v7.widget.AppCompatTextView;
import android.text.Layout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
/**
* 无内边距TextView控件
* @author liming Create date : 2019/01/30
*/
public class NoPaddingTextView extends AppCompatTextView {
//日志标记
private final String TAG = NoPaddingTextView.class.getSimpleName();
//文本画笔
private TextPaint textPaint;
//绘制矩形
private Rect rect;
//默认宽度
private int layoutWidth = -1;
//获得每行数据
private String[] lineContents;
//获取行间距的额外空间
private float line_space_height = 0.0f;
//获取行间距乘法器
private float line_space_height_mult = 1.0f;
/**
* 构造方法
*
* @param context
* @param attrs
*/
public NoPaddingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
/**
* 构造方法
*
* @param context
* @param attrs
* @param defStyleAttr
*/
public NoPaddingTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
/**
* 初始化方法
*/
private void init(Context context, AttributeSet attrs) {
//声明画笔对象
textPaint = new TextPaint();
//声明矩形绘制对象
rect = new Rect();
//获得行间距额外数据
line_space_height = getLineSpacingExtra();
//获得行间距方法器
line_space_height_mult = getLineSpacingMultiplier();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Layout _layout = getLayout();
if (_layout != null) {
//获得文本内容文本内容不可以修改,平切判断当前当前内容是否为null
final String _tvContent = TextUtils.isEmpty(getText()) ? "" : getText().toString();
//获取文本长度
final int _tvLenght = _tvContent.length();
//设置文本宽度
textPaint.getTextBounds(_tvContent, 0, _tvLenght, rect);
//设置文本大小
textPaint.setTextSize(getTextSize());
//设置文本颜色
textPaint.setColor(getCurrentTextColor());
//获取行数据
getTextContentData(_layout);
//获得行高
int _lineHeight = -rect.top + rect.bottom;
//初始化布局
initLayout(_layout);
//设置布局区域
int[] _area = getWidthAndHeigt(widthMeasureSpec, heightMeasureSpec, layoutWidth, _layout.getLineCount(), _lineHeight);
//设置布局
setMeasuredDimension(_area[0], _area[1]);
}
}
/**
* 初始化化布局高度
*
* @param _layout
*/
private void initLayout(Layout _layout) {
//获得布局大小
if (layoutWidth < 0) {
//获取第一次测量数据
layoutWidth = _layout.getWidth();
}
}
/**
* 获取布局数据
*
* @param pWidthMeasureSpec
* @param pHeightMeasureSpec
* @param pWidth
* @return 返回宽高数组
*/
private int[] getWidthAndHeigt(int pWidthMeasureSpec, int pHeightMeasureSpec, int pWidth, int pLineCount, int pLineHeight) {
int _widthMode = MeasureSpec.getMode(pWidthMeasureSpec); //获取宽的模式
int _heightMode = MeasureSpec.getMode(pHeightMeasureSpec); //获取高的模式
int _widthSize = MeasureSpec.getSize(pWidthMeasureSpec); //获取宽的尺寸
int _heightSize = MeasureSpec.getSize(pHeightMeasureSpec); //获取高的尺寸
//声明控件尺寸
int _width;
int _height;
//判断模式
if (_widthMode == MeasureSpec.EXACTLY) {
//如果match_parent或者具体的值,直接赋值
_width = _widthSize;
} else {
_width = pWidth - rect.left;
}
//高度跟宽度处理方式一样
if (_heightMode == MeasureSpec.EXACTLY) {
_height = _heightSize;
} else {
if(pLineCount > 1){
_height = pLineHeight * pLineCount + (int) (line_space_height * line_space_height_mult * (pLineCount -1));
}else{
_height = pLineHeight * pLineCount;
}
}
//初始化宽高数组
int[] _area = {
_width,
_height
};
return _area;
}
/**
* 获取行数据
*
* @param _layout 文本布局对象(注:该布局其实使用的是Layout子类对象StaticLayout)
*/
private void getTextContentData(Layout _layout) {
//初始化航速数据
lineContents = new String[_layout.getLineCount()];
//获得每行数据
for (int i = 0; i < _layout.getLineCount(); i++) {
int _start = _layout.getLineStart(i);
int _end = _layout.getLineEnd(i);
lineContents[i] = getText().subSequence(_start, _end).toString();
}
}
@Override
protected void onDraw(Canvas canvas) {
//行高
float _line_height = -rect.top + rect.bottom;
//行间距
float _line_space = line_space_height * line_space_height_mult;
//循环获取每行数据内容
for (int i = 0; i < lineContents.length; i++) {
//获得数据
String _drawContent = lineContents[i];
//显示日志
Log.e(TAG, "LINE[" + (i + 1) + "]=" + _drawContent);
//绘制每行数据
canvas.drawText(_drawContent, 0, -rect.top + (_line_height + _line_space) * i, textPaint);
}
}
}
其中getTextContentData()方法获取文本内容,并且获取每行数据将其缓存到lineContents数组中。
通过getWidthAndHeight()方法获取自定义控件的宽高(其实就是当控件设置为wrap_content或者match_parent的时候实际控件的高度)获取之后重新在设置到自定义控件。
onDraw()方法实现绘制这个就不多说了。
以下TextView中的属性依然奏效
android:lineSpacingExtra="设置行间距(默认为0.0f)" android:lineSpacingMultiplier="行间距基数(默认为1.0f)"
项目GitHub地址:GitHub - liming870906/NoBroderTextView: 实现Android自定义TextView控件去除内边距
android:lineSpacingExtra="设置行间距(默认为0.0f)" android:lineSpacingMultiplier="行间距基数(默认为1.0f)"
效果如下: