public final class

EmojiEditTextHelper

extends java.lang.Object

 java.lang.Object

↳androidx.emoji2.viewsintegration.EmojiEditTextHelper

Gradle dependencies

compile group: 'androidx.emoji2', name: 'emoji2-views-helper', version: '1.2.0-alpha04'

  • groupId: androidx.emoji2
  • artifactId: emoji2-views-helper
  • version: 1.2.0-alpha04

Artifact androidx.emoji2:emoji2-views-helper:1.2.0-alpha04 it located at Google repository (https://maven.google.com/)

Overview

Utility class to enhance custom EditText widgets with EmojiCompat.

 public class MyEmojiEditText extends EditText {
      public MyEmojiEditText(Context context) {
          super(context);
          init();
      }
      // ...
      private void init() {
          super.setKeyListener(getEmojiEditTextHelper().getKeyListener(getKeyListener()));
      }

       @Override
      public void setKeyListener(android.text.method.KeyListener keyListener) {
          super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener));
      }

       @Override
      public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
          InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
          return getEmojiEditTextHelper().onCreateInputConnection(inputConnection, outAttrs);
      }

      private EmojiEditTextHelper getEmojiEditTextHelper() {
          if (mEmojiEditTextHelper == null) {
              mEmojiEditTextHelper = new EmojiEditTextHelper(this);
          }
          return mEmojiEditTextHelper;
      }
 }
 

Summary

Constructors
publicEmojiEditTextHelper(EditText editText)

Default constructor.

publicEmojiEditTextHelper(EditText editText, boolean expectInitializedEmojiCompat)

Allows skipping of all processing until EmojiCompat.init is called.

Methods
public intgetEmojiReplaceStrategy()

Returns whether to replace all emoji with EmojiSpans.

public KeyListenergetKeyListener(KeyListener keyListener)

Attaches EmojiCompat KeyListener to the widget.

public intgetMaxEmojiCount()

Returns the maximum number of EmojiSpans to be added to a CharSequence.

public booleanisEnabled()

If this helper should add new EmojiSpans to display emoji using the emoji font when text changes.

public InputConnectiononCreateInputConnection(InputConnection inputConnection, EditorInfo outAttrs)

Updates the InputConnection with emoji support.

public voidsetEmojiReplaceStrategy(int replaceStrategy)

Sets whether to replace all emoji with EmojiSpans.

public voidsetEnabled(boolean isEnabled)

When helper is enabled, it will process text changes to add appropriate EmojiSpans for display.

public voidsetMaxEmojiCount(int maxEmojiCount)

Set the maximum number of EmojiSpans to be added to a CharSequence.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public EmojiEditTextHelper(EditText editText)

Default constructor.

Parameters:

editText: EditText instance

public EmojiEditTextHelper(EditText editText, boolean expectInitializedEmojiCompat)

Allows skipping of all processing until EmojiCompat.init is called. This is useful when integrating EmojiTextViewHelper into libraries that subclass TextView that do not have control over EmojiCompat initialization by the app that uses the TextView subclass. If this helper is initialized prior to EmojiCompat.init, the TextView it's configuring will not display emoji using EmojiCompat after init is called until the transformation method and filter are updated. The easiest way to do that is call EmojiEditTextHelper.setEnabled(boolean).

Parameters:

editText: EditText instance
expectInitializedEmojiCompat: if true, this helper will assume init has been called and throw if it has not. If false, the methods on this helper will have no effect until EmojiCompat.init is called.

Methods

public void setMaxEmojiCount(int maxEmojiCount)

Set the maximum number of EmojiSpans to be added to a CharSequence. The number of spans in a CharSequence affects the performance of the EditText insert/delete operations. Insert/delete operations slow down as the number of spans increases.

Parameters:

maxEmojiCount: maximum number of EmojiSpans to be added to a single CharSequence, should be equal or greater than 0

See also: EmojiCompat.process(CharSequence, int, int, int)

public int getMaxEmojiCount()

Returns the maximum number of EmojiSpans to be added to a CharSequence.

See also: EmojiEditTextHelper.setMaxEmojiCount(int), EmojiCompat.process(CharSequence, int, int, int)

public KeyListener getKeyListener(KeyListener keyListener)

Attaches EmojiCompat KeyListener to the widget. Should be called from TextView. Existing keyListener is wrapped into EmojiCompat KeyListener. When used on devices running API 18 or below, this method returns keyListener that is given as a parameter.

Parameters:

keyListener: KeyListener passed into TextView

Returns:

a new KeyListener instance that wraps keyListener, or null if passed null.

public InputConnection onCreateInputConnection(InputConnection inputConnection, EditorInfo outAttrs)

Updates the InputConnection with emoji support. Should be called from TextView. When used on devices running API 18 or below, this method returns inputConnection that is given as a parameter. If inputConnection is null, returns null.

Parameters:

inputConnection: InputConnection instance created by TextView
outAttrs: EditorInfo passed into TextView

Returns:

a new InputConnection instance that wraps inputConnection

public void setEmojiReplaceStrategy(int replaceStrategy)

Sets whether to replace all emoji with EmojiSpans. Default value is EmojiCompat.REPLACE_STRATEGY_DEFAULT.

Parameters:

replaceStrategy: should be one of EmojiCompat.REPLACE_STRATEGY_DEFAULT, EmojiCompat.REPLACE_STRATEGY_NON_EXISTENT, EmojiCompat.REPLACE_STRATEGY_ALL

public int getEmojiReplaceStrategy()

Returns whether to replace all emoji with EmojiSpans. Default value is EmojiCompat.REPLACE_STRATEGY_DEFAULT.

Returns:

one of EmojiCompat.REPLACE_STRATEGY_DEFAULT, EmojiCompat.REPLACE_STRATEGY_NON_EXISTENT, EmojiCompat.REPLACE_STRATEGY_ALL

public boolean isEnabled()

If this helper should add new EmojiSpans to display emoji using the emoji font when text changes.

Returns:

true if the helper will process emoji spans.

public void setEnabled(boolean isEnabled)

When helper is enabled, it will process text changes to add appropriate EmojiSpans for display. When helper is disabled, it will not process text changes, but existing spans will not be removed by disabling.

Parameters:

isEnabled: if this helper should process spans

Source

/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.emoji2.viewsintegration;

import android.os.Build;
import android.text.method.KeyListener;
import android.text.method.NumberKeyListener;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
import android.widget.TextView;

import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.util.Preconditions;
import androidx.emoji2.text.EmojiCompat;
import androidx.emoji2.text.EmojiDefaults;
import androidx.emoji2.text.EmojiSpan;

/**
 * Utility class to enhance custom EditText widgets with {@link EmojiCompat}.
 * <p/>
 * <pre>
 * public class MyEmojiEditText extends EditText {
 *      public MyEmojiEditText(Context context) {
 *          super(context);
 *          init();
 *      }
 *      // ...
 *      private void init() {
 *          super.setKeyListener(getEmojiEditTextHelper().getKeyListener(getKeyListener()));
 *      }
 *
 *      {@literal @}Override
 *      public void setKeyListener(android.text.method.KeyListener keyListener) {
 *          super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener));
 *      }
 *
 *      {@literal @}Override
 *      public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
 *          InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
 *          return getEmojiEditTextHelper().onCreateInputConnection(inputConnection, outAttrs);
 *      }
 *
 *      private EmojiEditTextHelper getEmojiEditTextHelper() {
 *          if (mEmojiEditTextHelper == null) {
 *              mEmojiEditTextHelper = new EmojiEditTextHelper(this);
 *          }
 *          return mEmojiEditTextHelper;
 *      }
 * }
 * </pre>
 *
 */
public final class EmojiEditTextHelper {
    private final HelperInternal mHelper;
    private int mMaxEmojiCount = EmojiDefaults.MAX_EMOJI_COUNT;
    @EmojiCompat.ReplaceStrategy
    private int mEmojiReplaceStrategy = EmojiCompat.REPLACE_STRATEGY_DEFAULT;

    /**
     * Default constructor.
     *
     * @param editText EditText instance
     */
    public EmojiEditTextHelper(@NonNull final EditText editText) {
        this(editText, /* expectInitializedEmojiCompat */true);
    }

    /**
     * Allows skipping of all processing until EmojiCompat.init is called.
     *
     * This is useful when integrating EmojiTextViewHelper into libraries that subclass TextView
     * that do not have control over EmojiCompat initialization by the app that uses the TextView
     * subclass.
     *
     * If this helper is initialized prior to EmojiCompat.init, the TextView it's configuring
     * will not display emoji using EmojiCompat after init is called until the transformation
     * method and filter are updated. The easiest way to do that is call
     * {@link EmojiEditTextHelper#setEnabled(boolean)}.
     *
     * @param editText EditText instance
     * @param expectInitializedEmojiCompat if true, this helper will assume init has been called
     *                                     and throw if it has not. If false, the methods on this
     *                                     helper will have no effect until EmojiCompat.init is
     *                                     called.
     */
    public EmojiEditTextHelper(@NonNull EditText editText,
            boolean expectInitializedEmojiCompat) {
        Preconditions.checkNotNull(editText, "editText cannot be null");
        if (Build.VERSION.SDK_INT < 19) {
            mHelper = new HelperInternal();
        } else {
            mHelper = new HelperInternal19(editText, expectInitializedEmojiCompat);
        }
    }

    /**
     * Set the maximum number of EmojiSpans to be added to a CharSequence. The number of spans in a
     * CharSequence affects the performance of the EditText insert/delete operations. Insert/delete
     * operations slow down as the number of spans increases.
     * <p/>
     *
     * @param maxEmojiCount maximum number of EmojiSpans to be added to a single CharSequence,
     *                      should be equal or greater than 0
     *
     * @see EmojiCompat#process(CharSequence, int, int, int)
     */
    public void setMaxEmojiCount(@IntRange(from = 0) int maxEmojiCount) {
        Preconditions.checkArgumentNonnegative(maxEmojiCount,
                "maxEmojiCount should be greater than 0");
        mMaxEmojiCount = maxEmojiCount;
        mHelper.setMaxEmojiCount(maxEmojiCount);
    }

    /**
     * Returns the maximum number of EmojiSpans to be added to a CharSequence.
     *
     * @see #setMaxEmojiCount(int)
     * @see EmojiCompat#process(CharSequence, int, int, int)
     */
    public int getMaxEmojiCount() {
        return mMaxEmojiCount;
    }

    /**
     * Attaches EmojiCompat KeyListener to the widget. Should be called from {@link
     * TextView#setKeyListener(KeyListener)}. Existing keyListener is wrapped into EmojiCompat
     * KeyListener. When used on devices running API 18 or below, this method returns
     * {@code keyListener} that is given as a parameter.
     *
     * @param keyListener KeyListener passed into {@link TextView#setKeyListener(KeyListener)}
     *
     * @return a new KeyListener instance that wraps {@code keyListener}, or null if passed null.
     */
    @SuppressWarnings("ExecutorRegistration")
    @Nullable
    public KeyListener getKeyListener(@Nullable final KeyListener keyListener) {
        return mHelper.getKeyListener(keyListener);
    }

    /**
     * Updates the InputConnection with emoji support. Should be called from {@link
     * TextView#onCreateInputConnection(EditorInfo)}. When used on devices running API 18 or below,
     * this method returns {@code inputConnection} that is given as a parameter. If
     * {@code inputConnection} is {@code null}, returns {@code null}.
     *
     * @param inputConnection InputConnection instance created by TextView
     * @param outAttrs        EditorInfo passed into
     *                        {@link TextView#onCreateInputConnection(EditorInfo)}
     *
     * @return a new InputConnection instance that wraps {@code inputConnection}
     */
    @Nullable
    public InputConnection onCreateInputConnection(@Nullable final InputConnection inputConnection,
            @NonNull final EditorInfo outAttrs) {
        if (inputConnection == null) return null;
        return mHelper.onCreateInputConnection(inputConnection, outAttrs);
    }

    /**
     * Sets whether to replace all emoji with {@link EmojiSpan}s. Default value is
     * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}.
     *
     * @param replaceStrategy should be one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT},
     *                        {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT},
     *                        {@link EmojiCompat#REPLACE_STRATEGY_ALL}
     *
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) {
        mEmojiReplaceStrategy = replaceStrategy;
        mHelper.setEmojiReplaceStrategy(replaceStrategy);
    }

    /**
     * Returns whether to replace all emoji with {@link EmojiSpan}s. Default value is
     * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}.
     *
     * @return one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT},
     *                        {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT},
     *                        {@link EmojiCompat#REPLACE_STRATEGY_ALL}
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public int getEmojiReplaceStrategy() {
        return mEmojiReplaceStrategy;
    }

    /**
     * If this helper should add new EmojiSpans to display emoji using the emoji font when text
     * changes.
     *
     * @return true if the helper will process emoji spans.
     */
    public boolean isEnabled() {
        return mHelper.isEnabled();
    }

    /**
     * When helper is enabled, it will process text changes to add appropriate EmojiSpans for
     * display.
     *
     * When helper is disabled, it will not process text changes, but existing spans will not be
     * removed by disabling.
     *
     * @param isEnabled if this helper should process spans
     */
    public void setEnabled(boolean isEnabled) {
        mHelper.setEnabled(isEnabled);
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    static class HelperInternal {

        @Nullable
        KeyListener getKeyListener(@Nullable KeyListener keyListener) {
            return keyListener;
        }

        InputConnection onCreateInputConnection(@NonNull InputConnection inputConnection,
                @NonNull EditorInfo outAttrs) {
            return inputConnection;
        }

        void setMaxEmojiCount(int maxEmojiCount) {
            // do nothing
        }

        void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) {
            // do nothing
        }

        void setEnabled(boolean isEnabled) {
            // do nothing
        }

        boolean isEnabled() {
            return false;
        }
    }

    @RequiresApi(19)
    private static class HelperInternal19 extends HelperInternal {
        private final EditText mEditText;
        private final EmojiTextWatcher mTextWatcher;

        HelperInternal19(@NonNull EditText editText, boolean expectInitializedEmojiCompat) {
            mEditText = editText;
            mTextWatcher = new EmojiTextWatcher(mEditText, expectInitializedEmojiCompat);
            mEditText.addTextChangedListener(mTextWatcher);
            mEditText.setEditableFactory(EmojiEditableFactory.getInstance());
        }

        @Override
        void setMaxEmojiCount(int maxEmojiCount) {
            mTextWatcher.setMaxEmojiCount(maxEmojiCount);
        }

        @Override
        void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) {
            mTextWatcher.setEmojiReplaceStrategy(replaceStrategy);
        }

        @Override
        KeyListener getKeyListener(@Nullable final KeyListener keyListener) {
            if (keyListener instanceof EmojiKeyListener) {
                return keyListener;
            }
            if (keyListener == null) {
                // don't wrap null key listener, as developer has explicitly request that editing
                // be disabled (this causes keyboard and soft keyboard interactions to not be
                // possible, and the EmojiKeyListener is not required)
                return null;
            }
            if (keyListener instanceof NumberKeyListener) {
                // don't wrap NumberKeyListener as it will never allow emoji input and TextView
                // needs the original type to do correct locale setting (b/207119921)
                return keyListener;
            }
            // make a KeyListener as it's always correct even if disabled
            return new EmojiKeyListener(keyListener);
        }

        @Override
        InputConnection onCreateInputConnection(@NonNull final InputConnection inputConnection,
                @NonNull final EditorInfo outAttrs) {
            if (inputConnection instanceof EmojiInputConnection) {
                return inputConnection;
            }
            // make an EmojiInputConnection even when disabled, as we may become enabled before
            // input connection is closed and it incurs little overhead
            return new EmojiInputConnection(mEditText, inputConnection, outAttrs);
        }

        @Override
        void setEnabled(boolean isEnabled) {
            mTextWatcher.setEnabled(isEnabled);
            // EmojiKeyListener and EmojiInputConnection are just for processing existing spans,
            // and should be left enabled

            // EmojiEditableFactory is just an optimization and should be left enabled
        }

        @Override
        boolean isEnabled() {
            return mTextWatcher.isEnabled();
        }
    }
}