From 2da94ad8cd8bd8c87ee1acad5021e09046e20565 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" 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 000000000..2628f59a8 --- /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 000000000..d1ccdc7b5 --- /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; + } +}