AdjustResize with animation

2019-04-04 14:42发布

问题:

I have used android:windowSoftInputMode="adjustResize" in the manifest to prevent keyboard from hiding the activity button. It works, but when keyboard opens, my button moves up. It is a bit jumpy, I wanted to know - can I have any animation for that to transition smoothly?

<activity
        android:name=".Activity"
        android:screenOrientation="portrait"
        android:windowSoftInputMode="adjustResize">
</activity>

回答1:

The method setSoftInputMode(int) cannot be overrided, its implementation is inside Window class and I don't think that's possible to replace the current window by your own. You cannot manage this from WindowManager too.
You could create a listener on a ViewGroup and catch the modification's layout when the SoftKeyboard is opening and closing. Indeed, when the SKB shows up, the container's layout redraws and changes its height. From this event, you could manage to set an Animation on views child and make a smooth effect.

Edit: The solution by using a GlobalLayoutListener on the rootview is also possible instead of (the solution presented below:) creating a custom viewgroup class.

You have to create your own viewgroup and make it as a parent container in the layout. It'll implement an interface which will be handled in the UI class (Activity, Fragment, whatever). I found this blog to detect the events on SKB for (~)all versions. According to it, here's the viewgroup's class to handle the height's changes:

public class ContainerViewHandler extends RelativeLayout {

    private boolean isKeyboardShown;
    private onKeyboardStateChange listener;

    public ContainerViewHandler(Context context) {
        super(context);
    }

    public ContainerViewHandler(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ContainerViewHandler(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setKeyboardStateListener(onKeyboardStateChange listener) {
        this.listener = listener;
    }

    // Callbacks
    public interface onKeyboardStateChange {
        void onKeyboardShow();
        void onKeyboardHide();
    }

    @Override
    public boolean dispatchKeyEventPreIme(@NonNull KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            // Keyboard is hiding
            if (isKeyboardShown) {
                isKeyboardShown = false;
                listener.onKeyboardHide();
            }
        }
        return super.dispatchKeyEventPreIme(event);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int proposedHeight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();
        if (actualHeight > proposedHeight) {
            // Keyboard is showing
            if (!isKeyboardShown) {
                isKeyboardShown = true;
                listener.onKeyboardShow();
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

Now, you've to add this viewgroup in layout (eg <com.package.name.ContainerViewHandler .../>). Then, you must implement the above interface setKeyboardStateListener in the activity, like the following:

ContainerViewHandler containerView = 
          (ContainerViewHandler) findViewById(R.id.container_view);
containerView.setKeyboardStateListener(new ContainerHandler.onKeyboardStateChange() {
    @Override
    public void onKeyboardShow() {
        Log.v("onKeyboardShow()", "SoftKeyboard is showing. Hello!");
    }

    @Override
    public void onKeyboardHide() {
        Log.v("onKeyboardHide()", "SoftKeyboard is hiding, Bye bye!");
    }
});

Thus, you can manage different animations to handle and prevent the button to "jump" directly above the SKB. To test this out, I try to reproduce a bounce effect:

This how my implementation looks like:

containerView.setKeyboardStateListener(new ContainerViewHandler.onKeyboardStateChange() {
    @Override
    public void onKeyboardShow() {
        setAnimationUp();
    }

    @Override
    public void onKeyboardHide() {
        setAnimationDown();
    }
});

private void setAnimationUp() {
    footerButton.setVisibility(View.GONE);
    float dpY = AppUtils.convertPxToDp(20, this); // custom conversion method

    Animation a1 = new TranslateAnimation(0, 0, footerButton.getHeight() * 4, -(dpY));
    a1.setDuration(250);
    a1.setFillAfter(true);

    final Animation a2 = new TranslateAnimation(0, 0, -(dpY), 0);
    a2.setDuration(320);
    a2.setFillAfter(true);

    a1.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
            footerButton.setVisibility(View.VISIBLE);
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            footerButton.startAnimation(a2);
        }

        @Override
        public void onAnimationRepeat(Animation animation) { }
    });

    footerButton.startAnimation(a1);
}

private void setAnimationDown() {
    float dpY = AppUtils.convertPxToDp(30, this); // custom conversion method
    Animation b1 = new TranslateAnimation(0, 0, -(dpY), dpY);
    b1.setDuration(300);
    b1.setFillAfter(true);

    final Animation b2 = new TranslateAnimation(0, 0, dpY, 0);
    b2.setDuration(320);
    b2.setFillAfter(true);

    b1.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) { }

        @Override
        public void onAnimationEnd(Animation animation) {
            footerButton.startAnimation(b2);
        }

        @Override
        public void onAnimationRepeat(Animation animation) { }
    });

    footerButton.startAnimation(b1);
}

I begin by setting two animations (a1, a2) in the first callback:

  • a1: start below (behind) the SKB (about 4xbutton's height) and up to 20dp above it,
  • a2: start at 20dp and return to 0dp (normal position).

And two others (b1, b2) in the second callback:

  • b1: start where the button is up to 30dp at top and go down to 30dp outside the parent container,
  • b2: finaly, from 30dp out to 0dp (the initial position).

PS: don't forget to use adjustResize in the Manifest and to make the content (eg edittexts in my tests) above the footer button which has alignParentBottom to true.