Arithmetic Operation - android

hello guys I am having this problem. When I enter a score in the editText, I want the app to generate the equivalent in the Textview(w/ red box). But the app crashes with this code.
private void calculateEquivalent(){
double x , y;
y = Double.valueOf(total_score.toString());
x = Double.valueOf(editScore.getText().toString());
if (x >= y * 0.65){
double equivalent = (Math.round((100 + (72 * (x - y)) / y)));
String equi = String.valueOf(equivalent);
textEquivalent.setText(equi);
} else {
double equivalent = (Math.round((75 + (23 * (x - y * 0.65)) / y)));
String equi = String.valueOf(equivalent);
textEquivalent.setText(equi);
}
}

The error is empty string when convert from string to double
In this code
y = Double.valueOf(total_score.toString());
x = Double.valueOf(editScore.getText().toString());
May be total_score.toString() or editScore.getText().toString() was empty
And what is type of total_score variable

You have a problem when trying to convert empty string to a double. You should check first that the text field is not empty and that it doesn't contain characters by catching NumberFormatException

As the error log suggests, you need to make sure that you have proper values before you start the calculation.
So before calling this function you need to check for below conditions:
try
{
if((total_score.toString() != null && !total_score.toString().isEmpty()) && (editScore.getText().toString()!=null && !editScore.getText().toString().isEmpty()))
{
y = Double.valueOf(total_score.toString());
x = Double.valueOf(editScore.getText().toString()); //chances of getting a Numberformat exception if entered value is not a number
calculateEquivalent();
}
}
catch(NumberFormatException e)
{
//Toast.makeText(m_context, "You entered a wrong value,Please enter only numeric values", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
catch(Throwable e)
{
e.printStackTrace();
}
Also in your calculateEquivalent(); method, you need to make sure that value of y should not be zero.
Hope this helps you :)

Hey #callmejeo the main problem with the function you wrote above is that you are coverting "NULL" values into String so one thing you can do is that to HANDLE exception.
private void calculateEquivalent(){
try{
double x , y;
y = Double.valueOf(total_score.toString());
x = Double.valueOf(editScore.getText().toString());
if (x >= y * 0.65){
double equivalent = (Math.round((100 + (72 * (x - y)) / y)));
String equi = String.valueOf(equivalent);
textEquivalent.setText(equi);
} else {
double equivalent = (Math.round((75 + (23 * (x - y * 0.65)) / y)));
String equi = String.valueOf(equivalent);
textEquivalent.setText(equi);
}
}
catch(Exception e){
Toast.makeText(this,"Please Enter some value",Toast.LENGTH_LONG).show();
}
}

Thanks a lot guys without your suggestions I probably stuck in this problem :)
and then I've come up with this
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_student_quiz);
TextWatcher inputTextWatcher = new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
calculateEquivalent();
}
};
editScore.addTextChangedListener(inputTextWatcher);
}
private void calculateEquivalent(){
try {
y = Double.parseDouble(total_score);
x = Double.parseDouble(editScore.getText().toString());
if (x >= y * 0.65){
double equivalent = (Math.round((100 + (72 * (x - y)) / y)));
String equi = String.valueOf(equivalent);
textEquivalent.setText(equi);
} else {
double equivalent = (Math.round((75 + (23 * (x - y * 0.65)) / y)));
String equi = String.valueOf(equivalent);
textEquivalent.setText(equi);
}
}catch (Exception e){
Toast.makeText(getApplicationContext(), "Please Enter a Number", Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}

Related

Not able to find element in pop up in appium

In my app profile I want to select Religion, so when I am clicking on Religion one pop up is opening and I have to choose one element from there,
Only "Index" and "text" values are different for each element, "Id" same for all element.
But while taking "xpath" using "text" and "index" values, not getting the element.
I am using scroll to method also but not getting the element.
Thanks in advance.
You can click by using click method:
driver.findElementByName("your religion name").click();
and scroll by using this method:
public void keepScrollingUntilElementFound(String name) {
for (int i = 0; i < 10; i++) {
if (isElementPresent("name=" + name)) {
break;
} else {
scrollDown();
scrollDown();
}
}
}
public void scrollDown() {
Dimension size = driver.manage().window().getSize();
int x = size.width / 2;
int starty = (int) (size.height * 0.60);
int endy = (int) (size.height * 0.10);
driver.swipe(x, starty, x, endy, 2000);
}
public boolean isElementPresent(String name) {
try {
driver.findElementByName(name);
return true;
} catch (Exception e) {
return false;
}
}
get all the index and its corresponding religions and click by index number
List<WebElement> ls = driver.findElementsById("android:id/numberpicker_input");
System.out.println("no of religions" + ls.size());
ls.get(0).click

Check if Edittext is empty that only accepts numbers

I have 2 Edittext which only accepts numbers.
I want to make sure that the fields are not empty before the submit button is pressed to stop the application from crashing.
These are the 2 edittext fields i have.
EditText weightText = (EditText)findViewById(R.id.WeightText);
EditText heightText = (EditText)findViewById(R.id.Heighttext);
Code:
public class BMI extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bmi);
}
public void calculateHandler(View view) {
// make sure we handle the click of the calculator button
if (view.getId() == R.id.calculate) {
// get the references to the widgets
EditText weightText = (EditText)findViewById(R.id.WeightText);
EditText heightText = (EditText)findViewById(R.id.Heighttext);
TextView resultText = (TextView)findViewById(R.id.resultLabel);
// get the users values from the widget references
float weight = Float.parseFloat(weightText.getText().toString());
float height = Float.parseFloat(heightText.getText().toString());
// calculate the bmi value
float bmiValue = calculateBMI(weight, height);
// interpret the meaning of the bmi value
String bmiInterpretation = interpretBMI(bmiValue);
// now set the value in the result text
resultText.setText(bmiValue + " = " + bmiInterpretation);
}
}
private float calculateBMI (float weight, float height) {
return (float)Math.round((weight / (height * height)) * 100) / 100 ;
}
// interpret what BMI means
private String interpretBMI(float bmiValue) {
if (bmiValue < 16) {
return "Severely underweight";
} else if (bmiValue < 18.5) {
return "Underweight";
} else if (bmiValue < 25) {
return "Normal";
} else if (bmiValue < 30) {
return "Overweight";
} else {
return "Obese";
}
}
}
You can check that your EditTexts are not empty like below:
weightText.getText().length() != 0 && heightText.getText().length() != 0
And you can use this condition inside the onClick(View) method, which is called when your button is clicked like below:
yourButton.setOnClickListener( new OnClickListener(){
public void onClick(){
if(weightText.getText().length() != 0 && heightText.getText().length() != 0){
//do sth
}
});
The second way, you can create TextWatcher which set to both EditTexts and in onTextChanged() you can check that both EditTexts are not empty like below:
private class YourTextWatcher implements TextWatcher{
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
yourButton.setEnabled(weightText.getText().length() != 0 && heightText.getText().length() != 0)
}
#Override
public void afterTextChanged(Editable editable) {
}
}
you can set this TextWatcher for your EditText like below:
weightText.addTextChangedListener(new YourTextWatcher());
heightText.addTextChangedListener(new YourTextWatcher());
Here is code that your Activity should look like:
public class BMI extends Activity {
EditText weightText;
EditText heightText;
TextView resultText;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bmi);
weightText = (EditText) findViewById(R.id.WeightText);
heightText = (EditText) findViewById(R.id.Heighttext);
resultText = (TextView) findViewById(R.id.resultLabel);
Button button = (Button) findViewById(R.id.calulate);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
calculateHandler();
}
});
}
public void calculateHandler() {
// make sure we handle the click of the calculator button
if (weightText.getText().length() == 0 || heightText.getText().length() == 0) {
return;
}
// get the users values from the widget references
float weight = Float.parseFloat(weightText.getText().toString());
float height = Float.parseFloat(heightText.getText().toString());
// calculate the bmi value
float bmiValue = calculateBMI(weight, height);
// interpret the meaning of the bmi value
String bmiInterpretation = interpretBMI(bmiValue);
// now set the value in the result text
resultText.setText(bmiValue + " = " + bmiInterpretation);
// display toast additionally to example
Toast.makeText(this, bmiValue + " = " + bmiInterpretation, Toast.LENGTH_LONG).show();
}
private float calculateBMI(float weight, float height) {
return (float) Math.round((weight / (height * height)) * 100) / 100;
}
// interpret what BMI means
private String interpretBMI(float bmiValue) {
if (bmiValue < 16) {
return "Severely underweight";
} else if (bmiValue < 18.5) {
return "Underweight";
} else if (bmiValue < 25) {
return "Normal";
} else if (bmiValue < 30) {
return "Overweight";
} else {
return "Obese";
}
}
}
Calculate method
public void calculateHandler() {
// make sure we handle the click of the calculator button
if (weightText.getText().toString().trim().length() == 0 || heightText.getText().toString().trim().length() == 0) {
Toast.makeText(this, "Please fill all field by numbers", Toast.LENGTH_LONG).show();
return;
}
// get the users values from the widget references
float weight = Float.parseFloat(weightText.getText().toString());
float height = Float.parseFloat(heightText.getText().toString());
// calculate the bmi value
float bmiValue = calculateBMI(weight, height);
// interpret the meaning of the bmi value
String bmiInterpretation = interpretBMI(bmiValue);
// now set the value in the result text
resultText.setText(bmiValue + " = " + bmiInterpretation);
// display toast additionally to example
}
weightText.getText().toString().isEmpty();
try like this..
String weight = weightText.getText().toString();
String height = heightText.getText().toString();
if((Integer.parseInt(weight)!=null) && (Integer.parseInt(height)!=null)){
//Not an empty
}
else{
//empty
}

Point in Polygon Test

I have searched for lot of threads but still I am facing the issue.
I have to find out if a lat/lng is inside or outside the polygon. I have used following method:
private boolean LineIntersect(LatLng tap, LatLng vertA, LatLng vertB) {
double aY = vertA.latitude;
double bY = vertB.latitude;
double aX = vertA.longitude;
double bX = vertB.longitude;
double pY = tap.latitude;
double pX = tap.longitude;
if ( (aY>pY && bY>pY) || (aY<pY && bY<pY) || (aX<pX && bX<pX) ) {
return false; }
double m = (aY-bY) / (aX-bX);
double bee = (-aX) * m + aY; // y = mx + b
double x = (pY - bee) / m;
return x > pX;
}
private boolean isPointInPolygon(LatLng tap, ArrayList<LatLng> vertices) {
int intersectCount = 0;
for(int j=0; j<vertices.size()-1; j++) {
if( LineIntersect(tap, vertices.get(j), vertices.get(j+1)) ) {
intersectCount++;
}
}
return ((intersectCount%2)==1); // odd = inside, even = outside;
}
I am calling it as:
if(isPointInPolygon(mLatLng, points))
{
Toast.makeText(getApplicationContext(), "inside",
Toast.LENGTH_LONG).show();
}
else
{
Toast.makeText(getApplicationContext(), "outside",
Toast.LENGTH_LONG).show();
}
The problem I am getting is, when I am inside the geofence I am getting both true and false. WHenever I am inside the geofence I am getting 2 toasts inside and outside both. Please let me know where I am wrong.
I have created geofence app that successfully worked
here is the code i used this https://github.com/sromku/polygon-contains-point to figure out our location inside or outside of polygon.
here is the code
public class MainActivity extends Activity {
private double Longitude, Latitude;
private LocationListener locListener;
private LocationManager lm;
private ProgressDialog gpsProgressDialog;
private Button button_gps;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button_gps = (Button)findViewById(R.id.button_gps);
lm = (LocationManager) MainActivity.this.getSystemService(MainActivity.this.LOCATION_SERVICE);
button_gps.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
getLoction();
// testSimplePolygon();
// testPolygonWithHoles();
}
});
}
class GpsLogation implements LocationListener {
#Override
public void onLocationChanged(Location location) {
if (location != null) {
lm.removeUpdates(locListener);
Longitude = location.getLongitude();
Latitude = location.getLatitude();
/* Toast.makeText(MainActivity.this,
"Longitude :" + Longitude + " Latitude :" + Latitude,
Toast.LENGTH_LONG).show();*/
Polygon polygon = Polygon.Builder()
.addVertex(new Point(6.048857f, 80.211021f)) // change polygon according to your location
.addVertex(new Point(6.048137f, 80.210546f))
.addVertex(new Point(6.048590f, 80.210197f))
.addVertex(new Point(6.049163f, 80.211050f)).build();
// isInside(polygon, new Point((float)Latitude, (float)Longitude));
if( polygon.contains(new Point((float)Latitude, (float)Longitude))==true)
{
Toast.makeText(MainActivity.this, "You are inside", Toast.LENGTH_LONG).show();
}
else
{
Toast.makeText(MainActivity.this, "You are outside", Toast.LENGTH_LONG).show();
}
// /////////////do something//////////////
// insertSalesOrder();
Log.i("TTTAG", "Latitude:" + Latitude);
Log.i("TTTAG", "Longitude:" + Longitude);
if (gpsProgressDialog.isShowing()) {
gpsProgressDialog.dismiss();
}
}
}
#Override
public void onProviderDisabled(String arg0) {
// TODO Auto-generated method stub
}
#Override
public void onProviderEnabled(String arg0) {
// TODO Auto-generated method stub
}
#Override
public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
// TODO Auto-generated method stub
}
}
public void getLoction() {
locListener = new GpsLogation();
boolean enabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
if (!enabled) {
Toast.makeText(MainActivity.this, "Please switch on the GPS",Toast.LENGTH_LONG).show();
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
MainActivity.this.startActivity(intent);
return;
}
Log.i("GPS INFO", " OK Gps Is ON");
gpsProgressDialog = ProgressDialog.show(MainActivity.this, "", "Please wait ... ", true);
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,locListener);
}
// Here is the polygon java class and i'm not author of it
public class Polygon
{
private final BoundingBox _boundingBox;
private final List<Line> _sides;
private Polygon(List<Line> sides, BoundingBox boundingBox)
{
_sides = sides;
_boundingBox = boundingBox;
}
/**
* Get the builder of the polygon
*
* #return The builder
*/
public static Builder Builder()
{
return new Builder();
}
/**
* Builder of the polygon
*
* #author Roman Kushnarenko (sromku#gmail.com)
*/
public static class Builder
{
private List<Point> _vertexes = new ArrayList<Point>();
private List<Line> _sides = new ArrayList<Line>();
private BoundingBox _boundingBox = null;
private boolean _firstPoint = true;
private boolean _isClosed = false;
/**
* Add vertex points of the polygon.<br>
* It is very important to add the vertexes by order, like you were drawing them one by one.
*
* #param point
* The vertex point
* #return The builder
*/
public Builder addVertex(Point point)
{
if (_isClosed)
{
// each hole we start with the new array of vertex points
_vertexes = new ArrayList<Point>();
_isClosed = false;
}
updateBoundingBox(point);
_vertexes.add(point);
// add line (edge) to the polygon
if (_vertexes.size() > 1)
{
Line Line = new Line(_vertexes.get(_vertexes.size() - 2), point);
_sides.add(Line);
}
return this;
}
/**
* Close the polygon shape. This will create a new side (edge) from the <b>last</b> vertex point to the <b>first</b> vertex point.
*
* #return The builder
*/
public Builder close()
{
validate();
// add last Line
_sides.add(new Line(_vertexes.get(_vertexes.size() - 1), _vertexes.get(0)));
_isClosed = true;
return this;
}
/**
* Build the instance of the polygon shape.
*
* #return The polygon
*/
public Polygon build()
{
validate();
// in case you forgot to close
if (!_isClosed)
{
// add last Line
_sides.add(new Line(_vertexes.get(_vertexes.size() - 1), _vertexes.get(0)));
}
Polygon polygon = new Polygon(_sides, _boundingBox);
return polygon;
}
/**
* Update bounding box with a new point.<br>
*
* #param point
* New point
*/
private void updateBoundingBox(Point point)
{
if (_firstPoint)
{
_boundingBox = new BoundingBox();
_boundingBox.xMax = point.x;
_boundingBox.xMin = point.x;
_boundingBox.yMax = point.y;
_boundingBox.yMin = point.y;
_firstPoint = false;
}
else
{
// set bounding box
if (point.x > _boundingBox.xMax)
{
_boundingBox.xMax = point.x;
}
else if (point.x < _boundingBox.xMin)
{
_boundingBox.xMin = point.x;
}
if (point.y > _boundingBox.yMax)
{
_boundingBox.yMax = point.y;
}
else if (point.y < _boundingBox.yMin)
{
_boundingBox.yMin = point.y;
}
}
}
private void validate()
{
if (_vertexes.size() < 3)
{
throw new RuntimeException("Polygon must have at least 3 points");
}
}
}
/**
* Check if the the given point is inside of the polygon.<br>
*
* #param point
* The point to check
* #return <code>True</code> if the point is inside the polygon, otherwise return <code>False</code>
*/
public boolean contains(Point point)
{
if (inBoundingBox(point))
{
Line ray = createRay(point);
int intersection = 0;
for (Line side : _sides)
{
if (intersect(ray, side))
{
// System.out.println("intersection++");
intersection++;
}
}
/*
* If the number of intersections is odd, then the point is inside the polygon
*/
if (intersection % 2 == 1)
{
return true;
}
}
return false;
}
public List<Line> getSides()
{
return _sides;
}
/**
* By given ray and one side of the polygon, check if both lines intersect.
*
* #param ray
* #param side
* #return <code>True</code> if both lines intersect, otherwise return <code>False</code>
*/
private boolean intersect(Line ray, Line side)
{
Point intersectPoint = null;
// if both vectors aren't from the kind of x=1 lines then go into
if (!ray.isVertical() && !side.isVertical())
{
// check if both vectors are parallel. If they are parallel then no intersection point will exist
if (ray.getA() - side.getA() == 0)
{
return false;
}
float x = ((side.getB() - ray.getB()) / (ray.getA() - side.getA())); // x = (b2-b1)/(a1-a2)
float y = side.getA() * x + side.getB(); // y = a2*x+b2
intersectPoint = new Point(x, y);
}
else if (ray.isVertical() && !side.isVertical())
{
float x = ray.getStart().x;
float y = side.getA() * x + side.getB();
intersectPoint = new Point(x, y);
}
else if (!ray.isVertical() && side.isVertical())
{
float x = side.getStart().x;
float y = ray.getA() * x + ray.getB();
intersectPoint = new Point(x, y);
}
else
{
return false;
}
// System.out.println("Ray: " + ray.toString() + " ,Side: " + side);
// System.out.println("Intersect point: " + intersectPoint.toString());
if (side.isInside(intersectPoint) && ray.isInside(intersectPoint))
{
return true;
}
return false;
}
/**
* Create a ray. The ray will be created by given point and on point outside of the polygon.<br>
* The outside point is calculated automatically.
*
* #param point
* #return
*/
private Line createRay(Point point)
{
// create outside point
float epsilon = (_boundingBox.xMax - _boundingBox.xMin) / 100f;
Point outsidePoint = new Point(_boundingBox.xMin - epsilon, _boundingBox.yMin);
Line vector = new Line(outsidePoint, point);
return vector;
}
/**
* Check if the given point is in bounding box
*
* #param point
* #return <code>True</code> if the point in bounding box, otherwise return <code>False</code>
*/
private boolean inBoundingBox(Point point)
{
if (point.x < _boundingBox.xMin || point.x > _boundingBox.xMax || point.y < _boundingBox.yMin || point.y > _boundingBox.yMax)
{
return false;
}
return true;
}
private static class BoundingBox
{
public float xMax = Float.NEGATIVE_INFINITY;
public float xMin = Float.NEGATIVE_INFINITY;
public float yMax = Float.NEGATIVE_INFINITY;
public float yMin = Float.NEGATIVE_INFINITY;
}
}
//// Here is the Line java class and i'm not author of it
public class Line
{
private final Point _start;
private final Point _end;
private float _a = Float.NaN;
private float _b = Float.NaN;
private boolean _vertical = false;
public Line(Point start, Point end)
{
_start = start;
_end = end;
if (_end.x - _start.x != 0)
{
_a = ((_end.y - _start.y) / (_end.x - _start.x));
_b = _start.y - _a * _start.x;
}
else
{
_vertical = true;
}
}
/**
* Indicate whereas the point lays on the line.
*
* #param point
* - The point to check
* #return <code>True</code> if the point lays on the line, otherwise return <code>False</code>
*/
public boolean isInside(Point point)
{
float maxX = _start.x > _end.x ? _start.x : _end.x;
float minX = _start.x < _end.x ? _start.x : _end.x;
float maxY = _start.y > _end.y ? _start.y : _end.y;
float minY = _start.y < _end.y ? _start.y : _end.y;
if ((point.x >= minX && point.x <= maxX) && (point.y >= minY && point.y <= maxY))
{
return true;
}
return false;
}
/**
* Indicate whereas the line is vertical. <br>
* For example, line like x=1 is vertical, in other words parallel to axis Y. <br>
* In this case the A is (+/-)infinite.
*
* #return <code>True</code> if the line is vertical, otherwise return <code>False</code>
*/
public boolean isVertical()
{
return _vertical;
}
/**
* y = <b>A</b>x + B
*
* #return The <b>A</b>
*/
public float getA()
{
return _a;
}
/**
* y = Ax + <b>B</b>
*
* #return The <b>B</b>
*/
public float getB()
{
return _b;
}
/**
* Get start point
*
* #return The start point
*/
public Point getStart()
{
return _start;
}
/**
* Get end point
*
* #return The end point
*/
public Point getEnd()
{
return _end;
}
#Override
public String toString()
{
return String.format("%s-%s", _start.toString(), _end.toString());
}
}
//// Here is the point java class and i'm not author of it
public class Point
{
public Point(float x, float y)
{
this.x = x;
this.y = y;
}
public float x;
public float y;
#Override
public String toString()
{
return String.format("(%.2f,%.2f)", x, y);
}
}
/*I coded only Mainactivity class other owner of other classes is https://github.com/sromku and all credit goes to him*/

Android animation equivalent for iOS "calculationMode"

I'm trying to animate some drawables in Android, I've set a path using PathEvaluator that animates along some curves along a full path.
When I set a duration (e.g. 6 seconds) it splits the duration to the number of curves I've set regardless of their length which causes the animation to be to slow on some segments and too fast on others.
On iOS this can be fixed using
animation.calculationMode = kCAAnimationCubicPaced;
animation.timingFunction = ...;
Which lets iOS to smooth your entire path into mid-points and span the duration according to each segment length.
Is there any way to get the same result in Android?
(besides breaking the path into discrete segments and assigning each segment its own duration manually which is really ugly and unmaintainable).
I don't think that anything can be done with ObjectAnimator because there seems to be no function that can be called to assert the relative duration of a certain fragment of the animation.
I did develop something similar to what you need a while back, but it works slightly differently - it inherits from Animation.
I've modified everything to work with your curving needs, and with the PathPoint class.
Here's an overview:
I supply the list of points to the animation in the constructor.
I calculate the length between all the points using a simple distance calculator. I then sum it all up to get the overall length of the path, and store the segment lengths in a map for future use (this is to improve efficiency during runtime).
When animating, I use the current interpolation time to figure out which 2 points I'm animating between, considering the ratio of time & the ratio of distance traveled.
I calculate the time it should take to animate between these 2 points according to the relative distance between them, compared to the overall distance.
I then interpolate separately between these 2 points using the calculation in the PathAnimator class.
Here's the code:
CurveAnimation.java:
public class CurveAnimation extends Animation
{
private static final float BEZIER_LENGTH_ACCURACY = 0.001f; // Must be divisible by one. Make smaller to improve accuracy, but will increase runtime at start of animation.
private List<PathPoint> mPathPoints;
private float mOverallLength;
private Map<PathPoint, Double> mSegmentLengths = new HashMap<PathPoint, Double>(); // map between the end point and the length of the path to it.
public CurveAnimation(List<PathPoint> pathPoints)
{
mPathPoints = pathPoints;
if (mPathPoints == null || mPathPoints.size() < 2)
{
Log.e("CurveAnimation", "There must be at least 2 points on the path. There will be an exception soon!");
}
calculateOverallLength();
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
PathPoint[] startEndPart = getStartEndForTime(interpolatedTime);
PathPoint startPoint = startEndPart[0];
PathPoint endPoint = startEndPart[1];
float startTime = getStartTimeOfPoint(startPoint);
float endTime = getStartTimeOfPoint(endPoint);
float progress = (interpolatedTime - startTime) / (endTime - startTime);
float x, y;
float[] xy;
if (endPoint.mOperation == PathPoint.CURVE)
{
xy = getBezierXY(startPoint, endPoint, progress);
x = xy[0];
y = xy[1];
}
else if (endPoint.mOperation == PathPoint.LINE)
{
x = startPoint.mX + progress * (endPoint.mX - startPoint.mX);
y = startPoint.mY + progress * (endPoint.mY - startPoint.mY);
}
else
{
x = endPoint.mX;
y = endPoint.mY;
}
t.getMatrix().setTranslate(x, y);
super.applyTransformation(interpolatedTime, t);
}
private PathPoint[] getStartEndForTime(float time)
{
double length = 0;
if (time == 1)
{
return new PathPoint[] { mPathPoints.get(mPathPoints.size() - 2), mPathPoints.get(mPathPoints.size() - 1) };
}
PathPoint[] result = new PathPoint[2];
for (int i = 0; i < mPathPoints.size() - 1; i++)
{
length += calculateLengthFromIndex(i);
if (length / mOverallLength >= time)
{
result[0] = mPathPoints.get(i);
result[1] = mPathPoints.get(i + 1);
break;
}
}
return result;
}
private float getStartTimeOfPoint(PathPoint point)
{
float result = 0;
int index = 0;
while (mPathPoints.get(index) != point && index < mPathPoints.size() - 1)
{
result += (calculateLengthFromIndex(index) / mOverallLength);
index++;
}
return result;
}
private void calculateOverallLength()
{
mOverallLength = 0;
mSegmentLengths.clear();
double segmentLength;
for (int i = 0; i < mPathPoints.size() - 1; i++)
{
segmentLength = calculateLengthFromIndex(i);
mSegmentLengths.put(mPathPoints.get(i + 1), segmentLength);
mOverallLength += segmentLength;
}
}
private double calculateLengthFromIndex(int index)
{
PathPoint start = mPathPoints.get(index);
PathPoint end = mPathPoints.get(index + 1);
return calculateLength(start, end);
}
private double calculateLength(PathPoint start, PathPoint end)
{
if (mSegmentLengths.containsKey(end))
{
return mSegmentLengths.get(end);
}
else if (end.mOperation == PathPoint.LINE)
{
return calculateLength(start.mX, end.mX, start.mY, end.mY);
}
else if (end.mOperation == PathPoint.CURVE)
{
return calculateBezeirLength(start, end);
}
else
{
return 0;
}
}
private double calculateLength(float x0, float x1, float y0, float y1)
{
return Math.sqrt(((x0 - x1) * (x0 - x1)) + ((y0 - y1) * (y0 - y1)));
}
private double calculateBezeirLength(PathPoint start, PathPoint end)
{
double result = 0;
float x, y, x0, y0;
float[] xy;
x0 = start.mX;
y0 = start.mY;
for (float progress = BEZIER_LENGTH_ACCURACY; progress <= 1; progress += BEZIER_LENGTH_ACCURACY)
{
xy = getBezierXY(start, end, progress);
x = xy[0];
y = xy[1];
result += calculateLength(x, x0, y, y0);
x0 = x;
y0 = y;
}
return result;
}
private float[] getBezierXY(PathPoint start, PathPoint end, float progress)
{
float[] result = new float[2];
float oneMinusT, x, y;
oneMinusT = 1 - progress;
x = oneMinusT * oneMinusT * oneMinusT * start.mX +
3 * oneMinusT * oneMinusT * progress * end.mControl0X +
3 * oneMinusT * progress * progress * end.mControl1X +
progress * progress * progress * end.mX;
y = oneMinusT * oneMinusT * oneMinusT * start.mY +
3 * oneMinusT * oneMinusT * progress * end.mControl0Y +
3 * oneMinusT * progress * progress * end.mControl1Y +
progress * progress * progress * end.mY;
result[0] = x;
result[1] = y;
return result;
}
}
Here's a sample that shows how to activate the animation:
private void animate()
{
AnimatorPath path = new AnimatorPath();
path.moveTo(0, 0);
path.lineTo(0, 300);
path.curveTo(100, 0, 300, 900, 400, 500);
CurveAnimation animation = new CurveAnimation(path.mPoints);
animation.setDuration(5000);
animation.setInterpolator(new LinearInterpolator());
btn.startAnimation(animation);
}
Now, keep in mind that I'm currently calculating the length of the curve according to an approximation. This will obviously cause some mild inaccuracies in the speed. If you feel it's not accurate enough, feel free to modify the code. Also, if you want to increase the length accuracy of the curve, try decreasing the value of BEZIER_LENGTH_ACCURACY. It must be dividable by 1, so accepted values can be 0.001, 0.000025, etc.
While you might notice some mild fluctuations in speed when using curves, I'm sure it's much better than simply dividing the time equally between all paths.
I hope this helps :)
I tried using Gil's answer, but it didn't fit how I was animating.
Gil wrote an Animation class which is used to animate Views.
I was using ObjectAnimator.ofObject() to animate custom classes using ValueProperties which can't be used with custom Animation.
So this is what I did:
I extend PathEvaluator and override its evaluate method.
I use Gil's logic to calculate path total length, and segmented lengths
Since PathEvaluator.evaluate is called for each PathPoint with t values
0..1, I needed to normalize the interpolated time given to me, so it'll be incremental and won't zero out for each segment.
I ignore the start/end PathPoints given to me so the current position can be
before start or after end along the path depending on the segment's duration.
I pass the current progress calculated to my super
(PathEvaluator) to calc the actual position.
This is the code:
public class NormalizedEvaluator extends PathEvaluator {
private static final float BEZIER_LENGTH_ACCURACY = 0.001f;
private List<PathPoint> mPathPoints;
private float mOverallLength;
private Map<PathPoint, Double> mSegmentLengths = new HashMap<PathPoint, Double>();
public NormalizedEvaluator(List<PathPoint> pathPoints) {
mPathPoints = pathPoints;
if (mPathPoints == null || mPathPoints.size() < 2) {
Log.e("CurveAnimation",
"There must be at least 2 points on the path. There will be an exception soon!");
}
calculateOverallLength();
}
#Override
public PathPoint evaluate(float interpolatedTime, PathPoint ignoredStartPoint,
PathPoint ignoredEndPoint) {
float index = getStartIndexOfPoint(ignoredStartPoint);
float normalizedInterpolatedTime = (interpolatedTime + index) / (mPathPoints.size() - 1);
PathPoint[] startEndPart = getStartEndForTime(normalizedInterpolatedTime);
PathPoint startPoint = startEndPart[0];
PathPoint endPoint = startEndPart[1];
float startTime = getStartTimeOfPoint(startPoint);
float endTime = getStartTimeOfPoint(endPoint);
float progress = (normalizedInterpolatedTime - startTime) / (endTime - startTime);
return super.evaluate(progress, startPoint, endPoint);
}
private PathPoint[] getStartEndForTime(float time) {
double length = 0;
if (time == 1) {
return new PathPoint[] { mPathPoints.get(mPathPoints.size() - 2),
mPathPoints.get(mPathPoints.size() - 1) };
}
PathPoint[] result = new PathPoint[2];
for (int i = 0; i < mPathPoints.size() - 1; i++) {
length += calculateLengthFromIndex(i);
if (length / mOverallLength >= time) {
result[0] = mPathPoints.get(i);
result[1] = mPathPoints.get(i + 1);
break;
}
}
return result;
}
private float getStartIndexOfPoint(PathPoint point) {
for (int ii = 0; ii < mPathPoints.size(); ii++) {
PathPoint current = mPathPoints.get(ii);
if (current == point) {
return ii;
}
}
return -1;
}
private float getStartTimeOfPoint(PathPoint point) {
float result = 0;
int index = 0;
while (mPathPoints.get(index) != point && index < mPathPoints.size() - 1) {
result += (calculateLengthFromIndex(index) / mOverallLength);
index++;
}
return result;
}
private void calculateOverallLength() {
mOverallLength = 0;
mSegmentLengths.clear();
double segmentLength;
for (int i = 0; i < mPathPoints.size() - 1; i++) {
segmentLength = calculateLengthFromIndex(i);
mSegmentLengths.put(mPathPoints.get(i + 1), segmentLength);
mOverallLength += segmentLength;
}
}
private double calculateLengthFromIndex(int index) {
PathPoint start = mPathPoints.get(index);
PathPoint end = mPathPoints.get(index + 1);
return calculateLength(start, end);
}
private double calculateLength(PathPoint start, PathPoint end) {
if (mSegmentLengths.containsKey(end)) {
return mSegmentLengths.get(end);
} else if (end.mOperation == PathPoint.LINE) {
return calculateLength(start.mX, end.mX, start.mY, end.mY);
} else if (end.mOperation == PathPoint.CURVE) {
return calculateBezeirLength(start, end);
} else {
return 0;
}
}
private double calculateLength(float x0, float x1, float y0, float y1) {
return Math.sqrt(((x0 - x1) * (x0 - x1)) + ((y0 - y1) * (y0 - y1)));
}
private double calculateBezeirLength(PathPoint start, PathPoint end) {
double result = 0;
float x, y, x0, y0;
float[] xy;
x0 = start.mX;
y0 = start.mY;
for (float progress = BEZIER_LENGTH_ACCURACY; progress <= 1; progress += BEZIER_LENGTH_ACCURACY) {
xy = getBezierXY(start, end, progress);
x = xy[0];
y = xy[1];
result += calculateLength(x, x0, y, y0);
x0 = x;
y0 = y;
}
return result;
}
private float[] getBezierXY(PathPoint start, PathPoint end, float progress) {
float[] result = new float[2];
float oneMinusT, x, y;
oneMinusT = 1 - progress;
x = oneMinusT * oneMinusT * oneMinusT * start.mX + 3 * oneMinusT * oneMinusT * progress
* end.mControl0X + 3 * oneMinusT * progress * progress * end.mControl1X + progress
* progress * progress * end.mX;
y = oneMinusT * oneMinusT * oneMinusT * start.mY + 3 * oneMinusT * oneMinusT * progress
* end.mControl0Y + 3 * oneMinusT * progress * progress * end.mControl1Y + progress
* progress * progress * end.mY;
result[0] = x;
result[1] = y;
return result;
}
}
This is the usage:
NormalizedEvaluator evaluator = new NormalizedEvaluator((List<PathPoint>) path.getPoints());
ObjectAnimator anim = ObjectAnimator.ofObject(object, "position", evaluator, path.getPoints().toArray());
UPDATE: I just realized that I might have reinvented the wheel, please look at Specifying Keyframes.
It is shocking to see that nothing is available of this kind. Anyways if you don't want to calculate path length at run time then I was able to add functionality of assigning weights to paths. Idea is to assign a weight to your path and run the animation if it feels OK then well and good otherwise just decrease or increase weight assigned to each Path.
Following code is modified code from official Android sample that you pointed in your question:
// Set up the path we're animating along
AnimatorPath path = new AnimatorPath();
path.moveTo(0, 0).setWeight(0);
path.lineTo(0, 300).setWeight(30);// assign arbitrary weight
path.curveTo(100, 0, 300, 900, 400, 500).setWeight(70);// assign arbitrary weight
final PathPoint[] points = path.getPoints().toArray(new PathPoint[] {});
mFirstKeyframe = points[0];
final int numFrames = points.length;
final PathEvaluator pathEvaluator = new PathEvaluator();
final ValueAnimator anim = ValueAnimator.ofInt(0, 1);// dummy values
anim.setDuration(1000);
anim.setInterpolator(new LinearInterpolator());
anim.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = animation.getAnimatedFraction();
// Special-case optimization for the common case of only two
// keyframes
if (numFrames == 2) {
PathPoint nextPoint = pathEvaluator.evaluate(fraction,
points[0], points[1]);
setButtonLoc(nextPoint);
} else {
PathPoint prevKeyframe = mFirstKeyframe;
for (int i = 1; i < numFrames; ++i) {
PathPoint nextKeyframe = points[i];
if (fraction < nextKeyframe.getFraction()) {
final float prevFraction = prevKeyframe
.getFraction();
float intervalFraction = (fraction - prevFraction)
/ (nextKeyframe.getFraction() - prevFraction);
PathPoint nextPoint = pathEvaluator.evaluate(
intervalFraction, prevKeyframe,
nextKeyframe);
setButtonLoc(nextPoint);
break;
}
prevKeyframe = nextKeyframe;
}
}
}
});
And that's it !!!.
Of course I modified other classes as well but nothing big was added. E.g. in PathPoint I added this:
float mWeight;
float mFraction;
public void setWeight(float weight) {
mWeight = weight;
}
public float getWeight() {
return mWeight;
}
public void setFraction(float fraction) {
mFraction = fraction;
}
public float getFraction() {
return mFraction;
}
In AnimatorPath I modified getPoints() method like this:
public Collection<PathPoint> getPoints() {
// calculate fractions
float totalWeight = 0.0F;
for (PathPoint p : mPoints) {
totalWeight += p.getWeight();
}
float lastWeight = 0F;
for (PathPoint p : mPoints) {
p.setFraction(lastWeight = lastWeight + p.getWeight() / totalWeight);
}
return mPoints;
}
And thats pretty much it. Oh and for better readability I added Builder Pattern in AnimatorPath, so all 3 methods were changed like this:
public PathPoint moveTo(float x, float y) {// same for lineTo and curveTo method
PathPoint p = PathPoint.moveTo(x, y);
mPoints.add(p);
return p;
}
NOTE: To handle Interpolators that can give fraction less then 0 or greater than 1 (e.g. AnticipateOvershootInterpolator) look at com.nineoldandroids.animation.KeyframeSet.getValue(float fraction) method and implement the logic in onAnimationUpdate(ValueAnimator animation).

Using android accelerometer to detect force peak value

I'm trying to find out the force of which a button is pressed with the on-board accelerometer.
I figured i'll have to do a series of readings and then compare the values.
My attempt at it was something like this:
velButton.setOnTouchListener(new OnTouchListener()
{
public boolean onTouch(View view, MotionEvent event)
{
int velocity = 0;
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
velocity = getVelocity();
velocityView.setText("This is the velocity: " + velocity);
break;
}
return false;
});
}
protected int getVelocity()
{
float accent = 0;
int velocity;
try
{
accent = getHighestValue();
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
velocity = (int) accent;
return velocity;
}
protected float getHighestValue() throws InterruptedException
{
float highest;
float[] value = new float[20];
String txt = "";
for(int i = 0; i < value.length; i++)
{
Thread.sleep(1);
value[i] = accelerometer_z;
txt += "Reading at: " + i + " ms returns: " + value[i] + "\n";
}
Arrays.sort(value);
highest = value[19];
textView2.setText(txt);
return highest;
}
The float accelerometer_z is initialized in my main activity and is updated continually through onSensorChanged.
My problem here is that I don't get a series of values, I only get One value from the for-loop in my getHighestValue method.
Cheers
/M
Added onSensorChanged:
#Override
public void onSensorChanged(SensorEvent event)
{
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
{
getAccelerometer(event);
}
}
private void getAccelerometer(SensorEvent event)
{
float[] value = event.values;
// Movement
accelerometer_x = value[0];
accelerometer_y = value[1];
accelerometer_z = value[2];
accelationSquareRoot = (accelerometer_x * accelerometer_x + accelerometer_y *
accelerometer_y + accelerometer_z * accelerometer_z)
/ (SensorManager.GRAVITY_EARTH * SensorManager.GRAVITY_EARTH);
long actualTime = System.currentTimeMillis();
if (accelationSquareRoot >= 2)
{
if (actualTime - lastUpdate < 200)
{
return;
}
lastUpdate = actualTime;
}
}
In your for loop, you're forcing Thread to sleep with
...
Thread.sleep(1);
...
where the expected parameter(1 in your case) is in milliseconds.
I guess, your intention was not to force Thread to sleep 1 milliseconds because you want an actual value from sensor. So that should be something like:
...
Thread.sleep(x * 1000);
...
where x is defined with respect to sensitivity.
It seems that only one event handler can be run at the same time and that's the reason why I'm only getting a single value from the gethighestValue.
The onSensorChanged will never be allowed to make a new reading as long as the motionEvent in the onTouch is waiting for new input.
I solved this by letting the onSensorChanged implement a method like this:
private void getAccelerometer(SensorEvent event)
{
float[] value = event.values;
float accelerometer_x = value[0];
float accelerometer_y = value[1];
float accelerometer_z = value[2];
accReadingZ.add(accelerometer_z);
while(accReadingZ.size() > LIST_SIZE)
accReadingZ.removeFirst();
}
accReadingZ is here a class member with a size of 10 elements.
I can now use Collections.max(accReadingZ) to get the highest value in the series of readings.

Categories

Resources