Android全屏模式下如何在软键盘可见时调整布局

197
我已经进行了大量的研究,以调整软键盘激活时的布局,并且已经成功实现了。但是当我在清单文件中的活动标记中使用android:theme="@android:style/Theme.NoTitleBar.Fullscreen"时,问题就出现了。
为此,我使用了不同选项的android:windowSoftInputMode="adjustPan|adjustResize|stateHidden",但没有成功。
之后,我通过编程方式实现了FullScreen,并尝试使用各种布局来与FullScreen配合使用,但都无济于事。
我参考了这些链接,并查看了许多与此问题相关的帖子:

http://android-developers.blogspot.com/2009/04/updating-applications-for-on-screen.html

http://davidwparker.com/2011/08/30/android-how-to-float-a-row-above-keyboard/

这里是XML代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:id="@+id/masterContainerView"
    android:layout_width="fill_parent" android:layout_height="fill_parent"
    android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="#ffffff">

    <ScrollView android:id="@+id/parentScrollView"
        android:layout_width="fill_parent" android:layout_height="wrap_content">

        <LinearLayout android:layout_width="fill_parent"
            android:layout_height="fill_parent" android:orientation="vertical">

            <TextView android:id="@+id/setup_txt" android:layout_width="wrap_content"
                android:layout_height="wrap_content" android:text="Setup - Step 1 of 3"
                android:textColor="@color/top_header_txt_color" android:textSize="20dp"
                android:padding="8dp" android:gravity="center_horizontal" />

            <TextView android:id="@+id/txt_header" android:layout_width="fill_parent"
                android:layout_height="40dp" android:text="AutoReply:"
                android:textColor="@color/top_header_txt_color" android:textSize="14dp"
                android:textStyle="bold" android:padding="10dp"
                android:layout_below="@+id/setup_txt" />

            <EditText android:id="@+id/edit_message"
                android:layout_width="fill_parent" android:layout_height="wrap_content"
                android:text="Some text here." android:textSize="16dp"
                android:textColor="@color/setting_editmsg_color" android:padding="10dp"
                android:minLines="5" android:maxLines="6" android:layout_below="@+id/txt_header"
                android:gravity="top" android:scrollbars="vertical"
                android:maxLength="132" />

            <ImageView android:id="@+id/image_bottom"
                android:layout_width="fill_parent" android:layout_height="wrap_content"
                android:layout_below="@+id/edit_message" />

        </LinearLayout>
    </ScrollView>

    <RelativeLayout android:id="@+id/scoringContainerView"
        android:layout_width="fill_parent" android:layout_height="50px"
        android:orientation="vertical" android:layout_alignParentBottom="true"
        android:background="#535254">

        <Button android:id="@+id/btn_save" android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:layout_alignParentRight="true"
            android:layout_marginTop="7dp" android:layout_marginRight="15dp"
            android:layout_below="@+id/edit_message"
            android:text = "Save" />

        <Button android:id="@+id/btn_cancel" android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:layout_marginTop="7dp"
            android:layout_marginRight="10dp" android:layout_below="@+id/edit_message"
            android:layout_toLeftOf="@+id/btn_save" android:text = "Cancel" />

    </RelativeLayout>
</RelativeLayout>

enter image description here

当软键盘出现时,我希望底部的两个按钮向上移动。

enter image description here


1
我认为你必须在ScrollView里面和EditText下面添加按钮。 - Balaji Khadake
我已经尝试了许多不起作用的选项... - Vineet Shukla
1
将您的按钮放在FrameLayout中,并将FrameLayout的权重设置为1,最后仅使用android:windowSoftInputMode="adjustPan"。请告诉我这是否有效。 - Sherif elKhatib
@VineetShukla,你找到全屏的工作了吗? - Muhammad Babar
4
请注意,根据android.view.WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE的文档,“你不能同时使用adjustResizeadjustPan”。这两个选项是互斥的。 - Denys Kniazhev-Support Ukraine
29个回答

5
为了使其与全屏模式兼容:
使用Ionic键盘插件。这允许您监听键盘何时出现和消失。
在OnDeviceReady中添加这些事件侦听器:
// Allow Screen to Move Up when Keyboard is Present
window.addEventListener('native.keyboardshow', onKeyboardShow);
// Reset Screen after Keyboard hides
window.addEventListener('native.keyboardhide', onKeyboardHide);

逻辑:
function onKeyboardShow(e) {
    // Get Focused Element
    var thisElement = $(':focus');
    // Get input size
    var i = thisElement.height();
    // Get Window Height
    var h = $(window).height()
    // Get Keyboard Height
    var kH = e.keyboardHeight
    // Get Focused Element Top Offset
    var eH = thisElement.offset().top;
    // Top of Input should still be visible (30 = Fixed Header)
    var vS = h - kH;
    i = i > vS ? (vS - 30) : i;
    // Get Difference
    var diff = (vS - eH - i);
    if (diff < 0) {
        var parent = $('.myOuter-xs.myOuter-md');
        // Add Padding
        var marginTop = parseInt(parent.css('marginTop')) + diff - 25;
        parent.css('marginTop', marginTop + 'px');
    }
}

function onKeyboardHide(e) {
  // Remove All Style Attributes from Parent Div
  $('.myOuter-xs.myOuter-md').removeAttr('style');
}

基本上,如果它们的差值为负数,则键盘覆盖了您的输入的像素数量。因此,如果您通过这个调整父级div,那么应该可以抵消它。
将逻辑添加到300毫秒的延迟时间也应该优化性能(因为这将允许键盘出现)。

4

我尝试了Joseph Johnson的课程,它可以工作,但并不完全符合我的需求。与模拟android:windowSoftInputMode =“adjustResize”不同,我需要模拟android:windowSoftInputMode =“adjustPan”。

我正在使用这个全屏Webview。为了将内容视图滑动到正确的位置,我需要使用一个JavaScript接口,该接口提供有关具有焦点的页面元素位置的详细信息,并因此接收键盘输入。我省略了这些细节,但提供了我对Joseph Johnson类的重写。它将为您实现自定义pan和他的resize提供非常可靠的基础。

package some.package.name;

import some.package.name.JavaScriptObject;

import android.app.Activity;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;

//-------------------------------------------------------
// ActivityPanner Class
//
// Convenience class to handle Activity attributes bug.
// Use this class instead of windowSoftInputMode="adjustPan".
//
// To implement, call enable() and pass a reference
// to an Activity which already has its content view set.
// Example:
//      setContentView( R.layout.someview );
//      ActivityPanner.enable( this );
//-------------------------------------------------------
//
// Notes:
//
// The standard method for handling screen panning
// when the virtual keyboard appears is to set an activity
// attribute in the manifest.
// Example:
// <activity
//      ...
//      android:windowSoftInputMode="adjustPan"
//      ... >
// Unfortunately, this is ignored when using the fullscreen attribute:
//      android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
//
//-------------------------------------------------------
public class ActivityPanner {

    private View contentView_;
    private int priorVisibleHeight_;

    public static void enable( Activity activity ) {
        new ActivityPanner( activity );
    }

    private ActivityPanner( Activity activity ) {
        FrameLayout content = (FrameLayout)
            activity.findViewById( android.R.id.content );
        contentView_ = content.getChildAt( 0 );
        contentView_.getViewTreeObserver().addOnGlobalLayoutListener(
            new ViewTreeObserver.OnGlobalLayoutListener() {
                public void onGlobalLayout() { panAsNeeded(); }
        });
    }

    private void panAsNeeded() {

        // Get current visible height
        int currentVisibleHeight = visibleHeight();

        // Determine if visible height changed
        if( currentVisibleHeight != priorVisibleHeight_ ) {

            // Determine if keyboard visiblity changed
            int screenHeight =
                contentView_.getRootView().getHeight();
            int coveredHeight =
                screenHeight - currentVisibleHeight;
            if( coveredHeight > (screenHeight/4) ) {
                // Keyboard probably just became visible

                // Get the current focus elements top & bottom
                // using a ratio to convert the values
                // to the native scale.
                float ratio = (float) screenHeight / viewPortHeight();
                int elTop = focusElementTop( ratio );
                int elBottom = focusElementBottom( ratio );

                // Determine the amount of the focus element covered
                // by the keyboard
                int elPixelsCovered = elBottom - currentVisibleHeight;

                // If any amount is covered
                if( elPixelsCovered > 0 ) {

                    // Pan by the amount of coverage
                    int panUpPixels = elPixelsCovered;

                    // Prevent panning so much the top of the element
                    // becomes hidden
                    panUpPixels = ( panUpPixels > elTop ?
                                    elTop : panUpPixels );

                    // Prevent panning more than the keyboard height
                    // (which produces an empty gap in the screen)
                    panUpPixels = ( panUpPixels > coveredHeight ?
                                    coveredHeight : panUpPixels );

                    // Pan up
                    contentView_.setY( -panUpPixels );
                }
            }
            else {
                // Keyboard probably just became hidden

                // Reset pan
                contentView_.setY( 0 );
            }

            // Save usabale height for the next comparison
            priorVisibleHeight_ = currentVisibleHeight;
        }
    }

    private int visibleHeight() {
        Rect r = new Rect();
        contentView_.getWindowVisibleDisplayFrame( r );
        return r.bottom - r.top;
    }

    // Customize this as needed...
    private int viewPortHeight() { return JavaScriptObject.viewPortHeight(); }
    private int focusElementTop( final float ratio ) {
        return (int) (ratio * JavaScriptObject.focusElementTop());
    }
    private int focusElementBottom( final float ratio ) {
        return (int) (ratio * JavaScriptObject.focusElementBottom());
    }

}

似乎是我需要的,能否请您添加一个完整的样例?非常感谢您的工作! - vilicvane
我并不想发布整个项目。但是我提供的内容可以让你朝着完美的解决方案迈进一大步。你需要自己定义一个“JavaScriptObject”类,并将其作为js接口注入到你的webview中(请查看webview文档)。如果你正在编写一个全面使用webview的应用程序,那么很有可能你已经做到了这一点。在webview中添加JavaScript代码以监听焦点事件,并将关于焦点元素位置的数据传递到你的JavaScriptObject类中。 - BuvinJ

2

实际上,软键盘的外观似乎不会影响Activity,无论我选择什么windowSoftInputModeFullScreen模式下。

虽然我在这个属性上找不到太多文档,但我认为FullScreen模式是为游戏应用程序设计的,这些应用程序不需要过多使用软键盘。如果您的活动需要通过软键盘与用户进行交互,请重新考虑使用非全屏主题。您可以使用NoTitleBar主题关闭标题栏。为什么要隐藏通知栏呢?


2

我没有遇到任何问题...我也尝试了您的 XML 文件。这个也可以运行...我正在使用 2.2 版本的操作系统。 - Balaji Khadake
我只尝试了全屏模式...我在我的Nexus One和Nexus S上测试它...它可以工作。 - Balaji Khadake
1
我已经尝试了Galaxy S、HTC Wildfire、HTC Hero、Motorola Deify和Sony XPeria,但在任何一个设备上都无法运行。 - Vineet Shukla
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/3482/discussion-between-balaji-and-vineet-shukla - Balaji Khadake

1

基于https://dev59.com/XGs05IYBdhLWcg3wJurp#19494006,并希望实现它...

更新的想法


从多个答案中组合

相关代码:

        if (heightDifference > (usableHeightSansKeyboard / 4)) {

            // keyboard probably just became visible
            frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
            activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        } else {

            // keyboard probably just became hidden
            if(usableHeightPrevious != 0) {
                frameLayoutParams.height = usableHeightSansKeyboard;
                activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
                activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

            }

完整源代码请查看https://github.com/CrandellWS/AndroidBug5497Workaround/blob/master/AndroidBug5497Workaround.java

旧的想法

在打开键盘之前创建容器高度的静态值 当键盘打开时,将容器高度设置为usableHeightSansKeyboard - heightDifference,并在关闭键盘时将其恢复为保存的值

if (heightDifference > (usableHeightSansKeyboard / 4)) {
                // keyboard probably just became visible
                frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
                int mStatusHeight = getStatusBarHeight();
                frameLayoutParams.topMargin = mStatusHeight;
                ((MainActivity)activity).setMyMainHeight(usableHeightSansKeyboard - heightDifference);

                if(BuildConfig.DEBUG){
                    Log.v("aBug5497", "keyboard probably just became visible");
                }
            } else {
                // keyboard probably just became hidden
                if(usableHeightPrevious != 0) {
                    frameLayoutParams.height = usableHeightSansKeyboard;
                    ((MainActivity)activity).setMyMainHeight();    
                }
                frameLayoutParams.topMargin = 0;

                if(BuildConfig.DEBUG){
                    Log.v("aBug5497", "keyboard probably just became hidden");
                }
            }

MainActivity中的方法

public void setMyMainHeight(final int myMainHeight) {

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            ConstraintLayout.LayoutParams rLparams =  (ConstraintLayout.LayoutParams) myContainer.getLayoutParams();
            rLparams.height = myMainHeight;

            myContainer.setLayoutParams(rLparams);
        }

    });

}

int mainHeight = 0;
public void setMyMainHeight() {

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            ConstraintLayout.LayoutParams rLparams =  (ConstraintLayout.LayoutParams) myContainer.getLayoutParams();
            rLparams.height = mainHeight;

            myContainer.setLayoutParams(rLparams);
        }

    });

}

示例容器XML

<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
        <android.support.constraint.ConstraintLayout
            android:id="@+id/my_container"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintHeight_percent=".8">

类似地,如果需要,可以添加边距...

另一个考虑因素是使用填充,一个示例可以在以下网址找到:

https://github.com/mikepenz/MaterialDrawer/issues/95#issuecomment-80519589


1
private void resizeWindowOnKeyboardVisible() {
            RelativeLayout rootLayout;
            rootLayout = findViewById(R.id.rootLayout);
            this.getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    
                ViewGroup.LayoutParams layoutParams = rootLayout.getLayoutParams();
                int height ;
    
                @Override
                public void onGlobalLayout() {
                    Rect r = new Rect();
                    getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
                    int screenHeight = rootLayout.getContext().getResources().getDisplayMetrics().heightPixels;
                    int heightDiff = screenHeight - r.bottom;
    
                    if (heightDiff > screenHeight*0.15)
                    {
                        height = screenHeight - heightDiff;
                        layoutParams.height=height;
                        rootLayout.setLayoutParams(layoutParams);
                    }else{
                        height=ViewGroup.LayoutParams.MATCH_PARENT;
                        if( height!=layoutParams.height) {
                            layoutParams.height = height;
                            rootLayout.setLayoutParams(layoutParams);
                        }
                    }
                }
            });
        }

使用android:windowSoftInputMode="adjustResize|stateHidden"可能在某些情况下不起作用,而当您使用SYSTEM_UI_FLAG_FULLSCREEN标记时,android:fitsSystemWindows="true"也无法帮助。要使视图/窗口/ Webview 在键盘可见时可调整大小,请执行以下操作:

  • 使用RelativeLayout作为根布局。
  • 在活动中声明上述方法resizeWindowOnKeyboardVisible(),并在onCreate()方法中的setContentView()之后调用它。

它适用于Android 11(API 30)。


1

基于@Sdghasemi的解决方案,这是我的Kotlin代码,没有使用过时的insets.getSystemWindowInsetBottom()。此外,我添加了一个填充动画,使键盘打开更加平滑。

val rootLayout = findViewById<RelativeLayout>(R.id.your_root_layout)      
ViewCompat.setOnApplyWindowInsetsListener(rootLayout) { v, insets ->
    val animator = ValueAnimator.ofInt(0, insets.getInsets(WindowInsetsCompat.Type.ime()).bottom))
    animator.addUpdateListener { 
        valueAnimator -> v.setPadding(0, 0, 0, valueAnimator.animatedValue as? Int ?: 0) 
    }
    animator.duration = 200
    animator.start()
    insets
}

在您的ActivityonCreate()方法中调用它。 在我的情况下,这个片段比在AndroidManifest.xml中设置android:windowSoftInputMode="adjustPan"更有效。


请给我一个解决问题的方法,谢谢。 - Future Deep Gone

1

只需使用android:windowSoftInputMode="adjustResize|stateHidden",就像使用AdjustPan一样,它会禁用调整大小属性。


我也用过它...请确保你正在全屏模式下进行测试,以及你在哪个设备上测试? - Vineet Shukla
HTC NEXUS One,好的,我还没有添加全屏。 - Mohammed Azharuddin Shaikh
你可以使用getWindow().requestFeature(Window.FEATURE_NO_TITLE); onCreate()来代替使用主题吗? - Mohammed Azharuddin Shaikh
12
以上代码在不使用全屏的情况下工作正常,但添加全屏模式,无论是从xml还是从代码中添加,都无法正常工作。请仔细阅读问题。 - Vineet Shukla

1
我使用了Joseph Johnson创建的AndroidBug5497Workaround类,但在软键盘和视图之间出现了黑色空白。我参考了这个链接Greg Ennis。在对上述内容进行一些更改后,这是我的最终工作代码。
 public class SignUpActivity extends Activity {

 private RelativeLayout rlRootView; // this is my root layout
 private View rootView;
 private ViewGroup contentContainer;
 private ViewTreeObserver viewTreeObserver;
 private ViewTreeObserver.OnGlobalLayoutListener listener;
 private Rect contentAreaOfWindowBounds = new Rect();
 private FrameLayout.LayoutParams rootViewLayout;
 private int usableHeightPrevious = 0;

 private View mDecorView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_sign_up);
  mDecorView = getWindow().getDecorView();
  contentContainer =
   (ViewGroup) this.findViewById(android.R.id.content);

  listener = new OnGlobalLayoutListener() {
   @Override
   public void onGlobalLayout() {
    possiblyResizeChildOfContent();
   }
  };

  rootView = contentContainer.getChildAt(0);
  rootViewLayout = (FrameLayout.LayoutParams)
  rootView.getLayoutParams();

  rlRootView = (RelativeLayout) findViewById(R.id.rlRootView);


  rlRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
   @Override
   public void onGlobalLayout() {
    int heightDiff = rlRootView.getRootView().getHeight() - rlRootView.getHeight();
    if (heightDiff > Util.dpToPx(SignUpActivity.this, 200)) {
     // if more than 200 dp, it's probably a keyboard...
     //  Logger.info("Soft Key Board ", "Key board is open");

    } else {
     Logger.info("Soft Key Board ", "Key board is CLOSED");

     hideSystemUI();
    }
   }
  });
 }

 // This snippet hides the system bars.
 protected void hideSystemUI() {
  // Set the IMMERSIVE flag.
  // Set the content to appear under the system bars so that the 
  content
  // doesn't resize when the system bars hide and show.
  mDecorView.setSystemUiVisibility(
   View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
 }
 @Override
 protected void onPause() {
  super.onPause();
  if (viewTreeObserver.isAlive()) {
   viewTreeObserver.removeOnGlobalLayoutListener(listener);
  }
 }

 @Override
 protected void onResume() {
  super.onResume();
  if (viewTreeObserver == null || !viewTreeObserver.isAlive()) {
   viewTreeObserver = rootView.getViewTreeObserver();
  }
  viewTreeObserver.addOnGlobalLayoutListener(listener);
 }

 @Override
 protected void onDestroy() {
  super.onDestroy();
  rootView = null;
  contentContainer = null;
  viewTreeObserver = null;
 }
 private void possiblyResizeChildOfContent() {
  contentContainer.getWindowVisibleDisplayFrame(contentAreaOfWindowBounds);

  int usableHeightNow = contentAreaOfWindowBounds.height();

  if (usableHeightNow != usableHeightPrevious) {
   rootViewLayout.height = usableHeightNow;
   rootView.layout(contentAreaOfWindowBounds.left,
    contentAreaOfWindowBounds.top, contentAreaOfWindowBounds.right, contentAreaOfWindowBounds.bottom);
   rootView.requestLayout();

   usableHeightPrevious = usableHeightNow;
  } else {

   this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
  }
 }
}

0
感谢Joseph的回答。然而,在可能调整内容子项大小的方法possiblyResizeChildOfContent()中,部分代码


else {
            // keyboard probably just became hidden
            frameLayoutParams.height = usableHeightSansKeyboard;
        }

对我来说不起作用,因为视图的下部分被隐藏了。 所以我不得不使用一个全局变量restoreHeight,在构造函数中插入了最后一行。

restoreHeight = frameLayoutParams.height;

然后我用新的部分替换了前面提到的部分

else {
            // keyboard probably just became hidden
            frameLayoutParams.height = restoreHeight;
        }

但我不知道为什么你的代码对我不起作用。如果有人能给予解答,那将是非常有帮助的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接