diff --git a/java/res/layout/emoji_keyboard_page.xml b/java/res/layout/emoji_keyboard_page.xml new file mode 100644 index 0000000000000000000000000000000000000000..e0b752b32b560050bc85c479426459d86c20325d --- /dev/null +++ b/java/res/layout/emoji_keyboard_page.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 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. +*/ +--> + +<com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/emoji_keyboard_scroller" + android:clipToPadding="false" + android:layout_width="match_parent" + android:layout_height="match_parent" +> + <com.android.inputmethod.keyboard.internal.ScrollKeyboardView + android:id="@+id/emoji_keyboard_page" + android:layoutDirection="ltr" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> +</com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier> diff --git a/java/res/layout/emoji_keyboard_tab_icon.xml b/java/res/layout/emoji_keyboard_tab_icon.xml new file mode 100644 index 0000000000000000000000000000000000000000..d79276eb93d047ce3b36bd311cb3e1abb116c6b5 --- /dev/null +++ b/java/res/layout/emoji_keyboard_tab_icon.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 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. +*/ +--> + +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="0dip" + android:layout_weight="1.0" + android:layout_height="wrap_content" + android:gravity="center" +/> diff --git a/java/res/layout/emoji_keyboard_tab_label.xml b/java/res/layout/emoji_keyboard_tab_label.xml new file mode 100644 index 0000000000000000000000000000000000000000..62c552dd8393dce994f2412eb38e2411157931d2 --- /dev/null +++ b/java/res/layout/emoji_keyboard_tab_label.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 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. +*/ +--> + +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="0dip" + android:layout_weight="1.0" + android:layout_height="wrap_content" + android:gravity="center" +/> diff --git a/java/res/layout/emoji_keyboard_view.xml b/java/res/layout/emoji_keyboard_view.xml new file mode 100644 index 0000000000000000000000000000000000000000..ccbcfdc6fb4ab3364125fabea7bd034076b48267 --- /dev/null +++ b/java/res/layout/emoji_keyboard_view.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 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. +*/ +--> + +<com.android.inputmethod.keyboard.EmojiKeyboardView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/emoji_keyboard_view" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?attr/emojiKeyboardViewStyle" +> + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="@dimen/suggestions_strip_height" + > + <TabHost + android:id="@+id/emoji_category_tabhost" + android:layout_width="0dip" + android:layout_weight="87.5" + android:layout_height="match_parent" + > + <TabWidget + android:id="@android:id/tabs" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/tab_selected" + android:divider="@null" + android:tabStripEnabled="true" + android:tabStripLeft="@drawable/tab_unselected" + android:tabStripRight="@drawable/tab_unselected" /> + <FrameLayout + android:id="@android:id/tabcontent" + android:layout_width="0dip" + android:layout_height="0dip" + > + <!-- Empty placeholder that TabHost requires. But we don't use it to actually + display anything. We monitor the tab changes and change the ViewPager. + Similarly the ViewPager swipes are intercepted and passed to the TabHost. --> + <View + android:id="@+id/emoji_keyboard_dummy" + android:layout_width="0dip" + android:layout_height="0dip" + android:visibility="gone" /> + </FrameLayout> + </TabHost> + <ImageButton + android:id="@+id/emoji_keyboard_delete" + android:layout_width="0dip" + android:layout_weight="12.5" + android:layout_height="match_parent" + android:src="@drawable/sym_keyboard_delete_holo" /> + </LinearLayout> + <android.support.v4.view.ViewPager + android:id="@+id/emoji_keyboard_pager" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + <LinearLayout + android:id="@+id/emoji_action_bar" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="@dimen/suggestions_strip_height" + > + <ImageButton + android:id="@+id/emoji_keyboard_alphabet" + android:layout_width="0dip" + android:layout_weight="0.825" + android:layout_height="match_parent" + android:src="@drawable/ic_ime_light" /> + <ImageButton + android:id="@+id/emoji_keyboard_send" + android:layout_width="0dip" + android:layout_weight="0.125" + android:layout_height="match_parent" + android:src="@drawable/sym_keyboard_return_holo" /> + </LinearLayout> +</com.android.inputmethod.keyboard.EmojiKeyboardView> diff --git a/java/res/xml/kbd_emoji_category6.xml b/java/res/xml/kbd_emoji_category6.xml index a07966b07ca5516c51cde625dc09834a87810e4c..838f3f52c98759e53028de1ddccbf352a2f700b9 100644 --- a/java/res/xml/kbd_emoji_category6.xml +++ b/java/res/xml/kbd_emoji_category6.xml @@ -22,6 +22,7 @@ xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" latin:keyWidth="@fraction/emoji_keyboard_key_width" latin:keyLetterSize="90%p" + latin:keyLabelSize="60%p" > <GridRows latin:textsArray="@array/emoji_emoticons" diff --git a/java/res/xml/kbd_emoji_recents.xml b/java/res/xml/kbd_emoji_recents.xml index 8b4fa958c2c2c9f805a236970ec5ddbe113c5dae..f56b79ab7f6eaa8818cc93500cee7d980dc63fec 100644 --- a/java/res/xml/kbd_emoji_recents.xml +++ b/java/res/xml/kbd_emoji_recents.xml @@ -22,6 +22,7 @@ xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" latin:keyWidth="@fraction/emoji_keyboard_key_width" latin:keyLetterSize="90%p" + latin:keyLabelSize="60%p" > <GridRows latin:codesArray="@array/emoji_recents" diff --git a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java new file mode 100644 index 0000000000000000000000000000000000000000..25ff8d0ce70eb58abc1ff63209963a710ede69a2 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java @@ -0,0 +1,370 @@ +/* + * 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; + +import static com.android.inputmethod.latin.Constants.NOT_A_COORDINATE; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TabHost; +import android.widget.TabHost.OnTabChangeListener; +import android.widget.TextView; + +import com.android.inputmethod.keyboard.internal.RecentsKeyboard; +import com.android.inputmethod.keyboard.internal.ScrollKeyboardView; +import com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SubtypeSwitcher; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.ResourceUtils; + +import java.util.HashMap; + +/** + * View class to implement Emoji keyboards. + * The Emoji keyboard consists of group of views {@link R.layout#emoji_keyboard_view}. + * <ol> + * <li> Emoji category tabs. + * <li> Delete button. + * <li> Emoji keyboard pages that can be scrolled by swiping horizontally or by selecting a tab. + * <li> Back to main keyboard button and enter button. + * </ol> + * Because of the above reasons, this class doesn't extend {@link KeyboardView}. + */ +public final class EmojiKeyboardView extends LinearLayout implements OnTabChangeListener, + ViewPager.OnPageChangeListener, View.OnClickListener, + ScrollKeyboardView.OnKeyClickListener { + private final int mKeyBackgroundId; + private final ColorStateList mTabLabelColor; + private final EmojiKeyboardAdapter mEmojiKeyboardAdapter; + + private TabHost mTabHost; + private ViewPager mEmojiPager; + + private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; + + private int mCurrentCategory = CATEGORY_UNSPECIFIED; + private static final int CATEGORY_UNSPECIFIED = -1; + private static final int CATEGORY_RECENTS = 0; + private static final int CATEGORY_PEOPLE = 1; + private static final int CATEGORY_OBJECTS = 2; + private static final int CATEGORY_NATURE = 3; + private static final int CATEGORY_PLACES = 4; + private static final int CATEGORY_SYMBOLS = 5; + private static final int CATEGORY_EMOTICONS = 6; + private static final HashMap<String, Integer> sCategoryNameToIdMap = + CollectionUtils.newHashMap(); + private static final String[] sCategoryName = { + "recents", "people", "objects", "nature", "places", "symbols", "emoticons" + }; + private static final int[] sCategoryIcon = new int[] { + R.drawable.ic_emoji_recent_light, + R.drawable.ic_emoji_people_light, + R.drawable.ic_emoji_objects_light, + R.drawable.ic_emoji_nature_light, + R.drawable.ic_emoji_places_light, + R.drawable.ic_emoji_symbols_light, + 0 + }; + private static final String[] sCategoryLabel = { + null, null, null, null, null, null, + ":-)" + }; + private static final int[] sCategoryElementId = { + KeyboardId.ELEMENT_EMOJI_RECENTS, + KeyboardId.ELEMENT_EMOJI_CATEGORY1, + KeyboardId.ELEMENT_EMOJI_CATEGORY2, + KeyboardId.ELEMENT_EMOJI_CATEGORY3, + KeyboardId.ELEMENT_EMOJI_CATEGORY4, + KeyboardId.ELEMENT_EMOJI_CATEGORY5, + KeyboardId.ELEMENT_EMOJI_CATEGORY6, + }; + + public EmojiKeyboardView(final Context context, final AttributeSet attrs) { + this(context, attrs, R.attr.emojiKeyboardViewStyle); + } + + public EmojiKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs, + R.styleable.KeyboardView, defStyle, R.style.KeyboardView); + mKeyBackgroundId = keyboardViewAttr.getResourceId( + R.styleable.KeyboardView_keyBackground, 0); + keyboardViewAttr.recycle(); + final TypedArray emojiKeyboardViewAttr = context.obtainStyledAttributes(attrs, + R.styleable.EmojiKeyboardView, defStyle, R.style.EmojiKeyboardView); + mTabLabelColor = emojiKeyboardViewAttr.getColorStateList( + R.styleable.EmojiKeyboardView_emojiTabLabelColor); + emojiKeyboardViewAttr.recycle(); + final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( + context, null /* editorInfo */); + final Resources res = context.getResources(); + builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype()); + // TODO: Make Keyboard height variable. + builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res), + (int)(ResourceUtils.getDefaultKeyboardHeight(res) + - res.getDimension(R.dimen.suggestions_strip_height))); + builder.setOptions(false, false, false /* lanuageSwitchKeyEnabled */); + final KeyboardLayoutSet layoutSet = builder.build(); + mEmojiKeyboardAdapter = new EmojiKeyboardAdapter(layoutSet, this); + // TODO: Save/restore recent keys from/to preferences. + } + + @Override + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + final Resources res = getContext().getResources(); + // The main keyboard expands to the entire this {@link KeyboardView}. + final int width = ResourceUtils.getDefaultKeyboardWidth(res) + + getPaddingLeft() + getPaddingRight(); + final int height = ResourceUtils.getDefaultKeyboardHeight(res) + + res.getDimensionPixelSize(R.dimen.suggestions_strip_height) + + getPaddingTop() + getPaddingBottom(); + setMeasuredDimension(width, height); + } + + private void addTab(final TabHost host, final int category) { + final String tabId = sCategoryName[category]; + sCategoryNameToIdMap.put(tabId, category); + final TabHost.TabSpec tspec = host.newTabSpec(tabId); + tspec.setContent(R.id.emoji_keyboard_dummy); + if (sCategoryIcon[category] != 0) { + final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate( + R.layout.emoji_keyboard_tab_icon, null); + iconView.setImageResource(sCategoryIcon[category]); + tspec.setIndicator(iconView); + } + if (sCategoryLabel[category] != null) { + final TextView textView = (TextView)LayoutInflater.from(getContext()).inflate( + R.layout.emoji_keyboard_tab_label, null); + textView.setText(sCategoryLabel[category]); + textView.setTextColor(mTabLabelColor); + textView.setBackgroundResource(mKeyBackgroundId); + tspec.setIndicator(textView); + } + host.addTab(tspec); + } + + @Override + protected void onFinishInflate() { + mTabHost = (TabHost)findViewById(R.id.emoji_category_tabhost); + mTabHost.setup(); + addTab(mTabHost, CATEGORY_RECENTS); + addTab(mTabHost, CATEGORY_PEOPLE); + addTab(mTabHost, CATEGORY_OBJECTS); + addTab(mTabHost, CATEGORY_NATURE); + addTab(mTabHost, CATEGORY_PLACES); + addTab(mTabHost, CATEGORY_SYMBOLS); + addTab(mTabHost, CATEGORY_EMOTICONS); + mTabHost.setOnTabChangedListener(this); + mTabHost.getTabWidget().setStripEnabled(true); + + mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager); + mEmojiPager.setAdapter(mEmojiKeyboardAdapter); + mEmojiPager.setOnPageChangeListener(this); + mEmojiPager.setOffscreenPageLimit(0); + final ViewGroup.LayoutParams lp = mEmojiPager.getLayoutParams(); + final Resources res = getResources(); + lp.height = ResourceUtils.getDefaultKeyboardHeight(res) + - res.getDimensionPixelSize(R.dimen.suggestions_strip_height); + mEmojiPager.setLayoutParams(lp); + + // TODO: Record current category. + final int category = CATEGORY_PEOPLE; + setCurrentCategory(category, true /* force */); + + // TODO: Implement auto repeat, using View.OnTouchListener? + final View deleteKey = findViewById(R.id.emoji_keyboard_delete); + deleteKey.setBackgroundResource(mKeyBackgroundId); + deleteKey.setTag(Constants.CODE_DELETE); + deleteKey.setOnClickListener(this); + final View alphabetKey = findViewById(R.id.emoji_keyboard_alphabet); + alphabetKey.setBackgroundResource(mKeyBackgroundId); + alphabetKey.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL); + alphabetKey.setOnClickListener(this); + final View sendKey = findViewById(R.id.emoji_keyboard_send); + sendKey.setBackgroundResource(mKeyBackgroundId); + sendKey.setTag(Constants.CODE_ENTER); + sendKey.setOnClickListener(this); + } + + @Override + public void onTabChanged(final String tabId) { + final int category = sCategoryNameToIdMap.get(tabId); + setCurrentCategory(category, false /* force */); + } + + + @Override + public void onPageSelected(final int position) { + setCurrentCategory(position, false /* force */); + } + + @Override + public void onPageScrollStateChanged(final int state) { + // Ignore this message. Only want the actual page selected. + } + + @Override + public void onPageScrolled(final int position, final float positionOffset, + final int positionOffsetPixels) { + // Ignore this message. Only want the actual page selected. + } + + @Override + public void onClick(final View v) { + if (v.getTag() instanceof Integer) { + final int code = (Integer)v.getTag(); + registerCode(code); + return; + } + } + + private void registerCode(final int code) { + mKeyboardActionListener.onPressKey(code, 0 /* repeatCount */, true /* isSinglePointer */); + mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE); + mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); + } + + @Override + public void onKeyClick(final Key key) { + mEmojiKeyboardAdapter.addRecentKey(key); + final int code = key.getCode(); + if (code == Constants.CODE_OUTPUT_TEXT) { + mKeyboardActionListener.onTextInput(key.getOutputText()); + return; + } + registerCode(code); + } + + public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { + // TODO: + } + + public void setKeyboardActionListener(final KeyboardActionListener listener) { + mKeyboardActionListener = listener; + } + + private void setCurrentCategory(final int category, final boolean force) { + if (mCurrentCategory == category && !force) { + return; + } + + mCurrentCategory = category; + if (force || mEmojiPager.getCurrentItem() != category) { + mEmojiPager.setCurrentItem(category, true /* smoothScroll */); + } + if (force || mTabHost.getCurrentTab() != category) { + mTabHost.setCurrentTab(category); + } + // TODO: Record current category + } + + private static class EmojiKeyboardAdapter extends PagerAdapter { + private final ScrollKeyboardView.OnKeyClickListener mListener; + private final KeyboardLayoutSet mLayoutSet; + private final RecentsKeyboard mRecentsKeyboard; + private final SparseArray<ScrollKeyboardView> mActiveKeyboardView = + CollectionUtils.newSparseArray(); + private int mActivePosition = CATEGORY_UNSPECIFIED; + + public EmojiKeyboardAdapter(final KeyboardLayoutSet layoutSet, + final ScrollKeyboardView.OnKeyClickListener listener) { + mListener = listener; + mLayoutSet = layoutSet; + mRecentsKeyboard = new RecentsKeyboard( + layoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS)); + } + + public void addRecentKey(final Key key) { + if (mActivePosition == CATEGORY_RECENTS) { + return; + } + mRecentsKeyboard.addRecentKey(key); + final KeyboardView recentKeyboardView = mActiveKeyboardView.get(CATEGORY_RECENTS); + if (recentKeyboardView != null) { + recentKeyboardView.invalidateAllKeys(); + } + } + + @Override + public int getCount() { + return sCategoryName.length; + } + + @Override + public void setPrimaryItem(final View container, final int position, final Object object) { + if (mActivePosition == position) { + return; + } + final ScrollKeyboardView oldKeyboardView = mActiveKeyboardView.get(mActivePosition); + if (oldKeyboardView != null) { + oldKeyboardView.releaseCurrentKey(); + oldKeyboardView.deallocateMemory(); + } + mActivePosition = position; + } + + @Override + public Object instantiateItem(final ViewGroup container, final int position) { + final int elementId = sCategoryElementId[position]; + final Keyboard keyboard = (elementId == KeyboardId.ELEMENT_EMOJI_RECENTS) + ? mRecentsKeyboard : mLayoutSet.getKeyboard(elementId); + final LayoutInflater inflater = LayoutInflater.from(container.getContext()); + final View view = inflater.inflate( + R.layout.emoji_keyboard_page, container, false /* attachToRoot */); + final ScrollKeyboardView keyboardView = (ScrollKeyboardView)view.findViewById( + R.id.emoji_keyboard_page); + keyboardView.setKeyboard(keyboard); + keyboardView.setOnKeyClickListener(mListener); + final ScrollViewWithNotifier scrollView = (ScrollViewWithNotifier)view.findViewById( + R.id.emoji_keyboard_scroller); + keyboardView.setScrollView(scrollView); + container.addView(view); + mActiveKeyboardView.put(position, keyboardView); + return view; + } + + @Override + public boolean isViewFromObject(final View view, final Object object) { + return view == object; + } + + @Override + public void destroyItem(final ViewGroup container, final int position, + final Object object) { + final ScrollKeyboardView keyboardView = mActiveKeyboardView.get(position); + if (keyboardView != null) { + keyboardView.deallocateMemory(); + mActiveKeyboardView.remove(position); + } + container.removeView(keyboardView); + } + } +}