How can i animate a line on a canvas in WearOS every 5 seconds?
I know that we have to use an AnimatorTask with a call to postInvalidate(). However since I am directly drawing the line on the canvas, I do not have a View object.
public class AnimatorTask extends TimerTask {
private WatchEventInfo eventInfo;
public AnimatorTask(WatchEventInfo eventInfo) {
this.eventInfo = eventInfo;
}
#Override
public void run() {
drawAndAnimate();
}
public WatchEventInfo getEventInfo() {
return eventInfo;
}
private void drawAndAnimate() {
Canvas canvas = eventInfo.getCanvas();
// For testing
canvas.drawLine(100, 100, 300, 300 markerPaint);
}
}
How do get access to the canvas.drawLine() method object and notify the canvas to redraw itself from the TimerTask assuming that my TimerTask exists in CanvasWatchFaceService subclass?
You get access to the canvas by overriding the onDraw(Canvas canvas, Rect bounds) method declared in the CanvasWatchFaceService.Engine class.
If you create your watch face using the New Project wizard in Android Studio, you'll notice a method at the end of the MyWatchFace class called handleUpdateTimeMessage(). This logic will ensure that your onDraw method gets called at the interval specified by INTERACTIVE_UPDATE_RATE_MS (default is once per second) in interactive mode.
Add a check in your onDraw method to only trigger your line animation if five seconds have past since the last update.
Related
I've got a fragment, which I construct and insert a GraphicalView, and I need to know when the GraphicalView (chart) is done being drawn. the api isChartDrawn always returns false, including within onResume of the containing fragment.
public void onResume()
{
Log.d(TAG, "onResume, the mChart isDrawn: " + mChart.isChartDrawn());
super.onResume();
mListener.didNotificyChartDrawn();
}
Is there a notification I'm not seeing, or strategy for knowing when the chart is done being rendered? I'm asking because I need to access the series points from within one of the series of the XYChart used to construct the graphical view, like this:
mChart = new GraphicalView(getActivity(), mXYChart);
where mXYChart is an instance of the LineChart.
The graph renders fine, and I'm able to access the points I need later on via touch handling, just need to get to them a little earlier now and am hitting this issue. Any work arounds, etc, appreciated.
you're getting that because during onResume still was not draw yet. That's whole Android, not just aChartEngine. I'm not sure it's the best design decision, but that's how it is.
But good news is: there's a nice trick.
getView().getViewTreeObserver().addOnDrawListener(new OnDrawListener(){
void onDraw(){
getView().getViewTreeObserver().removeOnDrawListener(this);
// do your stuff here
}
});
this trick is used A LOT for animation, so you can measure stuff on screen and do the proper animations.
If you look at the onDraw method in GraphicalView, it sets the boolean mDrawn to true at the very end. So what must be happening is you are calling
public boolean isChartDrawn() {
return mDrawn;
}
Before it has completed the onDraw method. I would either create a interval handler to keep checking if mDrawn has been changed to true, or modify the library file GraphicalView so that it has an optional listener that you can attach to be fired off when the thing is drawn:
DrawnInterface mCallback;
public interface DrawnInterface{
public void onDrawn();
}
.....
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.getClipBounds(mRect);
......
canvas.drawBitmap(fitZoomImage, left + width - zoomSize * 0.75f, buttonY, null);
}
mDrawn = true;
mCallback.onDrawn();
}
Then make your calling activity implement the DrawnInterface you defined, and initialize the interface inside the constructor of GraphicalView
The Setup : A RelativeLayout with a GLSurfaceView and a Button as shown in the image..
The Problem: Lets say I have other triangle models (The one in the picture being the initial model)... I wish to change the models cyclically on click of the button. Since button is on the UI thread and glSurfaceView runs on a separate thread, I don't exactly know how to pass the info/instruction to it. I know there is this thing called Handler in Android which might be useful in this case... But I need some help here..
Edit: If Handler is the right way, I need to know how to add Looper to that Handler... The documentation says add looper.prepare() at the start of run() method.. But the glSurfaceView creates thread implicitly, resulting in no run() method directly available..
I don't think it is necessary to use handlers to solve this issue but you may need to adjust the way you organise your classes.
Here is an example of an organisational structure that might solve your issue:
Activity Class
public class MainActivity extends Activity {
private int modelNumber = 0;
private ArrayList<Model> models = new ArrayList<Model>();
private YourRendererClass renderer;
#Override
public void onCreate(Bundle savedInstanceState) {
...
// Setup GLSurfaceView
GLSurfaceView surface = new GLSurfaceView(this);
setContentView(surface);
renderer = new YourRendererClass();
surface.setRenderer(renderer);
// Set up models
models.add(new Model(x, y, size etc..));
models.add(new Model(x, y, size etc..));
models.add(new Model(x, y, size etc..));
etc.
// Display first model
renderer.setCurrentModel(models.get(modelNumber));
...
}
// Called by the button press:
// Use android:onClick="onClick"
// in your layout xml file within button
public void onClick(View view){
// Make it loop round
modelNumber++;
if(modelNumber>=models.size()){
modelNumber=0;
}
// Display current model
renderer.setCurrentModel(models.get(modelNumber));
}
}
Renderer Class
public class YourRendererClass implements Renderer {
private Model currentModel;
#Override
public void onDrawFrame(GL10 gl) {
// ** Your existing set-up code **//
// Draw model
if (currentModel!=null){
currentModel.draw(gl);
}
}
public void setCurrentModel(Model model){
currentModel = model;
}
}
Model class
public class Model {
// Holds model information
private int size;
private int x;
private int y;
// etc...
public model(int x, int y, int size etc...){
this.x=x;
this.y=y;
this.size=size;
// etc....
}
public void draw(GL10 gl) {
// ** Draw model based on model information fields above **
}
}
The above code is untested as I don't have access to your drawing code but the structure should work if implemented correctly. I've tried to make it clear where you'll have to insert your own code to make it work. In particular I wasn't sure what defines each of your different models so you'll need to include sufficient local variables within the Model class to define them.
I hope my answer helps, let me know if you have any questions.
Tim
You should look at queueEvent! It's a very convenient way to pass informations from UI Thread to renderer Thread:
queueEvent(new Runnable(){
#Override
public void run() {
mRenderer.method();
}});
I created a live wallpaper service using AndEngine library. On screen there is a bird Sprite that flying repeatedly from the left to right. I'm using LoopEntityModifier and PathModifier for the solution. The bird is coded to start randomly on Y-position everytime it shows up from the left screen.
The code is like this:
public class MyLiveWallpaperService extends BaseLiveWallpaperService {
private AnimatedSprite birdSprite;
...
public Scene onLoadScene() {
...
float[] coordY = generateRandomCoordY(); // my custom function to generate random array of Y-coordinates
Path path = new Path(coordX, coordY); // set the coordinate to Path object
// register the modifiers (for the one who is curious, 1st argument of PathModifier is the duration,
// but it has nothing to do with the question)
birdSprite.registerEntityModifier(new LoopEntityModifier(new PathModifier(10, path)));
...
}
}
The problem is the Path's Y-coordinates value cannot be changed anymore when the LoopEntityModifier & PathModifier has run. I want everytime the loop started, I can set the new Path's Y-coordinate value again.
I think you can get around this problem by overriding onModifierFinished() and creating a new PathModifier with the changed path. It would look something like this:
public LoopEntityModifier createModifier(Path path) {
return new LoopEntityModifier(new PathModifier(path)) {
#Override
public void onModifierFinished(final IModifier<IEntity> pEntityModifier, final IEntity pEntity) {
birdSprite.registerEntityModifier(createModifier(path));
}
}
}
birdSprite.registerEntityModifier(createModifier());
This only works if onModifierFinished() is called at the end of every loop.
I use a surfaceview to draw a pie chart in Android. In order to know how big the pie slice should be I need to pass a parameter to the onDraw() method. How can I do this? Inside the onDraw() I make a query to a datahelper-class that fetches the right data.
I tried to call a static function in one Activity from the onDraw, and which function returned an integer. But I want something more dynamic, so I can send the integers I need to from the Activity to the onDraw and just get the result in form of a pie chart.
Any suggestions?
One of solutions can be like this:
public class MySurfaceView extends SurfaceView
{
private MyParameter parameter;
public void setParameter(MyParameter parameter)
{
this.parameter=parameter;
}
#Override
public void onDraw(Canvas canvas)
{
if(this.parameter==null)
return; //nothing to draw...
//draw here...
}
}
//somewhere in code
MySurfaceView sView=(SurfaceView )findViewById(R.id.pie_chart);
//
sView.setParameter(new MyParameter(100,2000, false));
}
#Override
protected void onDraw(Canvas canvas)
{
//Note:I do not want to use the canvas object from this function param
//If i do so its working , But i would like to understand why the following is not working
Canvas c =new Canvas();
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
c.drawText("HELLO CANVAS",200,300,paint);
}
MORE CODE
public class graphicProj extends Activity {
private Canvas canvas;
#Override
public void onCreate(Bundle savedInstanceState) {
{
....
SimpleView simpleview_obj = new SimpleView(this);
setContentView(simpleview_obj);
simpleview_obj.onDraw(canvas);
.....
new GetData().execute();
}
private static class SimpleView extends View {
private ShapeDrawable mDrawable = new ShapeDrawable();
....
protected void onDraw(Canvas canvas) {
//draw graphic objects
....
}
}
public class GetData extends AsyncTask<Void, String, Void> {
#Override
protected void onPreExecute() {
Log.d("PROJ","STARTIN");
}
#Override
protected Void doInBackground(Void... unused) {
////My calculation and reading frm DataStream
}
#Override
protected void onProgressUpdate(String... data) {
//I Keep updating the result...
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
canvas.drawText(result, 200, 300, paint);
}
#Override
protected void onPostExecute(Void unused) {
Log.d("PROJ","END");
}
}
}
Not here or in your other question have you provided enough information on why you can't do that. There is no reason to draw on a new canvas instead of the already existing one.
The code is not working because your new Canvas c isn't assigned to anything. Its like creating a String myString for a log but never using Log.d(tag, myString)
edit (after reading all the comments)
If you calculate a value in your onCreate() and want to display that value in your onDraw(), that simply do that. Store the result in a member variable and you can access it in the onDraw().
Otherwise: Please provide your complete code. I guess you just do it way more complex than it should be...
edit2
Your code is a bit messy and does a lot of stuff in areas where you shouldn't do it. So drawing inside the onProgressUpdate() is seriously wrong. You should encapsulate your calculation and drawing.
What you should do (I recommend using SurfaceView instead of View, anyway...):
You should start your AsynchTask which updates the string you want to draw. The string should be a variable inside your View, where you use it for drawing.
The drawing itself should be called by a drawing thread (I remember: use the SurfaceView instead of the View as a parent class). Inside that onDraw() you should just use your paint object, the given canvas and the string you want to draw (don't forget to make the paint variable also a member variable to prevent recreating the same object over and over again for performance/memory reasons).
If you do not know how to work with a SurfaceView or if you want to learn how you could work with a drawing thread please read my tutorial about 2d drawing: www.droidnova.com/2d-tutorial-series
A short last sentence: You did a lot of things in the right way, you just mixed up with the places where you do it. You should try to rethink what you really want to achieve and how it could be done the easiest way. Maybe my tutorial helps to clear your mind a bit.