Java 如何在TextView的扩展类中正确绘制文本?

Java 如何在TextView的扩展类中正确绘制文本?,java,android,textview,extend,outline,Java,Android,Textview,Extend,Outline,我目前正在扩展一个文本视图,在文本周围添加一个大纲。到目前为止,我遇到的唯一问题是我无法在文本后面正确定位“大纲”。如果我像下面描述的那样编写扩展类,我会得到一个如下所示的标签: 注意:在上面的屏幕截图中,我将填充颜色设置为白色,笔划颜色设置为黑色 我做错了什么 public class OutlinedTextView extends TextView { /* ===========================================================

我目前正在扩展一个文本视图,在文本周围添加一个大纲。到目前为止,我遇到的唯一问题是我无法在文本后面正确定位“大纲”。如果我像下面描述的那样编写扩展类,我会得到一个如下所示的标签:

注意:在上面的屏幕截图中,我将填充颜色设置为白色,笔划颜色设置为黑色

我做错了什么

public class OutlinedTextView extends TextView {
    /* ===========================================================
     * Constants
     * =========================================================== */
    private static final float OUTLINE_PROPORTION = 0.1f;

    /* ===========================================================
     * Members
     * =========================================================== */
    private final Paint mStrokePaint = new Paint();
    private int mOutlineColor = Color.TRANSPARENT;

    /* ===========================================================
     * Constructors
     * =========================================================== */
    public OutlinedTextView(Context context) {
        super(context);
        this.setupPaint();
    }
    public OutlinedTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setupPaint();
        this.setupAttributes(context, attrs);
    }
    public OutlinedTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.setupPaint();
        this.setupAttributes(context, attrs);
    }

    /* ===========================================================
     * Overrides
     * =========================================================== */
    @Override
    protected void onDraw(Canvas canvas) {
        // Get the text to print
        final float textSize = super.getTextSize();
        final String text = super.getText().toString();

        // setup stroke
        mStrokePaint.setColor(mOutlineColor);
        mStrokePaint.setStrokeWidth(textSize * OUTLINE_PROPORTION);
        mStrokePaint.setTextSize(textSize);
        mStrokePaint.setFlags(super.getPaintFlags());
        mStrokePaint.setTypeface(super.getTypeface());

        // Figure out the drawing coordinates
        //mStrokePaint.getTextBounds(text, 0, text.length(), mTextBounds);

        // draw everything
        canvas.drawText(text,
                super.getWidth() * 0.5f, super.getBottom() * 0.5f,
                mStrokePaint);
        super.onDraw(canvas);
    }

    /* ===========================================================
     * Private/Protected Methods
     * =========================================================== */
    private final void setupPaint() {
        mStrokePaint.setAntiAlias(true);
        mStrokePaint.setStyle(Paint.Style.STROKE);
        mStrokePaint.setTextAlign(Paint.Align.CENTER);
    }
    private final void setupAttributes(Context context, AttributeSet attrs) {
        final TypedArray array = context.obtainStyledAttributes(attrs,
                R.styleable.OutlinedTextView);
        mOutlineColor = array.getColor(
                R.styleable.OutlinedTextView_outlineColor, 0x00000000);
        array.recycle(); 

        // Force this text label to be centered
        super.setGravity(Gravity.CENTER_HORIZONTAL);
    }
}

TextView
类中有一些属性,比如
android:shadowColor
android:shadowDx
android:shadowDy
android:shadowRadius
。在我看来,他们做的事情和你想实现的一样。所以,也许你应该先尝试一个简单的
文本视图。

我已经尝试了一段时间,我有一个解决方案,但这只是一个特例!可以获取
布局
对象,该对象在
文本视图
中用于绘制文本。您可以创建此对象的副本,并在
onDraw(Canvas)
方法中使用它

    final Layout originalLayout = super.getLayout();
    final Layout layout = new StaticLayout(text, mStrokePaint,
    originalLayout.getWidth(), originalLayout.getAlignment(),
    originalLayout.getSpacingMultiplier(), originalLayout.getSpacingAdd(), true);

    canvas.save();
    canvas.translate( layout.getLineWidth(0) * 0.5f, 0.0f );
    layout.draw(canvas);
    canvas.restore();

但我肯定这不是画轮廓的好方法。我不知道如何跟踪
TextView.getLayout()对象中的更改。此外,它不适用于多行
TextView
s和不同的重力。最终,这段代码的性能非常差,因为它在每次绘制时都分配一个
布局
对象。我不太明白它是如何工作的,所以我宁愿不使用它。

呸,我真是太蠢了。我只需要更改注释掉的行:

super.getPaint().getTextBounds(text, 0, text.length(), mTextBounds);
此外,为了实际渲染文本,我需要平均此视图的高度和文本的高度:

// draw everything
canvas.drawText(text,
    super.getWidth() * 0.5f, (super.getHeight() + mTextBounds.height()) * 0.5f,
    mStrokePaint);
现在,整个代码如下所示:

public class OutlinedTextView extends TextView {
    /* ===========================================================
     * Constants
     * =========================================================== */
    private static final float OUTLINE_PROPORTION = 0.1f;

    /* ===========================================================
     * Members
     * =========================================================== */
    private final Paint mStrokePaint = new Paint();
    private final Rect mTextBounds = new Rect();
    private int mOutlineColor = Color.TRANSPARENT;

    /* ===========================================================
     * Constructors
     * =========================================================== */
    public OutlinedTextView(Context context) {
        super(context);
        this.setupPaint();
    }
    public OutlinedTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setupPaint();
        this.setupAttributes(context, attrs);
    }
    public OutlinedTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.setupPaint();
        this.setupAttributes(context, attrs);
    }

    /* ===========================================================
     * Overrides
     * =========================================================== */
    @Override
    protected void onDraw(Canvas canvas) {
        // Get the text to print
        final float textSize = super.getTextSize();
        final String text = super.getText().toString();

        // setup stroke
        mStrokePaint.setColor(mOutlineColor);
        mStrokePaint.setStrokeWidth(textSize * OUTLINE_PROPORTION);
        mStrokePaint.setTextSize(textSize);
        mStrokePaint.setFlags(super.getPaintFlags());
        mStrokePaint.setTypeface(super.getTypeface());

        // Figure out the drawing coordinates
        super.getPaint().getTextBounds(text, 0, text.length(), mTextBounds);

        // draw everything
        canvas.drawText(text,
                super.getWidth() * 0.5f, (super.getHeight() + mTextBounds.height()) * 0.5f,
                mStrokePaint);
        super.onDraw(canvas);
    }

    /* ===========================================================
     * Private/Protected Methods
     * =========================================================== */
    private final void setupPaint() {
        mStrokePaint.setAntiAlias(true);
        mStrokePaint.setStyle(Paint.Style.STROKE);
        mStrokePaint.setTextAlign(Paint.Align.CENTER);
    }
    private final void setupAttributes(Context context, AttributeSet attrs) {
        final TypedArray array = context.obtainStyledAttributes(attrs,
                R.styleable.OutlinedTextView);
        mOutlineColor = array.getColor(
                R.styleable.OutlinedTextView_outlineColor, 0x00000000);
        array.recycle(); 

        // Force this text label to be centered
        super.setGravity(Gravity.CENTER_HORIZONTAL);
    }
}

我已经花了一段时间研究其中的一些例子,因为没有一个是完全正确的,一旦我最终掌握了文本的情况,戴上我的数学帽,我改变了我的onDraw,使其成为以下内容,并且无论文本的大小或它包含的视图的大小和形状如何,它都能完美地放置

@Override
protected void onDraw(Canvas canvas) {
    if (!isInEditMode()){
        // Get the text to print
        final float textSize = super.getTextSize();
        final String text = super.getText().toString();

        // setup stroke
        mStrokePaint.setColor(mOutlineColor);
        mStrokePaint.setStrokeWidth(textSize * mOutlineSize);
        mStrokePaint.setTextSize(textSize);
        mStrokePaint.setFlags(super.getPaintFlags());
        mStrokePaint.setTypeface(super.getTypeface());

        // draw everything
        canvas.drawText(text,
                (this.getWidth()-mStrokePaint.measureText(text))/2, this.getBaseline(),
                mStrokePaint);
    }
    super.onDraw(canvas);
}
结果证明,与使用的许多解决方案相比,数学和直接计算要少得多


编辑:忘了提到我在初始化中复制了super的textalign,并且没有强制它居中。此处计算的drawText位置始终是笔划文本的正确居中位置。

文本阴影将与Japtar尝试实现的不同。事实上,我对这个问题的解决方案也很感兴趣。文本笔划(实际上还有阴影)将非常有用!是的,我以前试过添加阴影。然而,正如科考普克所说,它并没有实现我想要的。尽管如此,答案还是很好。我在项目中使用阴影来勾勒轮廓。很遗憾,这对您没有帮助。我现在忘了提到当前的XML布局。虽然我还没有把它放在身上,但基本上我正在尝试将文本视图垂直和水平居中对齐在图像按钮的顶部。希望这能帮助任何人,实际上你不必计算屏幕的绝对位置来确定在哪里绘制文本。OnDraw实际上将相对于其扩展的视图的边界进行绘制。因此,要使文本笔划居中到视图,我只需要视图的水平中心和垂直中心,由文本高度偏移(出于某种原因,canvas.drawText有一个选项可以水平居中对齐文本,但不能垂直对齐)。您知道如果文本右对齐,该选项的位置是否正确吗,中心对齐、顶部对齐等?它肯定会处理顶部和底部对齐,因为getBaseline是为“真实”文本计算的正确基线位置。这里的x位置总是居中的,但是你可以很容易地在对齐上切换并修改数学,因为一旦你找到正确的值来读取,它实际上非常简单。左对齐(忽略填充)为0,右对齐为getWidth()-measureText(文本)。如果需要,也可以从文本视图中读取左右两侧的填充。我相信我会在不久的将来的某一天为另一个项目处理好这一切。这是有道理的。我肯定可以应付水平对齐的缺乏,因为我想要中心对齐,无论如何。我知道
getBaseline()
,但出于某种原因从未想过使用它。谢谢你提醒我。