My code does not work correctly. SurfaceView shows a black screen instead of Bitmaps. I make a game loop that doesn't depend on CPU, like Fix Your Timestep!, but I can't get it to work.
public class DrawView extends SurfaceView implements SurfaceHolder.Callback {
private DrawThreat drawThread;
public Background background;
private int height;
private int width;
public DrawView(Context context) {
super(context);
getHolder().addCallback(this);
drawThread = new DrawThreat(getHolder(), this);
setFocusable(true);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Rect surface = getHolder().getSurfaceFrame();
this.width = surface.width();
this.height = surface.height();
Bitmap back= BitmapFactory.decodeResource(App.getContext().getResources(), R.drawable.fon);
back = Bitmap.createScaledBitmap(back, width, height, false);
background = new Background(back, back, 0, 0, 0, -height);
drawThread = new DrawThreat(getHolder(), this);
drawThread.start();
}
public class DrawThreat extends Thread {
private final DrawView drawView;
private SurfaceHolder surfaceHolder;
private boolean running=true;
private static final int UPDATES_PER_SECOND = 25;
private static final int UPDATE_INTERVAL = 1000 / UPDATES_PER_SECOND * 1000000;
private static final int MAX_FRAMESKIP = 5;
private long nextUpdate = System.nanoTime();
public DrawThreat(SurfaceHolder surfaceHolder, DrawView drawView){
super();
this.surfaceHolder = surfaceHolder;
this.drawView = drawView;
}
I tried to implement a game loop, but only a black screen is displayed
#Override
public void run(){
while (running){
Canvas canvas = surfaceHolder.lockCanvas();
int skippedFrames = 0;
while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) {
long delta = UPDATE_INTERVAL;
this.drawView.update(delta);
this.nextUpdate += UPDATE_INTERVAL;
skippedFrames++;
}
double interpolation = (System.nanoTime() + UPDATE_INTERVAL - this.nextUpdate) / (double) UPDATE_INTERVAL;
this.drawView.draw(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
Please point out the errors and tell me the right way to implement the idea.
Related
Im using the MLKit combined with the CameraX Image-Analysis Use Case to build an App, that lets you control a cursor, which gets drawn onto an overlay based on the position of the face (head based pointing essentially).
I have two activities:
Activity 1 previews the camera feed and draws the facial landmarks (in my case, just the nosetip) onto the overlay as well as the cursor. As I said before, the cursor position is being calculated based on the nosetip position in the image -> No problems here
Activity 2 runs a very simplistic game in a separate thread, which spawns obstacles that need to be avoided with the cursor. No live camera feed, "just" the face detection, the face -> cursor screen mapping, a custom gameView and the overlay for the cursor ontop.
My problem:
After some time, the face detector just stops detecting faces (thus the cursor gets "stuck") without any errors and oddly enough, the game incrementally speeds up (but still registers collision with the cursor).
I already followed the instructions for optimizing face detection in real-time by only enabling landmark-mode, enabled tracking and setting the performance mode to fast.
My guess was that running the face detection, mapping the cursor to a point on the screen, drawing onto the overlay for the cursor and having a game running at the same time might be just too much work, but I read somewhere that MLKit runs its detection on its own thread.
It must have something to do with my game, because as already mentioned above, in Activity 1 (Live camera preview + calculating cursor position + drawing landmarks & cursor on the overlay canvas) I never faced (no pun intended) a crash.
My custom GameView:
public class GameView extends SurfaceView implements SurfaceHolder.Callback{
private final int difficultyTime = 5;
private GameThread thread;
private long startTime;
private long timer;
private long difficultyTimer;
private long lastSpawned = 0;
private Paint textPaint;
private Random random;
private Pointer pointer;
private GameManager game;
public GameView(Context context) {
super(context);
init();
}
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GameView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void init() {
getHolder().addCallback(this);
thread = new GameThread(getHolder(), this);
game = new GameManager();
setFocusable(true);
textPaint = new Paint();
textPaint.setColor(Color.RED);
textPaint.setTextSize(70);
Paint collisionPaint = new Paint();
collisionPaint.setColor(Color.BLUE);
Paint pointerPaint = new Paint();
pointerPaint.setColor(Color.GREEN);
startTime = System.currentTimeMillis();
lastSpawned = System.currentTimeMillis();
difficultyTimer = System.currentTimeMillis() + difficultyTime * 1000;
random = new Random();
}
public void update() {
timer = (System.currentTimeMillis() - startTime) / 1000;
long currentTimeMS = System.currentTimeMillis();
if(currentTimeMS > lastSpawned + (game.getCooldown() * 1000L)) {
lastSpawned = System.currentTimeMillis();
game.setMayDraw(true);
}
if(checkCollision()) {
gameover();
}
// Raise difficulty
if(difficultyTimer <= System.currentTimeMillis()) {
difficultyTimer = System.currentTimeMillis() + difficultyTime * 1000;
game.advanceDifficulty();
}
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
int bgColor = Color.WHITE;
canvas.drawColor(bgColor);
// Spawn elements
if(game.getMayDraw()) {
game.setMayDraw(false);
spawnObstacles(canvas);
}
// Update existing elements
game.updateObstacles(canvas);
// Draw the timer
drawTimer(canvas);
// Draw difficulty text
drawDifficulty(canvas);
}
private void drawTimer(Canvas canvas) {
canvas.drawText(Long.toString(timer), (getWidth() / 2), 80, textPaint);
invalidate();
}
private void drawDifficulty(Canvas canvas) {
canvas.drawText("Difficulty: " + game.getCurrentDifficultyIndex(), (getWidth() / 2) + 250, 80, textPaint);
}
private void spawnObstacles(Canvas canvas) {
int random_number;
int last_random = 0;
for(int i = 0; i < game.getMAX_SPAWN(); i++) {
do {
random_number = random.nextInt(4) + 1;
}while(random_number == last_random);
switch(random_number) {
case 1:
game.spawnTop(canvas);
break;
case 2:
game.spawnBottom(canvas);
break;
case 3:
game.spawnLeft(canvas);
break;
case 4:
game.spawnRight(canvas);
break;
}
last_random = random_number;
}
}
public void gameover() {
startTime = System.currentTimeMillis();
game.getObstacles().clear();
game.resetDifficulty();
difficultyTimer = System.currentTimeMillis() + difficultyTime * 1000;
}
public boolean checkCollision() {
List<Pair<GameManager.Direction, Rect>> collidables = game.getObstacles();
for (Pair<GameManager.Direction, Rect> r : collidables) {
if(intersects(r.second, pointer.getCollisionRect()) || intersects(pointer.getCollisionRect(), r.second)) {
return true;
}
}
return false;
}
public static boolean intersects(Rect rect, Rect otherRect) {
return rect.left <= otherRect.right && otherRect.left <= rect.right
&& rect.top <= otherRect.bottom && otherRect.top <= rect.bottom;
}
#Override
public void surfaceCreated(#NonNull SurfaceHolder surfaceHolder) {
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceChanged(#NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {
System.out.println("Surface changed - Format: " + format + " W/H: ("+width +"/" +height+")");
}
#Override
public void surfaceDestroyed(#NonNull SurfaceHolder surfaceHolder) {
boolean retry = true;
while (retry) {
try {
thread.setRunning(false);
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
retry = false;
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
GameThread:
public class GameThread extends Thread{
private final SurfaceHolder surfaceHolder;
private final GameView gameView;
private boolean running;
public static Canvas canvas;
public GameThread(SurfaceHolder surfaceHolder, GameView gameView) {
super();
this.surfaceHolder = surfaceHolder;
this.gameView = gameView;
}
#Override
public void run() {
long startTime;
long timeMillis;
long waitTime;
long totalTime = 0;
int frameCount = 0;
int targetFPS = 15;
long targetTime = 1000 / targetFPS;
while (running) {
startTime = System.nanoTime();
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized(surfaceHolder) {
this.gameView.update();
this.gameView.draw(canvas);
}
} catch (Exception e) {
e.printStackTrace();
}
finally {
if (canvas != null) {
try {
surfaceHolder.unlockCanvasAndPost(canvas);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
timeMillis = (System.nanoTime() - startTime) / 1000000;
waitTime = targetTime - timeMillis;
try {
if(waitTime > 0) {
this.sleep(waitTime);
}
} catch (Exception e) {
e.printStackTrace();
}
totalTime += System.nanoTime() - startTime;
frameCount++;
if (frameCount == targetFPS) {
double averageFPS = 1000 / ((totalTime / frameCount) / 1000000);
frameCount = 0;
totalTime = 0;
Log.d("GameFPS", "" + averageFPS);
}
}
}
public void setRunning(boolean isRunning) {
running = isRunning;
}
FaceDetector:
public class Detector {
private final FaceDetector faceDetector;
private final GraphicOverlay overlay;
private final Pointer pointer;
private final ClickHandler clickHandler;
private final boolean drawFaces;
public Detector(GraphicOverlay overlay, Context context, boolean drawFaces) {
this.overlay = overlay;
this.drawFaces = drawFaces;
pointer = new Pointer(overlay, context);
clickHandler = new ClickHandler(context, this.pointer);
FaceDetectorOptions options =
new FaceDetectorOptions.Builder()
//.setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
/* disable for better performance *///.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
.enableTracking()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
.build();
faceDetector = FaceDetection.getClient(options);
}
public void detectFace(ImageProxy image) {
if(image == null) {
return;
}
InputImage inputImage = getInputImage(image);
int rotationDegrees = image.getImageInfo().getRotationDegrees();
if(rotationDegrees == 0 || rotationDegrees == 180) {
overlay.setImageSourceInfo(image.getWidth(), image.getHeight(), true);
}else{
overlay.setImageSourceInfo(image.getHeight(), image.getWidth(), true);
}
if(inputImage == null) {
return;
}
Task<List<Face>> result =
this.faceDetector.process(inputImage)
// When detection succeeded
.addOnSuccessListener(
faces -> {
overlay.clear();
if(faces.size() > 0) {
for(Face face: faces) {
if(drawFaces) {
// Adding faces to overlay
FaceGraphic g = new FaceGraphic(overlay, face);
overlay.add(g);
}
// Update Pointer/Cursor
pointer.update(face);
}
}
})
// When detection failed
.addOnFailureListener(
e -> Log.d("Processing failed", e.getMessage()))
// When detection is completed
.addOnCompleteListener(
task -> image.close()
);
}
#SuppressLint("UnsafeOptInUsageError")
private InputImage getInputImage(ImageProxy image) {
int rotation = image.getImageInfo().getRotationDegrees();
Image mediaImage = image.getImage();
if(mediaImage != null) {
return InputImage.fromMediaImage(mediaImage, rotation);
}else{
return null;
}
}
GameActivity:
public class GameActivity extends AppCompatActivity {
private GraphicOverlay mOverlay;
private GameView gameView;
private Camera camera;
private Pointer pointer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_game);
gameView = findViewById(R.id.gameView);
mOverlay = findViewById(overlay_game);
initCamera();
}
private void initCamera() {
camera = new Camera(this, mOverlay);
camera.setDrawFaces(false);
camera.setAnalysisUseCase();
camera.setAnalyzer();
camera.build();
pointer = camera.getDetector().getPointer();
gameView.setPointer(pointer);
}
#Override
protected void onResume() {
super.onResume();
}
#Override
protected void onPause() {
super.onPause();
}
The face detection "crash" can occur anywhere from 1 second to 5 minutes playing the game. I would gladly appreciate any hints or suggestions. Also im fairly new to java and mobile-app development, so please excuse my bad code.
If you need any further information/code i'll happily provide it.
Thanks in advance
I have this screen with two bird animatons using Bitmap. I want the bitmaps to be clickable but I am unable to figure out how. I can easily make the entire screen clickable by setting view.OnClickListener but that is not what I want, I want separate things to happen depending on which bird the user clicks on.
Also, how and where would I create a local variable for my bitmaps, for now I can only access them inside of the draw() method.
The run() method is for the animation and works fine.
I am new to all of this and appreciate any help!
Code
public class PlayActivity extends AppCompatActivity {
surfaceView view;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new surfaceView(this);
setContentView(view);
}
public class surfaceView extends SurfaceView {
public surfaceView(Context context) {
super(context);
new Anim().start();
}
private class Anim extends Thread {
int counter = 0;
int counter2 = 0;
#Override
public void run() {
long last_updated_time = 0;
long delay = 150;
int[] purple_bird = {
R.drawable.bird1,
R.drawable.bird2
};
int[] red_bird = {
R.drawable.red1,
R.drawable.red2,
R.drawable.red3,
R.drawable.red4,
R.drawable.red5,
R.drawable.red6,
R.drawable.red7,
R.drawable.red8,
R.drawable.red9,
R.drawable.red10
};
while (true) {
boolean playing = true;
if (playing) {
long current_time = System.currentTimeMillis();
if (current_time > last_updated_time + delay) {
if (counter >= 2) {
counter = 0;
}
if (counter2 >= 4) {
counter2 = 0;
}
draw(purple_bird[counter], red_bird[counter2]);
last_updated_time = current_time;
counter++;
counter2++;
}
}
}
}
private void draw(int red_bird, int purple_bird) {
SurfaceHolder holder = getHolder();
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.WHITE);
Paint paint = new Paint();
Bitmap purpleBird = BitmapFactory.decodeResource(getContext().getResources(), purple_bird);
Bitmap redBird = BitmapFactory.decodeResource(getContext().getResources(), red_bird);
Bitmap resizedRedBird = Bitmap.createScaledBitmap(redBird, (int) (redBird.getWidth() * 0.1), (int) (redBird.getHeight() * 0.1), true);
Bitmap resizedPurpleBird = Bitmap.createScaledBitmap(purpleBird, (int) (purpleBird.getWidth() * 0.8), (int) (purpleBird.getHeight() * 0.8), true);
canvas.drawBitmap(resizedRedBird, 100, 100, paint);
canvas.drawBitmap(resizedPurpleBird, 100, 600, paint);
holder.unlockCanvasAndPost(canvas);
}
}
}
}
}
I have an object on my canvas that I need to rotate based on user input. When the user touches the screen, and then moves their finger, the app calculates the angle between the two touch events, and that is the angle I need my Bitmap to face.
Here is a screenshot with a test Bitmap, I need the nose of the plane to face the direction determined by the input, and then update constantly as the input changes.
This GameView class is set at the ContentView for my activity:
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
MainThread thread;
private Player player;
private Point playerPoint;
private float originX;
private float originY;
private float currX;
private float currY;
private float playerAngle;
public GameView(Context context){
super(context);
getHolder().addCallback(this);
thread = new MainThread(getHolder(),this);
setBackgroundColor(getResources().getColor(R.color.background));
setFocusable(true);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){}
#Override
public void surfaceCreated(SurfaceHolder holder) {
int width = this.getWidth();
int height = this.getHeight();
playerPoint = new Point(width/2, height*3/5);
player = new Player(this.getContext(), playerPoint,
getResources().getColor(R.color.colorAccent));
thread = new MainThread(getHolder(), this);
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder){
boolean retry = true;
while(retry){
try{
thread.setRunning(false);
thread.join();
}catch(Exception e){
e.printStackTrace();
}
retry = false;
}
}
#Override
public boolean onTouchEvent(MotionEvent event){
if(event.getAction() == MotionEvent.ACTION_DOWN){
//System.out.println("Down");
originX = event.getX();
originY = event.getY();
}
currX = event.getX();
currY = event.getY();
//return super.onTouchEvent(event);
return true;
}
public float getTouchAngle(){
float dX = currX - originX;
float dY = currY - originY;
double angle = Math.atan2(dX,-dY);
angle *= 180;
angle /= Math.PI;
return (float) angle;
}
public void update(){
playerAngle = getTouchAngle();
//System.out.println(playerAngle);
player.update(playerAngle);
}
#Override
public void draw(Canvas canvas){
super.draw(canvas);
player.draw(canvas);
}
}
I have my MainThread which just calls the update and draw methods for the GameView class:
public class MainThread extends Thread {
public static final int MAX_FPS = 30;
private double averageFPS;
private SurfaceHolder holder;
private GameView gameView;
private boolean running;
public static Canvas canvas;
public void setRunning(boolean b){
this.running = b;
}
public MainThread(SurfaceHolder holder, GameView gameView){
super();
this.holder = holder;
this.gameView = gameView;
//canvas = null;
}
#Override
public void run(){
long startTime;
long timeMillis = 1000/MAX_FPS;
long waitTime;
int frameCount = 0;
long totalTime = 0;
long targetTime = 1000/MAX_FPS;
while(running){
startTime = System.nanoTime();
canvas = null;
try{
canvas = this.holder.lockCanvas();
synchronized (holder){
this.gameView.update();
this.gameView.draw(canvas);
}
}catch(Exception e){
e.printStackTrace();
} finally {
if(canvas !=null){
try{
holder.unlockCanvasAndPost(canvas);
}catch(Exception e){
e.printStackTrace();
}
}
}
timeMillis = (System.nanoTime() - startTime/1000000);
waitTime = targetTime - timeMillis;
try{
if(waitTime > 0){
this.sleep(waitTime);
}
}catch(Exception e){
e.printStackTrace();
}
totalTime += System.nanoTime() - startTime;
frameCount++;
if(frameCount == MAX_FPS){
averageFPS = 1000 / ((totalTime/frameCount)/1000000);
frameCount = 0;
totalTime = 0;
//System.out.println(averageFPS);
}
}
}
}
And then I have my Player class, which is where I have been trying to get the Bitmap rotation to update:
public class Player implements GameObject {
//private Rect rect;
private int color;
private Paint paint;
private float angle;
private Bitmap icon;
private Point pos;
//private Matrix matrix;
public Player(Context context, Point pos, int color){
//this.rect = rect;
this.pos = pos;
this.color = color;
paint = new Paint();
//paint.setColor(color);
//paint.setStyle(Paint.Style.FILL);
paint.setFilterBitmap(true);
icon = BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_action_name);
//matrix = new Matrix();
}
#Override
public void draw(Canvas canvas){
//Matrix matrix = new Matrix();
//matrix.setRotate(angle, 100, 100);
//canvas.save(Canvas.ALL_SAVE_FLAG);
canvas.rotate(-angle,pos.x,pos.y);
System.out.println(angle);
canvas.drawBitmap(icon,pos.x-50,pos.y-50,paint);
//canvas.restore();
}
#Override
public void update(){
}
public void update(float angle){
this.angle = angle;
}
}
I originally tried to perform the rotation using a Matrix, and I had it working at first, but then I made changes and haven't been able to get it to rotate at all since.
I can tell that the angle is getting computed properly, and the angle variable in the Player class is getting updated, because after a touch event, if I switch to a different app then go back into this activity, the bitmap is facing the correct direction. It just isn't updating in realtime anymore. I have no clue what I did.
(all the commented lines are me trying to tweak things to fix it)
I found the issue. After changing up my code several times to try and get my canvas to update, I tried commenting out the setBackgroundColor() method in my GameView class, and it suddenly started working again. Now I just set the background color on the Canvas itself and it works as it should. If anyone could explain why, I'd love to hear more
I'm dissecting this tutorial on 2d games and I don't understand exactly what the paintComponent() and drawImage() methods are doing in this code and why. Could someone please explain?
public class Board extends JPanel {
private final int B_WIDTH = 350;
private final int B_HEIGHT = 350;
private final int INITIAL_X = -40;
private final int INITIAL_Y = -40;
private final int INITIAL_DELAY = 100;
private final int PERIOD_INTERVAL = 25;
private Image star;
private Timer timer;
private int x, y;
public Board() {
initBoard();
}
private void loadImage() {
ImageIcon ii = new ImageIcon("star.png");
star = ii.getImage();
}
private void initBoard() {
setBackground(Color.BLACK);
setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT));
setDoubleBuffered(true);
loadImage();
x = INITIAL_X;
y = INITIAL_Y;
timer = new Timer();
timer.scheduleAtFixedRate(new ScheduleTask(),
INITIAL_DELAY, PERIOD_INTERVAL);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
drawStar(g);
}
private void drawStar(Graphics g) {
g.drawImage(star, x, y, this);
Toolkit.getDefaultToolkit().sync();
}
private class ScheduleTask extends TimerTask {
#Override
public void run() {
x += 1;
y += 1;
if (y > B_HEIGHT) {
y = INITIAL_Y;
x = INITIAL_X;
}
repaint();
}
}
Here is a great summary of the repaint operations in AWT and Swing:
Painting in AWT and Swing
The specifics on the methods you're asking about are here, but I would start with the higher level article first.
Graphics.drawImage javadoc
JComponent.paintComponent javadoc
I'm working on Android app which main purpose is to display CNN breaking news styled bar on the bottom and some pictures. I created two custom views, one for displaying photos and the second one for displaying bar. It displays one picture at a time for specified amount of time and swaps current picture with next one from the queue.
To animate text in bottom bar I used canvas, onDraw() and handler.postDelayed. This solution gives poor result. Text movement is not smooth especially when it comes to swap image.
What should I use instead of canvas? Is there any OpenGL-based lib which could make this task relatively painless? I tried to use AndEngine, but its lack of documentation and problems with threads discouraged me to working with it anymore.
public class Infobar extends View {
private List<Message> messages;
private Handler handler;
private Paint boxPaint;
private Paint defaultTextPaint;
private Paint importantTextPaint;
private long offset = 0;
private long maxOffset = 1000;
private int textWidth = 1000;
private int textHeight = 50;
private int measuredWidth;
private int measuredHeight;
private Runnable animateRunnable = new Runnable() {
public void run() {
animateMessage();
}
};
long startTime = new Date().getTime();
private int backgroundCol = Color.parseColor("#ffff00");
private int textColor = Color.parseColor("#000000");
private static final int FRAME_DELAY = 10;
private static final int FRAME_SHIFT = 3;
private static final int EMPTY_SPACE = 2;
private static final String SEPARATOR = " ✩ ";
private static final int TEXT_SIZE = 35;
public Infobar(Context context, AttributeSet attrs) {
super(context, attrs);
handler = new Handler();
messages = new ArrayList<Message>();
boxPaint = new Paint();
defaultTextPaint = new Paint();
defaultTextPaint.setColor(getResources().getColor(R.color.info_bar_default_text_color));
importantTextPaint = new Paint();
importantTextPaint.setColor(getResources().getColor(R.color.info_bar_important_text_color));
handler.postDelayed(animateRunnable, FRAME_DELAY);
}
public void setMessagesList(List<Message> list) {
messages = list;
}
public void setBackgroundColor(String color) {
backgroundCol = Color.parseColor(color);
}
public void setTextColor(String color) {
textColor = Color.parseColor(color);
}
public List<Message> getMessagesList() {
return messages;
}
private String getMessagesString() {
StringBuilder builder = new StringBuilder();
for(Message message : messages) {
builder.append(message.content);
if(messages.indexOf(message) != (messages.size() - 1)) {
builder.append(SEPARATOR);
}
}
return builder.toString();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measuredWidth = getMeasuredWidth();
measuredHeight = getMeasuredHeight();
}
#Override
protected void onDraw(Canvas canvas) {
drawBackground(canvas, false);
drawMessage(canvas, getMessagesString());
super.onDraw(canvas);
}
private void drawBackground(Canvas canvas, boolean important) {
boxPaint.setColor(backgroundCol) ;
canvas.drawRect(0, 0, measuredWidth, measuredHeight, boxPaint);
}
private void drawMessage(Canvas canvas, String message) {
defaultTextPaint.setTextSize(TEXT_SIZE);
Rect bounds = new Rect();
defaultTextPaint.getTextBounds(message, 0, message.length(), bounds);
defaultTextPaint.setColor(textColor);
textWidth = bounds.width();
textHeight = bounds.height();
int positionX = measuredWidth - (int)offset;
int positionY = measuredHeight - textHeight/2;
if(offset > (measuredWidth + textWidth)) {
offset = 0;
positionX = measuredWidth;
}
canvas.drawText(message, positionX, positionY, defaultTextPaint);
}
private void animateMessage() {
offset += FRAME_SHIFT;
//offset = Math.round((new Date().getTime() - startTime) * 0.2) % (measuredWidth + textWidth);
invalidate();
handler.removeCallbacks(animateRunnable);
handler.postDelayed(animateRunnable, FRAME_DELAY);
}
}
Unless there is a specific reason, you can use the built-in marquee function of TextView:
<LinearLayout>
<TextView
android:id="#+id/myText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="1"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:text="Your long text goes here. You can also change it programmatically" />
</LinearLayout>
Then in the activity code:
TextView myText=(TextView) findViewById(R.id.myText);
myText.setSelected(true); //needs this to work