仮想キーボードが表示されているかどうかに基づいてレイアウトを変更したいと思います。API とさまざまなブログを検索しましたが、役立つものが見つからないようです。
出来ますか?
ありがとう!
仮想キーボードが表示されているかどうかに基づいてレイアウトを変更したいと思います。API とさまざまなブログを検索しましたが、役立つものが見つからないようです。
出来ますか?
ありがとう!
これが可能になりました:
Android 11では、次のことができます
view.setWindowInsetsAnimationCallback(object : WindowInsetsAnimation.Callback {
override fun onEnd(animation: WindowInsetsAnimation) {
super.onEnd(animation)
val showingKeyboard = view.rootWindowInsets.isVisible(WindowInsets.Type.ime())
// now use the boolean for something
}
})
キーボードの表示/非表示のアニメーションを聞いて、対応するトランジションを実行することもできます。
Android 11 プレビューと対応するドキュメントを読むことをお勧めします
Android 11 より前
ただし、この作品はCompat
バージョンで公開されていないため、ハックに頼る必要があります。
ウィンドウのインセットを取得できます。下部のインセットが (実験により) 妥当であると判断された値よりも大きい場合は、キーボードが表示されていると見なすことができます。これは素晴らしいことではなく、場合によっては失敗する可能性がありますが、それをサポートするフレームワークはありません。
これは、この正確な質問https://stackoverflow.com/a/36259261/372076に対する良い答えです。または、Android 11 以前でこれを実現するためのいくつかの異なるアプローチを提供するページを次に示します。
このソリューションはソフト キーボードでは機能
onConfigurationChanged
せず、ソフト (仮想) キーボードでは呼び出されません。
構成の変更を自分で処理する必要があります。
http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange
サンプル:
// from the link above
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks whether a hardware keyboard is available
if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
Toast.makeText(this, "keyboard visible", Toast.LENGTH_SHORT).show();
} else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
Toast.makeText(this, "keyboard hidden", Toast.LENGTH_SHORT).show();
}
}
次に、いくつかのビューの可視性を変更し、フィールドを更新し、レイアウト ファイルを変更します。
これは、最も効果的なソリューションではない可能性があります。しかし、これは毎回うまくいきました...ソフトキーボードを聞く必要があるときはいつでもこの関数を呼び出します。
boolean isOpened = false;
public void setListenerToRootView() {
final View activityRootView = getWindow().getDecorView().findViewById(android.R.id.content);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
if (heightDiff > 100) { // 99% of the time the height diff will be due to a keyboard.
Toast.makeText(getApplicationContext(), "Gotcha!!! softKeyboardup", 0).show();
if (isOpened == false) {
//Do two things, make the view top visible and the editText smaller
}
isOpened = true;
} else if (isOpened == true) {
Toast.makeText(getApplicationContext(), "softkeyborad Down!!!", 0).show();
isOpened = false;
}
}
});
}
注: このアプローチは、ユーザーがフローティング キーボードを使用している場合に問題を引き起こします。
アクティビティから IMM (仮想) キーボード ウィンドウの表示/非表示を処理する場合は、レイアウトをサブクラス化し、onMesure メソッドをオーバーライドする必要があります (レイアウトの測定幅と測定高を判断できるようにするため)。その後、setContentView() によってサブクラス化されたレイアウトをアクティビティのメイン ビューとして設定します。これで、IMM のウィンドウの表示/非表示イベントを処理できるようになります。複雑に聞こえるかもしれませんが、実際にはそうではありません。コードは次のとおりです。
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<EditText
android:id="@+id/SearchText"
android:text=""
android:inputType="text"
android:layout_width="fill_parent"
android:layout_height="34dip"
android:singleLine="True"
/>
<Button
android:id="@+id/Search"
android:layout_width="60dip"
android:layout_height="34dip"
android:gravity = "center"
/>
</LinearLayout>
アクティビティ内で、レイアウトのサブクラスを宣言します(main.xml)
public class MainSearchLayout extends LinearLayout {
public MainSearchLayout(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.main, this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d("Search Layout", "Handling Keyboard Window shown");
final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
final int actualHeight = getHeight();
if (actualHeight > proposedheight){
// Keyboard is shown
} else {
// Keyboard is hidden
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
コードからわかるように、サブクラス コンストラクターで Activity のレイアウトをインフレートします。
inflater.inflate(R.layout.main, this);
次に、Activity のサブクラス化されたレイアウトのコンテンツ ビューを設定します。
public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainSearchLayout searchLayout = new MainSearchLayout(this, null);
setContentView(searchLayout);
}
// rest of the Activity code and subclassed layout...
}
@amalBit の回答のように、リスナーをグローバル レイアウトに登録し、dectorView の表示されている底とその提案された底の差を計算します。差がある値 (推定された IME の高さ) よりも大きい場合、IME が稼働していると考えます。
final EditText edit = (EditText) findViewById(R.id.edittext);
edit.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (keyboardShown(edit.getRootView())) {
Log.d("keyboard", "keyboard UP");
} else {
Log.d("keyboard", "keyboard Down");
}
}
});
private boolean keyboardShown(View rootView) {
final int softKeyboardHeight = 100;
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
int heightDiff = rootView.getBottom() - r.bottom;
return heightDiff > softKeyboardHeight * dm.density;
}
高さのしきい値 100 は、推定される IME の最小の高さです。
これは、adjustPan とadjustResize の両方で機能します。
Nebojsa Tomcic のコードに基づいて、次の RelativeLayout-Subclass を開発しました。
import java.util.ArrayList;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
public class KeyboardDetectorRelativeLayout extends RelativeLayout {
public interface IKeyboardChanged {
void onKeyboardShown();
void onKeyboardHidden();
}
private ArrayList<IKeyboardChanged> keyboardListener = new ArrayList<IKeyboardChanged>();
public KeyboardDetectorRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public KeyboardDetectorRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public KeyboardDetectorRelativeLayout(Context context) {
super(context);
}
public void addKeyboardStateChangedListener(IKeyboardChanged listener) {
keyboardListener.add(listener);
}
public void removeKeyboardStateChangedListener(IKeyboardChanged listener) {
keyboardListener.remove(listener);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
final int actualHeight = getHeight();
if (actualHeight > proposedheight) {
notifyKeyboardShown();
} else if (actualHeight < proposedheight) {
notifyKeyboardHidden();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void notifyKeyboardHidden() {
for (IKeyboardChanged listener : keyboardListener) {
listener.onKeyboardHidden();
}
}
private void notifyKeyboardShown() {
for (IKeyboardChanged listener : keyboardListener) {
listener.onKeyboardShown();
}
}
}
これは非常にうまく機能します...アクティビティのソフト入力モードが「WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE」に設定されている場合にのみ、このソリューションが機能することをマークしてください
Nebojsaのソリューションはほとんどうまくいきました。複数行の EditText 内をクリックすると、キーボードが表示されていることがわかりましたが、EditText 内で入力を開始すると、actualHeight と proposalHeight は同じままだったので、キーボードがまだ表示されていることを知りませんでした。最大の高さを保存するためにわずかな変更を加えましたが、正常に動作します。改訂されたサブクラスは次のとおりです。
public class CheckinLayout extends RelativeLayout {
private int largestHeight;
public CheckinLayout(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.checkin, this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
largestHeight = Math.max(largestHeight, getHeight());
if (largestHeight > proposedheight)
// Keyboard is shown
else
// Keyboard is hidden
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
誰かがこれを投稿したかどうかはわかりません。このソリューションは使いやすいことがわかりました。. SoftKeyboardクラスは gist.github.com にあります。しかし、キーボードの popup/hide イベント コールバック中に、UI で適切に処理するためのハンドラーが必要です。
/*
Somewhere else in your code
*/
RelativeLayout mainLayout = findViewById(R.layout.main_layout); // You must use your root layout
InputMethodManager im = (InputMethodManager) getSystemService(Service.INPUT_METHOD_SERVICE);
/*
Instantiate and pass a callback
*/
SoftKeyboard softKeyboard;
softKeyboard = new SoftKeyboard(mainLayout, im);
softKeyboard.setSoftKeyboardCallback(new SoftKeyboard.SoftKeyboardChanged()
{
@Override
public void onSoftKeyboardHide()
{
// Code here
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// Code here will run in UI thread
...
}
});
}
@Override
public void onSoftKeyboardShow()
{
// Code here
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// Code here will run in UI thread
...
}
});
}
});
これを行うには、一種のハックがあります。ソフト キーボードが表示または非表示になったことを検出する方法はないようですが、実際には、聞いOnFocusChangeListener
ているにを設定することで、表示または非表示になるタイミングを検出できEditText
ます。
EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
{
@Override
public void onFocusChange(View view, boolean hasFocus)
{
//hasFocus tells us whether soft keyboard is about to show
}
});
注:このハックで注意すべきことの 1 つは、このコールバックが がEditText
フォーカスを獲得または失ったときにすぐに起動されることです。これは、ソフト キーボードが表示または非表示になる直前に実際に起動します。キーボードが表示または非表示になった後に何かを行うために私が見つけた最良の方法は、 a を使用してHandler
、次のように約 400 ミリ秒遅らせることです。
EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
{
@Override
public void onFocusChange(View view, boolean hasFocus)
{
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
//do work here
}
}, 400);
}
});
androidx.core 1.5.0 がリリースされたため、これは、Android 11 以前のデバイスでキーボードの表示/非表示イベントをリッスンするために行うことです。
グレード:
implementation "androidx.core:core-ktx:1.5.0"
断片:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val view = activity?.window?.decorView ?: return
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
val showingKeyboard = insets.isVisible(WindowInsetsCompat.Type.ime())
if(showingKeyboard){
//do something
}
insets
}
}
ビューが破棄されたときにリスナーを削除して、メモリリークを回避してください。このソリューションは、ソフトウェア入力モードが の場合にのみ機能しますadjustResize
。 setOnApplyWindowInsetsListener はadjustPan
、 の場合はトリガーされませんadjustPan
。
ドキュメントによると、
* When running on devices with API Level 29 and before, the returned value is an
* approximation based on the information available. This is especially true for the {@link
* Type#ime IME} type, which currently only works when running on devices with SDK level 23
* and above.
*
insets.isVisible(ime) は、SDK レベルが 23 を超えるデバイスでのみ動作する必要があります
Sander さん、ソフト キーボードによってブロックされたビューを表示しようとしていると思います。このhttp://android-developers.blogspot.com/2009/04/updating-applications-for-on-screen.htmlを試してください。
1行のテキストビューのバックコーディングの問題を解決しました。
package com.helpingdoc;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
public class MainSearchLayout extends LinearLayout {
int hieght = 0;
public MainSearchLayout(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.main, this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d("Search Layout", "Handling Keyboard Window shown");
if(getHeight()>hieght){
hieght = getHeight();
}
final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
final int actualHeight = getHeight();
System.out.println("....hieght = "+ hieght);
System.out.println("....actualhieght = "+ actualHeight);
System.out.println("....proposedheight = "+ proposedheight);
if (actualHeight > proposedheight){
// Keyboard is shown
} else if(actualHeight<proposedheight){
// Keyboard is hidden
}
if(proposedheight == hieght){
// Keyboard is hidden
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
最初の DecorView の子の下部のパディングを確認することもできます。キーボードが表示されている場合、ゼロ以外の値に設定されます。
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
View view = getRootView();
if (view != null && (view = ((ViewGroup) view).getChildAt(0)) != null) {
setKeyboardVisible(view.getPaddingBottom() > 0);
}
super.onLayout(changed, left, top, right, bottom);
}
@Filipkowicz の上記の回答は、Android API < 30 で正常に機能します。Android API 30 以降では、 を使用する必要がありますsetWindowInsetsAnimationCallback
。したがって、以下の回答では、API 21 - 30 を機能させるために両方の方法を組み合わせています。
private fun isKeyboardVisible(insets: WindowInsets): Boolean {
val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets)
val systemWindow = insetsCompat.systemWindowInsets
val rootStable = insetsCompat.stableInsets
if (systemWindow.bottom > rootStable.bottom) {
// This handles the adjustResize case on < API 30, since
// systemWindow.bottom is probably going to be the IME
return true
}
return false
}
@JvmStatic
@BindingAdapter("goneWhenKeyboardVisible")
fun View.goneWhenKeyboardVisible() {
if (isRPlus()) {
setWindowInsetsAnimationCallback(object :
WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
override fun onProgress(
insets: WindowInsets,
runningAnimations: MutableList<WindowInsetsAnimation>
): WindowInsets {
return insets
}
override fun onStart(
animation: WindowInsetsAnimation,
bounds: WindowInsetsAnimation.Bounds
): WindowInsetsAnimation.Bounds {
if (isVisible)
isVisible = !rootWindowInsets.isVisible(WindowInsets.Type.ime())
return super.onStart(animation, bounds)
}
override fun onEnd(animation: WindowInsetsAnimation) {
super.onEnd(animation)
if (!isVisible)
isVisible = !rootWindowInsets.isVisible(WindowInsets.Type.ime())
}
})
} else {
setOnApplyWindowInsetsListener { _, insets ->
isVisible = !isKeyboardVisible(insets)
insets
}
}
}
Nebojsa Tomcic の回答は役に立ちませんでした。私はそれRelativeLayout
と一緒TextView
に持ってAutoCompleteTextView
います。TextView
キーボードが表示されているときと非表示になっているときは、一番下までスクロールする必要があります。これを達成するために、onLayout
メソッドをオーバーライドしましたが、うまく機能します。
public class ExtendedLayout extends RelativeLayout
{
public ExtendedLayout(Context context, AttributeSet attributeSet)
{
super(context, attributeSet);
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.main, this);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
super.onLayout(changed, l, t, r, b);
if (changed)
{
int scrollEnd = (textView.getLineCount() - textView.getHeight() /
textView.getLineHeight()) * textView.getLineHeight();
textView.scrollTo(0, scrollEnd);
}
}
}