Asked  6 Months ago    Answers:  5   Viewed   63 times

I need 2 ways of showing vertical label in Android:

  1. Horizontal label turned 90 degrees counterclockwise (letters on the side)
  2. Horizontal label with letters one under the other (like a store sign)

Do I need to develop custom widgets for both cases (one case), can I make TextView to render that way, and what would be a good way to do something like that if I need to go completely custom?

 Answers

39

Here is my elegant and simple vertical text implementation, extending TextView. This means that all standard styles of TextView may be used, because it is extended TextView.

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

By default, rotated text is from top to bottom. If you set android:gravity="bottom", then it's drawn from bottom to top.

Technically, it fools underlying TextView to think that it's normal rotation (swapping width/height in few places), while drawing it rotated. It works fine also when used in an xml layout.

EDIT: posting another version, above has problems with animations. This new version works better, but loses some TextView features, such as marquee and similar specialties.

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

EDIT Kotlin version:

import android.content.Context
import android.graphics.Canvas
import android.text.BoringLayout
import android.text.Layout
import android.text.TextUtils.TruncateAt
import android.util.AttributeSet
import android.view.Gravity
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.graphics.withSave

class VerticalTextView(context: Context, attrs: AttributeSet) : AppCompatTextView(context, attrs) {
    private val topDown = gravity.let { g ->
        !(Gravity.isVertical(g) && g.and(Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM)
    }
    private val metrics = BoringLayout.Metrics()
    private var padLeft = 0
    private var padTop = 0

    private var layout1: Layout? = null

    override fun setText(text: CharSequence, type: BufferType) {
        super.setText(text, type)
        layout1 = null
    }

    private fun makeLayout(): Layout {
        if (layout1 == null) {
            metrics.width = height
            paint.color = currentTextColor
            paint.drawableState = drawableState
            layout1 = BoringLayout.make(text, paint, metrics.width, Layout.Alignment.ALIGN_NORMAL, 2f, 0f, metrics, false, TruncateAt.END, height - compoundPaddingLeft - compoundPaddingRight)
            padLeft = compoundPaddingLeft
            padTop = extendedPaddingTop
        }
        return layout1!!
    }

    override fun onDraw(c: Canvas) {
        //      c.drawColor(0xffffff80); // TEST
        if (layout == null)
            return
        c.withSave {
            if (topDown) {
                val fm = paint.fontMetrics
                translate(textSize - (fm.bottom + fm.descent), 0f)
                rotate(90f)
            } else {
                translate(textSize, height.toFloat())
                rotate(-90f)
            }
            translate(padLeft.toFloat(), padTop.toFloat())
            makeLayout().draw(this)
        }
    }
}
Tuesday, June 1, 2021
 
kwhohasamullet
answered 6 Months ago
61

Actually, in this case it's quite simple: apply the vertical align to the image. Since it's all in one line, it's really the image you want aligned, not the text.

<!-- moved "vertical-align:middle" style from span to img -->
<div>
  <img style="vertical-align:middle" src="https://via.placeholder.com/60x60">
  <span style="">Works.</span>
</div>

Tested in FF3.

Now you can use flexbox for this type of layout.

.box {
   display: flex;
   align-items:center;
}
<div class="box">
    <img src="https://via.placeholder.com/60x60">
    <span style="">Works.</span>
</div>
Tuesday, June 1, 2021
 
alez
answered 6 Months ago
46

This UI Pattern is currently being referred to as Side Navigation and discussed in some detail here:

http://www.androiduipatterns.com/2012/06/emerging-ui-pattern-side-navigation.html

I think this is what you are looking for :)

https://github.com/korovyansk/android-fb-like-slideout-navigation

The side navigation isn't (yet) included in the Android SDK. A quick search in github does reveal one project that has implemented the UI pattern.

Here's another library project:

https://github.com/darvds/RibbonMenu

Two more library project:

https://bitbucket.org/jfeinstein10/slidingmenu/overview
https://github.com/Gregadeaux/android-fly-in-app-navigation

Hope this helps :)

Monday, August 9, 2021
 
tuckerjt07
answered 4 Months ago
49

You can achieve this by using the following code:

getTabWidget().setOrientation(LinearLayout.VERTICAL);

source

Saturday, August 21, 2021
 
mattltm
answered 4 Months ago
37

You can use the following solution using flexbox:

* {
  margin:0;
  padding:0;
  box-sizing:border-box;
}
button {
  align-items: center;
  display: flex;
  padding: 3px;
  justify-content: center;
  width: 300px;
}
button img {
  flex-shrink: 0;
  margin-right: 10px;
}
<button>
  <img src="https://lorempixel.com/50/50/" alt="">
  <span>Some Text Some Text Some Text Some Text Some Text Some Text Some Text</span>
</button>
<button>
  <img src="https://lorempixel.com/50/50/" alt="">
  <span>Some Text</span>
</button>
<button>
  <img src="https://lorempixel.com/50/50/" alt="">
</button>
Monday, August 30, 2021
 
user9891428
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share