From 2da94ad8cd8bd8c87ee1acad5021e09046e20565 Mon Sep 17 00:00:00 2001
From: "Tadashi G. Takaoka" <takaoka@google.com>
Date: Mon, 26 Aug 2013 13:24:48 +0900
Subject: [PATCH] Add scrollable KeyboardView

Bug: 6370846
Change-Id: I8b9a619e0e6a980c8b17788ad03c62effc7f35b5
---
 .../keyboard/internal/ScrollKeyboardView.java | 211 ++++++++++++++++++
 .../internal/ScrollViewWithNotifier.java      |  66 ++++++
 2 files changed, 277 insertions(+)
 create mode 100644 java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java
 create mode 100644 java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java

diff --git a/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java b/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java
new file mode 100644
index 0000000000..2628f59a8e
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java
@@ -0,0 +1,211 @@
+/*
+ * 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.
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java b/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java
new file mode 100644
index 0000000000..d1ccdc7b58
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+    }
+}
-- 
GitLab