如何在textview中绘制包裹文本长度的背景颜色?

时间:2017-12-12 16:46:15

标签: java android textview

this is what i want to achieve

我想创建一个多行文本视图,其背景颜色遵循文本长度并具有圆角。这就像instagram一样。

这是我到目前为止所尝试的内容

public class BgColorTextView extends TextView {
...
@Override
public void draw(Canvas canvas) {
    int lineCount = getLayout().getLineCount();
    RectF rect = new RectF();
    Paint paint = new Paint();
    paint.setColor(bgColor);
    for (int i = 0; i < lineCount; i++) {
        rect.top = (int) (getLayout().getLineTop(i)+20);
        rect.left = (int) (getLayout().getLineLeft(i)+10);
        rect.right = (int) (getLayout().getLineRight(i)+40);
        rect.bottom = (int) (getLayout().getLineBottom(i) - ((i + 1 == lineCount) ? 0 : getLayout().getSpacingAdd())+40);
        canvas.drawRoundRect(rect, 25, 25, paint);
    }
    super.draw(canvas);
}

这是我得到的结果:

the result i get

有人能指出如何解决这个问题吗?唯一的问题是其他两个角落没有四舍五入。

1 个答案:

答案 0 :(得分:0)

使用简单的Canvas内置形状绘制方法无法实现这一点,因为您需要绘制凹角圆弧。但是,您可以使用Path基元操作来构建所需的形状。

可以想象你可以创建一条环绕文本的单一路径,但我发现为每条线创建单独的路径更简单,并确定角是否需要像普通圆角矩形(即凸半径,正半径)一样转动或转动out(凹,负半径)取决于线的边界如何与上下对齐。

我在下面提供了一个示例,说明如何将其作为派生TextView类的完全实现的实现来完成。

有关此实施的一些其他注意事项:

生成路径的主要工作是在onSizeChanged()方法中完成的:在draw()方法中进行繁重的计算和/或分配是不好的做法。而draw()方法只是在画布上绘制预先计算的路径。

棘手的位在Path roundedRect()方法中,它为每一行创建路径。 RectF参数是矩形的形状,如果所有角都是零半径,并且每个float参数给出左上角,右上角,左下角和右下角的半径。角落分别。正半径指定正常的圆角,负半径指定翻出的凹角,零指定没有圆角的直角。

还可以添加自定义属性来控制自定义窗口小部件的外观。我在示例attrs.xml和小部件构造函数中显示了这一点,以指定要使用的最大角半径。您可能希望添加其他控件以控制颜色,边距等,而不是像我在示例中所做的那样对它们进行硬编码。

我希望这会有所帮助。

BgColourTextView.java:

package com.example.bgcolourtextview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.widget.TextView;
import java.util.ArrayList;

public class BgColourTextView extends TextView
{

    private static final float DEFAULT_MAX_CORNER_RADIUS_DIP = 10f;

    private final Paint mPaint;
    private final ArrayList<Path> mOutlines;
    private final float mMaxCornerRadius;

    public BgColourTextView(Context context) {
        this(context, null, 0);
    }

    public BgColourTextView(Context context, AttributeSet attr) {
        this(context, attr, 0);
    }

    public BgColourTextView(Context context, AttributeSet attr, int defStyle) {
        super(context, attr, defStyle);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mOutlines = new ArrayList<>();
        TypedArray ta = context.getTheme().obtainStyledAttributes(attr, R.styleable.BgColourTextView, 0, defStyle);
        try {
            mMaxCornerRadius = ta.getDimension(R.styleable.BgColourTextView_maxCornerRadius,
                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MAX_CORNER_RADIUS_DIP, 
                                          context.getResources().getDisplayMetrics()));
        } finally {
            ta.recycle();
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mOutlines.clear();
        final ArrayList<RectF> lineBounds = new ArrayList<>();
        for (int i = 0; i < getLayout().getLineCount(); i++) {
            RectF rect = new RectF(getLayout().getLineLeft(i), getLayout().getLineTop(i), getLayout().getLineRight(i), getLayout().getLineBottom(i));
            rect.offset(getPaddingLeft(), getPaddingTop());
            rect.inset(-getPaddingLeft() / 2f, 0f);
            lineBounds.add(rect);
        }
        for (int i = 0; i < lineBounds.size(); i++) {
            float rTl = limitRadius(i == 0 ? lineBounds.get(i).width() / 2f : (lineBounds.get(i - 1).left - lineBounds.get(i).left) / 2f);
            float rTr = limitRadius(i == 0 ? lineBounds.get(i).width() / 2f : (lineBounds.get(i).right - lineBounds.get(i - 1).right) / 2f);
            float rBl = limitRadius(i == lineBounds.size() - 1 ? lineBounds.get(i).width() / 2f : (lineBounds.get(i + 1).left - lineBounds.get(i).left) / 2f);
            float rBr = limitRadius(i == lineBounds.size() - 1 ? lineBounds.get(i).width() / 2f : (lineBounds.get(i).right - lineBounds.get(i + 1).right) / 2f);
            mOutlines.add(roundedRect(lineBounds.get(i), rTl, rTr, rBl, rBr));
        }
    }



    @Override
    public void draw(Canvas canvas) {
        for (Path p: mOutlines) {
            canvas.drawPath(p, mPaint);
        }
        super.draw(canvas);
    }   

    /**
     * Limit the corner radius to a maximum absolute value.
     */
    private float limitRadius(float aRadius) {
        return Math.abs(aRadius) < mMaxCornerRadius ? aRadius : mMaxCornerRadius * aRadius / Math.abs(aRadius);
    }

    /**
     * Generate a rectangular path with rounded corners, where a positive corner radius indicates a normal convex corner and
     * negative indicates a concave corner (turning out horizontally rather than round).
     */
    private Path roundedRect(RectF aRect, float rTl, float rTr, float rBl, float rBr) {
        Log.d("BgColourTextView", String.format("roundedRect(%s, %s, %s, %s, %s)", aRect, rTl, rTr, rBl, rBr));
        Path path = new Path();
        path.moveTo(aRect.right, aRect.top + Math.abs(rTr));
        if (rTr > 0) {
            path.arcTo(new RectF(aRect.right - 2 * rTr, aRect.top, aRect.right, aRect.top + 2 * rTr), 0, -90, false);
        } else if (rTr < 0) {
            path.arcTo(new RectF(aRect.right , aRect.top, aRect.right - 2 * rTr, aRect.top - 2 * rTr), 180, 90, false);
        }
        path.lineTo(aRect.left + 2 * Math.abs(rTl), aRect.top);
        if (rTl > 0) {
            path.arcTo(new RectF(aRect.left, aRect.top, aRect.left + 2 * rTl, aRect.top + 2 * rTl), 270, -90, false);
        } else if (rTl < 0) {
            path.arcTo(new RectF(aRect.left + 2 * rTl, aRect.top, aRect.left, aRect.top - 2 * rTl), 270, 90, false);
        } 
        path.lineTo(aRect.left, aRect.bottom - 2 * Math.abs(rBl));
        if (rBl > 0) {
            path.arcTo(new RectF(aRect.left, aRect.bottom - 2 * rBl, aRect.left + 2 * rBl, aRect.bottom), 180, -90, false);
        } else if (rBl < 0) {
            path.arcTo(new RectF(aRect.left + 2 * rBl, aRect.bottom + 2 * rBl, aRect.left, aRect.bottom), 0, 90, false);
        }
        path.lineTo(aRect.right - 2 * Math.abs(rBr), aRect.bottom);
        if (rBr > 0) {
            path.arcTo(new RectF(aRect.right - 2 * rBr, aRect.bottom - 2 * rBr, aRect.right, aRect.bottom), 90, -90, false); 
        } else if (rBr < 0) {
            path.arcTo(new RectF(aRect.right, aRect.bottom + 2 * rBr, aRect.right - 2 * rBr, aRect.bottom), 90, 90, false); 
        }
        path.lineTo(aRect.right, aRect.top + Math.abs(rTr));
        return path;
    }

RES /值/ attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="BgColourTextView">
        <attr name="maxCornerRadius" format="dimension" />
    </declare-styleable>

</resources>

RES /布局/ main.xml中

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <com.example.bgcolourtextview.BgColourTextView
        android:text="Example\n10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:gravity="center_horizontal"
        android:textSize="32sp"
        app:maxCornerRadius="10dp" />

    <com.example.bgcolourtextview.BgColourTextView
        android:text="Example 15dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:gravity="center_horizontal"
        android:textSize="32sp"
        app:maxCornerRadius="15dp" />

    <com.example.bgcolourtextview.BgColourTextView
        android:text="Example\n20dp radius\ntext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:gravity="center_horizontal"
        android:textSize="32sp"
        app:maxCornerRadius="20dp" />

    <com.example.bgcolourtextview.BgColourTextView
        android:text="Example\ntext\n5dp radius\ntext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:gravity="center_horizontal"
        android:textSize="32sp"
        app:maxCornerRadius="5dp" />

</LinearLayout>

示例输出:

Example output