图片+文本自动换行控件

做下自定义的练习。实现一个图片+文本自动换行的自定义控件,效果如下图所示:


第一步:设置自定义属性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>
现在这里没有对“\n”等特殊字符做处理。

第四步:在构造方法获取自定义属性

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;
}

完整代码传送门点击打开链接
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值