PopupWindow зарезал пользовательскую клавиатуру для Android API 28 - PullRequest
0 голосов
/ 22 октября 2018

Я сделал пользовательскую клавиатуру.Когда вы долго нажимаете клавишу, PopupWindow показывает некоторые дополнительные варианты над клавишей.Проблема в том, что в API 28 это всплывающее окно обрезается (или даже полностью скрывается для верхнего ряда).

enter image description here

Я решилэта проблема для API <28 </a> с

popupWindow.setClippingEnabled(false);

Однако с API 28 проблема вернулась.Вот еще код:

private void layoutAndShowPopupWindow(Key key, int xPosition) {
    popupWindow = new PopupWindow(popupView,
            LinearLayout.LayoutParams.WRAP_CONTENT,
            LinearLayout.LayoutParams.WRAP_CONTENT);
    popupWindow.setClippingEnabled(false);
    int location[] = new int[2];
    key.getLocationInWindow(location);
    int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    popupView.measure(measureSpec, measureSpec);
    int popupWidth = popupView.getMeasuredWidth();
    int spaceAboveKey = key.getHeight() / 4;
    int x = xPosition - popupWidth / popupView.getChildCount() / 2;
    int screenWidth = getScreenWidth();
    if (x < 0) {
        x = 0;
    } else if (x + popupWidth > screenWidth) {
        x = screenWidth - popupWidth;
    }
    int y = location[1] - popupView.getMeasuredHeight() - spaceAboveKey;
    popupWindow.showAtLocation(key, Gravity.NO_GRAVITY, x, y);
}

Что-то случилось, что больше не позволяло сторонним клавиатурам показывать контент вне вида клавиатуры?(Вот как это происходит в iOS.)

Что мне нужно сделать, чтобы PopupWindow перестал обрезаться?

Ответы [ 2 ]

0 голосов
/ 15 ноября 2018

Обновлено, чтобы показать более индивидуальный подход.Обновлено для работы с windowSoftInputMode="adjustResize".

Похоже, отсечение за пределами окон может быть новым фактом жизни Android, хотя я не нашел документации на этот счет.Несмотря на это, следующий метод может быть предпочтительным путем и, я полагаю, является стандартным, хотя и не очень хорошо задокументированным.

В следующем примере MyInputMethodService создает клавиатуру с восемью клавишами внизу ипустая полоса просмотра выше, где отображаются всплывающие окна для верхнего ряда клавиш.Когда клавиша нажата, значение клавиши отображается во всплывающем окне над клавишей на время нажатия клавиши.Поскольку пустое представление над клавишами закрывает всплывающие окна, отсечение не происходит.(Не очень полезная клавиатура, но она имеет смысл.)

enter image description here

Кнопка и «Низкий текст» EditText находятся под видом сверхуполоса.Вызов onComputeInsets() разрешает касания клавиш клавиатуры, но запрещает касания клавиатуры в пустой области, закрытой вставкой.В этой области прикосновения передаются к базовым представлениям - здесь «Низкий текст» EditText и Button, которые отображают «ОК!»при нажатии.

«Gboard», похоже, работает аналогичным образом, но использует сестру FrameLayout для отображения всплывающих окон с переводом.Вот как выглядит всплывающее окно «4» в Инспекторе макетов для «Gboard».

enter image description here

MyInputMethodService

public class MyInputMethodService extends InputMethodService
    implements View.OnTouchListener {
    private View mTopKey;
    private PopupWindow mPopupWindow;
    private View mPopupView;

    @Override
    public View onCreateInputView() {
        final ConstraintLayout keyboardView = (ConstraintLayout) getLayoutInflater().inflate(R.layout.keyboard, null);
        mTopKey = keyboardView.findViewById(R.id.a);
        mTopKey.setOnTouchListener(this);
        keyboardView.findViewById(R.id.b).setOnTouchListener(this);
        keyboardView.findViewById(R.id.c).setOnTouchListener(this);
        keyboardView.findViewById(R.id.d).setOnTouchListener(this);
        keyboardView.findViewById(R.id.e).setOnTouchListener(this);
        keyboardView.findViewById(R.id.f).setOnTouchListener(this);
        keyboardView.findViewById(R.id.g).setOnTouchListener(this);
        keyboardView.findViewById(R.id.h).setOnTouchListener(this);

        mPopupView = getLayoutInflater().inflate(R.layout.popup, keyboardView, false);
        int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        mPopupView.measure(measureSpec, measureSpec);
        mPopupWindow = new PopupWindow(mPopupView, ViewGroup.LayoutParams.WRAP_CONTENT,
                                       ViewGroup.LayoutParams.WRAP_CONTENT);

        return keyboardView;
    }

    @Override
    public void onComputeInsets(InputMethodService.Insets outInsets) {
        // Do the standard stuff.
        super.onComputeInsets(outInsets);

        // Only the keyboard are with the keys is touchable. The rest should pass touches
        // through to the views behind. contentTopInsets set to play nice with windowSoftInputMode
        // defined in the manifest.
        outInsets.visibleTopInsets = mTopKey.getTop();
        outInsets.contentTopInsets = mTopKey.getTop();
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                layoutAndShowPopupWindow((TextView) v);
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mPopupWindow.dismiss();
                break;
        }
        return true;
    }

    private void layoutAndShowPopupWindow(TextView key) {
        ((TextView) mPopupView.findViewById(R.id.popupKey)).setText(key.getText());
        int x = key.getLeft() + (key.getWidth() - mPopupView.getMeasuredWidth()) / 2;
        int y = key.getTop() - mPopupView.getMeasuredHeight();
        mPopupWindow.showAtLocation(key, Gravity.NO_GRAVITY, x, y);
    }
}

keyboard.xml
View определяется исключительно для того, чтобы дать всплывающим окнам возможность расширяться, и не имеет никакой другой цели.

<android.support.constraint.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toTopOf="@+id/a" />

    <Button
        android:id="@+id/a"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="A"
        app:layout_constraintBottom_toTopOf="@+id/e"
        app:layout_constraintEnd_toStartOf="@+id/b"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/b"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="B"
        app:layout_constraintBottom_toTopOf="@+id/f"
        app:layout_constraintEnd_toStartOf="@+id/c"
        app:layout_constraintStart_toEndOf="@+id/a" />

    <Button
        android:id="@+id/c"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="C"
        app:layout_constraintBottom_toTopOf="@+id/g"
        app:layout_constraintEnd_toStartOf="@+id/d"
        app:layout_constraintStart_toEndOf="@+id/b" />

    <Button
        android:id="@+id/d"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="D"
        app:layout_constraintBottom_toTopOf="@+id/h"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/c" />

    <Button
        android:id="@+id/e"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="E"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/f"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/f"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="F"
        app:layout_constraintEnd_toStartOf="@+id/g"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/e"
        app:layout_constraintTop_toTopOf="@+id/e" />

    <Button
        android:id="@+id/g"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="G"
        app:layout_constraintEnd_toStartOf="@+id/h"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/f"
        app:layout_constraintTop_toTopOf="@+id/e" />

    <Button
        android:id="@+id/h"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="H"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/g"
        app:layout_constraintTop_toTopOf="@+id/g" />
</android.support.constraint.ConstraintLayout>

popup.xml
Просто всплывающее окно.

<LinearLayout 
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@android:color/black"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="3dp">

    <TextView
        android:id="@+id/popupKey"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:text="A"
        android:textColor="@android:color/white" />

</LinearLayout>

activity_main

<android.support.constraint.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="High text"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="20dp"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <EditText
        android:id="@+id/editText"
        android:layout_width="133dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:ems="10"
        android:inputType="textPersonName"
        android:hint="Low text"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/button" />

</android.support.constraint.ConstraintLayout>
0 голосов
/ 15 ноября 2018

Общая идея показа всплывающих окон заключается в создании их с использованием WindowManager, который не имеет ограничений PopupWindow.

Я предполагаю, что InputMethodService отвечает за отображение всплывающего окна.Поскольку для отображения такого окна необходимо получить разрешение наложения в API 23 и выше, нам нужно сделать временную Activity, чтобы сделать это для нас.Результат получения разрешения будет доставлен на InputMethodService с использованием события EventBus.Вы можете проверить разрешение наложения в соответствии с архитектурой (например, каждый раз, когда клавиатура поднимается).

Вот реализация этой идеи, которая может потребовать некоторых манипуляций, чтобы работать именно так, как вам нужно.Надеюсь, это поможет.

MyInputMethodService.java

import android.content.Intent;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.provider.Settings;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

public class MyInputMethodService extends InputMethodService {

    private FloatViewManager mFloatViewManager;

    @Override
    public void onCreate() {
        super.onCreate();

        EventBus.getDefault().register(this);
        checkDrawOverlayPermission();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        EventBus.getDefault().unregister(this);
    }

    private boolean checkDrawOverlayPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
            Intent intent = new Intent(this, CheckPermissionActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
            return false;
        } else {
            return true;
        }
    }

    private void showPopup(Key key, int xPosition){
        mFloatViewManager = new FloatViewManager(this);
        if (checkDrawOverlayPermission()) {
            mFloatViewManager.showFloatView(key, xPosition);
        }
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(CanDrawOverlaysEvent event) {
        if (event.isAllowed()) {
            mFloatViewManager.showFloatView(key, xPosition);
        } else {
            // Maybe show an error
        }
    }

}

FloatViewManager.java

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;

import static android.content.Context.WINDOW_SERVICE;


public class FloatViewManager {

    private WindowManager mWindowManager;
    private View mFloatView;
    private WindowManager.LayoutParams mFloatViewLayoutParams;

    @SuppressLint("InflateParams")
    public FloatViewManager(Context context) {
        mWindowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE);
        LayoutInflater inflater = LayoutInflater.from(context);
        mFloatView = inflater.inflate(R.layout.float_view_layout, null);

        // --------- do initializations:
        TextView textView = mFloatView.findViewById(R.id.textView);
        // ...
        // ---------

        mFloatViewLayoutParams = new WindowManager.LayoutParams();
        mFloatViewLayoutParams.format = PixelFormat.TRANSLUCENT;
        mFloatViewLayoutParams.flags = WindowManager.LayoutParams.FORMAT_CHANGED;

        mFloatViewLayoutParams.type = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
                : WindowManager.LayoutParams.TYPE_PHONE;

        mFloatViewLayoutParams.gravity = Gravity.NO_GRAVITY;
        mFloatViewLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mFloatViewLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    }

    public void dismissFloatView() {
        mWindowManager.removeViewImmediate(mFloatView);
    }

    public void showFloatView(Key key, int xPosition) {

        // calculate x and y position as you did instead of 0
        mFloatViewLayoutParams.x = 0;
        mFloatViewLayoutParams.y = 0;

        mWindowManager.addView(mFloatView, mFloatViewLayoutParams);
        mWindowManager.updateViewLayout(mFloatView, mFloatViewLayoutParams);
    }

}

CheckPermissionActivity.java

import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import org.greenrobot.eventbus.EventBus;

public class CheckPermissionActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_DRAW_OVERLAY_PERMISSION = 5;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, REQUEST_CODE_DRAW_OVERLAY_PERMISSION);
        } else {
            finish();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == REQUEST_CODE_DRAW_OVERLAY_PERMISSION) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) {
                EventBus.getDefault().post(new CanDrawOverlaysEvent(true));
            } else {
                EventBus.getDefault().post(new CanDrawOverlaysEvent(false));
            }
            finish();
        }
    }

}

CanDrawOverlaysEvent.java

public class CanDrawOverlaysEvent {

    private boolean mIsAllowed;

    public CanDrawOverlaysEvent(boolean isAllowed) {
        mIsAllowed = isAllowed;
    }

    public boolean isAllowed() {
        return mIsAllowed;
    }

}

build.gradle

dependencies {
    implementation 'org.greenrobot:eventbus:3.1.1'
}
...