7 votes

Comment dessiner correctement du texte dans une classe étendue pour TextView ?

Je travaille actuellement sur l'extension d'un TextView, en ajoutant un contour autour du texte. Jusqu'à présent, le seul problème que j'ai rencontré est mon incapacité à positionner correctement le "contour" derrière un texte. Si je code la classe étendue comme celle illustrée ci-dessous, j'obtiens un libellé qui ressemble à ceci :

Note : dans la capture d'écran ci-dessus, j'ai défini la couleur de remplissage sur blanc et la couleur de trait sur noir.

Qu'est-ce que je fais de mal ?

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

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

    /* ===========================================================
     * Constructeurs
     * =========================================================== */
    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);
    }

    /* ===========================================================
     * Substitutions
     * =========================================================== */
    @Override
    protected void onDraw(Canvas canvas) {
        // Obtenez le texte à imprimer
        final float textSize = super.getTextSize();
        final String text = super.getText().toString();

        // configuration du contour
        mStrokePaint.setColor(mOutlineColor);
        mStrokePaint.setStrokeWidth(textSize * OUTLINE_PROPORTION);
        mStrokePaint.setTextSize(textSize);
        mStrokePaint.setFlags(super.getPaintFlags());
        mStrokePaint.setTypeface(super.getTypeface());

        // Découvrez les coordonnées de dessin
        // mStrokePaint.getTextBounds(text, 0, text.length(), mTextBounds);

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

    /* ===========================================================
     * Méthodes privées/protégées
     * =========================================================== */
    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(); 

        // Forcer ce libellé texte à être centré
        super.setGravity(Gravity.CENTER_HORIZONTAL);
    }
}

5voto

Japtar Points 423

Bah, c'était stupide de ma part. Je devais simplement modifier cette ligne en commentaire :

super.getPaint().getTextBounds(text, 0, text.length(), mTextBounds);

De plus, pour afficher le texte, je dois faire la moyenne de la hauteur de cette vue et de la hauteur du texte :

// dessiner tout
canvas.drawText(text,
    super.getWidth() * 0.5f, (super.getHeight() + mTextBounds.height()) * 0.5f,
    mStrokePaint);

Le code complet se lit maintenant comme suit :

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) {
        // Obtenez le texte à imprimer
        final float textSize = super.getTextSize();
        final String text = super.getText().toString();

        // configurer le contour
        mStrokePaint.setColor(mOutlineColor);
        mStrokePaint.setStrokeWidth(textSize * OUTLINE_PROPORTION);
        mStrokePaint.setTextSize(textSize);
        mStrokePaint.setFlags(super.getPaintFlags());
        mStrokePaint.setTypeface(super.getTypeface());

        // Déterminer les coordonnées de dessin
        super.getPaint().getTextBounds(text, 0, text.length(), mTextBounds);

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

    /* ===========================================================
     * Méthodes privées/protégées
     * =========================================================== */
    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(); 

        // Forcer cette étiquette de texte à être centrée
        super.setGravity(Gravity.CENTER_HORIZONTAL);
    }
}

4voto

Zulaxia Points 1572

J'ai travaillé sur certains de ces exemples depuis un certain temps, car aucun ne semblait être tout à fait approprié, et une fois que j'ai enfin compris ce qui se passait avec le texte et que j'ai mis mon chapeau de maths, j'ai changé mon onDraw pour que ce soit ce qui suit et cela s'adapte parfaitement quelle que soit la taille du texte ou la taille et la forme de la vue qui le contient...

@Override
protected void onDraw(Canvas canvas) {
    if (!isInEditMode()){
        // Récupérer le texte à imprimer
        final float textSize = super.getTextSize();
        final String text = super.getText().toString();

        // configuration du contour
        mStrokePaint.setColor(mOutlineColor);
        mStrokePaint.setStrokeWidth(textSize * mOutlineSize);
        mStrokePaint.setTextSize(textSize);
        mStrokePaint.setFlags(super.getPaintFlags());
        mStrokePaint.setTypeface(super.getTypeface());

        // dessiner tout
        canvas.drawText(text,
                (this.getWidth()-mStrokePaint.measureText(text))/2, this.getBaseline(),
                mStrokePaint);
    }
    super.onDraw(canvas);
}

S'est avéré être beaucoup moins de maths et de calculs Rect que ce que de nombreuses solutions utilisaient également.

Edit: J'ai oublié de mentionner que je copie le textalign du super lors de l'initialisation et ne le force pas à être centré. La position de dessin du drawText calculée ici sera toujours la position correctement centrée pour le texte souligné.

2voto

Michael Points 16659

J'ai essayé de le faire fonctionner pendant un certain temps et j'ai une solution, mais c'est seulement pour un cas spécial! Il est possible d'obtenir l'objet Layout qui est utilisé à l'intérieur du TextView pour dessiner du texte. Vous pouvez créer une copie de cet objet et l'utiliser à l'intérieur de la méthode 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();

Mais je suis sûr que ce n'est pas une bonne façon de dessiner des contours. Je ne sais pas comment suivre les changements dans un objet TextView.getLayout(). De plus, cela ne fonctionne pas pour les TextViews multi-lignes et les gravités différentes. Et finalement, ce code a une très mauvaise performance car il alloue un objet Layout à chaque dessin. Je ne comprends pas exactement comment cela fonctionne, donc je préférerais ne pas l'utiliser.

0voto

Michael Points 16659

Il y a quelques attributs dans la classe TextView, comme android:shadowColor, android:shadowDx, android:shadowDy et android:shadowRadius. Il me semble qu'ils font la même chose que ce que vous voulez implémenter. Donc peut-être devriez-vous essayer d'abord un simple TextView.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X