Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2da94ad8 authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka
Browse files

Add scrollable KeyboardView

Bug: 6370846
Change-Id: I8b9a619e0e6a980c8b17788ad03c62effc7f35b5
parent 78ab1bb6
Loading
Loading
Loading
Loading
+211 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal;

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.ScrollView;
import android.widget.Scroller;

import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.latin.R;

/**
 * This is an extended {@link KeyboardView} class that hosts a scroll keyboard.
 * Multi-touch unsupported. No {@link PointerTracker}s. No gesture support.
 */
public final class ScrollKeyboardView extends KeyboardView implements
        ScrollViewWithNotifier.ScrollListener, GestureDetector.OnGestureListener {
    private static final boolean PAGINATION = false;

    public interface OnKeyClickListener {
        public void onKeyClick(Key key);
    }

    private static final OnKeyClickListener EMPTY_LISTENER = new OnKeyClickListener() {
        @Override
        public void onKeyClick(final Key key) {}
    };

    private OnKeyClickListener mListener = EMPTY_LISTENER;
    private final KeyDetector mKeyDetector = new KeyDetector(0.0f /*keyHysteresisDistance */);
    private final GestureDetector mGestureDetector;

    private final Scroller mScroller;
    private ScrollViewWithNotifier mScrollView;

    public ScrollKeyboardView(final Context context, final AttributeSet attrs) {
        this(context, attrs, R.attr.keyboardViewStyle);
    }

    public ScrollKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
        mGestureDetector = new GestureDetector(context, this);
        mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */);
        mScroller = new Scroller(context);
    }

    public void setScrollView(final ScrollViewWithNotifier scrollView) {
        mScrollView = scrollView;
        scrollView.setScrollListener(this);
    }

    private final Runnable mScrollTask = new Runnable() {
        @Override
        public void run() {
            final Scroller scroller = mScroller;
            final ScrollView scrollView = mScrollView;
            scroller.computeScrollOffset();
            scrollView.scrollTo(0, scroller.getCurrY());
            if (!scroller.isFinished()) {
                scrollView.post(this);
            }
        }
    };

    // {@link ScrollViewWithNotified#ScrollListener} methods.
    @Override
    public void notifyScrollChanged(final int scrollX, final int scrollY, final int oldX,
            final int oldY) {
        if (PAGINATION) {
            mScroller.forceFinished(true /* finished */);
            mScrollView.removeCallbacks(mScrollTask);
            final int currentTop = mScrollView.getScrollY();
            final int pageHeight = getKeyboard().mBaseHeight;
            final int lastPageNo = currentTop / pageHeight;
            final int lastPageTop = lastPageNo * pageHeight;
            final int nextPageNo = lastPageNo + 1;
            final int nextPageTop = Math.min(nextPageNo * pageHeight, getHeight() - pageHeight);
            final int scrollTo = (currentTop - lastPageTop) < (nextPageTop - currentTop)
                    ? lastPageTop : nextPageTop;
            final int deltaY = scrollTo - currentTop;
            mScroller.startScroll(0, currentTop, 0, deltaY, 300);
            mScrollView.post(mScrollTask);
        }
    }

    @Override
    public void notifyOverScrolled(final int scrollX, final int scrollY, final boolean clampedX,
            final boolean clampedY) {
        releaseCurrentKey();
    }

    public void setOnKeyClickListener(final OnKeyClickListener listener) {
        mListener = listener;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setKeyboard(final Keyboard keyboard) {
        super.setKeyboard(keyboard);
        mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean onTouchEvent(final MotionEvent e) {
        if (mGestureDetector.onTouchEvent(e)) {
            return true;
        }
        final Key key = getKey(e);
        if (key != null && key != mCurrentKey) {
            releaseCurrentKey();
        }
        return true;
    }

    // {@link GestureDetector#OnGestureListener} methods.
    private Key mCurrentKey;

    private Key getKey(final MotionEvent e) {
        final int index = e.getActionIndex();
        final int x = (int)e.getX(index);
        final int y = (int)e.getY(index);
        return mKeyDetector.detectHitKey(x, y);
    }

    public void releaseCurrentKey() {
        final Key currentKey = mCurrentKey;
        if (currentKey == null) {
            return;
        }
        currentKey.onReleased();
        invalidateKey(currentKey);
        mCurrentKey = null;
    }

    @Override
    public boolean onDown(final MotionEvent e) {
        final Key key = getKey(e);
        releaseCurrentKey();
        mCurrentKey = key;
        if (key == null) {
            return false;
        }
        // TODO: May call {@link KeyboardActionListener#onPressKey(int,int,boolean)}.
        key.onPressed();
        invalidateKey(key);
        return false;
    }

    @Override
    public void onShowPress(final MotionEvent e) {
        // User feedback is done at {@link #onDown(MotionEvent)}.
    }

    @Override
    public boolean onSingleTapUp(final MotionEvent e) {
        final Key key = getKey(e);
        releaseCurrentKey();
        if (key == null) {
            return false;
        }
        // TODO: May call {@link KeyboardActionListener#onReleaseKey(int,boolean)}.
        key.onReleased();
        invalidateKey(key);
        mListener.onKeyClick(key);
        return true;
    }

    @Override
    public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX,
           final float distanceY) {
        releaseCurrentKey();
        return false;
    }

    @Override
    public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX,
            final float velocityY) {
        releaseCurrentKey();
        return false;
    }

    @Override
    public void onLongPress(final MotionEvent e) {
        // Long press detection of {@link #mGestureDetector} is disabled and not used.
    }
}
+66 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;

/**
 * This is an extended {@link ScrollView} that can notify
 * {@link ScrollView#onScrollChanged(int,int,int,int} and
 * {@link ScrollView#onOverScrolled(int,int,int,int)} to a content view.
 */
public class ScrollViewWithNotifier extends ScrollView {
    private ScrollListener mScrollListener = EMPTY_LISTER;

    public interface ScrollListener {
        public void notifyScrollChanged(int scrollX, int scrollY, int oldX, int oldY);
        public void notifyOverScrolled(int scrollX, int scrollY, boolean clampedX,
                boolean clampedY);
    }

    private static final ScrollListener EMPTY_LISTER = new ScrollListener() {
        @Override
        public void notifyScrollChanged(int scrollX, int scrollY, int oldX, int oldY) {}
        @Override
        public void notifyOverScrolled(int scrollX, int scrollY, boolean clampedX,
                boolean clampedY) {}
    };

    public ScrollViewWithNotifier(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onScrollChanged(final int scrollX, final int scrollY, final int oldX,
            final int oldY) {
        super.onScrollChanged(scrollX, scrollY, oldX, oldY);
        mScrollListener.notifyScrollChanged(scrollX, scrollY, oldX, oldY);
    }

    @Override
    protected void onOverScrolled(final int scrollX, final int scrollY, final boolean clampedX,
            final boolean clampedY) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
        mScrollListener.notifyOverScrolled(scrollX, scrollY, clampedX, clampedY);
    }

    public void setScrollListener(final ScrollListener listener) {
        mScrollListener = listener;
    }
}