天天看點

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

}