Add EmojiKeyboardView

Bug: 6370846
Change-Id: Ic7d75f1d242795e756e6fede988cfe4b5cc17f0e
main
Tadashi G. Takaoka 2013-08-28 09:14:00 +09:00
parent 8e3a90e58f
commit 8ac0eb59e1
7 changed files with 551 additions and 0 deletions

View File

@ -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>

View File

@ -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"
/>

View File

@ -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"
/>

View File

@ -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>

View File

@ -22,6 +22,7 @@
xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
latin:keyWidth="@fraction/emoji_keyboard_key_width" latin:keyWidth="@fraction/emoji_keyboard_key_width"
latin:keyLetterSize="90%p" latin:keyLetterSize="90%p"
latin:keyLabelSize="60%p"
> >
<GridRows <GridRows
latin:textsArray="@array/emoji_emoticons" latin:textsArray="@array/emoji_emoticons"

View File

@ -22,6 +22,7 @@
xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
latin:keyWidth="@fraction/emoji_keyboard_key_width" latin:keyWidth="@fraction/emoji_keyboard_key_width"
latin:keyLetterSize="90%p" latin:keyLetterSize="90%p"
latin:keyLabelSize="60%p"
> >
<GridRows <GridRows
latin:codesArray="@array/emoji_recents" latin:codesArray="@array/emoji_recents"

View File

@ -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);
}
}
}