I am trying to make an app view that, when presented with a subtraction problem, will show an animation of a growing rectangle (or fat line) that crawls up the number line to that integer.
I already have things set up so that I click "show me!" and bars are drawn along the number line showing the minuend, the subtrahend and the difference, but I'd like to be able to have a positive number's rectangle crawl up in a positive direction, negative from zero in the negative direction.
In looking through the documentation there seem to be several different ways to go about this. I am hoping somebody can suggest a way that's reasonably simple for this novice to implement. Here are the different approaches I've found:
This seems very much like this person's desire to have a bar graph where the bars "pop up," but it doesn't have an answer. Android Animated Bar Chart (invalidate())
I've perused http://developer.android.com/guide/topics/resources/drawable-resource.html -- but I don't have a "drawable" because it's being drawn in the View. I'm thinking of making the rest of the number line a background bitmap per Android View.getDrawingCache returns null, only null but I want three rectangles (for the minuend, subtrahend and difference).
I have thought of making a series of rectangle drawables and showing them frame-by-frame to show the growth.
I have looked at Animation at a specified rate using canvas / Ondraw but cannot discern just what code to wrap in that "if" statement, if in fact my problem is re-drawing...
I looked at using Paths -- and put the following code together. If direction matters, then it seems I should be able to slow things down and watch the path going in that direction, but it's instantaneous. I found I saw an example at http://www.curious-creature.org/2013/12/21/android-recipe-4-path-tracing/
if (minuendLength > 0) // start at 0 and go to the minuend length
{
path.addRect(interpX(0), yPosition(40), interpX(minuendLength), yPosition(43) , Path.Direction.CW);
// interpX lets me say what number on the number line it should align with;
//yPosition is percent of the way down the screen.
canvas.drawPath(path,minuendPaint);
// Seems same as drawRect -- instantaneous.
}
(The number line in the 'background' code is as follows, with different options for different sized integers entered:
if ( (minuendLength <10 && subtrahendLength <10 ) && (minuendLength >-10 && subtrahendLength >-10 ) )
{
this.setLineDimension(10); // default
super.onDraw(canvas);
canvas.drawLine(interpX(-this.getLineDimension()), yPosition(52 ),
interpX(this.getLineDimension()), yPosition(52), axisPaint);
int step = this.getLineDimension()/5; // so you're not writing *all* the numbers
// when they enter numbers and you make your own number line.
// paints the little hatch marks
for (int x = -this.getLineDimension(); x <= this.getLineDimension(); x+=step/2)
canvas.drawLine(interpX(x), yPosition(52), interpX(x), yPosition(53) , littleAxisPaint);
// draw the numbers on the hatch marks
textPaint.setTextAlign(Paint.Align.CENTER);
for (int x = -this.getLineDimension() + step; x < this.getLineDimension(); x += step)
{
canvas.drawText(Integer.toString(x), interpX(x), yPosition(56), textPaint);
}
}
I ended up doing this using Runnables.
animate.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if ( ((addend1 > 0) && (addend2 < 0)) )
{
tempNum1 =addend1 ;
tempNum2 = addend2;
h.post(shrink1bigger);
}
else if (addend1 < 0 && addend2 > 0)
{
tempNum1 =addend1 ;
tempNum2 = addend2;
h.post(shrink2bigger);
}
}
});
private Runnable shrink2bigger = new Runnable(){
#Override
public void run() {
tempNum1+=1;
tempNum2-=1;
shrinkDraw();
Log.i("WatchAnimActivity", "tempNum1 is " + tempNum1);
h.postDelayed(shrink2bigger, 500);
checkTemp(shrink2bigger);
}};
private void shrinkDraw()
{
numLine.setFirstNum(tempNum1);
numLine.setNextNum(tempNum2);
numLine.invalidate();
}
public void checkTemp(Runnable shrink)
{
Log.i("WatchAnimActivity", "tempNum1 in CHeckTemp is " + tempNum1);
if (tempNum1 ==0 || tempNum2==0)
h.removeCallbacks(shrink);
}
Related
Background
I'm developing an app for Android that plots data as a line graph using AndroidPlot. Because of the nature of the data, it's important that it be pannable and zoomable. I'm using AndroidPlot's sample code on bitbucket for panning and zooming, modified to allow panning and zooming in both X and Y directions.
Everything works as desired except that there are no X and Y axis lines. It is very disorienting to look at the data without them. The grid helps, but there's no guarantee that grid lines will actually fall on the axis.
To remedy this I have tried adding two series, one that falls on just the X axis and the other on the Y. The problem with this is that if one zooms out too far the axis simply end, and it becomes apparent that I have applied a 'hack'.
Question
Is it possible to add X and Y axis lines to AndroidPlot? Or will my sad hack have to do?
EDIT
Added tags
I figured it out. It wasn't trivial, took a joint effort with a collaborator, and sucked up many hours of our time.
Starting with the sample mentioned in my question, I had to extend XYPlot (which I called GraphView) and override the onPreInit method. Note that I have two PointF's, minXY and maxXY, that are defined in my overridden XYPlot and manipulated when I zoom or scroll.
#Override
protected void onPreInit() {
super.onPreInit();
final Paint axisPaint = new Paint();
axisPaint.setColor(getResources().getColor(R.color.MY_AXIS_COLOR));
axisPaint.setStrokeWidth(3); //or whatever stroke width you want
XYGraphWidget oldWidget = getGraphWidget();
XYGraphWidget widget = new XYGraphWidget(getLayoutManager(),
this,
new SizeMetrics(
oldWidget.getHeightMetric(),
oldWidget.getWidthMetric())) {
//We now override XYGraphWidget methods
RectF mGridRect;
#Override
protected void doOnDraw(Canvas canvas, RectF widgetRect)
throws PlotRenderException {
//In order to draw the x axis, we must obtain gridRect. I believe this is the only
//way to do so as the more convenient routes have private rather than protected access.
mGridRect = new RectF(widgetRect.left + ((isRangeAxisLeft())?getRangeLabelWidth():1),
widgetRect.top + ((isDomainAxisBottom())?1:getDomainLabelWidth()),
widgetRect.right - ((isRangeAxisLeft())?1:getRangeLabelWidth()),
widgetRect.bottom - ((isDomainAxisBottom())?getDomainLabelWidth():1));
super.doOnDraw(canvas, widgetRect);
}
#Override
protected void drawGrid(Canvas canvas) {
super.drawGrid(canvas);
if(mGridRect == null) return;
//minXY and maxXY are PointF's defined elsewhere. See my comment in the answer.
if(minXY.y <= 0 && maxXY.y >= 0) { //Draw the x axis
RectF paddedGridRect = getGridRect();
//Note: GraphView.this is the extended XYPlot instance.
XYStep rangeStep = XYStepCalculator.getStep(GraphView.this, XYAxisType.RANGE,
paddedGridRect, getCalculatedMinY().doubleValue(),
getCalculatedMaxY().doubleValue());
double rangeOriginF = paddedGridRect.bottom;
float yPix = (float) (rangeOriginF + getRangeOrigin().doubleValue() * rangeStep.getStepPix() /
rangeStep.getStepVal());
//Keep things consistent with drawing y axis even though drawRangeTick is public
//drawRangeTick(canvas, yPix, 0, getRangeLabelPaint(), axisPaint, true);
canvas.drawLine(mGridRect.left, yPix, mGridRect.right, yPix, axisPaint);
}
if(minXY.x <= 0 && maxXY.x >= 0) { //Draw the y axis
RectF paddedGridRect = getGridRect();
XYStep domianStep = XYStepCalculator.getStep(GraphView.this, XYAxisType.DOMAIN,
paddedGridRect, getCalculatedMinX().doubleValue(),
getCalculatedMaxX().doubleValue());
double domainOriginF = paddedGridRect.left;
float xPix = (float) (domainOriginF - getDomainOrigin().doubleValue() * domianStep.getStepPix() /
domianStep.getStepVal());
//Unfortunately, drawDomainTick has private access in XYGraphWidget
canvas.drawLine(xPix, mGridRect.top, xPix, mGridRect.bottom, axisPaint);
}
}
};
widget.setBackgroundPaint(oldWidget.getBackgroundPaint());
widget.setMarginTop(oldWidget.getMarginTop());
widget.setMarginRight(oldWidget.getMarginRight());
widget.setPositionMetrics(oldWidget.getPositionMetrics());
getLayoutManager().remove(oldWidget);
getLayoutManager().addToTop(widget);
setGraphWidget(widget);
//More customizations can go here
}
And that was that. I sure wish this was built into AndroidPlot; it'll be nasty trying to fix this when it breaks in an AndroidPlot update...
I want to moove two objects smoothely at Touching.
Here is my Code:
for(int i = 0; i <96; i++){
Asstest.rect_pipe_down.y--);
}
This should move the rect 96 pixels down (SMOOTH)
But it just close without smoothed...
What did I wrong?
If you Touch, the pipes should close, but not hard, smooth should they close.
But with following code they just close hard...
Here is the full touched code:
if(Gdx.input.isTouched()){
Assets.rect_pipe_down.y = 512 - 320/2;
Assets.rect_pipe_up.y = -320 + 320/2;
for (int i = 0; i < 96; i++){
smoothTime = TimeUtils.millis();
if(TimeUtils.millis() - smoothTime > 10) {
Assets.rect_pipe_down.y--;
Assets.rect_pipe_up.y++;
batch.begin();
batch.draw(Assets.region_pipe_down, Assets.rect_pipe_down.x, Assets.rect_pipe_down.y);
batch.draw(Assets.region_pipe_up, Assets.rect_pipe_up.x, Assets.rect_pipe_up.y);
batch.end();
}
}
closed = true;
}
You cannot do rendering multiple times in one render() call, one call is for drawing exactly one frame. In your current code, the later images simply overwrite the previous ones.
What you could do is have a variable which persists between frames which stores whether or not the pipes are currently closing, a constant for the speed and some condition to tell when they can stop - maybe when they are some given distance from each other, not sure what you would want here. Anyway, that's what I'll use in my example.
Then in the render() method, before drawing anything, you can do this:
if (closing) {
Assets.rect_pipe_down.y -= CLOSE_SPEED * delta;
Assets.rect_pipe_up.y += CLOSE_SPEED * delta;
if (Assets.rect_pipe_down.y - Assets.rect_pipe_up.y < TARGET_DIST) {
Assets.rect_pipe_down.y = Assets.rect_pipe_up.y + TARGET_DIST;
closing = false;
}
}
Here, closing is a variable you set to true when you want them to start closing, the others are constants. You could add some more variables/constants if you want to make sure they end up at a specific height independent on framerate.
I'm trying to make something that would allow me to press data points in a line graph. I created a subclass of GraphicalView and added a method to it, which gets called when the graph is clicked.
// This goes into the activity
graphicalView.setOnClickListener(new View.OnClickListener() {
graphicalView.displaySomething();
}
// This one is from the GraphicalView subclass
public void displaySomething() {
SeriesSelection seriesSelection = getCurrentSeriesAndPoint();
int pointIndex = seriesSelection.getPointIndex();
double xValue = seriesSelection.getXValue());
/*
Now in this method, I can do some pretty cool stuff like drawing
additional graphics near the clicked point in the line chart simply
by redrawing the graph and passing it some data. This is made possible
by subclassing both LineGraph and ScatterChart.
For the purposes of this question though, I would simply log the
values I'm curious about.
*/
Log.d(TAG, "pointIndex: " + pointIndex + ", xValue: " + xValue);
repaint();
}
Let's just say I have a graph that shows the weight of my pet dog over time and then I have an array of a list of strings which explains why my pet got that heavy, her favorite food at that time, her activites, etc. The data in the array will be displayed somewhere outside the graph (ie. to a group of TextViews). What I expect is to get the index of the clicked point, and then use that to index the array of data.
pointIndex and xValue are the variables I'm curious about. When drawing additional graphics to the graph, pointIndex was very helpful since ScatterChart's drawSeries knows how to use this variable properly. However, it can't be used for what I want to achieve because this value is not constant per point. For example, I have a point in the graph that lies in (5, 10) and it shows up as the fifth point with pointIndex 4 when the graph is not panned to the left. However, when I pan the graph to the left so that the mentioned point shows up as the first point, the pointIndex changes to 0. Which is why I can't use this variable to index the data array.
As with pointIndex, most of the values related to a point have the same behavior, but I've noticed that xValue is constant. I'm considering on using this value to index my external data source, perhaps by using a Map where xValue is the key. However, I can't figure out where to create this data structure. I've looked around achartengine's source code and it seems that the best place to do the Map's initialization is in ScatterChart's drawSeries method. The problem is the array of floats being passed to it also move around relative to the graph's panning, but my array of data does not move along with it. Can you please point me to the right direction?
this code will allows you to click data points in achartengin:
mySimpleXYPlot.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// handle the click event on the chart
if(event.getAction()==MotionEvent.ACTION_DOWN){
SeriesSelection seriesSelection = mySimpleXYPlot
.getCurrentSeriesAndPoint();
if (seriesSelection == null) {
Toast.makeText(Records.this, "No chart element",
Toast.LENGTH_SHORT).show();
} else {
// display information of the clicked point
Toast.makeText(
Records.this,
"Chart element in series index "
+ seriesSelection.getSeriesIndex()
+ " data point index "
+ seriesSelection.getPointIndex()
+ " was clicked"
+ " closest point value X="
+ seriesSelection.getXValue() + ", Y="
+ seriesSelection.getValue(),
Toast.LENGTH_LONG).show();
}
}
return false;
}
});
I finally got it. I overrided GraphicalView's onDraw method so that I can extract keys and attach them to my data. It goes something like this:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.getClipBounds(mRect);
int top = mRect.top;
int left = mRect.left;
int width = mRect.width();
int height = mRect.height();
if (mRenderer.isInScroll()) {
top = 0;
left = 0;
width = getMeasuredWidth();
height = getMeasuredHeight();
}
mChart.draw(canvas, left, top, width, height, mPaint);
/*
* This is where we build the map of <data point, info>
*/
if (mIsFirstDraw && mIsAttachData && mAdditionalPointData != null && mAdditionalPointData.size() > 0) {
// ClickablePoints is an interface that has a method to get the datapoints rendered by ScatterChart
List<float[]> pointCoords = ((ClickablePoints) mChart).getDataPointCoords();
mPointKeys = new double[pointCoords.size()];
mAdditionalDataMap = new HashMap<Double, String>();
for (int i = 0, j = mPointKeys.length; i < j; i++) {
float[] coords = pointCoords.get(i);
SeriesSelection seriesSelection = mChart.getSeriesAndPointForScreenCoordinate(
new Point(coords[0], coords[1]));
mPointKeys[i] = seriesSelection.getXValue();
mAdditionalDataMap.put(seriesSelection.getXValue(), mAdditionalPointData.get(i));
}
mIsFirstDraw = false;
}
}
mIsFirstDraw is a flag that indicates that it is the first time that the graph will be drawn. It needs to be taken down afterwards so that it does not repeat attaching keys to the data.
I'm working on a simple platformer and I've been through a couple of collision systems before finally finding the solution that has thus far been as stable as I could hope for. That is until the player collides with a block whose CENTER position in the y axis equals 0. It's very wierd although I suspect it's only in the y axis because I check the x movement/collision first. I have no idea.
I do a simple AABB type collision check/response where I apply the x velocity first then if overlap, reposition player so that right player bound = left block bound bringing them out of overlap. I then go through the same with the y axis taking the player vertical direction to work out whether player has hit bottom side or top side of block. The only controls are jump as the player has an acceleration force in positive x hence the player will never travel left.
The problem is that the player moves off the blue dotted block but when it hits the red dotted one a collision is detected in the Y axis thus the player gets moved up out of overlap but then when the next frame executes the player's velocity in x is added as usual but a collision gets registered and it then positions the player to the left of the red block. The next frame detects a collision with the blue block and thus it positions the player on top of it as shown below.
The setup up below makes the player loop this sequence over
Info:
player.centerPosition = (2, 2)
player.width = 0.5f
player.height = 0.8f
blueBlock.centerPosition = (1, 1)
redBlock.centerPosition = (4, 0)
block.width = 3
block.height = 1
private void checkBlockCollision(float deltaTime) {
List<GameObject> colliders = grid.getPotentialColliders(player);
int len = colliders.size();
for (int axis=0;axis<2;axis++) { // 0 = X-axis, 1 = Y-axis
if (axis == 0) {
player.position.add(player.velocity.x*deltaTime, 0);
player.updateBounds();
} else {
player.position.add(0, player.velocity.y*deltaTime);
player.updateBounds();
}
for (int i=0;i<len;i++) { // Cycle through all blocks found in broad phase
GameObject collider = colliders.get(i);
if (OverlapTester.overlapRectangles(player.bounds, collider.bounds)) {
if (axis == 0) {
player.position.x = collider.position.x - (Player.PLAYER_WIDTH + collider.bounds.width)/2;
player.velocity.x = 0f;
Log.d("TAG", "Move player LEFT");
} else {
if (player.velocity.y > 0) {
player.position.y = collider.position.y - (Player.PLAYER_HEIGHT + collider.bounds.height)/2;
player.velocity.y = -player.velocity.y*0.333f;
Log.d("TAG", "Move player DOWN");
} else {
player.position.y = collider.position.y + (Player.PLAYER_HEIGHT + collider.bounds.height)/2;
player.velocity.y = 0;
player.state = Player.PLAYER_STATE_GROUNDED;
Log.d("TAG", "Move player UP");
}
}
}
} // end for loop colliders
} // end for loop axis
} // END METHOD
If anyone can shed some light on what the truck is going on here that would be amazing.
Thanks for reading and I can provide any further info or source to anyone interested.
Marios Kalogerou
SOLUTION:
I found a quick and dirty fix to the my problem. I just simply moved the player up an extra 0.001 units and this actually seperated the objects. Strange that since other blocks worked fine. Thanks again if you read through that and I hope my solution helps anyone with similar issues.
I've created a customized keyboard of my own.
When I click & drag over the keyboard the key should be highlighted according to my finger move. When I lift up my finger the corresponding letter has to be printed on the EditText.
How could I do this?
Assuming you are just drawing your 'customized' keyboard onto a Canvas. Obviously using the built-in keyboard is better. However if you really want to do this yourself, here is a solution.
Draw each character of your keyboard, store it's x,y location in an array corresponding to each key as you draw it.
//initialise
int[] letterX = new int[29];
int[] letterY = new int[29];
char[] keyboardChar = {'Q','W','E','R','T','Y','U','I','O','P','A','S','D','F','G','H','J','K','L','Z','X','C','V','B','N','M',' ', '<','#'};
somewhere in your 'draw' method:
// first keyboard row (do this for each row moving adding to y for next row
x=10; y=50; keySpacing = 30; // starting x, y position
for ( int charIndex = 0; charIndex < 10; charIndex++) {
letterX[charIndex] = x;
letterY[charIndex] = y;
textWidth = mPaint.measureText(keyboardChar, charIndex, 1);
if ( !letterHit ) {
canvas.drawText(keyboardChar, charIndex, 1, x - (textWidth/2), y, mPaint);
} else {
// this key is being pressed so draw highlighted somehow
canvas.drawText(keyboardChar, charIndex, 1, x - (textWidth/2), y,mPaint);
}
x += keySpacing;
}
In the onTouchEvent, compare the x, y touch position to your array of letterX, letterY this will tell you if a key is being pressed. The array index tells you which character it is. You'd need some code in the draw method to print out the key highlighted if its being pressed (example below assumes 16px tolerance).
for (int j=0; j < 29; j++ ) {
if (( Math.abs(touchX - letterX[j]) < 16 ) && ( Math.abs(touchY - letterY[j]) < 16 ) && !keyboardLock ) {
letterHit = j;
}
}
You'll still need more logic (for deleting etc) and you'll need to maintain a string. That's why really this is best to use the system keyboard if possible.
I think you need something like this post .