I am fairly new to programming and have recently encountered a problem that I have not found a solution for online. I have created an ImageView in FXML and gave it an FXid of "gif1". I am using code from this stackoverflow post but I tried modifying it so that it would fit my needs. I put the entirety of the code in a separate java file that I called "Gif.java". Typically, "Gif.java" uses an hbox and other non applicable methods to set the ImageView. I removed those because I will be using an existing FXML document so there is no need to create a new one. In my code, I call on "Gif.java" and expect a returned ImageView. However, when I set my ImageView (gif1) to the returned ImageView, it doesn't update on the screen.
Here is the link to the gif post: How I can stop an animated GIF in JavaFX?
Here is my code that I use to update gif1:
#FXML
private void setUp(){
Gif newGif = new Gif("rightrock.gif"); // Creates object newGif and passes through path to gif
gif1 = newGif.returnImage2(); // Sets gif1 to returned ImageView
}
Here is the return code in Gif.java:
public class Gif{
private Animation ani;
private ImageView test;
public Gif(String image) {
// TODO: provide gif file, ie exchange banana.gif with your file
ani = new AnimatedGif(getClass().getResource(image).toExternalForm(), 1000);
ani.setCycleCount(1);
ani.play();
Button btPause = new Button( "Pause");
btPause.setOnAction( e -> ani.pause());
Button btResume = new Button( "Resume");
btResume.setOnAction( e -> ani.play());
}
public void returnImage(ImageView x){
test = x; // Sets ImageView test to parameter x
}
public ImageView returnImage2() {
return test; // Return instance field test
}
public static void main(String[] args) {
launch(args);
}
public class AnimatedGif extends Animation {
public AnimatedGif( String filename, double durationMs) {
GifDecoder d = new GifDecoder();
d.read( filename);
Image[] sequence = new Image[ d.getFrameCount()];
for( int i=0; i < d.getFrameCount(); i++) {
WritableImage wimg = null;
BufferedImage bimg = d.getFrame(i);
sequence[i] = SwingFXUtils.toFXImage( bimg, wimg);
}
super.init( sequence, durationMs);
}
}
public class Animation extends Transition {
private ImageView imageView;
private int count;
private int lastIndex;
private Image[] sequence;
private Animation() {
}
public Animation( Image[] sequence, double durationMs) {
init( sequence, durationMs);
}
private void init( Image[] sequence, double durationMs) {
this.imageView = new ImageView(sequence[0]);
this.sequence = sequence;
this.count = sequence.length;
setCycleCount(1);
setCycleDuration(Duration.millis(durationMs));
setInterpolator(Interpolator.LINEAR);
}
protected void interpolate(double k) {
final int index = Math.min((int) Math.floor(k * count), count - 1);
if (index != lastIndex) {
imageView.setImage(sequence[index]);
lastIndex = index;
}
}
public ImageView getView() {
returnImage(imageView); // This runs returnImage with the ImageView that I want to set
return imageView;
}
}
Any help would be greatly appreciated!
Related
I have problem with detecting if barcode is inside specified area. For testing purposes camera source preview and surface view has same size 1440x1080 to prevent scaling between camera and view. I get positive checks even if I see QR Code isn't in box what represents image. Whats wrong?
False positive check
ScannerActivity
public class ScannerActivity extends AppCompatActivity {
private static final String TAG = "ScannerActivity";
private SurfaceView mSurfaceView; // Its size is forced to 1440x1080 in XML
private CameraSource mCameraSource;
private ScannerOverlay mScannerOverlay; // Its size is forced to 1440x1080 in XML
#Override
protected void onCreate(Bundle savedInstanceState) {
// .. create and init views
// ...
BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(this)
.setBarcodeFormats(Barcode.ALL_FORMATS)
.build();
mCameraSource = new CameraSource.Builder(this, barcodeDetector)
.setRequestedPreviewSize(1440, 1080)
.setRequestedFps(20.0f)
.setFacing(CameraSource.CAMERA_FACING_BACK)
.setAutoFocusEnabled(true)
.build();
barcodeDetector.setProcessor(new Detector.Processor<Barcode>() {
#Override
public void release() {
}
#Override
public void receiveDetections(Detector.Detections<Barcode> detections) {
parseDetections(detections.getDetectedItems());
}
});
}
private void parseDetections(SparseArray<Barcode> barcodes) {
for (int i = 0; i < barcodes.size(); i++) {
Barcode barcode = barcodes.valueAt(i);
if (isInsideBox(barcode)) {
runOnUiThread(() -> {
Toast.makeText(this, "GOT DETECTION: " + barcode.displayValue, Toast.LENGTH_SHORT).show();
});
}
}
}
private boolean isInsideBox(Barcode barcode) {
Rect barcodeBoundingBox = barcode.getBoundingBox();
Rect scanBoundingBox = mScannerOverlay.getBox();
boolean checkResult = barcodeBoundingBox.left >= scanBoundingBox.left &&
barcodeBoundingBox.right <= scanBoundingBox.right &&
barcodeBoundingBox.top >= scanBoundingBox.top &&
barcodeBoundingBox.bottom <= scanBoundingBox.bottom;
Log.d(TAG, "isInsideBox: "+(checkResult ? "YES" : "NO"));
return checkResult;
}
}
Explanation to your issue is simple, but the solution is not trivial to explain.
The coordinates of the box from your UI will mostly not the be the same like the imaginary box on each preview frame. You must transform the coordinates from the UI box to scanBoundingBox.
I open sourced an example which implement the same usecase you are trying to accomplish. In this example I took another approach, I cut the box out of each frame first before feeding it to Google Vision, which is also more efficient, since Google Vision don't have to analyse the whole picture and waste tons of CPU...
I decied to cropp frame by wrapping barcode detector however I don't know why but cropped frame is rotated by 90 degress even smarphone is upright orientation.
Box Detector class
public class BoxDetector extends Detector<Barcode> {
private Detector<Barcode> mDelegate;
private int mBoxWidth;
private int mBoxHeight;
// Debugging
private CroppedFrameListener mCroppedFrameListener;
public BoxDetector(Detector<Barcode> delegate, int boxWidth, int boxHeight) {
mDelegate = delegate;
mBoxWidth = boxWidth;
mBoxHeight = boxHeight;
}
public void setCroppedFrameListener(CroppedFrameListener croppedFrameListener) {
mCroppedFrameListener = croppedFrameListener;
}
#Override
public SparseArray<Barcode> detect(Frame frame) {
int frameWidth = frame.getMetadata().getWidth();
int frameHeight = frame.getMetadata().getHeight();
// I assume that box is centered.
int left = (frameWidth / 2) - (mBoxWidth / 2);
int top = (frameHeight / 2) - (mBoxHeight / 2);
int right = (frameWidth / 2) + (mBoxWidth / 2);
int bottom = (frameHeight / 2) + (mBoxHeight / 2);
YuvImage yuvImage = new YuvImage(frame.getGrayscaleImageData().array(), ImageFormat.NV21, frameWidth, frameHeight, null);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
yuvImage.compressToJpeg(new Rect(left, top, right, bottom), 100, outputStream);
byte[] jpegArray = outputStream.toByteArray();
Bitmap bitmap = BitmapFactory.decodeByteArray(jpegArray, 0, jpegArray.length);
Frame croppedFrame = new Frame.Builder()
.setBitmap(bitmap)
.setRotation(frame.getMetadata().getRotation())
.build();
if(mCroppedFrameListener != null) {
mCroppedFrameListener.onNewCroppedFrame(croppedFrame.getBitmap(), croppedFrame.getMetadata().getRotation());
}
return mDelegate.detect(croppedFrame);
}
public interface CroppedFrameListener {
void onNewCroppedFrame(Bitmap bitmap, int rotation);
}
}
Box Detector usuage
BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(this)
.setBarcodeFormats(Barcode.ALL_FORMATS)
.build();
BoxDetector boxDetector = new BoxDetector(
barcodeDetector,
mBoxSize.getWidth(),
mBoxSize.getHeight());
boxDetector.setCroppedFrameListener(new BoxDetector.CroppedFrameListener() {
#Override
public void onNewCroppedFrame(final Bitmap bitmap, int rotation) {
Log.d(TAG, "onNewCroppedFrame: new bitmap, rotation: "+rotation);
runOnUiThread(new Runnable() {
#Override
public void run() {
mPreview.setImageBitmap(bitmap);
}
});
}
});
Cropped frame is rotated
I have made an application with JavaFX, where you have a picture with animated stuff in it, based on certain values in the app. The only problem I have: I don't really know, how to bind the values to the animations, so the animations move, based on the value put in. For example: if the value is 10, the animation should move fast. If the value is 1 the animation should move slow. Here is the code for the animation (it is smoke, coming out of a house):
smoke = new Image(HydroControl.class.getResource("/imgs/smoke.png").toExternalForm());
smokeView = new ImageView(smoke);
smokeView.setX(-190);
smokeView.setY(60);
Path path3 = new Path();
path3.getElements().add(new MoveTo(1,-60));
path3.getElements().add(new CubicCurveTo(0, 0, 0, 0, 0, 0));
PathTransition pathTransition3 = new PathTransition();
pathTransition3.setDuration(Duration.millis(2000));
pathTransition3.setPath(path3);
pathTransition3.setNode(smokeView);
pathTransition3.setCycleCount(Timeline.INDEFINITE);
pathTransition3.setAutoReverse(true);
pathTransition3.play();
Here is the code with the values:
public class PresentationModel {
private final DoubleProperty waterValue = new SimpleDoubleProperty();
private final DoubleProperty powerValue = new SimpleDoubleProperty();
private final BooleanProperty isOnValue = new SimpleBooleanProperty();
public double getWaterValue() {
return waterValue.get();
}
public DoubleProperty waterValueProperty() {
return waterValue;
}
public void setWaterValue(double waterValue) {
this.waterValue.set(waterValue);
}
public double getPowerValue() {
return powerValue.get();
}
public DoubleProperty powerValueProperty() {
return powerValue;
}
public void setPowerValue(double powerValue) {
this.powerValue.set(powerValue);
}
public boolean isIsOnValue() {
return isOnValue.get();
}
public BooleanProperty isOnValueProperty() {
return isOnValue;
}
public void setIsOnValue(boolean isOnValue) {
this.isOnValue.set(isOnValue);
}
}
I would be really glad if you could help me. Thank you.
Bind the properties of your transition to the values of your model with a calculation from the Bindings class. For example:
pathTransition3.durationProperty().bind(Bindings.multiply(Bindings.multiply(model.powerProperty(), model.waterValueProperty()), 500d));
model.isOnValueProperty().addListener((v, o, n) -> {
if (n) {
pathTransition3.play();
} else {
pathTransition3.pause();
}
});
I'm trying to implement a validation check for the contents of a TextField, displaying the validity as an icon next to it. However it doesn't seem to change the image. Here is the code I have so far, I've stripped out anything not related to the problem I'm experiencing.
Here's the view class:
package mypackage.view;
import mypackage.model.Foo;
// JavaFX imports
public class MyView extends VBox {
private final Foo model;
private final MyPresenter presenter;
HBox tokenValidationbox;
TextField tokentxt;
ImageView isValidimg;
public MyView(Foo model) {
this.model = model;
initFieldData();
layoutForm();
this.presenter = new MyPresenter(model, this);
}
private void initFieldData() {
tokenValidationbox = new HBox();
tokentxt = new TextField();
isValidimg = new ImageView();
}
private void layoutForm() {
tokenValidationbox.getChildren().addAll(tokentxt, isValidimg);
this.getChildren().add(tokenValidationbox);
}
}
And this is the presenter class that contains the logic:
package mypackage.view;
import mypackage.model.Foo;
// JavaFX imports
public class MyPresenter {
private final Foo model;
private final MyView view;
public MyPresenter(Foo model, MyView view) {
this.model = model;
this.view = view;
attachEvents();
}
private void attachEvents() {
view.tokentxt.setOnAction((ActionEvent event) -> {
view.isValidimg.setImage(new Image(validationImage(view.tokentxt.getText())))
});
}
public String validationImage(String token) {
String img = "dialog-error.png";
if(isValid(token)) img = "emblem-default.png";
return getClass().getClassLoader().getResource(img).toExternalForm();
}
private static boolean isValid(String token) {
// snip
}
}
As I understand this should check whether the entered token is valid whenever something is changed in the text field, and then load the corresponding image to display, however the image is not showing up.
emblem-default.png and dialog-error.png are located in the project resources folder and can be loaded statically (i.e. if I put an Image constructor inside the ImageView when initializing it, it works just fine)
Add a ChangeListener to the text property instead. onAction is only triggered, when Enter is pressed or similar.
Furthermore I recommend not recreating the images every time:
private static final Image VALID_IMG = new Image(MyPresenter.class.getClassLoader().getResource("emblem-default.png").toExternalForm());
private static final Image INVALID_IMG = new Image(MyPresenter.class.getClassLoader().getResource("dialog-error.png").toExternalForm());
public Image validationImage(String token) {
return isValid(token) ? VALID_IMG : INVALID_IMG;
}
view.tokentxt.textProperty().addListener((observable, oldValue, newValue) -> {
view.isValidimg.setImage(validationImage(newValue));
});
You could also use a Binding for this:
view.isValidating.imageProperty().bind(Bindings.createObjectBinding(() -> validationImage(view.tokentxt.getText()), view.tokentxt.textProperty()));
Which would update the image even before the text is modified.
I am putting together a slideshow program that will measure a user's time spent on each slide. The slideshow goes through several different magic tricks. Each trick is shown twice. Interim images are shown between the repetition. Transition images are shown between each trick.
On the first repetition of a trick the JPanel color flashes on the screen after a click before the next image is shown. This doesn't happen during the second repetition of the same trick. It's possible that the image is taking too long to load.
Is there an easy way to pre-load the images so that there isn't a delay the first time through?
NOTE: Original code deleted.
EDIT 1/10/2013: This code now works on slower machines. trashgod's second addendum helped the most. The mouseClick control structure periodically asks SwingWorker classes to load 40 images or less of the current trick while also setting the used images to null. I have simplified my code down for this to just two Image[]s and added a main method so it stands alone. Images are still required to run though. This is now pretty bare bones code, and if you're trying to make a slideshow with a lot of images I think it would be a good place to start.
NOTE: I think I figured out how to properly implement SwingWorker while still using multiple Image[]s. trashgod and kleopatra is this implementation in-line with what you were suggesting? I didn't end up using publish and process since I couldn't figure out how to get that to work appropriately with an indexed array, but because the StringWorker doesn't load all images in the array (only 40), and the code calls StringWorker every 20 images, there should be a pretty good buffer.
EDIT 1/10/2013 Changed out MouseListener by instead extending MouseAdapter on my Mouse class. Also fixed my paintComponent method to include a call to super.paintComponent(g).
Added publish/process methods to my SwingWorker class ImageWorker. Added a wrapper class, ArrayWrapper to allow passing imageArray[i] and its corresponding index int i with publish to process.
package slideshow3;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import java.util.List;
public class SlideShow3 extends JFrame
{
//screenImage will be replaced with each new slide
private Image screenImage;
private int width;
private int height;
//Create panel for displaying images using paintComponent()
private SlideShow3.PaintPanel mainImagePanel;
//Used for keybinding
private Action escapeAction;
//Image array variables for each trick
private Image[] handCuffs; //h
private Image[] cups; //c
//Used to step through the trick arrays one image at a time
private int h = 0;
private int c = 0;
//Used by timeStamp() for documenting time per slide
private long time0 = 0;
private long time1;
public SlideShow3()
{
super();
//Create instance of each Image array
handCuffs = new Image[50];
cups = new Image[176];
//start(handCuffsString);
start("handCuffs");
try
{
screenImage = ImageIO.read(new File("images/begin1.jpg"));
}
catch (IOException nm)
{
System.out.println("begin");
System.out.println(nm.getMessage());
System.exit(0);
}
/******************************************
* Removes window framing. The next line sets fullscreen mode.
* Once fullscreen is set width and height are determined for the window
******************************************/
this.setUndecorated(true);
GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this);
width = this.getWidth();
height = this.getHeight();
//Mouse click binding to slide advance control structure
addMouseListener(new Mouse());
//Create panel so that I can use key binding which requires JComponent
mainImagePanel = new PaintPanel();
add(mainImagePanel);
/******************************************
* Key Binding
* ESC will exit the slideshow
******************************************/
// Key bound AbstractAction items
escapeAction = new EscapeAction();
// Gets the mainImagePanel InputMap and pairs the key to the action
mainImagePanel.getInputMap().put(KeyStroke.getKeyStroke("ESCAPE"), "doEscapeAction");
// This line pairs the AbstractAction enterAction to the action "doEnterAction"
mainImagePanel.getActionMap().put("doEscapeAction", escapeAction);
/******************************************
* End Key Binding
******************************************/
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run()
{
SlideShow3 show = new SlideShow3();
show.setVisible(true);
}
});
}
//This method executes a specific SwingWorker class to preload images
public void start(String e)
{
if(e.equals("handCuffs"))
{
new ImageWorker(handCuffs.length, h, e).execute();
}
else if(e.equals("cups"))
{
new ImageWorker(cups.length, c, e).execute();
}
}
//Stretches and displays images in fullscreen window
private class PaintPanel extends JPanel
{
#Override
public void paintComponent(Graphics g)
{
if(screenImage != null)
{
super.paintComponent(g);
g.drawImage(screenImage, 0, 0, width, height, this);
}
}
}
/******************************************
* The following SwingWorker class Pre-loads all necessary images.
******************************************/
private class ArrayWrapper
{
private int i;
private Image image;
public ArrayWrapper(Image image, int i)
{
this.i = i;
this.image = image;
}
public int getIndex()
{
return i;
}
public Image getImage()
{
return image;
}
}
private class ImageWorker extends SwingWorker<Image[], ArrayWrapper>
{
private int currentPosition;
private int arraySize;
private String trickName;
private Image[] imageArray;
public ImageWorker(int arraySize, int currentPosition, String trick)
{
super();
this.currentPosition = currentPosition;
this.arraySize = arraySize;
this.trickName = trick;
}
#Override
public Image[] doInBackground()
{
imageArray = new Image[arraySize];
for(int i = currentPosition; i < currentPosition+40 && i < arraySize; i++)
{
try
{
imageArray[i] = ImageIO.read(new File("images/" + trickName + (i+1) + ".jpg"));
ArrayWrapper wrapArray = new ArrayWrapper(imageArray[i], i);
publish(wrapArray);
}
catch (IOException e)
{
System.out.println(trickName);
System.out.println(e.getMessage());
System.exit(0);
}
}
return imageArray;
}
#Override
public void process(List<ArrayWrapper> chunks)
{
for(ArrayWrapper element: chunks)
{
if(trickName.equals("handCuffs"))
{
handCuffs[element.getIndex()] = element.getImage();
}
else if(trickName.equals("cups"))
{
cups[element.getIndex()] = element.getImage();
}
}
}
#Override
public void done()
{
try
{
if(trickName.equals("handCuffs"))
{
handCuffs = get();
}
else if(trickName.equals("cups"))
{
cups = get();
}
}
catch(InterruptedException ignore){}
catch(java.util.concurrent.ExecutionException e)
{
String why = null;
Throwable cause = e.getCause();
if(cause != null)
{
why = cause.getMessage();
}
else
{
why = e.getMessage();
}
System.err.println("Error retrieving file: " + why);
}
}
}
/******************************************
* End SwingWorker Pre-Loading Classes
******************************************/
//Prints out time spent on each slide
public void timeStamp()
{
time1 = System.currentTimeMillis();
if(time0 != 0)
{
System.out.println(time1 - time0);
}
time0 = System.currentTimeMillis();
}
/******************************************
* User Input Classes for Key Binding Actions and Mouse Click Actions
******************************************/
private class EscapeAction extends AbstractAction
{
#Override
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
}
public class Mouse extends MouseAdapter
{
#Override
public void mouseClicked(MouseEvent e)
{
if(!(h<handCuffs.length) && !(c<cups.length))
{
timeStamp();
System.exit(0);
}
else if(h<handCuffs.length)
{
timeStamp();
screenImage = handCuffs[h];
repaint();
System.out.print("handCuffs[" + (h+1) + "]\t");
h++;
//purge used slides and refresh slide buffer
if(h == 20 || h == 40)
{
for(int i = 0; i < h; i++)
{
handCuffs[i] = null;
}
start("handCuffs");
}
if(h == 45)
{
start("cups");
}
}
else if(c<cups.length)
{
timeStamp();
screenImage = cups[c];
repaint();
System.out.print("cups[" + (c+1) + "]\t");
c++;
//purge used slides and refresh slide buffer
if(c == 20 || c == 40 || c == 60 || c == 80 || c == 100 || c == 120 || c == 140 || c == 160)
{
for(int i = 0; i < c; i++)
{
cups[i] = null;
}
start("cups");
}
}
}
}
/******************************************
* End User Input Classes for Key Binding Actions and Mouse Click Actions
******************************************/
}
This example uses a List<ImageIcon> as a cache of images returned by getImage(). Using getResource(), the delay is imperceptible. The next and previous buttons are bound to the Space key by default.
Addendum: You can control navigation by conditioning a button's setEnabled() state using an instance of javax.swing.Timer, for example.
Addendum: Your second example waits until the mouse is clicked to begin reading an image, an indeterminate process that may return a copy immediately or may not complete until after repaint(). Instead, begin reading the images in the background using ImageIO.read(), as shown here. You can process() your List<Image> and show progress, as seen here. The SwingWorker can be launched from the initial thread, running while you subsequently build your GUI on the EDT. You can display the first image as soon as it is processed.
I have a database filled with records in the following format: . What I want my application to do is select records from an external database, and display those records on the phone screen using a SurfaceView.
Currently, I have an Activity, and a Service responsible for the record-gathering portion of the application. The Activity passes an intent to the Service, and the Service responds by returning the records that need to be displayed. The records are stored in my program as instances of the Data class, and merely have the screen-coordinates of where the element should be drawn in the View (I am just drawing a circle for every record in the DB).
For the sake of brevity, I won't include the service but I will include a skeleton of the Activity class and the Data that I wish to display.
public class Data{
private int x;
private int y;
private int id;
private int shape;
/*
* Getters and setters for class properties
*/
}
public class Displayer extends Activity {
int ht;
int wt;
dataReceiver dreceiver;
public static Map<String, Data> Info;
private LinearLayout linear;
private static final int RADIUS = 20;
Panel panel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
panel=new Panel(this);
setContentView(panel);
Intent scv = new Intent(this, DataService.class);
startService(scv);
}
/*
* Various other methods to take care of onDestroy, onPause, etc.
*/
public class dataReceiver extends BroadcastReceiver {
//Get new data from intents sent by the Service and store it in Info variable
//Update the Testing Map data-structure in the Panel SurfaceView with new records
panel.setData(Info);
}
}
The problem that I am having pertains to the SurfaceView. I realize that many people are going to suggest that I use just a regular View, but my application involves a lot of elements, so a SurfaceView would be much more suitable for my needs. Below is a skeleton of my SurfaceView class that contains a nested class to manage the threads.
public class Panel extends SurfaceView implements SurfaceHolder.Callback {
private PanelThread _thread;
private Paint circlepaint;
private Paint textpaint;
private static int CircleColor = Color.YELLOW;
private static int TextColor = Color.RED;
private static Map<String, Data> Testing;
public Panel(Context context, Map<String, Data> entries) {
super(context);
getHolder().addCallback(this);
_thread = new PanelThread(getHolder(), this);
setFocusable(true);
textpaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textpaint.setStyle(Style.FILL_AND_STROKE);
textpaint.setColor(TextColor);
Testing = new HashMap<String, Data>();
// Log.d("TestingSize",String.valueOf(Testing.size()));
if (!(Testing.isEmpty())) {
Testing.clear();
}
for (Map.Entry<String, Data> e : entries.entrySet()) {
String keyvalue = e.getKey();
Data v = e.getValue();
Panel.Testing.put(keyvalue, v);
}
}
public Panel(Context context) {
super(context);
getHolder().addCallback(this);
_thread = new PanelThread(getHolder(), this);
setFocusable(true);
textpaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textpaint.setStyle(Style.FILL_AND_STROKE);
textpaint.setColor(TextColor);
Testing = new HashMap<String, Victims>();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
_thread.setRunning(true);
_thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
/* we have to tell thread to shut down
* & wait for it to finish,
* or else it might touch the Surface
* after we return and explode
*/
boolean retry = true;
_thread.setRunning(false);
while (retry) {
try {
_thread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
/*If new records are received
* from the service, they can be updated
* using this method
*/
public void setData(Map<String,Data>Info){
Testing=Info;
}
/*
* Iterate over all contents of Testing List and display them to the screen
*/
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!(Testing.isEmpty())) {
for (Map.Entry<String, Victims> e : Testing.entrySet()) {
Data d = e.getValue();
canvas.drawCircle(d.getX(), d.getY(), 10, circlepaint);
}
}
canvas.save();
canvas.restore();
}
/*
* Nested class to manage the threads for the SurfaceView
*/
class PanelThread extends Thread {
private SurfaceHolder _surfaceHolder;
private Panel _panel;
private boolean _run = false;
public PanelThread(SurfaceHolder surfaceHolder, Panel panel) {
_surfaceHolder = surfaceHolder;
_panel = panel;
}
public void setRunning(boolean run) {
_run = run;
}
public SurfaceHolder getSurfaceHolder() {
return _surfaceHolder;
}
#Override
public void run() {
Canvas c;
while (_run) {
c = null;
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
_panel.onDraw(c);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}//end of PanelThread
}//end of Panel
The problem that I'm having is that once I make the initial call to the Panel class, I'm going to be getting new records from the service, and consequently, my Info Map data-structure is going to get updated. However, my Panel class just gets stuck in a loop and never receives any new Data objects. All examples of SurfaceViews I've found have involved updating them methods within the SurfaceView class itself (e.g. touching the screen and creating a new image, etc.) Sadly, I'm stumped on this particular problem. Is there a better approach for designing my Activity/SurfaceView interaction? Is an additional View required?
From a quick look at your code I think the problem is that you are trying to update a data structure from the UI thread while acting on it in the PanelThread. It also looks like your Activity class and Panel are also out of sync - I don't see the setData method in your Panel class, but I assume it updates the Testing static member variable.
My suggestions:
1 - Testing should not be static - it's fundamentally associated with this instance of the class.
2 - Use Synchronize when touching the data (Testing). This should be done for both setting and reading the data.
3 - Put a sleep or a wait call in your run loop - this gives time for the activity UI thread to update Testing. Also saves battery and allows you to pick a frame rate for updates.