I need 2 ways of showing vertical label in Android:
Horizontal label turned 90 degrees counterclockwise (letters on the side)
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?
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)
}
}
}
I implemented this for my ChartDroid project. Create 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);
}
}
And in 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>
Tried both of the VerticalTextView classes in the approved answer, and they worked reasonably well.
But no matter what I tried, I was unable to position those VerticalTextViews in the center of the containing layout (a RelativeLayout which is part of an item inflated for a RecyclerView).
FWIW, after looking around, I found yoog568's VerticalTextView class on GitHub:
https://github.com/yoog568/VerticalTextView/blob/master/src/com/yoog/widget/VerticalTextView.java
which I was able to position as desired. You also need to include the following attributes definition in your project:
https://github.com/yoog568/VerticalTextView/blob/master/res/values/attr.xml
One way to achieve these would be:
Write your own custom view and override onDraw(Canvas). You can draw the text on the canvas and then rotate the canvas.
Same as 1. except this time use a Path and draw text using drawTextOnPath(...)
There are some minor things need to be pay attention on.
It depends on the charset when choosing the rotate or the path ways. for example, if the target charset is English like, and the expected effect looks like,
a
b
c
d
you can get this effect by drawing each character one by one, no rotate or path needed.
you may need rotate or path to get this effect.
the tricky part is when you try to render charset such like Mongolian. the glyph in the Typeface need to be rotated 90 degree, so drawTextOnPath() will be a good candidate to use.
check = (TextView)findViewById(R.id.check);
check.setRotation(-90);
This worked for me, just fine. As for the vertically going down letters - I dont' know.
Following Pointer Null's answer, I've been able to center the text horizontally by modifying the onDraw method this way:
#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();
}
You might need to add a portion of the TextView measuredWidth to center a multilined text.
You can just add to your TextView or other View xml rotation value. This is the easiest way and for me working correct.
<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>
My initial approach to rendering vertical text inside a vertical LinearLayout was as follows (this is Kotlin, in Java use setRoatation etc.):
val tv = TextView(context)
tv.gravity = Gravity.CENTER
tv.rotation = 90F
tv.height = calcHeight(...)
linearLabels.addView(tv)
As you can see the problem is that the TextView goes vertically but still treats its width as if it were oriented horizontally! =/
Thus approach #2 consisted of additionally switching width and height manually to account for this:
tv.measure(0, 0)
// tv.setSingleLine()
tv.width = tv.measuredHeight
tv.height = calcHeight(...)
This however resulted in the labels wrapping around to the next line (or being cropped if you setSingleLine) after the relatively short width. Again, this boils down to confusing x with y.
My approach #3 was thus to wrap the TextView in a RelativeLayout. The idea is to allow the TextView any width it wants by extending it far to the left and the right (here, 200 pixels in both directions). But then I give the RelativeLayout negative margins to ensure it is drawn as a narrow column. Here is my full code for this screenshot:
val tv = TextView(context)
tv.text = getLabel(...)
tv.gravity = Gravity.CENTER
tv.rotation = 90F
tv.measure(0, 0)
tv.width = tv.measuredHeight + 400 // 400 IQ
tv.height = calcHeight(...)
val tvHolder = RelativeLayout(context)
val lp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
lp.setMargins(-200, 0, -200, 0)
tvHolder.layoutParams = lp
tvHolder.addView(tv)
linearLabels.addView(tvHolder)
val iv = ImageView(context)
iv.setImageResource(R.drawable.divider)
linearLabels.addView(iv)
As a general tip, this strategy of having a view "hold" another view has been really useful for me in positioning things in Android! For example, the info window below the ActionBar uses the same tactic!
For text appearing like a store sign just insert newlines after each character, e.g. "N\nu\nt\ns" will be:
I liked #kostmo's approach. I modified it a bit, because I had an issue - cutting off vertically rotated label when I set its params as WRAP_CONTENT. Thus, a text was not fully visible.
This is how I solved it:
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);
}
}
}
If you want to have a text from top to down, then use topToDown(true) method.
I'm currently using a BluringView XML Object as gotten from the BlurringView.java file (https://github.com/500px/500px-android-blur). It's basically just a custom view that blurs a sibling view that's beneath it. The problem I'm having is that I can't populate the BlurringView with other objects. Here's the code:
package com.fivehundredpx.android.blur;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.support.v8.renderscript.Allocation;
import android.support.v8.renderscript.Element;
import android.support.v8.renderscript.RenderScript;
import android.support.v8.renderscript.ScriptIntrinsicBlur;
import android.util.AttributeSet;
import android.view.View;
/**
* A custom view for presenting a dynamically blurred version of another view's content.
* <p/>
* Use {#link #setBlurredView(android.view.View)} to set up the reference to the view to be blurred.
* After that, call {#link #invalidate()} to trigger blurring whenever necessary.
*/
public class BlurringView extends View {
public BlurringView(Context context) {
this(context, null);
}
public BlurringView(Context context, AttributeSet attrs) {
super(context, attrs);
final Resources res = getResources();
final int defaultBlurRadius = res.getInteger(R.integer.default_blur_radius);
final int defaultDownsampleFactor = res.getInteger(R.integer.default_downsample_factor);
final int defaultOverlayColor = res.getColor(R.color.default_overlay_color);
initializeRenderScript(context);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PxBlurringView);
setBlurRadius(a.getInt(R.styleable.PxBlurringView_blurRadius, defaultBlurRadius));
setDownsampleFactor(a.getInt(R.styleable.PxBlurringView_downsampleFactor,
defaultDownsampleFactor));
setOverlayColor(a.getColor(R.styleable.PxBlurringView_overlayColor, defaultOverlayColor));
a.recycle();
}
public void setBlurredView(View blurredView) {
mBlurredView = blurredView;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBlurredView != null) {
if (prepare()) {
// If the background of the blurred view is a color drawable, we use it to clear
// the blurring canvas, which ensures that edges of the child views are blurred
// as well; otherwise we clear the blurring canvas with a transparent color.
if (mBlurredView.getBackground() != null && mBlurredView.getBackground() instanceof ColorDrawable){
mBitmapToBlur.eraseColor(((ColorDrawable) mBlurredView.getBackground()).getColor());
}else {
mBitmapToBlur.eraseColor(Color.TRANSPARENT);
}
mBlurredView.draw(mBlurringCanvas);
blur();
canvas.save();
canvas.translate(mBlurredView.getX() - getX(), mBlurredView.getY() - getY());
canvas.scale(mDownsampleFactor, mDownsampleFactor);
canvas.drawBitmap(mBlurredBitmap, 0, 0, null);
canvas.restore();
}
canvas.drawColor(mOverlayColor);
}
}
public void setBlurRadius(int radius) {
mBlurScript.setRadius(radius);
}
public void setDownsampleFactor(int factor) {
if (factor <= 0) {
throw new IllegalArgumentException("Downsample factor must be greater than 0.");
}
if (mDownsampleFactor != factor) {
mDownsampleFactor = factor;
mDownsampleFactorChanged = true;
}
}
public void setOverlayColor(int color) {
mOverlayColor = color;
}
private void initializeRenderScript(Context context) {
mRenderScript = RenderScript.create(context);
mBlurScript = ScriptIntrinsicBlur.create(mRenderScript, Element.U8_4(mRenderScript));
}
protected boolean prepare() {
final int width = mBlurredView.getWidth();
final int height = mBlurredView.getHeight();
if (mBlurringCanvas == null || mDownsampleFactorChanged
|| mBlurredViewWidth != width || mBlurredViewHeight != height) {
mDownsampleFactorChanged = false;
mBlurredViewWidth = width;
mBlurredViewHeight = height;
int scaledWidth = width / mDownsampleFactor;
int scaledHeight = height / mDownsampleFactor;
// The following manipulation is to avoid some RenderScript artifacts at the edge.
scaledWidth = scaledWidth - scaledWidth % 4 + 4;
scaledHeight = scaledHeight - scaledHeight % 4 + 4;
if (mBlurredBitmap == null
|| mBlurredBitmap.getWidth() != scaledWidth
|| mBlurredBitmap.getHeight() != scaledHeight) {
mBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight,
Bitmap.Config.ARGB_8888);
if (mBitmapToBlur == null) {
return false;
}
mBlurredBitmap = Bitmap.createBitmap(scaledWidth, scaledHeight,
Bitmap.Config.ARGB_8888);
if (mBlurredBitmap == null) {
return false;
}
}
mBlurringCanvas = new Canvas(mBitmapToBlur);
mBlurringCanvas.scale(1f / mDownsampleFactor, 1f / mDownsampleFactor);
mBlurInput = Allocation.createFromBitmap(mRenderScript, mBitmapToBlur,
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
mBlurOutput = Allocation.createTyped(mRenderScript, mBlurInput.getType());
}
return true;
}
protected void blur() {
mBlurInput.copyFrom(mBitmapToBlur);
mBlurScript.setInput(mBlurInput);
mBlurScript.forEach(mBlurOutput);
mBlurOutput.copyTo(mBlurredBitmap);
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mRenderScript != null){
mRenderScript.destroy();
}
}
private int mDownsampleFactor;
private int mOverlayColor;
private View mBlurredView;
private int mBlurredViewWidth, mBlurredViewHeight;
private boolean mDownsampleFactorChanged;
private Bitmap mBitmapToBlur, mBlurredBitmap;
private Canvas mBlurringCanvas;
private RenderScript mRenderScript;
private ScriptIntrinsicBlur mBlurScript;
private Allocation mBlurInput, mBlurOutput;
}
Is there any way to modify the BlurringView.java class so the BlurringView xml object can be used like a RelativeView or something similar? I want to be able to have the BlurringView be the parent to other objects.
I need this, because I'm using the BlurringView together with a SlidingUpPanelLayout (https://github.com/umano/AndroidSlidingUpPanel) and the SlidingUpPanelLayout only allows two children for it to function. Thus I can't just overlay items over the BlurringView. I need the BlurringView to be their parent.
Please let me know if you need clarification.
-R
Blockquote Did you try to do an extract method of your constructor, implement the same methon in each of the three default constructors and extend from RelativeLayout?
The RelativeLayout actually have a onDraw method, maybe isn't being called because you didn't invoke the method setWillNotDraw(false), try using that method to trigger the onDraw or use an invalidate() at the end of the constructors.
#astinx answered this question.
All I had to do was change the BlurringView class to extend a RelativeLayout instead of a View. I then called the setWillNotDraw(false) method as well as the invalidate() method in the constructor and now I'm able to populate the BlurringView with children! Magic.
-R
I know its possible to paint the background of canvas using
mPaint = new Paint();
mPaint.setColor(Color.RED);
Im just wondering how to i set a permanent background for it. Ive tried using the xml file but nothing happens. Any ideas?
This is the source code of the project, ive been following a tutorial how to do it because im fairly unfamiliar with bitmaps.
Canvas Class
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
public class GameBoard extends View{
private int mFlagX = -1;
private int mFlagY = -1;
private Bitmap mBitmap = null;
private Bitmap nBitmap = null;
private Paint mPaint = null;
private boolean isFlagHidden = false;
private int mBoundX = -1;
private int mBoundY = -1;
//play with these values to make the app more or less challenging
public final int CLOSER = 50;
public final int CLOSE = 100;
public GameBoard(Context context, AttributeSet aSet) {
super(context, aSet);
//load our bitmap
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.star);
//create a paint brush
mPaint = new Paint();
mPaint.setColor(Color.RED);
}
#Override
public void onDraw(Canvas canvas) {
//initialize
if ((mFlagX < 1) || (mFlagY < 1)) {
mFlagX = (int) (getWidth() / 2) - mBitmap.getWidth() / 2;
mFlagY = (int) (getHeight() / 2) - mBitmap.getHeight() / 2;
mBoundX = (int)getWidth() - mBitmap.getWidth();
mBoundY = (int)getHeight() - mBitmap.getHeight();
}
//draw background
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
//draw the flag
if (!isFlagHidden) {
canvas.drawBitmap(mBitmap, mFlagX, mFlagY, null);
}
}
public void hideTheFlag(){
//randomize flag location
mFlagX = (int) Math.ceil(Math.random() * mBoundX);
mFlagY = (int) Math.ceil(Math.random() * mBoundY);
isFlagHidden = true;
//force redraw
invalidate();
}
public void giveUp(){
isFlagHidden = false;
//force redraw
invalidate();
}
public Indicators takeAGuess(float x, float y) {
//this is our "warm" area
Rect prettyClose = new Rect(mFlagX - CLOSE, mFlagY - CLOSE, mFlagX+mBitmap.getWidth() + CLOSE, mFlagY+mBitmap.getHeight() + CLOSE);
//normalize
if (prettyClose.left < 0) prettyClose.left = 0;
if (prettyClose.top < 0) prettyClose.top = 0;
if (prettyClose.right > mBoundX) prettyClose.right = mBoundX;
if (prettyClose.bottom > mBoundY) prettyClose.bottom = mBoundY;
//this is our "hot" area
Rect reallyClose = new Rect(mFlagX - CLOSER, mFlagY - CLOSER, mFlagX+mBitmap.getWidth() + CLOSER, mFlagY+mBitmap.getHeight() + CLOSER);
//normalize
if (reallyClose.left < 0) reallyClose.left = 0;
if (reallyClose.top < 0) reallyClose.top = 0;
if (reallyClose.right > mBoundX) reallyClose.right = mBoundX;
if (reallyClose.bottom > mBoundY) reallyClose.bottom = mBoundY;
//this is the area that contains our flag
Rect bullsEye = new Rect(mFlagX, mFlagY, mFlagX+mBitmap.getWidth(), mFlagY+mBitmap.getHeight());
//check to see where on the board the user pressed
if (bullsEye.contains((int) x, (int)y)) {
//found it
isFlagHidden = false;
invalidate();
return Indicators.BULLSEYE;
} else if (reallyClose.contains((int) x, (int)y)) {
//hot
return Indicators.HOT;
} else if (prettyClose.contains((int)x, (int)y)) {
//warm
return Indicators.WARM;
} else {
//not even close
return Indicators.COLD;
}
}
}
Game Class
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.TextView;
public class FindTheStar extends Activity implements OnTouchListener, OnClickListener{
private GameBoard mGameBoard = null;
private boolean isFlagHidden = false;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_star);
mGameBoard = (GameBoard) findViewById(R.id.Hide_canvas);
mGameBoard.setOnTouchListener(this);
Button b = (Button) findViewById(R.id.the_button);
b.setOnClickListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.Hide_canvas) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (isFlagHidden) {
TextView tv = (TextView)findViewById (R.id.the_label);
switch (mGameBoard.takeAGuess(event.getX(), event.getY())) {
case BULLSEYE:
Button b = (Button) findViewById(R.id.the_button);
isFlagHidden = false;
b.setText("Go Hide!");
tv.setText("You found me!");
tv.setTextColor(Color.GREEN);
break;
case HOT:
tv.setText("You're hot!");
tv.setTextColor(Color.RED);
break;
case WARM:
tv.setText("Getting warm...");
tv.setTextColor(Color.YELLOW);
break;
case COLD:
tv.setText("You're cold.");
tv.setTextColor(Color.BLUE);
break;
}
}
}
return true;
}
return false;
}
#Override
public void onClick(View v) {
if (v.getId() == R.id.the_button) {
TextView tv = (TextView)findViewById (R.id.the_label);
tv.setText("");
Button b = (Button) findViewById(R.id.the_button);
isFlagHidden = !isFlagHidden;
if (isFlagHidden) {
b.setText("Can't find me?");
mGameBoard.hideTheFlag();
} else {
b.setText("Go Hide!");
mGameBoard.giveUp();
}
}
}
}
XML File
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="#+id/the_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:textSize="20sp"
android:layout_marginBottom="10dip"
android:text="Lets Play Hide and Seek!"/>
<Button
android:id="#+id/the_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:layout_marginBottom="10dip"
android:text="Go Hide!"/>
<app.autismapp.GameBoard
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/Hide_canvas"/>
</LinearLayout>
yes you can set your permanent background using xml layout..i done this by creating two class.
this is my code in MainACtivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final BrushView view=new BrushView(this);
setContentView(R.layout.mylayout);//removed this one if the paint doesnt work
view.setBackgroundResource(R.drawable.background);//to set background
setContentView(view);// to display the background
and my second class
public class PaintView extends View {
private Paint paint = new Paint();
public LayoutParams params;
public PaintView(Context context) {
super(context);
paint.setAntiAlias(true);
paint.setColor(Color.BLUE);
i hope it gives you an idea
How do I implement background scrolling with a gridview? If it sounds vague, I mean like implementing a bookshelf using a gridview where the shelf image is attached to an item in the gridview.
It took me forever to figure this out, so for everybody trying to do this, here's the code from my e-book reader.
It's based on Shelves by Romain Guy so he deserves the credit for the original code.
package net.nightwhistler.pageturner.view;
import net.nightwhistler.pageturner.R;
import net.nightwhistler.pageturner.library.LibraryBook;
import net.nightwhistler.pageturner.library.QueryResult;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.GridView;
public class BookCaseView extends GridView {
private Bitmap background;
private int mShelfWidth;
private int mShelfHeight;
private QueryResult<LibraryBook> result;
private LibraryBook selectedBook;
public BookCaseView(Context context, AttributeSet attributes) {
super(context, attributes);
this.setFocusableInTouchMode(true);
this.setClickable(false);
final Bitmap shelfBackground = BitmapFactory.decodeResource(context.getResources(),
R.drawable.shelf_single);
setBackground(shelfBackground);
this.setFocusable(true);
}
public void setBackground(Bitmap background) {
this.background = background;
mShelfWidth = background.getWidth();
mShelfHeight = background.getHeight();
}
protected void onClick( int bookIndex ) {
LibraryBook book = this.result.getItemAt(bookIndex);
this.selectedBook = book;
invalidate();
}
#Override
protected void dispatchDraw(Canvas canvas) {
final int count = getChildCount();
final int top = count > 0 ? getChildAt(0).getTop() : 0;
final int shelfWidth = mShelfWidth;
final int shelfHeight = mShelfHeight;
final int width = getWidth();
final int height = getHeight();
final Bitmap background = this.background;
for (int x = 0; x < width; x += shelfWidth) {
for (int y = top; y < height; y += shelfHeight) {
canvas.drawBitmap(background, x, y, null);
}
//This draws the top pixels of the shelf above the current one
Rect source = new Rect(0, mShelfHeight - top, mShelfWidth, mShelfHeight);
Rect dest = new Rect(x, 0, x + mShelfWidth, top );
canvas.drawBitmap(background, source, dest, null);
}
super.dispatchDraw(canvas);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ( keyCode == KeyEvent.KEYCODE_BACK && this.selectedBook != null ) {
this.selectedBook = null;
invalidate();
return true;
}
return false;
}
}
What I did was to split my background image in n gridview columns and in my gridview getView method add the view background according to the position in the grid.
It worked perfectly.
If you want the code just ask.
My previous answer only adds the background but doesn't let it scroll with the items. what you wan is NightWhistler's answer :)
Sorry for misinterpreting the question.
I have a small test app on Android that is meant to test tracking multitouch input, but I am only ever getting two touches at the same time on my Evo. Does anyone know if this is a limitation to Android or the hardware?
By the way, here's my test class so you can try it out yourself.
import java.util.HashMap;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.view.MotionEvent;
import android.view.View;
public class PressureView extends View
{
private HashMap<Integer, Spot> mSpots = new HashMap<Integer, Spot>();
private final int[] mColors;
private final Paint mPaint;
public PressureView(Context context)
{
super(context);
mPaint = new Paint();
mPaint.setStyle(Style.FILL);
mColors = new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.MAGENTA};
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
for(int id : mSpots.keySet())
{
Spot spot = mSpots.get(id);
mPaint.setColor(spot.Color);
canvas.drawCircle(spot.X, spot.Y, spot.Pressure*500, mPaint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
System.out.println("************************** " + event.getPointerCount() + " Pointers");
for(int i = 0; i < event.getPointerCount(); i++)
{
int id = event.getPointerId(i);
Spot spot = null;
if(mSpots.containsKey(id))
{
spot = mSpots.get(id);
}
else
{
spot = new Spot();
spot.Color = mColors[mSpots.size()];
}
if(event.getAction() == MotionEvent.ACTION_UP) spot.Pressure = 0;
else spot.Pressure = event.getPressure(id);
spot.X = event.getX(id);
spot.Y = event.getY(id);
mSpots.put(id, spot);
}
invalidate();
return true;
}
private class Spot
{
public float X, Y, Pressure;
public int Color;
}
}
It seems that currently all HTC devices only can 2 finger multi-touch, but the Android SDK supports more fingers. E.g. the Galaxy S i9000 has support for more http://www.youtube.com/watch?v=KRCDRXYJBCY .