ti.modules.titanium.ui.widget.TiUIEditText.java Source code

Java tutorial

Introduction

Here is the source code for ti.modules.titanium.ui.widget.TiUIEditText.java

Source

/**
 * Appcelerator Titanium Mobile
 * Copyright (c) 2017 by Axway, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 */
package ti.modules.titanium.ui.widget;

import android.content.Context;
import android.os.Build;
import android.support.design.widget.TextInputEditText;
import android.support.v4.view.NestedScrollingChild2;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.inputmethod.EditorInfo;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewGroup;

/**
 * EditText derived class used by Titanium's "Ti.UI.TextField" and "Ti.UI.TextArea" types.
 * <p>
 * Provides nested scrolling support within a NestedScrollingParent view group such as
 * Google's NestedScrollView and Titanium's TiNestedListView.
 */
public class TiUIEditText extends TextInputEditText implements NestedScrollingChild2 {
    /** Helper object used to handle nested scrolling within a cooperating NestedScrollingParent view. */
    private NestedScrollingChildHelper nestedScrollingHelper;

    /**
     * Stores the current nested scrolling direction this view is currently in such as
     * ViewCompat.SCROLL_AXIS_VERTICAL and ViewCompat.SCROLL_AXIS_HORIZONTAL.
     * <p>
     * Will be set to ViewCompat.SCROLL_AXIS_NONE if not currently performing a nested scroll.
     */
    private int scrollAxisDirection = ViewCompat.SCROLL_AXIS_NONE;

    /** Stores the start point of a nested touch event in screen coordinates along the x-axis. */
    private int startRawTouchX;

    /** Stores the start point of a nested touch event in screen coordinates along the y-axis. */
    private int startRawTouchY;

    /** Stores the last received nested touch event in screen coordinates along the x-axis. */
    private int lastRawTouchX;

    /** Stores the last received nested touch event in screen coordinates along the y-axis. */
    private int lastRawTouchY;

    /** Min pixel distance a touch move must cover before it's considered to be a drag/scroll event. */
    private int minDragStartDistance;

    /** Set true if we're in the middle of doing a nested drag/scroll. */
    private boolean isDragging;

    /** Creates a new EditText view. */
    public TiUIEditText(Context context) {
        super(context);
        initializeView();
    }

    /** Creates a new EditText view. */
    public TiUIEditText(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        initializeView();
    }

    /** Creates a new EditText view. */
    public TiUIEditText(Context context, AttributeSet attributeSet, int defaultStyleAttributes) {
        super(context, attributeSet, defaultStyleAttributes);
        initializeView();
    }

    /**
     * Initializes this view's settings and member variables.
     * This method is only expected to be called once from this class' constructor.
     */
    private void initializeView() {
        // Fetch the system's min touch move distance until it's considered to be a drag event.
        // Note: This is the same setting Android's ScrollViews use.
        ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
        if (viewConfiguration != null) {
            this.minDragStartDistance = viewConfiguration.getScaledTouchSlop();
        }

        // Set up this view for nested scrolling.
        this.nestedScrollingHelper = new NestedScrollingChildHelper(this);
        setNestedScrollingEnabled(true);
    }

    /**
     * Called when key input has been received, but before it has been processed by the IME.
     * @param keyCode Unique integer ID of the key that was pressed/released.
     * @param event Provides additional key event details.
     * @return
     * Returns true if this method has handled the key and it should not be passed to the IME.
     * <p>
     * Returns false to allow the IME to handle the key.
     */
    @Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        // Work-around Android bug where center-alisgned and right-aligned EditText won't
        // always pan above the virtual keyboard when given the focus. (See TIMOB-23757)
        boolean isLeftAligned = (getGravity() & Gravity.LEFT) != 0;
        if ((Build.VERSION.SDK_INT < 24) && !isLeftAligned && (keyCode == KeyEvent.KEYCODE_BACK)) {
            ViewGroup view = (ViewGroup) getParent();
            view.setFocusableInTouchMode(true);
            view.requestFocus();
        }
        return super.onKeyPreIme(keyCode, event);
    }

    /**
     * Called when a touch down/move/up event has been received.
     * @param event Provides information about the touch event.
     * @return Returns true if the touch event was consumed by this view. Returns false if not.
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Validate argument.
        if (event == null) {
            return false;
        }

        // Let the base class handle the event if touch or nested-scrolling is disabled.
        if (!isEnabled() || !isNestedScrollingEnabled()) {
            return super.onTouchEvent(event);
        }

        // Handle nested touch input and scroll handling.
        boolean wasHandled = false;
        switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN: {
            // Determine if the EditText can be scrolled vertically or horizontally.
            // Note: There is a bug in EditText where canScrollHorizontally() will return
            //       true when it's not scrollable for "center" or "right" aligned text.
            boolean isVertical = ((getInputType() & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) != 0);
            boolean isScrollable;
            if (isVertical) {
                isScrollable = canScrollVertically(1) || canScrollVertically(-1);
            } else {
                isScrollable = canScrollHorizontally(1) || canScrollHorizontally(-1);
            }

            // Start nested scrolling if the EditText is scrollable.
            this.isDragging = false;
            if (isScrollable) {
                if (isVertical) {
                    this.scrollAxisDirection = ViewCompat.SCROLL_AXIS_VERTICAL;
                } else {
                    this.scrollAxisDirection = ViewCompat.SCROLL_AXIS_HORIZONTAL;
                }
                this.startRawTouchX = (int) event.getRawX();
                this.startRawTouchY = (int) event.getRawY();
                this.lastRawTouchX = this.startRawTouchX;
                this.lastRawTouchY = this.startRawTouchY;
                boolean wasStarted = startNestedScroll(this.scrollAxisDirection);
                if (!wasStarted) {
                    this.scrollAxisDirection = ViewCompat.SCROLL_AXIS_NONE;
                }
            }

            // Let the base class handle the touch "down" event.
            wasHandled = super.onTouchEvent(event);
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            // Handle nested scrolling, if enabled.
            if (this.scrollAxisDirection != ViewCompat.SCROLL_AXIS_NONE) {
                // Determine if the touch point has moved far enough to be considered a drag event.
                // Note: Touch down/up events within this min distance are considered taps/clicks.
                boolean isVertical = (this.scrollAxisDirection == ViewCompat.SCROLL_AXIS_VERTICAL);
                if (!this.isDragging) {
                    int dragDistance;
                    if (isVertical) {
                        dragDistance = this.startRawTouchY - (int) event.getRawY();
                    } else {
                        dragDistance = this.startRawTouchX - (int) event.getRawX();
                    }
                    if (Math.abs(dragDistance) > this.minDragStartDistance) {
                        this.isDragging = true;
                    }
                }

                // Check if we need to scroll the parent, if currently dragging.
                if (this.isDragging) {
                    // Determine scroll direction, distance, and if EditText has hit scroll limit.
                    computeScroll();
                    int deltaX = this.lastRawTouchX - (int) event.getRawX();
                    int deltaY = this.lastRawTouchY - (int) event.getRawY();
                    int deltaValue = isVertical ? deltaY : deltaX;
                    boolean isScrollEnabled;
                    boolean canScrollFurther;
                    if (isVertical) {
                        isScrollEnabled = canScrollVertically(1) || canScrollVertically(-1);
                        canScrollFurther = canScrollVertically(deltaValue);
                    } else {
                        isScrollEnabled = canScrollHorizontally(1) || canScrollHorizontally(-1);
                        canScrollFurther = canScrollHorizontally(deltaValue);
                    }

                    // Request the parent to scroll if one of the following is true:
                    // - EditText is not scrollable. (ie: All text fits within the box.)
                    // - EditText is scrollabe, but cannot scroll any further in given direction.
                    if (!isScrollEnabled || !canScrollFurther) {
                        wasHandled = dispatchNestedPreScroll(deltaX, deltaY, null, null);
                        wasHandled |= dispatchNestedScroll(0, 0, deltaX, deltaY, null);
                    }

                    // Cancel EditText's long-press timer if parent was scrolled.
                    // Note: EditText will move with the user's finger while scrolling the parent
                    //       in this case and we don't want it to trigger a long-press text selection.
                    if (wasHandled) {
                        cancelLongPress();
                    }
                }

                // Store the last received touch point in screen coordinates.
                // This is needed to calculate nested scroll distances.
                this.lastRawTouchX = (int) event.getRawX();
                this.lastRawTouchY = (int) event.getRawY();
            }

            // Let the EditText handle the event if the parent wasn't scrolled via the above.
            if (!wasHandled) {
                wasHandled = super.onTouchEvent(event);
            }
            break;
        }
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP: {
            // Handle the touch release event.
            wasHandled = super.onTouchEvent(event);

            // Stop nested-scrolling if active.
            this.isDragging = false;
            if (this.scrollAxisDirection != ViewCompat.SCROLL_AXIS_NONE) {
                stopNestedScroll();
                this.scrollAxisDirection = ViewCompat.SCROLL_AXIS_NONE;
            }
            break;
        }
        default: {
            // Let the base class handle all other events, such as multi-touch.
            wasHandled = super.onTouchEvent(event);
            break;
        }
        }
        return wasHandled;
    }

    @Override
    public void setEnabled(boolean value) {
        // If we're disabling touch input for this view, then stop nested scrolling if active.
        if (!value && (this.scrollAxisDirection != ViewCompat.SCROLL_AXIS_NONE)) {
            stopNestedScroll();
            this.scrollAxisDirection = ViewCompat.SCROLL_AXIS_NONE;
        }

        // Update this view's enable state.
        super.setEnabled(value);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return this.nestedScrollingHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return this.nestedScrollingHelper.dispatchNestedPreFling(velocityX, velocityY);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        return this.nestedScrollingHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type) {
        return this.nestedScrollingHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
            int[] offsetInWindow) {
        return this.nestedScrollingHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                offsetInWindow);
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
            int[] offsetInWindow, int type) {
        return this.nestedScrollingHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                offsetInWindow, type);
    }

    @Override
    public boolean hasNestedScrollingParent() {
        return this.nestedScrollingHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean hasNestedScrollingParent(int type) {
        return this.nestedScrollingHelper.hasNestedScrollingParent(type);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return this.nestedScrollingHelper.isNestedScrollingEnabled();
    }

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        this.nestedScrollingHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean startNestedScroll(int axes) {
        return this.nestedScrollingHelper.startNestedScroll(axes);
    }

    @Override
    public boolean startNestedScroll(int axes, int type) {
        return this.nestedScrollingHelper.startNestedScroll(axes, type);
    }

    @Override
    public void stopNestedScroll() {
        this.nestedScrollingHelper.stopNestedScroll();
    }

    @Override
    public void stopNestedScroll(int type) {
        this.nestedScrollingHelper.stopNestedScroll(type);
    }
}