做下自定义的练习。实现一个图片+文本自动换行的自定义控件,效果如下图所示:
第一步:设置自定义属性attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ImageText">
<attr name="src" format="reference" />
<attr name="text" format="string|reference" />
<attr name="textSize" format="dimension" />
</declare-styleable>
</resources>
设置一下这三个属性,在这个自定义view会使用到。
src:drawable路径
text:文字内容
textSize:文字的sp大小
第二步:创建ImageTextView类继承View,定义一下构造方法
package com.example.androiddemo;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
/**
* 图片+文字自动换行view
* @author lwd
*/
public class ImageTextView extends View {
public ImageTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ImageTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ImageTextView(Context context) {
this(context, null);
}
}
第三步:写一个使用这个自定义的布局文件:activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:ImageText="http://schemas.android.com/apk/res/com.example.androiddemo"
android:id="@+id/layout_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:orientation="vertical" >
<com.example.androiddemo.ImageTextView
android:id="@+id/imagetext1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
ImageText:src="@drawable/test"
ImageText:text="每个人都有自己的不容易,工作的压力,生活的烦恼。有太多的情绪,我们都是在用力克制着。因为我们知道,如果自己不坚强,没有人可以替我们勇敢。曾经看过一段话说,有这么几样东西,我们绝对不可以丢掉。扬在脸上的自信,长在心底的里善良,融进血液里的骨气,还有刻进生命里的坚强。"
ImageText:textSize="18sp"
android:background="#FFFFFFFF" />
</LinearLayout>
第四步:在构造方法获取自定义属性
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.ImageText);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(R.styleable.ImageText_src, R.drawable.ic_launcher));
text = typedArray.getString(R.styleable.ImageText_text);
textSize = typedArray.getDimension(R.styleable.ImageText_textSize, 14);//textSize默认大小14
TypeArray使用完后,记得recycle(),还有无用的bitmap对象也进行recycle();
对获取到文本进行判空,为空时,赋值“”,其他两个属性有默认值。
创建画笔Paint,先设置paint.setTextSize(textSize);,再获取FontMetrics
paint.setTextSize(textSize);
paint.setAntiAlias(true);//抗锯齿
fm = paint.getFontMetrics();
baseLine = fm.descent - fm.ascent;
获取文本绘制矩形的宽和对图片进行缩放适配文字高度
Rect rect = new Rect();
//返回包围整个字符串的最小的一个Rect区域
paint.getTextBounds(text, 0, text.length(), rect);
textWidth = rect.width();
//bitmap宽高
int bitmapHeight = bitmap.getHeight();
int bitmapWidth = bitmap.getWidth();
// 计算缩放比例.
float scaleHeight = (float) (((float) baseLine) / bitmapHeight);
float scaleWidth = scaleHeight;
// 取得想要缩放的matrix参数.
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
// 得到新的图片.
bmp = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, true);
// bitmap宽高
bmpHeight = bmp.getHeight();
bmpWidth = bmp.getWidth();
bitmap.recycle();
typedArray.recycle();
第五步:重写onMeasure,计算在AT_MOST的情况下View的width和height的值
1、确定图片+文本需要显示行数,图片+文本的宽/父容器的宽=显示行数
2、View的宽不能超过父容器的宽
3、view的高height,文本的行高:baseLine = fm.descent - fm.ascent; 第一行的时候,因为图片比文本高,采用图片的高度,第二行再加上文本的高度
4、EXACTLY和UNSPECIFIED的情况下,不计算。直接MeasureSpec.getSize();
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 如果自定义TextView中的宽高给的是确定的值,比如10dp、20dp、match_parent,这个时候不需要计算,给的多少就是多少
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
// 如果自定义TextView中的宽高给的是wrap_content,则需要计算宽高
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int lineCount = 0;//默认存在第0行(即默认存在一行)
int parentWidth = 0;//父view宽
try {
ViewGroup mViewGroup = (ViewGroup) getParent();
parentWidth = mViewGroup.getMeasuredWidth();
//向下取整
lineCount = (int) Math.floor((textWidth + getPaddingLeft() + getPaddingRight() + bmpWidth) / parentWidth);
} catch (Exception e) {
e.printStackTrace();
}
int width = MeasureSpec.getSize(widthMeasureSpec);
// 2. 如果文字的大小给的是wrap_content,则需要计算大小
if (widthMode == MeasureSpec.AT_MOST) {
//width不超过父容器的宽度
if (lineCount == 0) {
width = textWidth + getPaddingLeft() + getPaddingRight() + bmpWidth;
}else {
width = parentWidth;
}
}
int height = (int) (MeasureSpec.getSize(heightMeasureSpec) + baseLine * 0.3);
if (heightMode == MeasureSpec.AT_MOST) {
// 区域
height = (int) (bmpHeight + ((baseLine + fm.leading)* lineCount) + getPaddingTop() + getPaddingBottom() + baseLine * 0.3);
}
viewWidth = width;
// 设置控件的宽高,这里就是给文字设置宽高
setMeasuredDimension(width, height);
}
第六步:重写onDraw,drawBitmap和drawText,对text分段装到一个String数组里面
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float x = bmpWidth;
float y = baseLine;
canvas.drawBitmap(bmp, 0, 0, paint);
String texts[] = autoSplit(text, paint, viewWidth);
for (int i = 0; i < texts.length; i++) {
if (i == 0) {
canvas.drawText("" + texts[i], x, bmpHeight, paint);
}else {
x = 0;
canvas.drawText("" + texts[i], x, bmpHeight + y, paint);
y += baseLine + fm.leading; //添加字体行间距
}
}
}
在拆分第一行文本,文本宽度加上图片宽度与控件宽度比较
把拆分出来的文本最后一个文字摘出去,因为加上最后一个文字,最后这个文字会显示不全,放到下一行文本中。
private String[] autoSplit(String content, Paint paint, float viewWidth) {
int length = content.length();
int start = 0;
int end = 1;
int i = 0;
int lines = (int) Math.ceil((textWidth + bmpWidth) / viewWidth);
String[] lineTexts = new String[lines];
Rect rect = new Rect();
int textLineWidth = 0;
paint.getTextBounds(text, start, text.length(), rect);
float textWidth = rect.width();//获取文字的显示宽度
if ((textWidth + bmpWidth) <= viewWidth) {
return new String[]{content};
}
while (start < end) {
if (start == 0) {
paint.getTextBounds(text, start, end, rect);
textLineWidth = rect.width();
if ((textLineWidth + bmpWidth) > viewWidth) {
String subContent = (String) content.subSequence(start, end - 1);
lineTexts[i++] = subContent;
start = end - 1;
}
}else {
paint.getTextBounds(text, start, end, rect);
textLineWidth = rect.width();
if (textLineWidth > viewWidth) {
String subContent = (String) content.subSequence(start, end - 1);
lineTexts[i++] = subContent;
start = end - 1;
}
}
if (end == length) {
lineTexts[i] = (String) content.subSequence(start, end);
break;
}
end += 1;
}
rect = null;
return lineTexts;
}
完整代码传送门: 点击打开链接