【问题标题】:How to draw background color that wraps text length in textview?如何在textview中绘制包含文本长度的背景颜色?
【发布时间】:2017-12-14 00:17:28
【问题描述】:

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

谁能告诉我如何解决这个问题,好吗?唯一的问题是其他两个角没有变圆。

【问题讨论】:

    标签: java android textview


    【解决方案1】:

    这真的不能使用简单的 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/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>
    

    示例输出:

    【讨论】:

    • 非常感谢!!这正是我所需要的。谢谢
    • 我从我的项目文件中抄录下来的包名称有点混淆。我现在已经编辑了代码 sn-ps,以便 XML 和 Java 现在应该匹配。如果造成任何混淆,我们深表歉意。
    • 还有一件事,需要禁用硬件加速,否则它会在文本行之间创建一些透明空间。
    • 有趣。我没有看到这种效果。当然,是否设置了硬件加速并不重要,因为理论上它应该与软件渲染相同。但我敢说这将取决于硬件和制造商对 OpenGL 代码的实现。
    • 另一个想法。行之间有空间的事实表明布局算法存在差异。一种可能的解决方法是通过修改它们的顶部和底部值以匹配上方和下方的线来确保我的示例中的 lineBounds 矩形彼此邻接。但因为我没有问题,所以我不能说。
    猜你喜欢
    • 2019-11-26
    • 1970-01-01
    • 2014-02-21
    • 2010-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多