Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android, how to know how much my view is shifted by soft keyboard adjustPan

I've got an fullscreen landscape app (Cocos2d-x/NDK) with an input field which position I set during runtime like this

RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mInstance.mEditText.getLayoutParams();
params.topMargin = (int)newY;
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
mInstance.mEditText.setLayoutParams(params);

it works cool at most devices, but I've got one device where soft keyboard grows dynamically with 'suggestions' line above it. That's when strange things happen.

If my EditText is positioned somewhere at the top of screen, i.e. its rect doesn't intersect with keyboard, nothing changes, EditText stays where it is, everything is ok. But if my EditText is at the middle of the screen so the keyboard is going to overlap it, it starts to move upwards (with animation).

I'm successfully using SOFT_INPUT_ADJUST_PAN to determine keyboard height (via difference between getWindowVisibleDisplayFrame calls), so I can't just disable panning by setting adjustNothing flag, I'm going to stick to adjustPan.

The problem is that I can't understand what is exactly done with my view position when soft keyboard is resized (for suggestions line). in my onGlobalLayout I tried to watch:

- TextView getY(),
- TextView getTranslationY(),
- TextView RelativeLayout params (topMargin, bottomMargin),
- TextView getLocationOnScreen,
- TextView getGlobalVisibleRect top & bottom,
- Activity getWindowVisibleDisplayFrame rect top & bottom.

Nothing of above is changed! But TextView is moving upwards anyway. I believe there is some offset is applied SOMEWHERE in view tree, after soft keyboards appears. But then why getLocationOnScreen doesn't show this? And why getGlobalVisibleRect doesn't know anything about position changes as well?

I want to set exact position for my EditText, so even if I can't prevent it from moving up (tradeoff of having adjustPan flag set), at least I need to know how much it moved up, to apply back transformation.

I tried a very hacky workaround: in my EditText beforeTextChanged() method I set TextView's layout topMargin to 0, to be sure that when suggestions line appears, it won't shift my TextView because it's not overlapped anymore. Then in onGlobalLayout() I restore correct EditText position back. It works, but I don't like it this way, and also it causes my text to blink.

Some code for better understanding of what I'm doing.

private void showKeyboard(final boolean show) {
    final InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
    if (show) {
        mEditText.requestFocus();
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                imm.showSoftInput(mEditText, 0);
            }
        }, 50);
        mVisible = true;
    } else {
        imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
        mEditText.clearFocus();
        mVisible = false;
    }
}

@Override
protected void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.native_keyboard);
    mLayout = findViewById(R.id.keyboard_layout);
    mEditText = (EditText) findViewById(R.id.keyboard_input);
    mEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
    getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
    mEditText.setInputType(mEditText.getInputType() | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE);
    mInitialFrame = getVisibleFrame();
    mPreviousFrame = mInitialFrame;
    mLayout.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            final int kbAssumedH = 100;
            Rect currentFrame = getVisibleFrame();
            final int dResize = (int) (mPreviousFrame.height() - currentFrame.height());
            if (mWasShown && isVisible() && dResize != 0) {
                NativeKeyboard.onKeyboardResized();
            }
            mPreviousFrame = currentFrame;

            if (isVisible() && (mInitialFrame.height() - currentFrame.height() > kbAssumedH)) {
                if (!mWasShown) {
                    NativeKeyboard.onKeyboardDidShow();
                    mWasShown = true;
                }
            } else {
                if (mWasShown) {
                    NativeKeyboard.onKeyboardDidHide();
                    mWasShown = false;
                }
            }
        }
    });     
}

that's how I get current window space that is not overlapped with keyboard:

public static synchronized android.graphics.Rect getVisibleFrame() {
    View rootView = mInstance.getWindow().getDecorView();
    android.graphics.Rect r = new android.graphics.Rect();
    rootView.getWindowVisibleDisplayFrame(r);
    return r;
}

this is the method I use to set my EditText position. Once after suggestions line appears, my EditText visually becomes shifted upwards relatively to position I pass here as Y param (and the shift is permanent, i.e. I can call setRect with new position, and it also will be shifted)

public static synchronized void setRect(final float x, final float y, final float w, final float h) {
    if (mInstance != null) {
        Cocos2dxHelper.runOnUIThread(new Runnable() {
            @Override
            public void run() {
                    mInstance.mEditText.setX((int) x);
                    float height = mInstance.mEditText.getHeight();
                    mInstance.mEditText.setWidth((int) w);
                    float yCenter = y + h / 2;
                    float newY = yCenter - height / 2.f;
                    RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mInstance.mEditText.getLayoutParams();
                    params.topMargin = (int)newY;
                    params.width = (int)w;
                    params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
                    mInstance.mEditText.setLayoutParams(params);
            }
        });
    }
}

I tried on 4 different devices (3 phones & 1 tablet), problem occurs only on Acer Liquid Jade Z, 4.4.4.

Thanks in advance.

like image 661
Mihail Baranov Avatar asked Nov 08 '22 13:11

Mihail Baranov


1 Answers

Well, I didn't manage to catch the transition itself. Instead, the workaround that was ok in my particular situation goes like this:

add new invisible dialog, use it only for getWindowVisibleDisplayFrame, so you'll get all you need when keyboard is shown/hidden.

...AND for sure you'll want SOFT_INPUT_ADJUST_NOTHING for your main (visible) dialog now, why not, if you don't need its displayFrame anymore? So, no hidden unobvious shifts applied to anything in your view.

That simple. Hope it helps.

like image 159
Mihail Baranov Avatar answered Nov 14 '22 21:11

Mihail Baranov