Evaluate a String in Android Studio - android

I am writing a program, and user input ends up as a mathematical expression inside a string. How do I evaluate it into a double?
I don't have a lot of experience in this language, I am mostly familiar with BASIC(lol). So if anyone can give me the simplest step by step instructions to do this, it would be very much appreciated.

It is unfortunately not too straightforward in Java. The two top options seem to be using the built-in javascript engine or using the exp4j library.
You can read more about them in these answers: evaluating-a-math-expression-given-in-string-form and java-parse-a-mathematical-expression-given-as-a-string

You can try this program written in this answer.
https://stackoverflow.com/a/26227947.
I have copy pasted the code here. The explanation of the code is in the original answer if you follow the link.
public static double eval(final String str) {
return new Object() {
int pos = -1, ch;
void nextChar() {
ch = (++pos < str.length()) ? str.charAt(pos) : -1;
}
boolean eat(int charToEat) {
while (ch == ' ') nextChar();
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
double parse() {
nextChar();
double x = parseExpression();
if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
return x;
}
// Grammar:
// expression = term | expression `+` term | expression `-` term
// term = factor | term `*` factor | term `/` factor
// factor = `+` factor | `-` factor | `(` expression `)`
// | number | functionName factor | factor `^` factor
double parseExpression() {
double x = parseTerm();
for (;;) {
if (eat('+')) x += parseTerm(); // addition
else if (eat('-')) x -= parseTerm(); // subtraction
else return x;
}
}
double parseTerm() {
double x = parseFactor();
for (;;) {
if (eat('*')) x *= parseFactor(); // multiplication
else if (eat('/')) x /= parseFactor(); // division
else return x;
}
}
double parseFactor() {
if (eat('+')) return parseFactor(); // unary plus
if (eat('-')) return -parseFactor(); // unary minus
double x;
int startPos = this.pos;
if (eat('(')) { // parentheses
x = parseExpression();
eat(')');
} else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
x = Double.parseDouble(str.substring(startPos, this.pos));
} else if (ch >= 'a' && ch <= 'z') { // functions
while (ch >= 'a' && ch <= 'z') nextChar();
String func = str.substring(startPos, this.pos);
x = parseFactor();
if (func.equals("sqrt")) x = Math.sqrt(x);
else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));
else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));
else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));
else throw new RuntimeException("Unknown function: " + func);
} else {
throw new RuntimeException("Unexpected: " + (char)ch);
}
if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation
return x;
}
}.parse();
}`

Ok. I guess you basically have a String like
String abc="(56+55(6/655)-5522*1222)";
and you want to evaluate this without changing its type.
Yes, there is a library available for this.
Library
Update The Library if there is any update
implementation 'com.udojava:EvalEx:2.7'
----------`
import com.udojava.evalex.Expression;
String memory="";
equal.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
memory=editText.getText().toString(); //Get User's Entered Equation
Expression expression=new Expression(memory); //This Library Evaluate It
String abc=expression.eval().toString(); //Insert The Data into A String
textView.setText(abc); //Show The Data
}
});

Related

RangeError (index): Invalid value: Not in inclusive range 0..13: 14 Flutter

Someone to help me? I'm a beginner. I'm trying to get the controller numbers from a textfield. For this I tried to convert the characters to int. Note: in the textfield the user will enter numbers up to three digits
This error appears: RangeError (index): Invalid value: Not in inclusive range 0..13: 14
validate(textController)
{
List textCode = List();
List messageInCode = List();
int validateRepeticions;
var stringText = textController;
for (var b = 0; b < stringText.length; b++)
{
textCode.insert(b, stringText[b]);
}
validateRepeticions = textCode.length;
print(validateRepeticions);
int g = 0;
int k = 0;
do
{
bool comma = true;
List tryToParseInt = List();
int d = 0;// 'd' is responsible for identifying the positional value of a digit
int a = 1;// 'a' increment values to k
int c = 0;// 'c' defines the positions used in tryToParseInt
int completeNumber;
tryToParseInt.insert(c, int.tryParse(textCode[k]));
if (tryToParseInt[c] == null)
{
validateRepeticions--;
}
else
{
d++;
c++;
do
{
tryToParseInt.insert(c, int.tryParse(textCode[k+a]));
if (tryToParseInt[c] == null)
{
completeNumber = tryToParseInt[0];
messageInCode.insert(g, completeNumber);
g++;
comma = false;
validateRepeticions -= 2;
}
else
{
d++;
a++;
c++;
tryToParseInt.insert(c, int.tryParse(textCode[k+a]));
if (tryToParseInt[c] == null)
{
completeNumber = tryToParseInt[0]*10 + tryToParseInt[1];
messageInCode.add(completeNumber);
g++;
comma = false;
validateRepeticions -= 3;
d++;
}
else
{
d++;
completeNumber = tryToParseInt[0]*100 + tryToParseInt[1]*10 + tryToParseInt[2];
messageInCode.add(completeNumber);
g++;
comma = false;
validateRepeticions -= 3;
}
}
} while (comma);
}
d++;
k += d;
} while (validateRepeticions >= 0);
}
Updated code
validate(textEditingController) {
int validateRepeticions = textEditingController.text.length;
List textString = textEditingController.text.split('');
List messageInCode = List();
int k = 0;
do {
bool comma = true;
List tryToParseInt = List();
int d = 0; // 'd' is responsible for identifying the value of a number that has already been verified
int a = 1; // 'a' increment values to k
int c = 0; // 'c' defines the positions used in tryToParseInt
int completeNumber;
tryToParseInt.insert(c, int.tryParse(textString[k]));
if (tryToParseInt[c] == null) {
validateRepeticions--;
} else {
d++; //1
c++; //1
do {
tryToParseInt.insert(c, int.tryParse(textString[k + a]));
if (tryToParseInt[c] == null) {
completeNumber = tryToParseInt[0];
messageInCode.add(completeNumber);
comma = false;
validateRepeticions -= 2;
} else {
d++; //2
a++; //2
c++; //2
tryToParseInt.insert(c, int.tryParse(textString[k + a]));
if (tryToParseInt[c] == null) {
completeNumber = tryToParseInt[0] * 10 + tryToParseInt[1];
messageInCode.add(completeNumber);
comma = false;
validateRepeticions -= 3;
d++;
} else {
d++;
completeNumber = tryToParseInt[0] * 100 + tryToParseInt[1] * 10 + tryToParseInt[2];
messageInCode.add(completeNumber);
comma = false;
validateRepeticions -= 3;
}
}
} while (comma);
}
d++;
k += d;
} while (validateRepeticions > 0);
}
Now it's almost working, although an error still appears:
The method '_addFromInteger' was called on null.
Receiver: null
Tried calling: _addFromInteger(0)
validate(textEditingController) {
int validateRepeticions = textEditingController.text.length;
List textString = textEditingController.text.split('');
List messageInCode = List();
int k = 0;
messageInCode.clear();
do {
bool comma = true;
List tryToParseInt = List();
int d = 0; // 'd' is responsible for identifying the positional value of a digit
int a = 0; // 'a' increment values to k
int c = 0; // 'c' defines the positions used in tryToParseInt
int completeNumber;
if (k < textEditingController.text.length) {
tryToParseInt.insert(c, int.tryParse(textString[k]));
if (tryToParseInt[c] == null) {
validateRepeticions--;
} else {
d++; //1 these comments are examples
c++; //1
a++; //1
do {
if ((k + a) < textEditingController.text.length) {
tryToParseInt.insert(c, int.tryParse(textString[k + a]));
if (tryToParseInt[c] == null) {
completeNumber = tryToParseInt[0];
messageInCode.add(completeNumber);
comma = false;
validateRepeticions -= 2;
} else {
d++; //2
a++; //2
c++; //2
if ((k + a) < textEditingController.text.length) {
tryToParseInt.insert(c, int.tryParse(textString[k + a]));
if (tryToParseInt[c] == null) {
completeNumber = tryToParseInt[0] * 10 + tryToParseInt[1];
messageInCode.add(completeNumber);
comma = false;
validateRepeticions -= 3;
d++;
} else {
d++;
completeNumber = tryToParseInt[0] * 100 + tryToParseInt[1] * 10 + tryToParseInt[2];
messageInCode.add(completeNumber);
comma = false;
validateRepeticions -= 3;
}
} else {
completeNumber = tryToParseInt[0] * 10 + tryToParseInt[1];
messageInCode.add(completeNumber);
comma = false;
validateRepeticions -= 3;
d++;
}
}
} else {
completeNumber = tryToParseInt[0];
messageInCode.add(completeNumber);
comma = false;
validateRepeticions -= 2;
}
} while (comma);
}
} else {
validateRepeticions--;
}
d++;
k += d;
} while (validateRepeticions > 0 && k < textEditingController.text.length);
}
In your function, I'm assuming the param textController is the TextEditingController. In that case you need to check the length with textController.text.length
To create a list of characters from the string, you just need to use split() instead of creating a new list and loop through the string. For example: textController.text.split('')
Replace textCode.insert(b, stringText[b]); with textCode.add(stringText[b]);

Validate string with parse

I am trying to validate 3 values, one that is entered manually and the other two are selected with the picker, in the end it is not validated and an error appears, I need that when the user has not entered any value, an alert pops up saying that all fields must be completed, but I can't see how to implement it.
void CalculateBMI(object sender, EventArgs e)
{
double weight = Double.Parse(peso.Text);
double feets = Double.Parse(Pies.SelectedItem.ToString());
double inches = Double.Parse(Pulgadas.SelectedItem.ToString());
double totalInches = (feets * 12) + inches;
double bmi = (weight * 703) / (totalInches * totalInches);
lblBMIValue.Text = String.Format("Your BMI is {0:0.00}", bmi);
string bmiLevel = "";
Color color = Color.Transparent;
lblBMI.TextColor = Color.White;
if ((weight <= 0) || (inches <= 0) || (feets <= 0))
{
DisplayAlert("Attention", "Please complete all the data.", "Ok");
return;
}
else if (bmi < 18.5)
{
bmiLevel = "Underweight"; color = Color.FromHex("FECD57");
}
else if (bmi < 25)
{
bmiLevel = "Normal"; color = Color.FromHex("1287CE");
}
else if (bmi < 30)
{
bmiLevel = "Overweight"; color = Color.FromHex("FECD57");
}
else
{
bmiLevel = "Obese"; color = Color.FromHex("EC5564");
}
lblBMI.Text = bmiLevel;
bmiStack.BackgroundColor = color;
}
If a user does not enter anything in the entry, the Text of entry is Empty("");
If a user does not select anything in the picker, the SelectedItem of picker is null;
And in your CalculateBMI function, you should first validate 3 values and then calculate BMI:
void CalculateBMI(object sender, EventArgs e)
{
if ((string.IsNullOrEmpty(peso.Text)) || (Pies.SelectedItem == null) || (Pulgadas.SelectedItem == null))
{
DisplayAlert("Attention", "Please complete all the data.", "Ok");
return;
}
double weight = Double.Parse(peso.Text);
double feets = Double.Parse(Pies.SelectedItem.ToString());
double inches = Double.Parse(Pulgadas.SelectedItem.ToString());
double totalInches = (feets * 12) + inches;
double bmi = (weight * 703) / (totalInches * totalInches);
lblBMIValue.Text = String.Format("Your BMI is {0:0.00}", bmi);
string bmiLevel = "";
Color color = Color.Transparent;
lblBMI.TextColor = Color.White;
if (bmi < 18.5)
{
bmiLevel = "Underweight"; color = Color.FromHex("FECD57");
}
else if (bmi < 25)
{
bmiLevel = "Normal"; color = Color.FromHex("1287CE");
}
else if (bmi < 30)
{
bmiLevel = "Overweight"; color = Color.FromHex("FECD57");
}
else
{
bmiLevel = "Obese"; color = Color.FromHex("EC5564");
}
lblBMI.Text = bmiLevel;
}
bool error = false;
if (string.IsNullOrEmpty(peso.Text) ||
Pies.SelectedItem == null ||
Pulgadas.SelectedItem == null)
{
error = true;
// display error message
}

Android Picasso auto rotates image

I am using Picasso to load images from the web in my application. I have noticed that some images are shown rotated by 90degrees although when I open the image in my browser I see it correctly positioned. I assume that these images have EXIF data. Is there any way to instruct Picasso to ignore EXIF?
As we know, Picasso supports EXIF from local storage, this is done via Android inner Utils. Providing the same functionality can't be done easy due to ability to use custom Http loading libraries.
My solution is simple: we must override caching and apply Exif rotation before item is cached.
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(chain -> {
Response originalResponse = chain.proceed(chain.request());
byte[] body = originalResponse.body().bytes();
ResponseBody newBody = ResponseBody
.create(originalResponse.body().contentType(), ImageUtils.processImage(body));
return originalResponse.newBuilder().body(newBody).build();
})
.cache(cache)
.build();
Here we add NetworkInterceptor that can transform request and response before it gets cached.
public class ImageUtils {
public static byte[] processImage(byte[] originalImg) {
int orientation = Exif.getOrientation(originalImg);
if (orientation != 0) {
Bitmap bmp = BitmapFactory.decodeByteArray(originalImg, 0, originalImg.length);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
rotateImage(orientation, bmp).compress(Bitmap.CompressFormat.PNG, 100, stream);
return stream.toByteArray();
}
return originalImg;
}
private static Bitmap rotateImage(int angle, Bitmap bitmapSrc) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(bitmapSrc, 0, 0,
bitmapSrc.getWidth(), bitmapSrc.getHeight(), matrix, true);
}
}
Exif transformation:
public class Exif {
private static final String TAG = "Exif";
// Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
public static int getOrientation(byte[] jpeg) {
if (jpeg == null) {
return 0;
}
int offset = 0;
int length = 0;
// ISO/IEC 10918-1:1993(E)
while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
int marker = jpeg[offset] & 0xFF;
// Check if the marker is a padding.
if (marker == 0xFF) {
continue;
}
offset++;
// Check if the marker is SOI or TEM.
if (marker == 0xD8 || marker == 0x01) {
continue;
}
// Check if the marker is EOI or SOS.
if (marker == 0xD9 || marker == 0xDA) {
break;
}
// Get the length and check if it is reasonable.
length = pack(jpeg, offset, 2, false);
if (length < 2 || offset + length > jpeg.length) {
Log.e(TAG, "Invalid length");
return 0;
}
// Break if the marker is EXIF in APP1.
if (marker == 0xE1 && length >= 8 &&
pack(jpeg, offset + 2, 4, false) == 0x45786966 &&
pack(jpeg, offset + 6, 2, false) == 0) {
offset += 8;
length -= 8;
break;
}
// Skip other markers.
offset += length;
length = 0;
}
// JEITA CP-3451 Exif Version 2.2
if (length > 8) {
// Identify the byte order.
int tag = pack(jpeg, offset, 4, false);
if (tag != 0x49492A00 && tag != 0x4D4D002A) {
Log.e(TAG, "Invalid byte order");
return 0;
}
boolean littleEndian = (tag == 0x49492A00);
// Get the offset and check if it is reasonable.
int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
if (count < 10 || count > length) {
Log.e(TAG, "Invalid offset");
return 0;
}
offset += count;
length -= count;
// Get the count and go through all the elements.
count = pack(jpeg, offset - 2, 2, littleEndian);
while (count-- > 0 && length >= 12) {
// Get the tag and check if it is orientation.
tag = pack(jpeg, offset, 2, littleEndian);
if (tag == 0x0112) {
// We do not really care about type and count, do we?
int orientation = pack(jpeg, offset + 8, 2, littleEndian);
switch (orientation) {
case 1:
return 0;
case 3:
return 180;
case 6:
return 90;
case 8:
return 270;
}
Log.i(TAG, "Unsupported orientation");
return 0;
}
offset += 12;
length -= 12;
}
}
Log.i(TAG, "Orientation not found");
return 0;
}
private static int pack(byte[] bytes, int offset, int length,
boolean littleEndian) {
int step = 1;
if (littleEndian) {
offset += length - 1;
step = -1;
}
int value = 0;
while (length-- > 0) {
value = (value << 8) | (bytes[offset] & 0xFF);
offset += step;
}
return value;
}
}
This solution is experimental and must be tested for leaks and probably improved. In most cases Samsung and iOs devices return 90 DEG rotation and this solution works. Other cases also must be tested.
Can you post the image you're using?
because as this thread said, exif orientation for images loaded from web is ignored(only content provider and local files).
I also try to display this image in picasso 2.5.2, the real orientation of the image is facing rightside(the bottom code in image is facing right). The exif orientation, is 90deg clockwise. Try open it in chrome(chrome is honoring exif rotation), the image will be faced down(bottom code in image is facing down).
based on #ph0en1x response this version use google exif library and kotlin: add this interceptor to okhttpclient used by picasso
addNetworkInterceptor {
val response = it.proceed(it.request())
val body = response.body
if (body?.contentType()?.type == "image") {
val bytes = body.bytes()
val degrees = bytes.inputStream().use { input ->
when (ExifInterface(input).getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
ExifInterface.ORIENTATION_ROTATE_270 -> 270
ExifInterface.ORIENTATION_ROTATE_180 -> 180
ExifInterface.ORIENTATION_ROTATE_90 -> 90
else -> 0
}
}
if (degrees != 0) {
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
ByteArrayOutputStream().use { output ->
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, Matrix().apply { postRotate(degrees.toFloat()) }, true)
.compress(Bitmap.CompressFormat.PNG, 100, output)
response.newBuilder().body(output.toByteArray().toResponseBody(body.contentType())).build()
}
} else
response.newBuilder().body(bytes.toResponseBody(body.contentType())).build()
} else
response
}

Prime Factorization in Android

I am developing a small Prime Number application for Android devices and am nearly done, however I would like some help with optimizing my factorization class.
I am still having one or two problems with some large numbers(Even Numbers) being factored within a reasonable amount of time. I won't be able to use the sieve of Eratosthenes for this particular project I think as I can only sieve up to 10 million without the app crashing on my physical device (Samsung Galaxy S4 Mini). So my work around algorithm is below. I am not sure if I can maybe make the Pollard Rho algorithm that I implemented any better.
Once I have established that the number being tested isn't prime or isn't a prime square, I quickly do trial division up to 10 000, after that if the number still isn't factored completely I use the Pollard Rho method to reduce it the rest of the way.
I want to be able to factor numbers in the range of 2 > 2^64.
This is an example of a number taking roughly 15 seconds 256332652145852
It's factorization is [2, 2, 1671053, 38348971].
Any help would be gladly appreciated.
try {
long num = Long.valueOf(input);
if(num == 1) {
return "1" + " = " + input;
} else if(num < 1) {
return "Cannot factor a number less than 1";
} else if(PrimeNumbers.isPrime(num) == true) {
return result = num + " is a Prime Number.";
} else if(isSquare(num) == true && PrimeNumbers.isPrime((long) Math.sqrt(num)) == true) {
return result = (int) Math.sqrt(num) + "<sup><small>" + 2 + "</small></sup>" + " = " + input;
} else {
factors(num, pFactors);
return result = exponentialForm(pFactors, num) + " = " + input;
}
} catch(NumberFormatException e) {
return result = "Unfortunately the number entered is too large";
}
}
public static void factors(long n, ArrayList<Long> arr) {
long number = trialDiv(n, arr);
if(number > 1) {
while(true) {
long divisor = pollard(number, 1);
if(PrimeNumbers.isPrime(divisor) == true) {
number /= divisor;
arr.add(divisor);
if(PrimeNumbers.isPrime(number) == true) {
arr.add(number);
break;
}
}
}
}
}
private static long trialDiv(long n, ArrayList<Long> arr) {
while(n % 2 == 0) {
n /= 2;
arr.add((long) 2);
}
for(long i = 3; i < 10000; i += 2) {
if(PrimeNumbers.isPrime(i) == true) {
while(n % i == 0) {
arr.add(i);
n /= i;
}
}
}
if(PrimeNumbers.isPrime(n) == true) {
arr.add(n);
return 1;
}
return n;
}
public static long pollard(long n, long c) {
long x = 2;
long y = 2;
long d = 1;
while (d == 1) {
x = g(x, n, c);
y = g(g(y, n, c), n, c);
d = gcd(Math.abs(y - x), n);
}
if (d == n) {
return pollard(n, c + 1);
} else {
return d;
}
}
static long g(long x, long n, long c) {
long g = (((x * x) + c) % n);
return g;
}
static long gcd(long a, long b) {
if (b == 0) {
return a;
} else {
return gcd(b, a % b);
}
}
Your pollard function is okay but not great. You are using Pollard's original algorithm, but it would be better to use Brent's variant. But that's probably not the source of your slow performance.
Your trial division function is slow. Checking each possible divisor for primality is very expensive, and not necessary. It doesn't matter if you divide by a composite; the division will always fail, but you don't waste the time checking primality. A better approach is wheel factorization.

SoftKeyboard for android

I am creating softkeyboard for android 2.2 and higher. everything is fine but when i type really quick then some time my ACTION_DOWN method is not calling. Actual flow of called method should look like
1) motionEvent.ACTION_DOWN
2) OnPress()
3) motionEvent.ACTION_UP
4) OnRelease() and repeat same order for next word.
if i type at normal speed then it works fine but if i type fast then above order of method execution looks like
1) motionEvent.ACTION_DOWN
2) OnPress()
3) OnRelease()
4) motionEvent.ACTION_UP and for next word OnPress and OnRelease() methods are being called.
any suggestions?
Edit
My LatinKeyboardView class that contains MotionActionEvents
enter code here #Override
public boolean onTouchEvent(MotionEvent me) {
// Moved next line and added lines to help solve reentrant problem.
int action = me.getAction();
// next 2 lines required for multitouch Andr 2+
int act = action & MotionEvent.ACTION_MASK;
final int ptrIndex = (act & MotionEvent.ACTION_POINTER_ID_MASK) //Renamed to ACTION_POINTER_INDEX_MASK in later Andro versions
>> MotionEvent.ACTION_POINTER_ID_SHIFT;//Renamed to ACTION_POINTER_INDEX_SHIFT in later Andro versions
// currentX = me.getX();
// currentY = me.getY();
calcMinSlide();
// int act = me.getAction();
if (act == android.view.MotionEvent.ACTION_DOWN) {
Log.v(tag, "ANGLE_ACTION_DOWN : ");
if (pw != null) {
pw.dismiss();
pw = null;
}
lastDirection = direction = 0;
touchDownPoint.set(me.getX(), me.getY());
// Will added next two lines
touchDragPoint.set(me.getX(), me.getY());
thresholdPoint.set(me.getX(), me.getY());
// Will6 added to improve accuracy
thresholdPoint1_5 = false;
// Will7 added next 4 for Andro 2+
currentX = me.getX();
currentY = me.getY();
// Save the ID of this first pointer (touch) down
currentPointerID = me.getPointerId(0);
nextPointerID = INVALID_POINTER_ID;
previousDownTime = me.getEventTime();
me.setLocation(touchDownPoint.x, touchDownPoint.y);
// start timer on touch down
startTimer(me, 300); // 150); Will7 changed this and removed method: checkLongPress
} else if (act == android.view.MotionEvent.ACTION_UP
|| act == android.view.MotionEvent.ACTION_MOVE) {
Log.v(tag, "ANGLE_ACTION_UP : ");
//touchdragPoint and previoustouchPoint for calculating velocity
PointF previousTouchPoint = new PointF(touchDragPoint.x,touchDragPoint.y);
//Will7 added next if for Andro 2+: Find the index of the active pointer and fetch its position
if (act == android.view.MotionEvent.ACTION_MOVE && me.getPointerId(ptrIndex) != currentPointerID) {
//Log.v(tag, "Cancel ATION_MOVE!! ID: "+me.getPointerId(ptrIndex));
return super.onTouchEvent(me);
}
touchDragPoint.set(me.getX(), me.getY());
dy = me.getY() - touchDownPoint.y;
dx = me.getX() - touchDownPoint.x;
// added for Andro 2+
currentX = touchDragPoint.x;
currentY = touchDragPoint.y;
//calculate time interval from down time to current time
long timeInterval = me.getEventTime() - previousDownTime;
previousDownTime = me.getEventTime();
velocityThresDir = VELOCITY_THRESHOLD;
float touchVelocity = Math.abs(distanceBetweenPoints(touchDragPoint, previousTouchPoint) / timeInterval);
if (distanceFromCenter(dx,dy) > minSlide) {
// Log.v(tag, "direction to detect angle....after... dx..."+dx+" dy "+dy);
//Log.v(tag, "ANGLE angle.... after..."+distanceFromCenter(dx,dy)+" slide distance "+ minSlide);
/* cancel the timer*/
if (cDownTimer != null) {
cDownTimer.cancel();
cDownTimer = null;
}
/* coding for calculating velocity threshold*/
float angleThreshold = 0.0f;
if ((thresholdPoint.x == touchDownPoint.x) && (thresholdPoint.y == touchDownPoint.y)){
thresholdPoint.set(touchDragPoint.x, touchDragPoint.y);
}
else {
//Will6 - added next if to improve accuracy
if ((distanceFromCenter(dx,dy) > (minSlide * 1.5)) && !thresholdPoint1_5){
thresholdPoint.set(me.getX(),me.getY());
thresholdPoint1_5 = true;
}
float angleP1= calcAngle(touchDownPoint, thresholdPoint);
float angleP2= calcAngle(previousTouchPoint, touchDragPoint);
angleThreshold = Math.abs(angleP1 - angleP2);
if (angleThreshold > Math.PI) angleThreshold = (float) (2.0 * Math.PI) - angleThreshold;
}
// velocityThresDir = (float) Math.abs((Math.cos(angleThreshold) * touchVelocity*1000));
velocityThresDir = (float) (Math.cos(angleThreshold) * touchVelocity*1000);
//end of calculation for velocity threshold
double angle = newM(touchDownPoint.x, touchDownPoint.y, touchDragPoint.x, touchDragPoint.y);
// Log.v(tag, "ANGLE_FIRST_X "+touchDownPoint.x+"FIRST_Y "+touchDownPoint.y);
// Log.v(tag, "ANGLE_SECOND_X "+touchDragPoint.x+"SECOND_Y "+touchDragPoint.y);
// Log.v(tag, "ANGLE_FIRST"+angle);
if ((touchDownPoint.x != thresholdPoint.x) || (touchDownPoint.y != thresholdPoint.y)) {
double angleThresh = newM(touchDownPoint.x, touchDownPoint.y, thresholdPoint.x, thresholdPoint.y);
double angleBetween = Math.abs(angle - angleThresh);
if(angleBetween < 45 || angleBetween > 315){
if(angleBetween > 315) {
if (angle < angleThresh) {
angle += 360;
}
else if (angle > angleThresh) {
angleThresh += 360;
}
angle = Math.abs((angle - angleThresh)%360) / 2.0;
// Log.v(tag, "ANGLE_SECOND"+angle);
}
else {
angle = (angle + angleThresh * 1.0) / 2.0;
// Log.v(tag, "ANGLE_THIRD"+angle);
}
}
}
if (angle > 337.5){
direction = 3;
}else if (angle > 292.5){
direction = 5;
}else if (angle > 247.5){
direction = 4;
}else if (angle > 202.5){
direction = 6;
}else if (angle > 157.5){
direction = 1;
}else if (angle > 112.5){
direction = 7;
}else if (angle > 67.5){
direction = 2;
}else if (angle > 22.5){
direction = 8;
}else{
direction = 3;
}
/* start timer if velocity is below velocity threshold*/
if ((velocityThresDir < VELOCITY_THRESHOLD) &&
(act == android.view.MotionEvent.ACTION_MOVE) && (cDownTimer == null) &&
(pw == null)) { //"&& cDownTimer" can be removed I think
/* start timer with motionEvent and time in ms as a parameter */
// added next two lines
callOnLongPress(me);
startTimerShowPopup(me,100);//Will changed from 150
}
} else {
direction = 0;
}
if (act == android.view.MotionEvent.ACTION_MOVE) {
return true;
} else if (act == android.view.MotionEvent.ACTION_UP) {
if (cDownTimer != null) {
cDownTimer.cancel();
cDownTimer = null;
}
if (pw != null)
pw.dismiss();
if (longPressedKey) {
SoftKeyboard.mComposing
.append(charset[mappedKey][direction]);
popUpTextEntryScheme = true;
}
longPressedKey = false;
currentPointerID = INVALID_POINTER_ID;
}
}
else if (act == android.view.MotionEvent.ACTION_POINTER_DOWN) {
// if (me.getPointerCount() > 1) { //Should always be true, I think
nextPointerID = me.getPointerId(ptrIndex);
nextTouchDownPoint.set(me.getX(ptrIndex),me.getY(ptrIndex));
// }
}
else if (act == android.view.MotionEvent.ACTION_CANCEL) {
currentPointerID = INVALID_POINTER_ID;
nextPointerID = INVALID_POINTER_ID;
}
else if (act == android.view.MotionEvent.ACTION_POINTER_UP) {
// Extract the index of the pointer that left the touch sensor
final int pointerId = me.getPointerId(ptrIndex);
if (pointerId == currentPointerID) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = ptrIndex == 0 ? 1 : 0;
currentPointerID = nextPointerID;//(0);
touchDownPoint.set(nextTouchDownPoint.x,nextTouchDownPoint.y);
if (cDownTimer != null) {
cDownTimer.cancel();
cDownTimer = null;
}
if (pw != null) {
pw.dismiss();
pw = null;
}
if (longPressedKey) {
SoftKeyboard.mComposing
.append(charset[mappedKey][direction]);
popUpTextEntryScheme = true;
}
longPressedKey = false;
lastDirection = direction = 0; // keysAtOnce=0;
touchDragPoint.set(me.getX(newPointerIndex),me.getY(newPointerIndex));
thresholdPoint.set(nextTouchDownPoint.x,nextTouchDownPoint.y);
//added to improve accuracy
thresholdPoint1_5 = false;
// added next 3 for Andro 2+
currentX = touchDragPoint.x;
currentY = touchDragPoint.y;
// Save the ID of this first pointer (touch) down
previousDownTime = me.getEventTime();
me.setLocation(touchDownPoint.x, touchDownPoint.y);
//start timer on touch down
startTimer(me,300); //150); Will7 changed this and removed method: checkLongPress
} else { //Second pointer up before first. (Not handling 3 or more pointers yet!)
// nextPointerID = INVALID_POINTER_ID;
}
} //else
return super.onTouchEvent(me); // after we return here the service will get notified, etc
// return true;
}
and my SoftKeyboard class..
public void onPress(int primaryCode) {
Log.v("SoftKeyboard", "ANGLE_ACTION_ON_PRESS : ");
// added next section for repeating backspace
if (RepeatBSTimer != null) {
RepeatBSTimer.cancel();
RepeatBSTimer = null;
}
if (mp != null) { // /Will7 moved this from just above keystroke
// statement
mp.release();
mp = null;
}
// added for Andro 2+ multitouch
if (primaryCode == pressedCode
&& LatinKeyboardView.nextPointerID != LatinKeyboardView.INVALID_POINTER_ID) {
// I need to look up the real primaryCode here. (Not sure how!)
// Android gives wrong values when touches overlap.
wrongPrimaryCode = true;
return;
} else
wrongPrimaryCode = false;
pressedCode = primaryCode;
// added next section for repeating backspace
if (primaryCode == Keyboard.KEYCODE_DELETE) {
RepeatBSTimer = new CountDownTimer(1500000, 75) {
#Override
public void onTick(long millisUntilFinished) {
int primaryCode2;
if (LatinKeyboardView.longPressedKey
|| (1500000 - millisUntilFinished > 500)) {
primaryCode2 = getCharFromKey(pressedCode,
LatinKeyboardView.direction, mInputView
.getKeyboard());
if (primaryCode2 == Keyboard.KEYCODE_DELETE) {
repeating = true;
handleBackspace();
} else if (primaryCode2 == KEYCODE_DELETEWORD
&& (millisUntilFinished % 150) < 75) {
repeating = true;
deleteLastWord();
}
}
}
#Override
public void onFinish() {
}
};
RepeatBSTimer.start();
}
// added section for repeating backspace
Uri uri = Uri.parse("android.resource://" + getPackageName() + "/"
+ R.raw.keystroke);// Play Key Click
try {
mp = new MediaPlayer();
mp.setDataSource(this, uri);
mp.prepare();
mp.start();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void onRelease(int primaryCode) {
// Will7 added next line if for Andro 2+ multitouch
if (wrongPrimaryCode
&& LatinKeyboardView.nextPointerID != LatinKeyboardView.INVALID_POINTER_ID) {
return;
}
// else pressedCode = primaryCode;
// added next sections for repeating backspace
primaryCode = getCharFromKey(pressedCode, LatinKeyboardView.direction,mInputView.getKeyboard());
if (primaryCode == Keyboard.KEYCODE_DELETE && !repeating)
handleBackspace();
if (primaryCode == KEYCODE_DELETEWORD && !repeating)
deleteLastWord();
repeating = false;
if (RepeatBSTimer != null) {
RepeatBSTimer.cancel();
RepeatBSTimer = null;
}
// moved all the rest of this method from onKey()
int[] keyCodes;
// added this var for Andro 2+ multitouch
keyCodes = keyCodesSave;
commitTyped(getCurrentInputConnection());
if (isWordSeparator(primaryCode) && (char) primaryCode != '.'
&& (char) primaryCode != '!' && (char) primaryCode != '?') {
// Handle separator
if (mComposing.length() > 0) {
commitTyped(getCurrentInputConnection());
}
sendKey(primaryCode);
updateShiftKeyState(getCurrentInputEditorInfo());
} else if (primaryCode == Keyboard.KEYCODE_DELETE) {
// commented out next line for repeating backspace
// handleBackspace();
} else if (primaryCode == Keyboard.KEYCODE_SHIFT || primaryCode == -1) {
handleShift();
} else if (primaryCode == Keyboard.KEYCODE_CANCEL) {
handleClose();
return;
} else if (primaryCode == KEYCODE_ESCAPE) {
// Do nothing on Escape key
} else if (primaryCode == LatinKeyboardView.KEYCODE_OPTIONS) {
// Show a menu or something
} else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE
&& mInputView != null) {
Keyboard current = mInputView.getKeyboard();
if (current == mSymbolsKeyboard
|| current == mSymbolsShiftedKeyboard) {
getCurrentInputConnection().finishComposingText();
current = mQwertyKeyboard;
} else {
getCurrentInputConnection().finishComposingText();
current = mSymbolsKeyboard;
}
mInputView.setKeyboard(current);
if (current == mSymbolsKeyboard) {
current.setShifted(false);
}
} else if (primaryCode == KEYCODE_CAPSLOCK)// handle caps lock
{
if (mInputView.getKeyboard() == mQwertyKeyboard
|| mInputView.getKeyboard() == mSymbolsKeyboard) {
mInputView.setKeyboard(mQwertyKeyboardUpperCase);
mQwertyKeyboardUpperCase.setShifted(true);
mCapsLock = true;
} else {
mQwertyKeyboard.setShifted(false);
mInputView.setKeyboard(mQwertyKeyboard);
mCapsLock = false;
}
} else if (primaryCode == KEYCODE_DELETEWORD) {
// commented out next line for repeating backspace
// deleteLastWord();
} else if (primaryCode == KEYCODE_FULL_STOP_AND_SPACE) {
// added next line
backspaceIfSpaceLeft();
getCurrentInputConnection().finishComposingText();
handleCharacter((int) '.', keyCodes);
handleCharacter((int) ' ', keyCodes);
handleShift();
}
// added next 5 KEYCODES
else if (primaryCode == KEYCODE_EXCLAMATION) {
// added next line
backspaceIfSpaceLeft();
getCurrentInputConnection().finishComposingText();
handleCharacter((int) '!', keyCodes);
handleCharacter((int) ' ', keyCodes);
handleShift();
} else if (primaryCode == KEYCODE_QUESTION_MARK) {
// added next line
backspaceIfSpaceLeft();
getCurrentInputConnection().finishComposingText();
handleCharacter((int) '?', keyCodes);
handleCharacter((int) ' ', keyCodes);
handleShift();
} else if (primaryCode == KEYCODE_COMMA) {
// added next line
backspaceIfSpaceLeft();
getCurrentInputConnection().finishComposingText();
handleCharacter((int) ',', keyCodes);
handleCharacter((int) ' ', keyCodes);
} else if (primaryCode == KEYCODE_COLON) {
// added next line
backspaceIfSpaceLeft();
getCurrentInputConnection().finishComposingText();
handleCharacter((int) ':', keyCodes);
handleCharacter((int) ' ', keyCodes);
} else if (primaryCode == KEYCODE_SEMICOLON) {
// added next line
backspaceIfSpaceLeft();
getCurrentInputConnection().finishComposingText();
handleCharacter((int) ';', keyCodes);
handleCharacter((int) ' ', keyCodes);
} else {
handleCharacter(primaryCode, keyCodes);
}
}
Thanks..
This is a very long onTouchEvent handler, I suggest breaking it up into more logical steps. I also have had the issue of seemingly "out-of-order" events when trying to handle the touchscreen.
I found that I wasn't handling the events per pointer ID correctly. I'd check to make sure you are handling multiple pointers as expected. The device I test with (N1) only supports two pointers, but others support many more, and those should be accounted for.
For handling touchscreen "soft-buttons" as an onTouchEvent event, I've found it useful to create a state machine class. Use the MotionEvent parameters as input events to the state machine, and cause state transitions to trigger your wanted events. An explicit, state-driven approach will give you the expected results you are looking for.

Categories

Resources