Smooth progressbar from textview - android

I have a class progress bar. Depending on the set value is partially fills the text with a different color. I want when I change the current value progress filling was going smoothly from one value to another for some time, rather than by a jump, as it is now. How can I achieve that?
Code of my TextProgressBar below:
package com.example.app.customviews;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.widget.TextView;
import com.example.app.R;
public class TextProgressBar extends TextView {
private final int minValue = 0;
private int maxValue = 100;
private int currentValue = 0;
private float onePercentLenght = 1;
private int[] colors = {Color.WHITE, Color.BLACK};
private float[] pos = {0,1};
public TextProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.TextProgressBar,
0, 0);
try {
maxValue = a.getInt(R.styleable.TextProgressBar_MaxProgress, 100);
currentValue = a.getInteger(R.styleable.TextProgressBar_CurrentProgress, 0);
colors[0] = a.getColor(R.styleable.TextProgressBar_ProgressColor, Color.WHITE);
colors[1] = a.getColor(R.styleable.TextProgressBar_MainColor, Color.BLACK);
} finally {
a.recycle();
}
initShader();
}
public TextProgressBar(Context context) {
super(context);
initShader();
}
#Override
protected void onDraw(Canvas canvas) {
onePercentLenght = getWidth() / maxValue;
super.onDraw(canvas);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
initShader();
}
public void setProgress(int progress) {
if(progress < minValue) {
currentValue = minValue;
}
else if( progress > maxValue) {
currentValue = maxValue;
}
else {
currentValue = progress;
}
initShader();
invalidate();
}
public void setMaxProgress(int progress) {
if(progress>minValue) {
maxValue = progress;
}
else {
maxValue = minValue;
}
invalidate();
}
public int getMaxProgress() {
return maxValue;
}
public int getProgress() {
return currentValue;
}
private void initShader() {
float x = 0, dx = (currentValue+1) * onePercentLenght;
if(dx < 0) {
dx = 0;
}
if(dx - 1 < 0) {
x = 0;
}
else {
x = dx - 1;
}
Shader textShader = new LinearGradient(x, 0, dx, 0, colors, pos, TileMode.CLAMP);
getPaint().setShader(textShader);
}
}

I tested following approach triggering progress following way:
10, 30 with 150ms delay, 40 with 250ms delay, 80 with 50ms delay, and 100 with 450ms delay. One little weird thing is that it seems the drawing happens with little bit delay, although log print is on time, not sure if this is about my device. But this could give you something to start with. Adjusting the MAX_INTERVAL_TIME you can get it smoother depending how often you are settings the progress. If the callback is something you get from network, then it's nearly possible to get it all smooth since you can't predict the progress.
private static final int MAX_INTERVAL_TIME = 500;
private CountDownTimer mCountDownTimer;
public void setProgress(final int progress) {
final int startWidth = currentValue == maxValue ? 0 : currentValue;
final int endWidth = (int) (maxValue * ((float) progress / maxValue));
if (progress < minValue) {
currentValue = minValue;
} else if (progress > maxValue) {
currentValue = maxValue;
} else {
currentValue = progress;
}
if (mCountDownTimer != null) {
mCountDownTimer.cancel();
}
mCountDownTimer = new CountDownTimer(MAX_INTERVAL_TIME, 10) {
#Override
public void onTick(long millisUntilFinished) {
float interpolatedTime = 1 - ((float) millisUntilFinished / MAX_INTERVAL_TIME);
currentValue = startWidth
+ ((int) ((endWidth - startWidth) * interpolatedTime));
initShader();
invalidate();
}
#Override
public void onFinish() {
if (progress == maxValue) {
currentValue = maxValue;
initShader();
invalidate();
}
}
}.start();
}
If you know the time you wan't to animate the progress you could simply try following with your existing code:
final TextProgressBar text = (TextProgressBar) getView().findViewById(
R.id.test);
final int animTime = 1000;
new CountDownTimer(animTime, 200) {
#Override
public void onTick(long millisUntilFinished) {
float progress = (1 - ((float) millisUntilFinished / (float) animTime))
* text.getMaxProgress();
text.setProgress((int) progress);
}
#Override
public void onFinish() {
text.setProgress(text.getMaxProgress());
}
}.start();

Related

Android memory leak Custom view

I'm trying to make small App. This App have Activity, Custom View Class, and service.
1) Activity ask service for new Data and redraw Custom view
2) Service is listning to Bluetooth device and parse data.
Everything was fine, but I noticed that App is slowing down after 40 mins working.
I made another project remove service and find that it slowing too! So problem is my Customview class, maybe i have memory leaks in service to... but i have problem with drawings 100%.
I found that i have some objects that i'm creating on onDraw() method.. i try to move all thise staff to onSizeChanged() - but get more lags.
And now i need help. I need some example with simple drawings that depends on device width and height (I think my method is wrong - i use proportions of my 'Design' to calculate demetions in px)
By the way i'm using animator which make animations more smooth))
public class Dynamics {
/**
* Used to compare floats, if the difference is smaller than this, they are
* considered equal
*/
private static final float TOLERANCE = 0.01f;
/** The position the dynamics should to be at */
private float targetPosition;
/** The current position of the dynamics */
private float position;
/** The current velocity of the dynamics */
private float velocity;
/** The time the last update happened */
private long lastTime;
/** The amount of springiness that the dynamics has */
private float springiness;
/** The damping that the dynamics has */
private double damping;
public Dynamics(float springiness, float dampingRatio) {
this.springiness = springiness;
this.damping = dampingRatio * 2 * Math.sqrt(springiness);
}
public void setPosition(float position, long now) {
this.position = position;
lastTime = now;
}
public void setVelocity(float velocity, long now) {
this.velocity = velocity;
lastTime = now;
}
public void setTargetPosition(float targetPosition, long now) {
this.targetPosition = targetPosition;
lastTime = now;
}
public void update(long now) {
float dt = Math.min(now - lastTime, 50) / 1000f;
float x = position - targetPosition;
double acceleration = -springiness * x - damping * velocity;
velocity += acceleration * dt;
position += velocity * dt;
lastTime = now;
}
public boolean isAtRest() {
final boolean standingStill = Math.abs(velocity) < TOLERANCE;
final boolean isAtTarget = (targetPosition - position) < TOLERANCE;
return standingStill && isAtTarget;
}
public float getPosition() {
return position;
}
public float getTargetPos() {
return targetPosition;
}
public float getVelocity() {
return velocity;
}
}
In my Custom view i have this to set new data:
public void SetData(int[] NewData2,float[]newDatapoints)
{
this.NewData=NewData2;
long now = AnimationUtils.currentAnimationTimeMillis();
if (datapoints == null || datapoints.length != newDatapoints.length) {
datapoints = new Dynamics[newDatapoints.length];
for (int i = 0; i < newDatapoints.length; i++) {
datapoints[i] = new Dynamics(70f, 0.50f);
datapoints[i].setPosition(newDatapoints[i], now);
datapoints[i].setTargetPosition(newDatapoints[i], now);
}
invalidate();
} else {
for (int i = 0; i < newDatapoints.length; i++) {
datapoints[i].setTargetPosition(newDatapoints[i], now);
}
removeCallbacks(animator);
post(animator);
}
LastData=NewData;
//redraw();
}
Thise is "code" of my custom view, after all changes it's look terible, so i cut 90% of it. And i make some test code insted:
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ComposeShader;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.animation.AnimationUtils;
import java.io.IOException;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Random;
public class CustomDisplayView extends View {
//paint for drawing custom view
private Paint RectPaint = new Paint();
//Динамические данные float
private Dynamics[] datapoints;
//Динамические статические Int
private int[] NewData = new int[500];
//созадем новый объект квадрат
private RectF rectf= new RectF();
//Задаем массив динамических цветов
int[] CurColors= new int[100];
int[] TargetColors= new int[100];
public CustomDisplayView(Context context, AttributeSet attrs){
super(context, attrs);
//Установка парметров красок
RectPaint.setAntiAlias(true);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != 0 && h != 0) {
//create Bitmap here
}
}
/**
* Override the onDraw method to specify custom view appearance using canvas
*/
#Override
protected void onDraw(Canvas canvas) {
//Get Screen size
//int viewWidth=this.getMeasuredWidth();
// int viewHeight = this.getMeasuredHeight();
//Выводим код цвета
RectPaint.setColor(0xff000000);
RectPaint.setTextSize(40);
canvas.restore();
canvas.drawText("V: " + datapoints[1].getPosition(), 20, 60, RectPaint);
//int saveCount = canvas.save();
for(int a=0;a<1000;a++)
{
rectf.set(datapoints[a].getPosition(), datapoints[a + 1].getPosition(), datapoints[a].getPosition() + datapoints[a + 1].getPosition() / 10, datapoints[a + 1].getPosition() + datapoints[a + 1].getPosition() / 10);
RectPaint.setColor(0x88005020);
//RectPaint.setColor(CurColor[a]);
//canvas.rotate(datapoints[1].getPosition(), viewWidth/2, viewHeight/2);
canvas.drawRoundRect(rectf, 0, 0, RectPaint);
//canvas.restore();
}
//canvas.restoreToCount(saveCount);
/*
for(int a=0;a<999;a++)
{
CurColors[a]=progressiveColor(CurColors[a], TargetColors[a], 2);
if(CurColors[a]==TargetColors[a])
{
TargetColors[a]=randomColor();
}
}
*/
canvas.restore();
}
//Рандом колор
public static int randomColor(){
Random random = new Random();
int[] ColorParams= new int[4];
ColorParams[0]=random.nextInt(235)+20;
ColorParams[1]=random.nextInt(255);
ColorParams[2]=random.nextInt(255);
ColorParams[3]=random.nextInt(255);
return Color.argb(ColorParams[0], ColorParams[1], ColorParams[2], ColorParams[3]);
}
//Интерполяция цвета
public static int progressiveColor(int CurColor,int TargetColor,int Step){
//Current color
int[] ColorParams= new int[4];
ColorParams[0]=(CurColor >> 24) & 0xFF;
ColorParams[1]=(CurColor >> 16) & 0xFF;
ColorParams[2]=(CurColor >> 8) & 0xFF;
ColorParams[3]=CurColor & 0xFF;
//TargetColor
int[] TargetColorParams= new int[4];
TargetColorParams[0]=(TargetColor >> 24) & 0xFF;
TargetColorParams[1]=(TargetColor >> 16) & 0xFF;
TargetColorParams[2]=(TargetColor >> 8) & 0xFF;
TargetColorParams[3]=TargetColor & 0xFF;
for(int i=0;i<4;i++)
{
if(ColorParams[i]<TargetColorParams[i])
{
ColorParams[i]+=Step;
if(ColorParams[i]>TargetColorParams[i])
{
ColorParams[i]=TargetColorParams[i];
}
}
else if(ColorParams[i]>TargetColorParams[i])
{
ColorParams[i]-=Step;
if(ColorParams[i]<TargetColorParams[i])
{
ColorParams[i]=TargetColorParams[i];
}
}
}
//int red = r - (int)((float)(r*255)/(float)all);
//int green = (int)((float)(g*255)/(float)all);
return Color.argb(ColorParams[0], ColorParams[1], ColorParams[2], ColorParams[3]);
//return String.format("#%06X", (0xFFFFFF & Color.argb(ColorParams[0], ColorParams[1], ColorParams[2], ColorParams[3])));
//return " "+opacity+" "+red+" "+green+" "+blue;
}
//each custom attribute should have a get and set method
//this allows updating these properties in Java
//we call these in the main Activity class
/**
* Get the current text label color
* #return color as an int
*/
public int getLabelColor(){
return 1;
}
/**
* Set the label color
* #param newColor new color as an int
*/
public void setLabelColor(int newColor){
//update the instance variable
//labelCol=newColor;
//redraw the view
invalidate();
requestLayout();
}
public void redraw(){
//redraw the view
invalidate();
requestLayout();
}
public void SetData(int[] NewData2,float[]newDatapoints)
{
this.NewData=NewData2;
long now = AnimationUtils.currentAnimationTimeMillis();
if (datapoints == null || datapoints.length != newDatapoints.length) {
datapoints = new Dynamics[newDatapoints.length];
for (int i = 0; i < newDatapoints.length; i++) {
datapoints[i] = new Dynamics(70f, 0.50f);
datapoints[i].setPosition(newDatapoints[i], now);
datapoints[i].setTargetPosition(newDatapoints[i], now);
}
invalidate();
} else {
for (int i = 0; i < newDatapoints.length; i++) {
datapoints[i].setTargetPosition(newDatapoints[i], now);
}
removeCallbacks(animator);
post(animator);
}
//redraw();
}
public int GetAction(float x,float y)
{
/*
if(x>(DicsCenterX-LineHalfSpeedZone) && x<(DicsCenterX+LineHalfSpeedZone) && y>(DicsCenterY-PowerOutRadius) && y<(DicsCenterY-SpeedZoneRadius2))
{
// private int SpeedZoneRadius2=0;
// private int PowerOutRadius=0;
//Смена режима
//начинаем смену размеру index / ms
ChangeVal(0,700);
return 1;
}
else if(x>(CofCantBGDrop*2) && x<(CofCantBGDrop*4) && y>(DicsCenterY-PowerOutRadius) && y<(DicsCenterY-SpeedZoneRadius2))
{
return 2;
}
else
{
return 0;
}
//return 0;
*/
return 1;
}
public static String fmt(double d)
{
double val = d/100;
String result;
if(val == (long) val)
result= String.format("%d",(long)d);
else
result= String.format("%s",d);
if(result.length()<2)
{
String result2=result;
result="0"+result2;
}
return result;
}
private Runnable animator = new Runnable() {
#Override
public void run() {
boolean needNewFrame = false;
long now = AnimationUtils.currentAnimationTimeMillis();
for (Dynamics dynamics : datapoints) {
dynamics.update(now);
if (!dynamics.isAtRest()) {
needNewFrame = true;
}
}
if (needNewFrame) {
postDelayed(this, 15);
}
invalidate();
}
};
}
I just want to understand where i need to declare scale values, where i need to calc real dimensions in px.. and et.c. to have no memory leaks..
If i remove color change and incrice number of Rects up to 1000 - i get lags.
All methods o any information how to debug memory leaks - you are wellcome!
You have to remove runnable for animation when view is detached.
if (needNewFrame) {
postDelayed(this, 15); <--- memory leak
}
Try like this.
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
removeCallback(your runnable);
}
Also refreshing every 15 milliseconds is very heavy.

Android TextView: Phantom Padding When Padding Set To 0

I have TextView:
text.setTypeface(font);
text.setWidth((int)width);
text.setGravity(Gravity.CENTER);
text.setHeight((int)height);
text.setIncludeFontPadding(false);
text.setPadding(0,0,0,0);
However even with the padding set to 0, and setIncludeFontPadding to false and I set the font size to the height of the TextView I still get this:
If I set the padding to have negative values like -30, It fixes it:
text.setPadding(0,-30,0,-30);
My question is why do I have to do this? Where is this phantom padding coming from? And where am I finding this arbitrary (-30 in this specific case) value I need to set it to, so that the text fills the height?
Update
Trying #Mike M. 's solution I got these results. When it was a positive number, it was a smaller size than when it was a negative one. Both of them still had padding:
Update 2 Here the custom class in full:
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.text.Layout;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.widget.TextView;
/**
* Special textview which gracefully handles resizing.
*/
public class iTextView extends TextView
{
// Set true to remove phantom padding
public boolean trimPadding = false;
//region Interfaces
private interface SizeTester
{
/**
* Interface for scaling text to fit.
* #param suggestedSize Size of text to be tested.
* #param availableSpace Available space in which text must fit.
* #return An integer < 0 if after applying {#code suggestedSize} to
* text, it takes less space than {#code availableSpace}, > 0
* otherwise.
*/
int onTestSize(int suggestedSize, RectF availableSpace);
}
//endregion
//region Variables
private static final int NO_LINE_LIMIT = -1;
private RectF _textRect = new RectF();
private RectF _availableSpaceRect;
private SparseIntArray _textCachedSizes;
private TextPaint _paint;
private float _maxTextSize;
private float _spacingMult;
private float _spacingAdd;
private float _minTextSize;
private int _widthLimit;
private int _maxLines;
private boolean _enableSizeCache;
private boolean _initialized;
//endregion
//region Constructors
public iTextView(Context context)
{
super(context);
initialize();
}
public iTextView(Context context, AttributeSet attrs)
{
super(context, attrs);
initialize();
}
public iTextView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
initialize();
}
//endregion
//region Initialization
private void initialize()
{
_spacingMult = 1.0f;
_spacingAdd = 0.0f;
_minTextSize = 20;
_enableSizeCache = true;
_paint = new TextPaint(getPaint());
_maxTextSize = getTextSize();
_availableSpaceRect = new RectF();
_textCachedSizes = new SparseIntArray();
if (_maxLines == 0)
{
// No value was assigned during construction
_maxLines = NO_LINE_LIMIT;
}
_initialized = true;
}
//endregion
//region Text Value
#Override
public void setText(final CharSequence text, BufferType type)
{
super.setText(text, type);
adjustTextSize(text.toString());
}
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before,
final int after)
{
super.onTextChanged(text, start, before, after);
reAdjust();
}
//endregion
//region Text Sizing
#Override
public void setTextSize(float size)
{
_maxTextSize = size;
_textCachedSizes.clear();
adjustTextSize(getText().toString());
}
/**
* Ensures the text is as big as possible for the text area.
*/
public void setTextSizeToMaxFit()
{
_maxTextSize = 999;
_textCachedSizes.clear();
adjustTextSize(getText().toString());
}
#Override
public void setTextSize(int unit, float size)
{
Context context = getContext();
Resources resources;
if (context == null)
{
resources = Resources.getSystem();
} else
{
resources = context.getResources();
}
_maxTextSize = TypedValue.applyDimension(unit, size, resources.getDisplayMetrics());
_textCachedSizes.clear();
adjustTextSize(getText().toString());
}
/**
* Set the lower text size limit and invalidate the view
*
* #param minTextSize
*/
public void setMinTextSize(float minTextSize)
{
_minTextSize = minTextSize;
reAdjust();
}
private void reAdjust() {
adjustTextSize(getText().toString());
}
private void adjustTextSize(String string)
{
if (!_initialized)
{
return;
}
int startSize = (int) _minTextSize;
int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
_widthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
_availableSpaceRect.right = _widthLimit;
_availableSpaceRect.bottom = heightLimit;
super.setTextSize
(
TypedValue.COMPLEX_UNIT_PX,
efficientTextSizeSearch(startSize, (int)_maxTextSize, mSizeTester, _availableSpaceRect)
);
}
private final SizeTester mSizeTester = new SizeTester()
{
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
public int onTestSize(int suggestedSize, RectF availableSPace)
{
_paint.setTextSize(suggestedSize);
String text = getText().toString();
boolean singleLine = getMaxLines() == 1;
if (singleLine)
{
_textRect.bottom = _paint.getFontSpacing();
_textRect.right = _paint.measureText(text);
} else
{
StaticLayout layout = new StaticLayout
(
text,
_paint,
_widthLimit,
Alignment.ALIGN_NORMAL,
_spacingMult,
_spacingAdd,
true
);
// Return early if no more lines
if (getMaxLines() != NO_LINE_LIMIT && layout.getLineCount() > getMaxLines())
{
return 1;
}
_textRect.bottom = layout.getHeight();
int maxWidth = -1;
for (int i = 0; i < layout.getLineCount(); i++)
{
if (maxWidth < layout.getLineWidth(i))
{
maxWidth = (int)layout.getLineWidth(i);
}
}
_textRect.right = maxWidth;
}
_textRect.offsetTo(0, 0);
if (availableSPace.contains(_textRect))
{
// May be too small, Will find the best match later
return -1;
} else
{
// Too big
return 1;
}
}
};
/**
* Enables or disables size caching, enabling it will improve performance
* where its animating a value inside TextView. This stores the font
* size against getText().length() Enabling it as 0
* takes more space than 1 on some fonts and so on.
* #param enable Enable font size caching
*/
public void enableSizeCache(boolean enable)
{
_enableSizeCache = enable;
_textCachedSizes.clear();
adjustTextSize(getText().toString());
}
private int efficientTextSizeSearch(int start, int end, SizeTester sizeTester, RectF availableSpace)
{
if (!_enableSizeCache)
{
return binarySearch(start, end, sizeTester, availableSpace);
}
String text = getText().toString();
int key = text == null ? 0 : text.length();
int size = _textCachedSizes.get(key);
if (size != 0)
{
return size;
}
size = binarySearch(start, end, sizeTester, availableSpace);
_textCachedSizes.put(key, size);
return size;
}
#Override
protected void onSizeChanged(int width, int height, int oldwidth,int oldheight)
{
_textCachedSizes.clear();
super.onSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight)
{
reAdjust();
}
}
private static int binarySearch(int start, int end, SizeTester sizeTester,RectF availableSpace)
{
int lastBest = start;
int low = start;
int high = end - 1;
int mid = 0;
while (low <= high)
{
mid = (low + high) >>> 1;
int midValCmp = sizeTester.onTestSize(mid, availableSpace);
if (midValCmp < 0)
{
lastBest = low;
low = mid + 1;
} else if (midValCmp > 0)
{
high = mid - 1;
lastBest = high;
} else
{
return mid;
}
}
// Make sure to return last best
// This is what should always be returned
return lastBest;
}
//endregion
//region Text Lines
#Override
public void setMaxLines(int maxlines)
{
super.setMaxLines(maxlines);
_maxLines = maxlines;
reAdjust();
}
public int getMaxLines()
{
return _maxLines;
}
#Override
public void setSingleLine()
{
super.setSingleLine();
_maxLines = 1;
reAdjust();
}
#Override
public void setSingleLine(boolean singleLine)
{
super.setSingleLine(singleLine);
if (singleLine)
{
_maxLines = 1;
} else
{
_maxLines = NO_LINE_LIMIT;
}
reAdjust();
}
#Override
public void setLines(int lines)
{
super.setLines(lines);
_maxLines = lines;
reAdjust();
}
#Override
public void setLineSpacing(float add, float mult)
{
super.setLineSpacing(add, mult);
_spacingMult = mult;
_spacingAdd = add;
}
//endregion
//region Padding Fix
#Override
protected void onDraw(Canvas canvas)
{
if (trimPadding)
{
trimVertical();
}
super.onDraw(canvas);
}
private void trimVertical()
{
final Layout layout = getLayout();
final Rect textBounds = new Rect();
if (layout == null)
{
Log.d("Layout is null","" + layout);
return;
}
int baseline = layout.getLineBaseline(0);
getTextBounds(0, textBounds);
final int pTop = baseline + textBounds.top;
final int lastLine = getLineCount() - 1;
baseline = layout.getLineBaseline(lastLine);
getTextBounds(lastLine, textBounds);
final int pBottom = layout.getHeight() - baseline - textBounds.bottom + 1;
setPadding(getPaddingLeft(), -pTop, getPaddingRight(), -pBottom);
}
private void getTextBounds(int line, Rect bounds)
{
final String s = getText().toString();
final int start = getLayout().getLineStart(line);
final int end = getLayout().getLineEnd(line);
getPaint().getTextBounds(s, start, end, bounds);
}
//endregion
}

How come my auto resize text view does not work when I set the text in programatically?

I have created a class to auto resize TextView here is the code:
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Build;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.widget.TextView;
public class AutoResizeTextView extends TextView {
private interface SizeTester {
/**
*
* #param suggestedSize
* Size of text to be tested
* #param availableSpace
* available space in which text must fit
* #return an integer < 0 if after applying {#code suggestedSize} to
* text, it takes less space than {#code availableSpace}, > 0
* otherwise
*/
public int onTestSize(int suggestedSize, RectF availableSpace);
}
private RectF mTextRect = new RectF();
private RectF mAvailableSpaceRect;
private SparseIntArray mTextCachedSizes;
private TextPaint mPaint;
private float mMaxTextSize;
private float mSpacingMult = 1.0f;
private float mSpacingAdd = 0.0f;
private float mMinTextSize = 5;
private int mWidthLimit;
private static final int NO_LINE_LIMIT = -1;
private int mMaxLines;
private boolean mEnableSizeCache = true;
private boolean mInitiallized;
public AutoResizeTextView(Context context) {
super(context);
initialize();
}
public AutoResizeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
private void initialize() {
mPaint = new TextPaint(getPaint());
mMaxTextSize = getTextSize();
mAvailableSpaceRect = new RectF();
mTextCachedSizes = new SparseIntArray();
if (mMaxLines == 0) {
// no value was assigned during construction
mMaxLines = NO_LINE_LIMIT;
}
mInitiallized = true;
}
#Override
public void setText(final CharSequence text, BufferType type) {
super.setText(text, type);
adjustTextSize(text.toString());
}
#Override
public void setTextSize(float size) {
mMaxTextSize = size;
mTextCachedSizes.clear();
adjustTextSize(getText().toString());
}
#Override
public void setMaxLines(int maxlines) {
super.setMaxLines(maxlines);
mMaxLines = maxlines;
reAdjust();
}
public int getMaxLines() {
return mMaxLines;
}
#Override
public void setSingleLine() {
super.setSingleLine();
mMaxLines = 1;
reAdjust();
}
#Override
public void setSingleLine(boolean singleLine) {
super.setSingleLine(singleLine);
if (singleLine) {
mMaxLines = 1;
} else {
mMaxLines = NO_LINE_LIMIT;
}
reAdjust();
}
#Override
public void setLines(int lines) {
super.setLines(lines);
mMaxLines = lines;
reAdjust();
}
#Override
public void setTextSize(int unit, float size) {
Context c = getContext();
Resources r;
if (c == null)
r = Resources.getSystem();
else
r = c.getResources();
mMaxTextSize = TypedValue.applyDimension(unit, size,
r.getDisplayMetrics());
mTextCachedSizes.clear();
adjustTextSize(getText().toString());
}
#Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the lower text size limit and invalidate the view
*
* #param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
reAdjust();
}
private void reAdjust() {
adjustTextSize(getText().toString());
}
private void adjustTextSize(String string) {
if (!mInitiallized) {
return;
}
int startSize = (int) mMinTextSize;
int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom()
- getCompoundPaddingTop();
mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
- getCompoundPaddingRight();
mAvailableSpaceRect.right = mWidthLimit;
mAvailableSpaceRect.bottom = heightLimit;
super.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
efficientTextSizeSearch(startSize, (int) mMaxTextSize,
mSizeTester, mAvailableSpaceRect));
}
private final SizeTester mSizeTester = new SizeTester() {
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
public int onTestSize(int suggestedSize, RectF availableSPace) {
mPaint.setTextSize(suggestedSize);
String text = getText().toString();
boolean singleline = getMaxLines() == 1;
if (singleline) {
mTextRect.bottom = mPaint.getFontSpacing();
mTextRect.right = mPaint.measureText(text);
} else {
StaticLayout layout = new StaticLayout(text, mPaint,
mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
mSpacingAdd, true);
// return early if we have more lines
if (getMaxLines() != NO_LINE_LIMIT
&& layout.getLineCount() > getMaxLines()) {
return 1;
}
mTextRect.bottom = layout.getHeight();
int maxWidth = -1;
for (int i = 0; i < layout.getLineCount(); i++) {
if (maxWidth < layout.getLineWidth(i)) {
maxWidth = (int) layout.getLineWidth(i);
}
}
mTextRect.right = maxWidth;
}
mTextRect.offsetTo(0, 0);
if (availableSPace.contains(mTextRect)) {
// may be too small, don't worry we will find the best match
return -1;
} else {
// too big
return 1;
}
}
};
/**
* Enables or disables size caching, enabling it will improve performance
* where you are animating a value inside TextView. This stores the font
* size against getText().length() Be careful though while enabling it as 0
* takes more space than 1 on some fonts and so on.
*
* #param enable
* enable font size caching
*/
public void enableSizeCache(boolean enable) {
mEnableSizeCache = enable;
mTextCachedSizes.clear();
adjustTextSize(getText().toString());
}
private int efficientTextSizeSearch(int start, int end,
SizeTester sizeTester, RectF availableSpace) {
if (!mEnableSizeCache) {
return binarySearch(start, end, sizeTester, availableSpace);
}
String text = getText().toString();
int key = text == null ? 0 : text.length();
int size = mTextCachedSizes.get(key);
if (size != 0) {
return size;
}
size = binarySearch(start, end, sizeTester, availableSpace);
mTextCachedSizes.put(key, size);
return size;
}
private static int binarySearch(int start, int end, SizeTester sizeTester,
RectF availableSpace) {
int lastBest = start;
int lo = start;
int hi = end - 1;
int mid = 0;
while (lo <= hi) {
mid = (lo + hi) >>> 1;
int midValCmp = sizeTester.onTestSize(mid, availableSpace);
if (midValCmp < 0) {
lastBest = lo;
lo = mid + 1;
} else if (midValCmp > 0) {
hi = mid - 1;
lastBest = hi;
} else {
return mid;
}
}
// make sure to return last best
// this is what should always be returned
return lastBest;
}
#Override
protected void onTextChanged(final CharSequence text, final int start,
final int before, final int after) {
super.onTextChanged(text, start, before, after);
reAdjust();
}
#Override
protected void onSizeChanged(int width, int height, int oldwidth,
int oldheight) {
mTextCachedSizes.clear();
super.onSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight) {
reAdjust();
}
}
}
As you can see in this class I have set the minimum text size to 5. The resizing works perfectly when I type in the text in my xml file. However when I try to change the text during the activity after grabbing the text from a network connection, I change the text in my onPostExecute() method, however when the text loads on screen it appears really small (i'm assuming it is appearing as the minimum size 5 text I set in the class). I don't understand why it is not getting resized bigger since it fits. In my xml file I set the textSize to 12 (which means 12 would be the max size). I have even tried changing the text size manually in the activity but this does not work either, the text still displays as size 5. Here is my code:
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONObject;
import org.w3c.dom.Text;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
public class ConfirmBookBuy extends Activity
{
Globals g;
LinearLayout mainLinearLayout;
ProgressBar loadingWheel;
TextView titleInfo;
TextView authorInfo;
TextView editionInfo;
TextView publisherInfo;
TextView datePublishedInfo;
TextView bindingInfo;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.buy_confirm_book_layout);
g = Globals.getInstance();
mainLinearLayout = (LinearLayout)findViewById(R.id.mainLinearLayout);
loadingWheel = (ProgressBar)findViewById(R.id.loadingWheel);
titleInfo = (TextView)findViewById(R.id.titleInfo);
authorInfo = (TextView)findViewById(R.id.authorInfo);
editionInfo = (TextView)findViewById(R.id.editionInfo);
publisherInfo = (TextView)findViewById(R.id.publisherInfo);
datePublishedInfo = (TextView)findViewById(R.id.datePublishedInfo);
bindingInfo = (TextView)findViewById(R.id.bindingInfo);
new ConfirmIsbn().execute();
}
public class ConfirmIsbn extends AsyncTask<String, Void, Void>
{
#Override
protected Void doInBackground(String... params) {
// grabbing the text from internet, this works, I have tested to see that it is actually grabbing the text
return null;
}
#Override
protected void onPostExecute(Void aVoid)
{
super.onPostExecute(aVoid);
titleInfo.setText(g.getBuyingTitle());
authorInfo.setText(g.getBuyingAuthor());
editionInfo.setText(g.getBuyingEdition());
publisherInfo.setText(g.getBuyingPublisher());
datePublishedInfo.setText(g.getBuyingDatePublished());
bindingInfo.setText(g.getBuyingBinding());
titleInfo.setTextSize(12);
authorInfo.setTextSize(12);
editionInfo.setTextSize(12);
publisherInfo.setTextSize(12);
datePublishedInfo.setTextSize(12);
bindingInfo.setTextSize(12);
}
}
}
I just want my text view to auto resize to the biggest text size that will fit (max of 12). I don't get why when I set the text in xml the resizing works perfectly, however when I try to set it during the running of activity it will always just display the minimum text of size 5... even if I try to manually set the text in code too.

Scrolling credits text in Android

I need to make a credits screen (Activity) in my game. It would be just a vertically scrolling text lines without any images. Scrolling is to be performed automatically and no user interaction is allowed. Just like movie credits that goes from bottom to top. After the last text line has disappeared above top of the screen, it should restart.
How do I do it? It is sufficient to just use TextView and animate it somehow? Or should I put that TextView into ScrollView? What would you suggest?
I am using this :-
/**
* A TextView that scrolls it contents across the screen, in a similar fashion as movie credits roll
* across the theater screen.
*
* #author Matthias Kaeppler
*/
public class ScrollingTextView extends TextView implements Runnable {
private static final float DEFAULT_SPEED = 15.0f;
private Scroller scroller;
private float speed = DEFAULT_SPEED;
private boolean continuousScrolling = true;
public ScrollingTextView(Context context) {
super(context);
setup(context);
}
public ScrollingTextView(Context context, AttributeSet attributes) {
super(context, attributes);
setup(context);
}
private void setup(Context context) {
scroller = new Scroller(context, new LinearInterpolator());
setScroller(scroller);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (scroller.isFinished()) {
scroll();
}
}
private void scroll() {
int viewHeight = getHeight();
int visibleHeight = viewHeight - getPaddingBottom() - getPaddingTop();
int lineHeight = getLineHeight();
int offset = -1 * visibleHeight;
int totallineHeight = getLineCount() * lineHeight;
int distance = totallineHeight + visibleHeight;
int duration = (int) (distance * speed);
if (totallineHeight > visibleHeight) {
scroller.startScroll(0, offset, 0, distance, duration);
if (continuousScrolling) {
post(this);
}
}
}
#Override
public void run() {
if (scroller.isFinished()) {
scroll();
} else {
post(this);
}
}
public void setSpeed(float speed) {
this.speed = speed;
}
public float getSpeed() {
return speed;
}
public void setContinuousScrolling(boolean continuousScrolling) {
this.continuousScrolling = continuousScrolling;
}
public boolean isContinuousScrolling() {
return continuousScrolling;
}
}

OnDraw( ) not getting called when invalidate is called

I am using a View extended from View group and the onDraw is not getting called when I call invalidate.Can any one please explain?
The code is given below
public class BarGraph extends View {
private int viewHeight;
private int viewWidth; // height and width of bar graph dynamically calculated.
private int mGraphColor; // the colour of the bar graph.
private ArrayList<Integer> mTodays; // The array list that handles the input to draw the graph.
private final int barCount = 20; // Number of bars in the bar graph...here set as 20.
/*
* The maximum action value a bar can take.
* This is calculated based on the action array
* passed to the chart.
*/
private int yMax = 0;
private Paint graphColor;
public BarGraph(Context context, int graphColor) {
super(context);
this.setWillNotDraw(false);
mGraphColor = graphColor;
setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
initializePaintObjects();
}
public BarGraph(Context context, AttributeSet attrs) {
super(context, attrs);
this.setWillNotDraw(false);
initializePaintObjects();
}
private void initializePaintObjects(){
graphColor = new Paint();
graphColor.setColor(mGraphColor);
graphColor.setStyle(Style.FILL);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mTodays == null) // If the array list is empty.
return;
if(yMax <= 0)
return;
int left = 0;
viewHeight = getHeight();
viewWidth = getWidth();
if((viewWidth % barCount) > 0){ //adjusting the view width so that bars correctly fits in
int newWidth = (int) (Math.floor(viewWidth / barCount) * barCount);
left = (int) Math.floor(((viewWidth - newWidth)/2));
viewWidth = (int) Math.floor((viewWidth / barCount) * barCount);
}
int columnWidth = 2;
columnWidth = (int) Math.floor(viewWidth / barCount);
int xFactor = 1;
xFactor = (int) Math.ceil(columnWidth * 0.33);
int barWidth = 1;
barWidth = columnWidth - xFactor;
graphColor.setStrokeWidth(barWidth);
int j = 0;
for(int i = 0; i < mTodays.size() ; i++){
int todayValue = mTodays.get(i);
float todaysHeight;
if(todayValue == 0){
todaysHeight = (float) (viewHeight-viewHeight*(.001));
}else{
todaysHeight = getYValue(todayValue);
}
canvas.drawLine(((j*columnWidth)+xFactor + left) , viewHeight, ((j*columnWidth)+xFactor + left), todaysHeight, graphColor);
j++;
}
}
public void setData(ArrayList<Integer>todays){
mTodays = todays;
yMax = 0;
for (int val : mTodays){
yMax = yMax > val ? yMax : val;
}
invalidate();
}
private int getYValue(int item){
int percent = (item * 100)/yMax;
return (viewHeight) - ((percent * viewHeight)/100);
}
}
Thanks for all your attempts.The problem was actually in the Main Activity.
The following is the correct code
final Handler handler = new Handler();
Timer timer2 = new Timer();
TimerTask testing = new TimerTask() {
public void run() {
handler.post(new Runnable() {
public void run() {
for(int j=0;j<19;j++){
todays.set(j, todays.get(j+1));
}
Random diceRoller = new Random();
todays.set(19, diceRoller.nextInt(100)*10+1);
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
bargraph.setData(todays);
}
});
}
});
I had not written the runOnUiThread at first...My bad...

Categories

Resources