Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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..

like image 288
Piyush Avatar asked Mar 12 '12 11:03

Piyush


1 Answers

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.

like image 117
Kaiged Avatar answered Oct 23 '22 03:10

Kaiged