I have custom span which should be repeatedly redrawn after specific interval. I try to call invalidate method of TextView instance but nothing happens.
My custom span is redrawn only when I append new text to TextView instance.
So, my question is how to achive "force" redraw/invalidate of TextView without change it's text?
UPD:
My custom span TypingAnimationSpan inherited from ReplacementSpan.
On Draw method in TypingAnimationSpan only part of text is drawing to canvas. On next Draw call length of drawing text part is increases.
As result "typing" effect should be achived.
My custom AnimatedTextView in Draw method checks - are any of it's TypingAnimationSpans are in animation state (should be redrawn) and calls PostInvalidateDelayed
Part of TypingAnimationSpan:
public override void Draw(Canvas canvas, ICharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint)
{
count += 0.1f * Speed;
count = Math.Min(count, end - start);
canvas.DrawText(text, start, start + (int) count, x, y, paint);
IsAnimationRunning = count <= end - start;
}
public override int GetSize(Paint paint, ICharSequence text, int start, int end, Paint.FontMetricsInt fm)
{
return (int) paint.MeasureText(text, start, start + (int) count);
}
Part of AnimatedTextView:
public override void Draw(Canvas canvas)
{
base.Draw(canvas);
var targetSpans = (this.TextFormatted as ISpanned)
?.GetSpans(0, this.TextFormatted.Length(), Java.Lang.Class.FromType(typeof(TypingAnimationSpan)))
?.ToList();
bool needInvalidate = targetSpans?.Any(x => ((TypingAnimationSpan ) x).IsAnimationRunning) == true;
if (needInvalidate)
{
this.PostInvalidateDelayed(1);
}
}
So, I see that this.PostInvalidateDelayed(1); is called, but span is not redrawn.
Related
I'm writing a simple custom View that should draw the an RSSI signal shape (two overlapping triangles one filled proportionally with the level of the signal). The components works except for the clearing of canvas.
I tought I need to do something like that:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int mWidth = getMeasuredWidth();
final int mHeight = getMeasuredHeight();
final float _fillw = mWidth * (mLevel) / 100f;
final float _fillh = mHeight * (100f - mLevel) / 100f;
//canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.drawColor(Color.BLACK);
if (mLevel < mWarnLevel)
mFillPaint.setColor(mWarnFillColor);
else if ((mLevel >= mWarnLevel) && (mLevel < mSafeLevel))
mFillPaint.setColor(mFillColor);
else if (mLevel >= mSafeLevel)
mFillPaint.setColor(mSafeFillColor);
mFramePath.moveTo(0, mHeight);
mFramePath.lineTo(mWidth, mHeight);
mFramePath.lineTo(mWidth, 0);
mFramePath.close();
mFillPath.moveTo(0, mHeight);
mFillPath.lineTo(_fillw, mHeight);
mFillPath.lineTo(_fillw, _fillh);
mFillPath.close();
canvas.drawPath(mFramePath, mFramePaint);
canvas.drawPath(mFillPath, mFillPaint);
}
public void setLevel(int level) {
if (this.mLevel != level) {
this.mLevel = level;
invalidate();
}
}
The level is drawn correctly until it grows and the triangle area also get larger. When it start to decrease the area is not updated anymore probably beacuse the background is not cleared.
I tried also to use the commented .drawColor() line without luck.
P.S. I need to have a transparent background on this View.
You just have to clear the path
public void clear()
{
mFramePath.reset();
mFillPath.reset();
invalidate();
}
public class Physics {
private int weight;
private int durability;
private int xPos;
private int yPos;
private int x;
private int y;
private Level level;
int tileWidth = Main.WIDTH / 16;
int tileHeight = Main.HEIGHT / 16;
public Physics(Level level) {
this.level = level;
}
private void timer(int time) {//this is a subroutine that creates a slight pause
long currTime = System.currentTimeMillis();//this is getting the current time in miliseconds
long target = currTime + time;//this gets the current time and sets the target pause time to the current time plus the time you want to pause for in miliseconds
while(System.currentTimeMillis() < target);//this makes the program idle for the target amount of time
}
public void gravity(int weight,int durability, int xPos, int yPos, Tile tile, TileType tileTypes, boolean newblock){//this is the physics subroutine for when you place a block
if(tile.getType() == TileType.SKY) {//if the tile you clicked on is a sky tile
tile.setType(tileTypes, weight, durability);//it sets the new tiles type weight and durability to the ones that were entered dependant on which block the user chose from the pallet
timer(50);//this is the pause time between the block falling one y coordinate down
tile.setType(TileType.SKY, 0, 0);//once the tile is drawn one tile down it changeds the tile above it back to a sky tile
yPos++;//this the increases the y possition by one to foxus on the next block down
gravity(weight,durability, xPos, yPos, level.tiles[xPos][yPos],tileTypes,true);//this then calls the subroutine within itself and this keeps going until it hits the bottom
}
//if (newblock == true) {
if(tile.getType() != TileType.SKY){//once the tile below the tile we're focusing on is not a sky tile this if statement is run
Tile tile1 = level.tiles[xPos][yPos-1];//it sets the tile were focusing on to the tile above the non skytile
tile1.setType(tileTypes, weight, durability);//it then sets this tile to the tile you chose from the pallet
weight = tile1.getWeight();
durability = tile1.getDurability();
System.out.println(weight+"kg, "+durability);
}
//}
//if (newblock == false) {
//}
}
public void blockDestruction(Tile tile) {
}
public void tick(int x, int y) {
Tile tile = level.tiles[x][y];
Tile tile1 = level.tiles[x][y-1];
TileType tilecheck = tile1.getType();
if(tile1.getType() != TileType.SKY && tile1.getType() != TileType.GRASS) {
gravity(tile.getWeight(),tile.getDurability(),x,y,tile,tile.getType(),false);
y = y-1;
/*for (int i = y-1; i>0;i--) {
if (tilecheck != TileType.SKY) {
tile1 = level.tiles[x][i];
tilecheck = tile1.getType();
}else
tile1 = level.tiles[x][i+1];
tile1.setType(TileType.SKY);
}*/
}
}
}
the tick method at the end there is what I'm struggling with what im trying to create is a method that i would be able to put inside of my tick method in my main class which will check all blocks to see if there is a block underneath them and if not then it will apply the gravity method to that block. Sorry for the code being such a mess ive been trying loads of solutions and have gotten completely stuck so it is currently a bit of a mess if anything need explaining then please let me know.
this is what the program looks like
I am trying to achieve a count badge-like effect in a text view with a replacement span. I am using a drawable (circle defined in xml) as background as for some reason the canvas.drawCircle method did not seem to work. I want to draw the text on top of this drawable, however, no matter what I try, it never appears. Does anybody have any idea what the problem could be?
My custom replacement span class:
public class CircleBackgroundSpan extends ReplacementSpan {
private Drawable circle;
public CircleBackgroundSpan(Drawable drawable)
{
circle = drawable;
}
#Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
int textWidth = Math.round(measureText(paint, text, start, end));
return Math.max(textWidth, circle.getIntrinsicWidth());
}
#Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
Rect bounds = new Rect();
circle.setBounds((int) x, top, (int) x + circle.getIntrinsicWidth(), top + circle.getIntrinsicHeight());
circle.draw(canvas);
paint.setColor(0x0000A8);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(16);
paint.getTextBounds(text.toString(), 0, text.subSequence(start, end).length(), bounds);
canvas.drawText(text, start, end, x + circle.getIntrinsicWidth()/2f , y, paint);
}
private float measureText(Paint paint, CharSequence text, int start, int end)
{
return paint.measureText(text, start, end);
}
}
Usage:
SpannableString spannableString = new SpannableString(selected+" items selected");
String numberString = String.valueOf(selected);
spannableString.setSpan(new CircleBackgroundSpan(getResources().getDrawable(R.drawable.multiselect_circle)), 0, numberString.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
multiselect.setText(spannableString, TextView.BufferType.SPANNABLE);
And the result:
I am trying to get something like this:
Any ideas?
Turns out that the problem was with setting the paint colour. After I changed paint.setColor(0x0000A8) to paint.setColor(Color.parseColor("#0000A8"), the text was shown.
I need to get the graphics context in Android and I have read that it is not possible to draw outside the onDraw(), how can I handle the problem because I need the graphic context to draw outside the onDraw()
I have posted the part of code I have problems with.
The is Prof. David Eck’s code in the applet where I am getting the problem.
synchronized void putSquare(int row, int col, int colorNum) {
// draw one cell of the maze, to the graphics context "graphics"
int w = totalWidth / columns; // width of each cell
int h = totalHeight / rows; // height of each cell
Graphics graphics = getGraphics();// This is the problem
graphics.setColor(colorArray[colorNum]);
graphics.fillRect( (col * w) + left, (row * h) + top, w, h );
}
I have tried to convert it to Android
synchronized void putSquare(int row, int col, int colorNum, Canvas canvas) {
try {
canvas = surfaceHolder.lockCanvas(null);// nullpointerexception
synchronized (surfaceHolder) {
Paint paint = new Paint():
// draw one cell of the maze, to the graphics context "graphics"
int w = totalWidth / columns; // width of each cell
int h = totalHeight / rows; // height of each cell
apaint.drawColor(color[colorNum]);
canvas.drawRect( (col * w) + left, (row * h) + top, w, h, paint);
}
}
finally {
if (aCanvas != null) {
surfaceHolder.unlockCanvasAndPost(aCanvas);
}
}
}
putSquare() is called by makeMaze()
makeMaze() {
maze = new int[rows][columns];
putSquare(rows, columns, colorNumber, mainCanvas);
}
makeMaze() is called in the run() but not in onDraw()
public void run() {
makeMaze()
}
I get a NullPointerException
In the onDraw() I call another method.
onDraw(Canvas conv) {
//it also also putSquare()
redrawMaze(conv)
}
If you do not know the size of your dirty rectangle, call lockCanvas without parameters to lock the whole drawing area rather than passing null.
I am new to libgdx.
This is what I know:
We can create a rectangular box with red color using this:
shapeRenderer.begin(ShapeType.FilledRectangle);
shapeRenderer.setColor(Color.RED);
shapeRenderer.filledRect(0, 0, 300, 20);
shapeRenderer.end();
What I want to know:
I want this rectangle to persist between different frames.what I want is to create a rectangle object, and increase its length after every 3 seconds or so.
How to do this esactly? as from what i gather, if i use this code in betwen batch.begin() and batch.end() of render, it will create a new box in every frame.
You could create an integer instance in your main class such as shape_length, and have it increase every every 3 second through storing a another variable that would be set to 0 after its accumulated delta time is more than or equal to 3 seconds.
//member functions
private int shape_length = 300;
private float total_time = 0f;
//inside render loop
public void render(float deltaTime){
total_time += deltaTime;
if(total_time >= 3.0f){
//add 1 to length every 3 seconds
shape_length += 1;
total_time = 0.f;
}
shapeRenderer.begin(ShapeType.FilledRectangle);
shapeRenderer.setColor(Color.RED);
shapeRenderer.filledRect(0, 0, shape_length, 20);
shapeRenderer.end();
//fun times here
}
Update: The author wanted a way to have the rectangle as a usable class and not something constantly drawn every frame, so here's an alternative solution:
What could work is that you create a class such as RectShape that could store relative information such as height, width, a Vector2 position, etc, and then have a method on your main class that would specifically render your RectShape as follows:
class RectShape {
private float width;
private float height;
private Vector2 pos;
private Color color;
public RectShape(float w, float h, Vector2 p){
width = w;
height = h;
pos = p;
}
public void increaseLength(){
width += 2.f;
}
//getters: getColor(), getPosition(), getWidth, getHeight()
}
Create a drawRectangle method that takes both the RectShape object and ShapeRenderer
public void drawRectangle(RectShape mainRect, ShapeRenderer renderer){
renderer.begin(ShapeType.FilledRectangle);
renderer.setColor(mainRect.getColor());
renderer.filledRect(mainRect.getPosition().x,mainRect.getPosition().y,mainRect.getWidth(),mainRect.getHeight());
Then, on your main file
//instantiate object
RectShape s = new RectShape(300.f,200.f,new Vector2(3,2));
//render loop
total_time += deltaTime;
if(total_time >= 3.0f){
//add 1 to length every 3 seconds
s.increaseLength();
total_time = 0.f;
}
shapeRenderer.begin();
drawRectangle(s,shapeRenderer);
shapeRenderer.end();
The box is redrawn every frame yes.
To increase the length (a.k.a width) of the rectangle over time you could work with a timer:
In your game class have a variable for length
int rectWidth = 300;
int counter = 0; // And a counter
In your update(float delta) method you can now modify the width of the rectangle.
If your games runs at 60 Frames per second ** , the render method is called 60times/second. So to update the width of the rectangle every 3 seconds do:
public void update(float delta) {
if(counter > 60 * 3) {
rectWidth++;
counter = 0;
}
}
public void render() {
[...]
shapeRenderer.filledRect(0, 0, rectWidth, 20);
[...]
counter++;
}
There are better methods to do time dependent tasks but this should give you a starting point.
** You can define the framerate by setting ForegroundFPS
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.foregroundFPS = 60;
In order to increase the rectangles length you need to replace the 300 and 20 with variables.
shapeRenderer.filledRect(0, 0, 300, 20);
should be
shapeRenderer.filledRect(0, 0, rWidth, rHeight);
Then declare those variables in your class
int rWidth,rHeight;
and put this in your default constructor
rWidth=300;
rHeight=20;
Then in your render class you will have a seperate method in the beginning of it that will increase the rWidth and rHeight. To make it grow very quickly you can just put this in the top of your render method.
rWidth++;
rHeight++;
try this for now so you get the idea then you can use Gdx.graphics.getDeltaTime() to adjust it based on time rather than rendering frames.