简体   繁体   中英

How to draw background color that wraps text length in textview?

this is what i want to achieve

i want to create a multiline textview with a background color that following the text length and has rounded corner. This is just like what instagram does.

this is what i have been trying so far

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

and this is the result i get:

the result i get

Can anybody point me out how to solve this,please? The only problem is the other two corners are not getting rounded.

This can't really be done using simple Canvas built-in shape drawing methods as you need to draw concave corner arcs. However, you can use Path primitive operations to build the required shapes.

It is conceivable that you can create a single path to surround the text, but I found it simpler to create individual paths for each line and decide whether corners need turned in like normal rounded rect (ie convex, positive radius) or turned out (concave, negative radius) depending on how the line's bounds align with those above and below.

I have provided an example below of how this can be done as a fully-working implementation of a derived TextView class.

A few additional things to note about this implementation:

The main work generating the paths is done in the onSizeChanged() method: it's bad practice to do heavy calculation and/or allocation in the draw() method. The draw() method instead simply draws the pre-calculated paths on the canvas.

The tricky bit is in the Path roundedRect() method, which creates the paths for each line. The RectF argument is the shape of the rectangle if all corners were zero radius, and each float argument gives the radius of each of the top-left, top-right, bottom-left and bottom-right corners respectively. A positive radius specifies a normal rounded corner, a negative radius specifies a turned-out concave corner, and zero specifies a straight corner with no rounding.

It is also possible to add custom attributes to control the look and feel of your custom widget. I've shown this in the example attrs.xml and in the widget constructor to specify the maximum corner radius to be used. You may wish to add others to control colour, margins etc. rather than hard-coding them as I have done in my example.

I hope this helps.

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/values/attrs.xml

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

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

</resources>

res/layout/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:

输出示例

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM