Android 自定義View----文字繪製(文字居中自動換行)

c

使用canvas繪製文字非常簡單,但文字繪製根據baseLine無法劇中,網上說法很多,有點麻煩,這裏用到一個非常簡單的辦法,先來看下文字的繪製參考線

圖片直接百度複製的,不是自己畫的,有時間自己畫一個;

文字上下居中其實很簡單

        // 單行繪製 (先計算出基線和到文字中間的距離,mPaint不是TextPaint)
        float offset = Math.abs(mPaint.ascent() + mPaint.descent()) / 2;
        canvas.drawText(
                singleTxt,
                getWidth() / 2,
                getHeight() / 2 + offset,
                mPaint  
        );

4多行繪製有兩種辦法: 

先來看下StaticLayout

        // 多行繪製
        StaticLayout staticLayout = new StaticLayout(
                txtStr,             // 需要繪製的文本
                mTextPaint,         // 畫筆對象(這裏需要TextPaint)
                getWidth() / 2,         // layout的寬度,文本超出寬度時自動換行
                Layout.Alignment.ALIGN_NORMAL,  // layout的對其方式
                1f,         // 文字行間距,根據文字大小比例計算 0.8f  1.2f
                0,          // 文字行間距,填寫具體值
                false       // 縱向是否加額外高度

        );
        // 默認從0,0開始繪製,如果需要調整位置,可通過移動Canvas的方式解決
        canvas.save();
        canvas.translate(getWidth()/4,0);
        staticLayout.draw(canvas);
        canvas.restore();

StaticLayout很好用,系統的TextView裏面也用到了,但是如果有自己的需求,隨意在任何位置換行就無法實現了。

比如有這樣的需求,如何繪製?這裏可使用breakText()進行隨意折行

class ImageBeginTextView constructor(context: Context) : View(context) {

    private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val txtStr = "人之初,性本善。性相近,習相遠。苟不教,性乃遷。教之道,貴以專。" +
            "昔孟母,擇鄰處。子不學,斷機杼。竇燕山,有義方。教五子,名俱揚。養不教,父之過。"

    // 圖片左右padding值
    private var mPadding = UnitUtil.dp2px(5)
    // 文字大小
    private var txtSize = UnitUtil.sp2px(16)
    // 圖片寬度(繪製文字的時候使用)
    private var imgWidth = UnitUtil.dp2px(20)

    // 聲明一個長度爲1的Float數組
    private val measuredWidth = FloatArray(1)

    private var fontMetrics: Paint.FontMetrics? = null

    constructor(context: Context, attrs: AttributeSet?) : this(context) {
        initXmlAttrs(context, attrs)
        initPaint()
    }

    private fun initPaint() {
        mPaint.textSize = txtSize
        fontMetrics = mPaint.fontMetrics
        mPaint.strokeWidth = 5f
        mPaint.color = Color.RED
    }

    private fun initXmlAttrs(context: Context, attrs: AttributeSet?) {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImageBeginTextView)
                ?: return
        // xml中配置的dp,這裏獲取的數據是轉換後的像素,所以不用再次轉換,可直接使用 (後面默認值是像素)
        mPadding = typedArray.getDimension(R.styleable.ImageBeginTextView_image_padding, 20f)
        txtSize = typedArray.getDimension(R.styleable.ImageBeginTextView_text_size, 12f)
        typedArray.recycle()
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        // 繪製圖片
        canvas?.drawBitmap(
                // 根據單行文字高度等比例縮放圖片
                getAvatar(mPaint.fontSpacing.toInt(), R.drawable.flag),
                mPadding, 0f,
                mPaint
        )

        // 基線Y軸
        var verticalOffset = abs(fontMetrics?.top ?: mPaint.ascent())

        var start = 0
        while (start < txtStr.length) {

            // 首行需要減掉圖片寬度和開始padding,圖片寬度,圖片右邊padding,文字右邊padding;
            val maxWidth = if (start == 0)
                width - mPadding - imgWidth - mPadding - mPadding
            else
                width - mPadding * 2

            // 返回一個長度,本行繪製了多少個字
            val breakTextCount = mPaint.breakText(
                    txtStr,
                    true,     // 是否正向繪製
                    maxWidth,                   // 最大繪製寬度
                    measuredWidth               // 繪製本行文字的寬度存在這個數組中
            )

            /*
            繪製文字
            text:  需要繪製的文本
            start:  從哪個位置開始繪製
            end:    繪製到哪裏,最多繪製到最後一位
            x,y:    如果是第一行,從圖片後邊開始繪製
            paint:  畫筆
             */
            canvas?.drawText(
                    txtStr,
                    start,
                    if (start + breakTextCount > txtStr.length) txtStr.length else start + breakTextCount,   //
                    if (start == 0) mPadding + imgWidth + mPadding else mPadding, verticalOffset,
                    mPaint
            )
            start += breakTextCount
            verticalOffset += mPaint.fontSpacing
        }

    }

    private fun getAvatar(height: Int, img: Int): Bitmap {
        val options = BitmapFactory.Options()
        // inJustDecodeBounds爲true,不返回bitmap,只返回這個bitmap的尺寸
        options.inJustDecodeBounds = true
        // 從資源中讀取(比較浪費資源,所以上面設置爲true,只獲取圖片寬高)
        BitmapFactory.decodeResource(resources, img, options)

        // 根據縮放比例重新計算寬高
        options.inDensity = options.outHeight
        options.inTargetDensity = height

        imgWidth = options.outWidth.toFloat() * height / options.outHeight

        // 再設置爲false,最後要返回bitmap
        options.inJustDecodeBounds = false

        return BitmapFactory.decodeResource(resources, img, options)
    }

}

上面只是簡單的設置了兩個自定義屬性,如果有需要,可隨意增加

在styles.xml中添加

    <declare-styleable name="ImageBeginTextView">
        <!--圖片左右padding,也是文字左右兩邊的padding-->
        <attr name="image_padding" format="dimension" />
        <!--文字大小-->
        <attr name="text_size" format="dimension" />
    </declare-styleable>

在佈局文件中可直接設置參數:

    <com.example.hencoderplus.view.ImageBeginTextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:image_padding="5dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:text_size="30sp" />

   

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章