Skip to content

PopupWindow viene ritagliato sulla tastiera personalizzata per Android API 28

Potrebbe capitare di trovare un bug nel codice o nel progetto, ricordarsi di testare sempre in un ambiente di test prima di caricare il codice nel progetto finale.

Soluzione:

Aggiornato per mostrare un approccio più personalizzato.
Aggiornato per lavorare con windowSoftInputMode="adjustResize".

Sembra che il ritaglio al di fuori delle finestre possa essere un nuovo fatto della vita di Android, anche se non ho trovato documentazione in merito. In ogni caso, il metodo seguente potrebbe essere il metodo preferito ed è, credo, standard anche se non molto ben documentato.

Di seguito, MyInputMethodService istanzia una tastiera con otto tasti in basso e una striscia di visualizzazione vuota in alto, dove vengono visualizzati i popup per la fila di tasti superiore. Quando si preme un tasto, il valore del tasto viene mostrato in una finestra a comparsa sopra il tasto per la durata della pressione del tasto. Poiché la vista vuota sopra i tasti racchiude i popup, il clipping non si verifica. (Non è una tastiera molto utile, ma rende l'idea).

enter image description here

Il pulsante e il "testo basso" EditText sono sotto la striscia di vista superiore. Invocazione di onComputeInsets() consente di toccare i tasti della tastiera, ma non consente di toccare la tastiera nell'area vuota coperta dall'inserto. In quest'area, i tocchi vengono trasmessi alle viste sottostanti, in questo caso il "Testo basso". EditText e una vista Button che visualizza "OK!" quando viene cliccato.

"Gboard" sembra funzionare in modo simile, ma utilizza una sorella FrameLayout per visualizzare i popup con la traduzione. Ecco come appare un popup "4" nel Layout Inspector per "Gboard".

enter image description here

MioServizioMetodoInput

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
Il View è definito solo per dare ai popup un posto in cui espandersi e non ha altri scopi.



    

    

popup.xml
Solo il popup.



    


attività_principale



    

    

Un'idea generale per mostrare le viste a comparsa è quella di crearle usando WindowManager che non ha le limitazioni di PopupWindow.

Suppongo che l'opzione InputMethodService sia responsabile di mostrare la vista a comparsa.
Poiché per mostrare una finestra di questo tipo è necessario ottenere il permesso di sovrapposizione nell'API 23 e successive, è necessario creare una finestra temporanea Activity che lo faccia per noi. Il risultato dell'ottenimento dei permessi sarà consegnato al file InputMethodService utilizzando un EventBus . È possibile controllare i permessi in sovrimpressione quando si vuole, a seconda dell'architettura (ad esempio ogni volta che la tastiera si alza).

Ecco un'implementazione di questa idea che potrebbe richiedere alcune manipolazioni per funzionare esattamente come si desidera. Spero che sia d'aiuto.

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'
}

L'ho risolto con LatinIME (AOSP) come:

  • il mio file xml del layout della vista di input è



    

  • copiare la funzione "updateSoftInputWindowLayoutParameters" da LatinIME.java
    private void updateSoftInputWindowLayoutParameters() {
        // Override layout parameters to expand {@link SoftInputWindow} to the entire screen.
        // See {@link InputMethodService#setinputView(View)} and
        // {@link SoftInputWindow#updateWidthHeight(WindowManager.LayoutParams)}.
        final Window window = getWindow().getWindow();
        ViewLayoutUtils.updateLayoutHeightOf(window, LayoutParams.MATCH_PARENT);
        // This method may be called before {@link #setInputView(View)}.
        if (mInputView != null) {
            // In non-fullscreen mode, {@link InputView} and its parent inputArea should expand to
            // the entire screen and be placed at the bottom of {@link SoftInputWindow}.
            // In fullscreen mode, these shouldn't expand to the entire screen and should be
            // coexistent with {@link #mExtractedArea} above.
            // See {@link InputMethodService#setInputView(View) and
            // com.android.internal.R.layout.input_method.xml.
            final int layoutHeight = isFullscreenMode()
                    ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT;
            final View inputArea = window.findViewById(android.R.id.inputArea);
            ViewLayoutUtils.updateLayoutHeightOf(inputArea, layoutHeight);
            ViewLayoutUtils.updateLayoutGravityOf(inputArea, Gravity.BOTTOM);
            ViewLayoutUtils.updateLayoutHeightOf(mInputView, layoutHeight);
        }
    }
  • Sovrascrivere la funzione: "updateFullscreenMode", "setInputView", "onComputeInsets" e copiare il codice da LatinIME.java - infine modificare il codice come segue
    private View mInputView;
    private InsetsUpdater mInsetsUpdater;

    ...

    @Override
    public void onStartInputView(EditorInfo info, boolean restarting) {

        ...

        updateFullscreenMode();
        super.onStartInputView(info, restarting);
    }

    @Override
    public void updateFullscreenMode() {
        super.updateFullscreenMode();
        updateSoftInputWindowLayoutParameters();
    }

    @Override
    public void setInputView(final View view) {
        super.setInputView(view);
        mInputView = view;
        mInsetsUpdater = ViewOutlineProviderCompatUtils.setInsetsOutlineProvider(view);
        updateSoftInputWindowLayoutParameters();
        //mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view);
        //if (hasSuggestionStripView()) {
        //    mSuggestionStripView.setListener(this, view);
        //}
    }

    @Override
    public void onComputeInsets(final InputMethodService.Insets outInsets) {
        super.onComputeInsets(outInsets);
        // This method may be called before {@link #setInputView(View)}.
        if (mInputView == null) {
            return;
        }
        //final SettingsValues settingsValues = mSettings.getCurrent();
        //final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView();
        final View visibleKeyboardView = mInputView.findViewById(R.id.input_view);
        //if (visibleKeyboardView == null || !hasSuggestionStripView()) {
        //    return;
        //}
        final int inputHeight = mInputView.getHeight();
        //if (isImeSuppressedByHardwareKeyboard() && !visibleKeyboardView.isShown()) {
        //    // If there is a hardware keyboard and a visible software keyboard view has been hidden,
        //    // no visual element will be shown on the screen.
        //    outInsets.contentTopInsets = inputHeight;
        //    outInsets.visibleTopInsets = inputHeight;
        //    mInsetsUpdater.setInsets(outInsets);
        //    return;
        //}
        //final int suggestionsHeight = (!mKeyboardSwitcher.isShowingEmojiPalettes()
        //        && mSuggestionStripView.getVisibility() == View.VISIBLE)
        //        ? mSuggestionStripView.getHeight() : 0;
        final int visibleTopY = inputHeight - visibleKeyboardView.getHeight();// - suggestionsHeight;
        //mSuggestionStripView.setMoreSuggestionsHeight(visibleTopY);
        // Need to set expanded touchable region only if a keyboard view is being shown.
        if (visibleKeyboardView.isShown()) {
            final int touchLeft = 0;
            //final int touchTop = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY;
            final int touchTop = visibleTopY;
            final int touchRight = visibleKeyboardView.getWidth();
            final int touchBottom = inputHeight;
            outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
            outInsets.touchableRegion.set(touchLeft, touchTop, touchRight, touchBottom);
            Log.i(TAG, "onComputeInsets: left=" + touchLeft + ", top=" + touchTop + ", right=" + touchRight + ", bottom=" + touchBottom);
        }
        Log.i(TAG, "onComputeInsets: visibleTopY=" + visibleTopY);
        outInsets.contentTopInsets = visibleTopY;
        outInsets.visibleTopInsets = visibleTopY;
        mInsetsUpdater.setInsets(outInsets);
    }
  • copiare i file "ViewLayoutUtils.java", "ViewOutlineProviderCompatUtils.java", "ViewOutlineProviderCompatUtilsLXX.java" dal pacchetto LatinIME(AOSP) e modificarne il nome

Se accetti, hai la possibilità di lasciare una divisione su cosa aggiungeresti a questa notizia.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.