天天看点

Android 单行图文混合绘制

此控件是为了解决单行文本后面跟多张图片,当文字超长时省略号显示

此控件分几种情况只显示图片,只显示文字,图文混合显示(都是单行)任何情况下会自动计算单行显示最大数量
此控件要求多张图片高度必须是一致的,宽度不限制,
CustomTextImageMix customTextImageMix= (CustomTextImageMix) findViewById(R.id.tv_custom);
 List<Integer>  list=new ArrayList<>();
 list.add(R.mipmap.mingqix1);
 customTextImageMix.setImageIndexList(list);
           
<com.cx.wdiget.CustomTextImageMix
        android:id="@+id/tv_custom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@+id/tv_3"
        android:layout_toRightOf="@+id/tv_2"
        android:visibility="visible"
        imageText:imagePadding="@dimen/dimen_8dp"
        imageText:textImagePadding="@dimen/dimen_8dp"
        imageText:text="---------"
        imageText:textColor="#00c0c7"
        imageText:textSize="10sp" />
           
package com.cx.wdiget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * 单行文本和多张图片混合展示
 * (任意文字或者图片超长会自动计算合适的展示方式)
 *
 * 绘制的图片和文字默认是居中的
 *
 * Created by ChenXin on 2017/10/12
 */
public class CustomTextImageMix extends View {

    /**展示的图片高度**/
    private int bitmapHeight = ;
    /**整个控件的宽度**/
    private int viewWidth = ;
    /**绘制字体的最大宽度**/
    private float textMaxWidth = ;
    /**文字展示的最小字数**/
    private int minTextLength = ;
    /**绘制的文本**/
    private String mText;
    /**文字的大小**/
    private int mTextSize;
    /**图片之间的间距**/
    private int mImagePadding;
    /**文字和第一张图片之间的间距**/
    private int textImagePadding;
    /**字体颜色(R.color.xx)**/
    private int mTextColor;
    /**图片引用id(R.mipmap.xx)**/
    private List<Integer> mImageIndexList = new ArrayList<>();
    /****/
    private final String ELLIPSIS = "...";
    private Paint paint;
    private Paint.FontMetricsInt fontMetrics;

    public CustomTextImageMix(Context context) {
        this(context, null);
    }

    public CustomTextImageMix(Context context, AttributeSet attrs) {
        this(context, attrs, );
    }

    public CustomTextImageMix(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Custom_textImage, defStyleAttr, );

        mText = typedArray.getString(R.styleable.Custom_textImage_text);
        mTextSize = (int) typedArray.getDimension(R.styleable.Custom_textImage_textSize, R.dimen.dimen_11sp);
        mTextColor = typedArray.getColor(R.styleable.Custom_textImage_textColor, Color.BLACK);
        mImagePadding = (int) typedArray.getDimension(R.styleable.Custom_textImage_imagePadding, R.dimen.dimen_2dp);
        textImagePadding = (int) typedArray.getDimension(R.styleable.Custom_textImage_textImagePadding, R.dimen.dimen_2dp);
        minTextLength=typedArray.getInteger(R.styleable.Custom_textImage_minTextLength,);

        paint = new Paint();
        paint.setColor(mTextColor);
        paint.setTextSize(mTextSize);
        fontMetrics = paint.getFontMetricsInt();

        typedArray.recycle();

    }

    /**
     * 展示图片的数据集合
     *
     * @param imageIndexList 展示图片的id
     */
    public void setImageIndexList(List<Integer> imageIndexList) {

        if (imageIndexList != null) {
            mImageIndexList = imageIndexList;
            bitmapHeight = getBitmapHeight(mImageIndexList.get());
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int textHeight=fontMetrics.bottom-fontMetrics.top;
        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(bitmapHeight>textHeight?bitmapHeight:textHeight));

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (TextUtils.isEmpty(mText) && mImageIndexList.size() == ) {
            return;
        }


        viewWidth = getWidth();
        executeOnDraw(canvas);
    }


    /**
     * 绘制逻辑
     *
     * @param canvas canvas
     */
    private void executeOnDraw(Canvas canvas) {


        if (!TextUtils.isEmpty(mText) && mImageIndexList.size() == ) { //只绘制文字
            textMaxWidth = viewWidth;
            showText();
            drawTextImage(canvas);

        } else if (TextUtils.isEmpty(mText) && mImageIndexList.size() > ) {//只绘制图片

            drawTextImage(canvas);


        } else { //图片文字都有

            float allImageWidth = getAllImageWidth();
            float allTextWidth = paint.measureText(mText);

            if (allImageWidth + allTextWidth <= viewWidth) { //图片文字刚好都能绘制

                drawTextImage(canvas);

            } else { //图片文字进行测量后绘制合适文字字数和图片数量

                float minTextWidth = getMinTextWidth();
                textMaxWidth = viewWidth - allImageWidth;
                if (textMaxWidth <=  || textMaxWidth < minTextWidth) {

                    mText = (mText.length() > minTextLength) ? mText.substring(, minTextLength) + ELLIPSIS : mText;
                    drawTextImage(canvas);

                } else {

                    showText();
                    drawTextImage(canvas);

                }

            }
        }


    }


    /**
     * 执行图文绘制
     * <p/>
     * 这里绘制的文字和图片都是计算后能够允许绘制的
     *
     * @param canvas canvas
     */
    private void drawTextImage(Canvas canvas) {

        if (canvas == null) {
            return;
        }

        Rect rect = new Rect();

        boolean haveText = !TextUtils.isEmpty(mText);


        if (haveText) {
            paint.getTextBounds(mText, , mText.length(), rect);


            //当图片的高度大于文字的高度时要以图片为中心文字和图片居中
            //文字的baseLine=(viewHeight - (paint.descent() - paint.ascent())) / 2 - paint.ascent()
            //viewHeight是控件的高度,如果我们绘制的控件要求刚好包裹,那么当图片高度大于字体高度时则viewHeight=BitmapHeight(居中)
            //当字体的高度大于图片高度时viewHeight=TextHeight=(-fontMetrics.top+fontMetrics.bottom)
            if (bitmapHeight > mTextSize) {

                canvas.drawText(mText, rect.left, (bitmapHeight - (paint.descent() - paint.ascent())) /  - paint.ascent(), paint);
            } else {
                canvas.drawText(mText, rect.left, -fontMetrics.top, paint);
            }
        }


        float left = Math.abs(rect.left) + Math.abs(rect.right);

        for (int i = ; i < mImageIndexList.size(); i++) {

            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mImageIndexList.get(i));

            if (i == ) {
                left = left + (haveText ? textImagePadding : );
            } else {
                left = left + bitmap.getWidth() + mImagePadding;
            }
            if (left <= viewWidth) {

                //当图片高度大于文字时,绘制从顶点0开始绘制
                //当文字高度大于图片高度时要图片和文字居中则绘制顶点Math.abs((-fontMetrics.top + fontMetrics.bottom) / 2 - bitmap.getHeight() / 2
                canvas.drawBitmap(bitmap, left, (bitmapHeight > mTextSize) ?  : Math.abs((-fontMetrics.top + fontMetrics.bottom) /  - bitmap.getHeight() / ), paint);
            }
        }
    }


    /**
     * 计算能显示的文字
     * <p/>
     * 当文字的总宽度大于了文字绘制的最大宽度,则需要截取文字加上省略号
     * <p/>
     * 计算后会生成新的绘制文字
     */
    private void showText() {

        if (TextUtils.isEmpty(mText) || textMaxWidth <= ) {
            return;
        }
        float allTextWidth = paint.measureText(mText);//文字总宽度

        if (allTextWidth > textMaxWidth) {

            int showTextLength=paint.breakText(mText,true,textMaxWidth-getEllipsisWidth(),null);
            mText=mText.substring(,showTextLength)+ELLIPSIS;

        }
    }


    /**
     * 计算文字绘制的最小宽度
     * <p/>
     * 当图片的宽度之和大于了整个view的宽度则必须预留绘制字体的最小宽度
     *
     * @return 文本绘制最小距离
     */
    private float getMinTextWidth() {

        float minWidth = ;

        if (TextUtils.isEmpty(mText)) {
            return minWidth;
        }

        int textLength = mText.length();

        if (textLength <= minTextLength) {

            minWidth = paint.measureText(mText);

        } else {

            minWidth = paint.measureText(mText.substring(, minTextLength) + ELLIPSIS);

        }

        return minWidth;
    }


    private float getEllipsisWidth() {
        return paint.measureText(ELLIPSIS);
    }


    /**
     * @return 获取所有需要展示的图片宽度和
     */
    private int getAllImageWidth() {

        int width = ;

        if (mImageIndexList.size() <= ) {
            return width;
        }

        boolean haveText = !TextUtils.isEmpty(mText);

        for (int i = ; i < mImageIndexList.size(); i++) {

            if (i == ) {
                width = width + getBitmapWidth(mImageIndexList.get(i)) + (haveText ? textImagePadding : );
            } else {
                width = width + getBitmapWidth(mImageIndexList.get(i)) + mImagePadding;
            }
        }

        return width;

    }

    /**
     *
     * 获取图片的宽度
     *
     * @param imageIndex 图片的id
     * @return 图片宽度
     */
    private int getBitmapWidth(int imageIndex) {

        try {

            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(getResources(), imageIndex, options); // 此时返回的bitmap为null
            return options.outWidth;

        } catch (Exception e) {
            e.getLocalizedMessage();
        }

        return ;
    }
    /**
     *
     * 获取图片的高度
     *
     * @param imageIndex 图片的id
     * @return 图片高度
     */
    private int getBitmapHeight(int imageIndex) {

        try {

            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(getResources(), imageIndex, options); // 此时返回的bitmap为null
            return options.outHeight;

        } catch (Exception e) {
            e.getLocalizedMessage();
        }

        return ;
    }

    private void writeLog(String msg) {
        Log.v("TextImageView--:", msg);
    }

}