J'ai besoin de 2 façons de montrer une étiquette verticale dans Android:
Dois-je développer des widgets personnalisés pour les deux cas (un cas), puis-je utiliser TextView pour rendre le rendu de cette manière, et quel serait un bon moyen de faire quelque chose comme ça si je devais devenir complètement personnalisé?
Voici mon implémentation de texte vertical élégante et simple, qui étend TextView. Cela signifie que tous les styles standard de TextView peuvent être utilisés, car il s'agit de TextView étendu.
public class VerticalTextView extends TextView{
final boolean topDown;
public VerticalTextView(Context context, AttributeSet attrs){
super(context, attrs);
final int gravity = getGravity();
if(Gravity.isVertical(gravity) && (gravity&Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
setGravity((gravity&Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP);
topDown = false;
}else
topDown = true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
@Override
protected boolean setFrame(int l, int t, int r, int b){
return super.setFrame(l, t, l+(b-t), t+(r-l));
}
@Override
public void draw(Canvas canvas){
if(topDown){
canvas.translate(getHeight(), 0);
canvas.rotate(90);
}else {
canvas.translate(0, getWidth());
canvas.rotate(-90);
}
canvas.clipRect(0, 0, getWidth(), getHeight(), Android.graphics.Region.Op.REPLACE);
super.draw(canvas);
}
}
Par défaut, le texte pivoté est de haut en bas. Si vous définissez Android: gravity = "bottom", il est tracé de bas en haut.
Techniquement, il trompe TextView sous-jacent de penser que sa rotation est normale (permutation largeur/hauteur à quelques endroits), tout en la dessinant en rotation. Cela fonctionne bien également lorsqu'il est utilisé dans une mise en page XML.
EDIT: poster une autre version, ci-dessus a des problèmes avec les animations. Cette nouvelle version fonctionne mieux, mais perd certaines fonctionnalités de TextView, telles que Marquee et des spécialités similaires.
public class VerticalTextView extends TextView{
final boolean topDown;
public VerticalTextView(Context context, AttributeSet attrs){
super(context, attrs);
final int gravity = getGravity();
if(Gravity.isVertical(gravity) && (gravity&Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
setGravity((gravity&Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP);
topDown = false;
}else
topDown = true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
@Override
protected void onDraw(Canvas canvas){
TextPaint textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
textPaint.drawableState = getDrawableState();
canvas.save();
if(topDown){
canvas.translate(getWidth(), 0);
canvas.rotate(90);
}else {
canvas.translate(0, getHeight());
canvas.rotate(-90);
}
canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());
getLayout().draw(canvas);
canvas.restore();
}
}
Je l'ai implémenté pour mon projet ChartDroid . Créer VerticalLabelView.Java
:
public class VerticalLabelView extends View {
private TextPaint mTextPaint;
private String mText;
private int mAscent;
private Rect text_bounds = new Rect();
final static int DEFAULT_TEXT_SIZE = 15;
public VerticalLabelView(Context context) {
super(context);
initLabelView();
}
public VerticalLabelView(Context context, AttributeSet attrs) {
super(context, attrs);
initLabelView();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VerticalLabelView);
CharSequence s = a.getString(R.styleable.VerticalLabelView_text);
if (s != null) setText(s.toString());
setTextColor(a.getColor(R.styleable.VerticalLabelView_textColor, 0xFF000000));
int textSize = a.getDimensionPixelOffset(R.styleable.VerticalLabelView_textSize, 0);
if (textSize > 0) setTextSize(textSize);
a.recycle();
}
private final void initLabelView() {
mTextPaint = new TextPaint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(DEFAULT_TEXT_SIZE);
mTextPaint.setColor(0xFF000000);
mTextPaint.setTextAlign(Align.CENTER);
setPadding(3, 3, 3, 3);
}
public void setText(String text) {
mText = text;
requestLayout();
invalidate();
}
public void setTextSize(int size) {
mTextPaint.setTextSize(size);
requestLayout();
invalidate();
}
public void setTextColor(int color) {
mTextPaint.setColor(color);
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mTextPaint.getTextBounds(mText, 0, mText.length(), text_bounds);
setMeasuredDimension(
measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = text_bounds.height() + getPaddingLeft() + getPaddingRight();
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
result = Math.min(result, specSize);
}
}
return result;
}
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
mAscent = (int) mTextPaint.ascent();
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = text_bounds.width() + getPaddingTop() + getPaddingBottom();
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
result = Math.min(result, specSize);
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float text_horizontally_centered_Origin_x = getPaddingLeft() + text_bounds.width()/2f;
float text_horizontally_centered_Origin_y = getPaddingTop() - mAscent;
canvas.translate(text_horizontally_centered_Origin_y, text_horizontally_centered_Origin_x);
canvas.rotate(-90);
canvas.drawText(mText, 0, 0, mTextPaint);
}
}
Et en attrs.xml
:
<resources>
<declare-styleable name="VerticalLabelView">
<attr name="text" format="string" />
<attr name="textColor" format="color" />
<attr name="textSize" format="dimension" />
</declare-styleable>
</resources>
Un moyen d'y parvenir serait:
J'ai essayé les deux classes de VerticalTextView dans la réponse approuvée et elles ont fonctionné raisonnablement bien.
Mais peu importe ce que j'ai essayé, je ne pouvais pas positionner ces VerticalTextViews au centre de la disposition contenant (un RelativeLayout qui fait partie d'un élément gonflé pour un RecyclerView).
FWIW, après avoir regardé autour de moi, j'ai trouvé la classe VerticalTextView de yoog568 sur GitHub:
https://github.com/yoog568/VerticalTextView/blob/master/src/com/yoog/widget/VerticalTextView.Java
que j'ai pu positionner comme souhaité. Vous devez également inclure la définition d'attributs suivante dans votre projet:
https://github.com/yoog568/VerticalTextView/blob/master/res/values/attr.xml
check = (TextView)findViewById(R.id.check);
check.setRotation(-90);
Cela a fonctionné pour moi, très bien. Quant aux lettres descendant verticalement - je ne sais pas.
Il y a quelques petites choses auxquelles il faut faire attention.
Cela dépend du jeu de caractères lors du choix de la rotation ou du chemin. par exemple, si le jeu de caractères cible est anglais et que l'effet attendu ressemble à,
a
b
c
d
vous pouvez obtenir cet effet en dessinant chaque caractère un par un, sans rotation ni chemin.
vous aurez peut-être besoin d'une rotation ou d'un chemin pour obtenir cet effet.
la partie la plus délicate est lorsque vous essayez de rendre un jeu de caractères tel que mongol. le glyphe dans la police de caractères doit être pivoté de 90 degrés; drawTextOnPath () sera donc un bon candidat à utiliser.
Suite à la réponse de Pointer Null , j'ai pu centrer le texte horizontalement en modifiant la méthode onDraw
de cette façon:
@Override
protected void onDraw(Canvas canvas){
TextPaint textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
textPaint.drawableState = getDrawableState();
canvas.save();
if(topDown){
canvas.translate(getWidth()/2, 0);
canvas.rotate(90);
}else{
TextView temp = new TextView(getContext());
temp.setText(this.getText().toString());
temp.setTypeface(this.getTypeface());
temp.measure(0, 0);
canvas.rotate(-90);
int max = -1 * ((getWidth() - temp.getMeasuredHeight())/2);
canvas.translate(canvas.getClipBounds().left, canvas.getClipBounds().top - max);
}
canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());
getLayout().draw(canvas);
canvas.restore();
}
Vous devrez peut-être ajouter une partie de TextView mesurée à largeur pour centrer un texte multiligne.
Vous pouvez simplement ajouter à votre TextView ou à une autre valeur de rotation View xml. C’est le moyen le plus simple et, pour moi, de travailler correctement.
<LinearLayout
Android:rotation="-90"
Android:layout_below="@id/image_view_qr_code"
Android:layout_above="@+id/text_view_savva_club"
Android:layout_marginTop="20dp"
Android:gravity="bottom"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:orientation="vertical">
<TextView
Android:textColor="@color/colorPrimary"
Android:layout_marginStart="40dp"
Android:textSize="20sp"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:text="Дмитриевский Дмитрий Дмитриевич"
Android:maxLines="2"
Android:id="@+id/vertical_text_view_name"/>
<TextView
Android:textColor="#B32B2A29"
Android:layout_marginStart="40dp"
Android:layout_marginTop="15dp"
Android:textSize="16sp"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:id="@+id/vertical_text_view_phone"
Android:text="+38 (000) 000-00-00"/>
</LinearLayout>
J'ai bien aimé l'approche de @ kostmo. Je l'ai un peu modifié, car j'avais un problème: couper une étiquette tournée verticalement lorsque je définissais ses paramètres comme WRAP_CONTENT
. Ainsi, un texte n'était pas entièrement visible.
Voici comment je l'ai résolu:
import Android.annotation.TargetApi;
import Android.content.Context;
import Android.graphics.Canvas;
import Android.graphics.Paint;
import Android.graphics.Rect;
import Android.graphics.Typeface;
import Android.os.Build;
import Android.text.TextPaint;
import Android.util.AttributeSet;
import Android.util.Log;
import Android.util.TypedValue;
import Android.view.View;
import Android.view.ViewGroup;
import Android.widget.TextView;
public class VerticalLabelView extends View
{
private final String LOG_TAG = "VerticalLabelView";
private final int DEFAULT_TEXT_SIZE = 30;
private int _ascent = 0;
private int _leftPadding = 0;
private int _topPadding = 0;
private int _rightPadding = 0;
private int _bottomPadding = 0;
private int _textSize = 0;
private int _measuredWidth;
private int _measuredHeight;
private Rect _textBounds;
private TextPaint _textPaint;
private String _text = "";
private TextView _tempView;
private Typeface _typeface = null;
private boolean _topToDown = false;
public VerticalLabelView(Context context)
{
super(context);
initLabelView();
}
public VerticalLabelView(Context context, AttributeSet attrs)
{
super(context, attrs);
initLabelView();
}
public VerticalLabelView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
initLabelView();
}
@TargetApi(Build.VERSION_CODES.Lollipop)
public VerticalLabelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
initLabelView();
}
private final void initLabelView()
{
this._textBounds = new Rect();
this._textPaint = new TextPaint();
this._textPaint.setAntiAlias(true);
this._textPaint.setTextAlign(Paint.Align.CENTER);
this._textPaint.setTextSize(DEFAULT_TEXT_SIZE);
this._textSize = DEFAULT_TEXT_SIZE;
}
public void setText(String text)
{
this._text = text;
requestLayout();
invalidate();
}
public void topToDown(boolean topToDown)
{
this._topToDown = topToDown;
}
public void setPadding(int padding)
{
setPadding(padding, padding, padding, padding);
}
public void setPadding(int left, int top, int right, int bottom)
{
this._leftPadding = left;
this._topPadding = top;
this._rightPadding = right;
this._bottomPadding = bottom;
requestLayout();
invalidate();
}
public void setTextSize(int size)
{
this._textSize = size;
this._textPaint.setTextSize(size);
requestLayout();
invalidate();
}
public void setTextColor(int color)
{
this._textPaint.setColor(color);
invalidate();
}
public void setTypeFace(Typeface typeface)
{
this._typeface = typeface;
this._textPaint.setTypeface(typeface);
requestLayout();
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
try
{
this._textPaint.getTextBounds(this._text, 0, this._text.length(), this._textBounds);
this._tempView = new TextView(getContext());
this._tempView.setPadding(this._leftPadding, this._topPadding, this._rightPadding, this._bottomPadding);
this._tempView.setText(this._text);
this._tempView.setTextSize(TypedValue.COMPLEX_UNIT_PX, this._textSize);
this._tempView.setTypeface(this._typeface);
this._tempView.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
this._measuredWidth = this._tempView.getMeasuredHeight();
this._measuredHeight = this._tempView.getMeasuredWidth();
this._ascent = this._textBounds.height() / 2 + this._measuredWidth / 2;
setMeasuredDimension(this._measuredWidth, this._measuredHeight);
}
catch (Exception e)
{
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
Log.e(LOG_TAG, Log.getStackTraceString(e));
}
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if (!this._text.isEmpty())
{
float textHorizontallyCenteredOriginX = this._measuredHeight / 2f;
float textHorizontallyCenteredOriginY = this._ascent;
canvas.translate(textHorizontallyCenteredOriginY, textHorizontallyCenteredOriginX);
float rotateDegree = -90;
float y = 0;
if (this._topToDown)
{
rotateDegree = 90;
y = this._measuredWidth / 2;
}
canvas.rotate(rotateDegree);
canvas.drawText(this._text, 0, y, this._textPaint);
}
}
}
Si vous voulez avoir un texte de haut en bas, utilisez la méthode topToDown(true)
.