TextDecoratorUi.java revision bea17c49ec23bf0f646cb548445c7756aa50d233
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.inputmethod.keyboard; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.graphics.Canvas; 22import android.graphics.Color; 23import android.graphics.Matrix; 24import android.graphics.Paint; 25import android.graphics.Path; 26import android.graphics.RectF; 27import android.graphics.drawable.ColorDrawable; 28import android.inputmethodservice.InputMethodService; 29import android.util.TypedValue; 30import android.view.Gravity; 31import android.view.View; 32import android.view.View.OnClickListener; 33import android.view.ViewGroup; 34import android.view.ViewGroup.LayoutParams; 35import android.view.ViewParent; 36import android.widget.PopupWindow; 37import android.widget.RelativeLayout; 38 39import com.android.inputmethod.latin.R; 40 41/** 42 * Used as the UI component of {@link TextDecorator}. 43 */ 44public final class TextDecoratorUi implements TextDecoratorUiOperator { 45 private static final boolean VISUAL_DEBUG = false; 46 private static final int VISUAL_DEBUG_HIT_AREA_COLOR = 0x80ff8000; 47 48 private final RelativeLayout mLocalRootView; 49 private final CommitIndicatorView mCommitIndicatorView; 50 private final AddToDictionaryIndicatorView mAddToDictionaryIndicatorView; 51 private final PopupWindow mTouchEventWindow; 52 private final View mTouchEventWindowClickListenerView; 53 private final float mHitAreaMarginInPixels; 54 55 /** 56 * This constructor is designed to be called from {@link InputMethodService#setInputView(View)}. 57 * Other usages are not supported. 58 * 59 * @param context the context of the input method. 60 * @param inputView the view that is passed to {@link InputMethodService#setInputView(View)}. 61 */ 62 public TextDecoratorUi(final Context context, final View inputView) { 63 final Resources resources = context.getResources(); 64 final int hitAreaMarginInDP = resources.getInteger( 65 R.integer.text_decorator_hit_area_margin_in_dp); 66 mHitAreaMarginInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 67 hitAreaMarginInDP, resources.getDisplayMetrics()); 68 69 mLocalRootView = new RelativeLayout(context); 70 mLocalRootView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 71 LayoutParams.MATCH_PARENT)); 72 // TODO: Use #setBackground(null) for API Level >= 16. 73 mLocalRootView.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 74 75 final ViewGroup contentView = getContentView(inputView); 76 mCommitIndicatorView = new CommitIndicatorView(context); 77 mAddToDictionaryIndicatorView = new AddToDictionaryIndicatorView(context); 78 mLocalRootView.addView(mCommitIndicatorView); 79 mLocalRootView.addView(mAddToDictionaryIndicatorView); 80 if (contentView != null) { 81 contentView.addView(mLocalRootView); 82 } 83 84 // This popup window is used to avoid the limitation that the input method is not able to 85 // observe the touch events happening outside of InputMethodService.Insets#touchableRegion. 86 // We don't use this popup window for rendering the UI for performance reasons though. 87 mTouchEventWindow = new PopupWindow(context); 88 if (VISUAL_DEBUG) { 89 mTouchEventWindow.setBackgroundDrawable(new ColorDrawable(VISUAL_DEBUG_HIT_AREA_COLOR)); 90 } else { 91 mTouchEventWindow.setBackgroundDrawable(null); 92 } 93 mTouchEventWindowClickListenerView = new View(context); 94 mTouchEventWindow.setContentView(mTouchEventWindowClickListenerView); 95 } 96 97 @Override 98 public void disposeUi() { 99 if (mLocalRootView != null) { 100 final ViewParent parent = mLocalRootView.getParent(); 101 if (parent != null && parent instanceof ViewGroup) { 102 ((ViewGroup) parent).removeView(mLocalRootView); 103 } 104 mLocalRootView.removeAllViews(); 105 } 106 if (mTouchEventWindow != null) { 107 mTouchEventWindow.dismiss(); 108 } 109 } 110 111 @Override 112 public void hideUi() { 113 mCommitIndicatorView.setVisibility(View.GONE); 114 mAddToDictionaryIndicatorView.setVisibility(View.GONE); 115 mTouchEventWindow.dismiss(); 116 } 117 118 @Override 119 public void layoutUi(final boolean isCommitMode, final Matrix matrix, 120 final RectF indicatorBounds, final RectF composingTextBounds) { 121 final RectF indicatorBoundsInScreenCoordinates = new RectF(); 122 matrix.mapRect(indicatorBoundsInScreenCoordinates, indicatorBounds); 123 mCommitIndicatorView.setBounds(indicatorBoundsInScreenCoordinates); 124 mAddToDictionaryIndicatorView.setBounds(indicatorBoundsInScreenCoordinates); 125 126 final RectF hitAreaBounds = new RectF(composingTextBounds); 127 hitAreaBounds.union(indicatorBounds); 128 final RectF hitAreaBoundsInScreenCoordinates = new RectF(); 129 matrix.mapRect(hitAreaBoundsInScreenCoordinates, hitAreaBounds); 130 hitAreaBoundsInScreenCoordinates.inset(-mHitAreaMarginInPixels, -mHitAreaMarginInPixels); 131 132 final int[] originScreen = new int[2]; 133 mLocalRootView.getLocationOnScreen(originScreen); 134 final int viewOriginX = originScreen[0]; 135 final int viewOriginY = originScreen[1]; 136 137 final View toBeShown; 138 final View toBeHidden; 139 if (isCommitMode) { 140 toBeShown = mCommitIndicatorView; 141 toBeHidden = mAddToDictionaryIndicatorView; 142 } else { 143 toBeShown = mAddToDictionaryIndicatorView; 144 toBeHidden = mCommitIndicatorView; 145 } 146 toBeShown.setX(indicatorBoundsInScreenCoordinates.left - viewOriginX); 147 toBeShown.setY(indicatorBoundsInScreenCoordinates.top - viewOriginY); 148 toBeShown.setVisibility(View.VISIBLE); 149 toBeHidden.setVisibility(View.GONE); 150 151 if (mTouchEventWindow.isShowing()) { 152 mTouchEventWindow.update((int)hitAreaBoundsInScreenCoordinates.left - viewOriginX, 153 (int)hitAreaBoundsInScreenCoordinates.top - viewOriginY, 154 (int)hitAreaBoundsInScreenCoordinates.width(), 155 (int)hitAreaBoundsInScreenCoordinates.height()); 156 } else { 157 mTouchEventWindow.setWidth((int)hitAreaBoundsInScreenCoordinates.width()); 158 mTouchEventWindow.setHeight((int)hitAreaBoundsInScreenCoordinates.height()); 159 mTouchEventWindow.showAtLocation(mLocalRootView, Gravity.NO_GRAVITY, 160 (int)hitAreaBoundsInScreenCoordinates.left - viewOriginX, 161 (int)hitAreaBoundsInScreenCoordinates.top - viewOriginY); 162 } 163 } 164 165 @Override 166 public void setOnClickListener(final Runnable listener) { 167 mTouchEventWindowClickListenerView.setOnClickListener(new OnClickListener() { 168 @Override 169 public void onClick(final View arg0) { 170 listener.run(); 171 } 172 }); 173 } 174 175 private static class IndicatorView extends View { 176 private final Path mPath; 177 private final Path mTmpPath = new Path(); 178 private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 179 private final Matrix mMatrix = new Matrix(); 180 private final int mBackgroundColor; 181 private final int mForegroundColor; 182 private final RectF mBounds = new RectF(); 183 public IndicatorView(Context context, final int pathResourceId, 184 final int sizeResourceId, final int backgroundColorResourceId, 185 final int foregroundColroResourceId) { 186 super(context); 187 final Resources resources = context.getResources(); 188 mPath = createPath(resources, pathResourceId, sizeResourceId); 189 mBackgroundColor = resources.getColor(backgroundColorResourceId); 190 mForegroundColor = resources.getColor(foregroundColroResourceId); 191 } 192 193 public void setBounds(final RectF rect) { 194 mBounds.set(rect); 195 } 196 197 @Override 198 protected void onDraw(Canvas canvas) { 199 mPaint.setColor(mBackgroundColor); 200 mPaint.setStyle(Paint.Style.FILL); 201 canvas.drawRect(0.0f, 0.0f, mBounds.width(), mBounds.height(), mPaint); 202 203 mMatrix.reset(); 204 mMatrix.postScale(mBounds.width(), mBounds.height()); 205 mPath.transform(mMatrix, mTmpPath); 206 mPaint.setColor(mForegroundColor); 207 canvas.drawPath(mTmpPath, mPaint); 208 } 209 210 private static Path createPath(final Resources resources, final int pathResourceId, 211 final int sizeResourceId) { 212 final int size = resources.getInteger(sizeResourceId); 213 final float normalizationFactor = 1.0f / size; 214 final int[] array = resources.getIntArray(pathResourceId); 215 216 final Path path = new Path(); 217 for (int i = 0; i < array.length; i += 2) { 218 if (i == 0) { 219 path.moveTo(array[i] * normalizationFactor, array[i + 1] * normalizationFactor); 220 } else { 221 path.lineTo(array[i] * normalizationFactor, array[i + 1] * normalizationFactor); 222 } 223 } 224 path.close(); 225 return path; 226 } 227 } 228 229 private static ViewGroup getContentView(final View view) { 230 final View rootView = view.getRootView(); 231 if (rootView == null) { 232 return null; 233 } 234 235 final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content); 236 if (windowContentView == null) { 237 return null; 238 } 239 return windowContentView; 240 } 241 242 private static final class CommitIndicatorView extends TextDecoratorUi.IndicatorView { 243 public CommitIndicatorView(final Context context) { 244 super(context, R.array.text_decorator_commit_indicator_path, 245 R.integer.text_decorator_commit_indicator_path_size, 246 R.color.text_decorator_commit_indicator_background_color, 247 R.color.text_decorator_commit_indicator_foreground_color); 248 } 249 } 250 251 private static final class AddToDictionaryIndicatorView extends TextDecoratorUi.IndicatorView { 252 public AddToDictionaryIndicatorView(final Context context) { 253 super(context, R.array.text_decorator_add_to_dictionary_indicator_path, 254 R.integer.text_decorator_add_to_dictionary_indicator_path_size, 255 R.color.text_decorator_add_to_dictionary_indicator_background_color, 256 R.color.text_decorator_add_to_dictionary_indicator_foreground_color); 257 } 258 } 259}