|
|
@@ -0,0 +1,453 @@
|
|
|
+package com.ht.gate.view;
|
|
|
+
|
|
|
+import android.animation.Animator;
|
|
|
+import android.annotation.SuppressLint;
|
|
|
+import android.content.Context;
|
|
|
+import android.content.res.TypedArray;
|
|
|
+import android.graphics.Bitmap;
|
|
|
+import android.graphics.BitmapFactory;
|
|
|
+import android.graphics.Canvas;
|
|
|
+import android.graphics.Color;
|
|
|
+import android.graphics.ColorMatrix;
|
|
|
+import android.graphics.ColorMatrixColorFilter;
|
|
|
+import android.graphics.DrawFilter;
|
|
|
+import android.graphics.Paint;
|
|
|
+import android.graphics.PaintFlagsDrawFilter;
|
|
|
+import android.graphics.Rect;
|
|
|
+import android.graphics.RectF;
|
|
|
+import android.graphics.Typeface;
|
|
|
+import android.inputmethodservice.Keyboard;
|
|
|
+import android.inputmethodservice.KeyboardView;
|
|
|
+import android.os.Handler;
|
|
|
+import android.os.Message;
|
|
|
+import android.text.TextUtils;
|
|
|
+import android.util.AttributeSet;
|
|
|
+import android.util.DisplayMetrics;
|
|
|
+import android.util.TypedValue;
|
|
|
+import android.view.MotionEvent;
|
|
|
+import android.view.View;
|
|
|
+import android.view.ViewConfiguration;
|
|
|
+import android.view.animation.AccelerateDecelerateInterpolator;
|
|
|
+import android.view.animation.Animation;
|
|
|
+import android.view.animation.LinearInterpolator;
|
|
|
+import android.view.animation.TranslateAnimation;
|
|
|
+
|
|
|
+import androidx.annotation.Nullable;
|
|
|
+
|
|
|
+import com.ht.gate.R;
|
|
|
+
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+@SuppressWarnings("deprecation")
|
|
|
+public class CustomKeyboardView extends View {
|
|
|
+
|
|
|
+ public interface OnKeyboardActionListener {
|
|
|
+ void onPress(int primaryCode);
|
|
|
+
|
|
|
+ void onRelease(int primaryCode);
|
|
|
+
|
|
|
+ void onKey(int primaryCode, int[] keyCodes);
|
|
|
+
|
|
|
+ void onText(CharSequence text);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static final int KEYCODE_CANCEL = -3;
|
|
|
+ public static final int KEYCODE_DONE = -4;
|
|
|
+ public static final int KEYCODE_DELETE = -5;
|
|
|
+
|
|
|
+ private static final int NOT_A_KEY = -1;
|
|
|
+ private static final int[] KEY_DELETE = {Keyboard.KEYCODE_DELETE};
|
|
|
+ private static int MAX_NEARBY_KEYS = 12;
|
|
|
+ private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
|
|
|
+ private static final int REPEAT_START_DELAY = 400;
|
|
|
+ private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
|
|
|
+
|
|
|
+ private int[] mDistances = new int[MAX_NEARBY_KEYS];
|
|
|
+ private int mLastSentIndex;
|
|
|
+ private int mTapCount;
|
|
|
+ private long mLastTapTime;
|
|
|
+ private static final int MULTITAP_INTERVAL = 800; // milliseconds
|
|
|
+ private StringBuilder mPreviewLabel = new StringBuilder(1);
|
|
|
+
|
|
|
+ private int mVerticalCorrection;
|
|
|
+ private int mProximityThreshold;
|
|
|
+ private boolean mProximityCorrectOn;
|
|
|
+
|
|
|
+ private int mOldPointerCount = 1;
|
|
|
+ private float mOldPointerX;
|
|
|
+ private float mOldPointerY;
|
|
|
+
|
|
|
+ private int mLastX;
|
|
|
+ private int mLastY;
|
|
|
+ private int mStartX;
|
|
|
+ private int mStartY;
|
|
|
+
|
|
|
+ private long mDownTime;
|
|
|
+ private long mLastMoveTime;
|
|
|
+ private int mLastKey;
|
|
|
+ private int mLastCodeX;
|
|
|
+ private int mLastCodeY;
|
|
|
+ private int mCurrentKey = NOT_A_KEY;
|
|
|
+ private int mDownKey = NOT_A_KEY;
|
|
|
+ private long mLastKeyTime;
|
|
|
+ private long mCurrentKeyTime;
|
|
|
+
|
|
|
+ private static final int MSG_SHOW_PREVIEW = 1;
|
|
|
+ private static final int MSG_REMOVE_PREVIEW = 2;
|
|
|
+ private static final int MSG_REPEAT = 3;
|
|
|
+ private static final int MSG_LONGPRESS = 4;
|
|
|
+
|
|
|
+
|
|
|
+ private Keyboard mKeyboard;
|
|
|
+ private Keyboard.Key[] mKeys;
|
|
|
+ private Paint mPaint;
|
|
|
+ private Paint mBitmapPaint;
|
|
|
+ private int mPaddingH;
|
|
|
+ private int mPaddingV;
|
|
|
+ private int radius;
|
|
|
+ private int mKeyOffsetX;
|
|
|
+ private int mKeyOffsetY;
|
|
|
+ private int mLegendHeight;
|
|
|
+ private int mLegendBtnWidth;
|
|
|
+ private int mBtnCancelColor = 0x80ffffff;
|
|
|
+ private int mBtnConfirmColor = 0xFFE4BF85;
|
|
|
+ private int mKeyBackgroundColor = 0xff444857;
|
|
|
+ private int mLegendBackground = 0xFF2B2E3A;
|
|
|
+ private Typeface source_han_sans_sc_normal;
|
|
|
+ private Typeface source_han_sans_sc_medium;
|
|
|
+ private Typeface roboto_normal;
|
|
|
+ private DrawFilter filter;
|
|
|
+ private ColorMatrixColorFilter colorFilterWhite;
|
|
|
+ private ColorMatrixColorFilter colorFilterBlack;
|
|
|
+
|
|
|
+ private OnKeyboardActionListener mKeyboardActionListener;
|
|
|
+ private String mLegend;
|
|
|
+
|
|
|
+ private boolean cap;
|
|
|
+
|
|
|
+ Handler mHandler;
|
|
|
+
|
|
|
+ public void setKeyboardActionListener(OnKeyboardActionListener mKeyboardActionListener) {
|
|
|
+ this.mKeyboardActionListener = mKeyboardActionListener;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setCap(boolean cap) {
|
|
|
+ this.cap = cap;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void show() {
|
|
|
+ int width = getWidth();
|
|
|
+ if (width == 0) {
|
|
|
+ DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
|
|
|
+ width = (int) (displayMetrics.widthPixels / displayMetrics.density);
|
|
|
+ }
|
|
|
+
|
|
|
+ measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
|
|
|
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
|
|
|
+
|
|
|
+ final int targetHeight = getMeasuredHeight();
|
|
|
+
|
|
|
+ setVisibility(VISIBLE);
|
|
|
+ TranslateAnimation animate = new TranslateAnimation(0, 0, getHeight() <= 0 ? targetHeight : getHeight(), 0);
|
|
|
+ animate.setDuration(200);
|
|
|
+ animate.setFillAfter(true);
|
|
|
+ animate.setInterpolator(new AccelerateDecelerateInterpolator());
|
|
|
+ startAnimation(animate);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void hide() {
|
|
|
+ TranslateAnimation animate = new TranslateAnimation(0, 0, 0, getHeight());
|
|
|
+ animate.setDuration(200);
|
|
|
+ animate.setFillAfter(true);
|
|
|
+ animate.setAnimationListener(new Animation.AnimationListener() {
|
|
|
+ @Override
|
|
|
+ public void onAnimationStart(Animation animation) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onAnimationEnd(Animation animation) {
|
|
|
+ setVisibility(GONE);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onAnimationRepeat(Animation animation) {
|
|
|
+
|
|
|
+ }
|
|
|
+ });
|
|
|
+ startAnimation(animate);
|
|
|
+ }
|
|
|
+
|
|
|
+ public CustomKeyboardView(Context context, @Nullable AttributeSet attrs) {
|
|
|
+ this(context, attrs, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ public CustomKeyboardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
|
|
+ super(context, attrs, defStyleAttr);
|
|
|
+ setKeyboard(new Keyboard(getContext(), R.xml.custom_keyboard));
|
|
|
+ mPaint = new Paint();
|
|
|
+ mPaint.setAntiAlias(true);
|
|
|
+
|
|
|
+ mPaddingH = 0;
|
|
|
+ mPaddingV = dp2px(12);
|
|
|
+ radius = dp2px(2);
|
|
|
+ mLegendHeight = dp2px(65);
|
|
|
+ mLegendBtnWidth = dp2px(75);
|
|
|
+ mKeyOffsetX = 0;
|
|
|
+ mKeyOffsetY = dp2px(12);
|
|
|
+ source_han_sans_sc_normal = Typeface.createFromAsset(this.getContext().getAssets(), "font/source_han_sans_sc_normal.otf");
|
|
|
+ source_han_sans_sc_medium = Typeface.createFromAsset(this.getContext().getAssets(), "font/source_han_sans_sc_medium.otf");
|
|
|
+ roboto_normal = Typeface.createFromAsset(this.getContext().getAssets(), "font/roboto_normal.ttf");
|
|
|
+ filter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
|
|
+
|
|
|
+ mBitmapPaint = new Paint();
|
|
|
+ mBitmapPaint.setAntiAlias(true);
|
|
|
+ mBitmapPaint.setDither(true);
|
|
|
+ colorFilterWhite = new ColorMatrixColorFilter(new ColorMatrix(new float[]{
|
|
|
+ 0, 0, 0, 0, 255,
|
|
|
+ 0, 0, 0, 0, 255,
|
|
|
+ 0, 0, 0, 0, 255,
|
|
|
+ 0, 0, 0, 1, 0,
|
|
|
+ }));
|
|
|
+ colorFilterBlack = new ColorMatrixColorFilter(new ColorMatrix(new float[]{
|
|
|
+ 0, 0, 0, 0, 68,
|
|
|
+ 0, 0, 0, 0, 72,
|
|
|
+ 0, 0, 0, 0, 87,
|
|
|
+ 0, 0, 0, 1, 0,
|
|
|
+ }));
|
|
|
+
|
|
|
+ TypedArray t = getContext().obtainStyledAttributes(attrs,
|
|
|
+ R.styleable.CustomKeyboardView);
|
|
|
+ mLegend = t.getString(R.styleable.CustomKeyboardView_legend);
|
|
|
+ t.recycle();
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressLint("HandlerLeak")
|
|
|
+ @Override
|
|
|
+ protected void onAttachedToWindow() {
|
|
|
+ super.onAttachedToWindow();
|
|
|
+ if (mHandler == null) {
|
|
|
+ mHandler = new Handler() {
|
|
|
+ @Override
|
|
|
+ public void handleMessage(Message msg) {
|
|
|
+ switch (msg.what) {
|
|
|
+ case MSG_SHOW_PREVIEW:
|
|
|
+ break;
|
|
|
+ case MSG_REPEAT:
|
|
|
+ break;
|
|
|
+ case MSG_REMOVE_PREVIEW:
|
|
|
+ break;
|
|
|
+ case MSG_LONGPRESS:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setKeyboard(Keyboard keyboard) {
|
|
|
+ mKeyboard = keyboard;
|
|
|
+ List<Keyboard.Key> keys = mKeyboard.getKeys();
|
|
|
+ mKeys = keys.toArray(new Keyboard.Key[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
|
+ // Round up a little
|
|
|
+ if (mKeyboard == null) {
|
|
|
+ setMeasuredDimension(mPaddingH * 2, mPaddingV * 2);
|
|
|
+ } else {
|
|
|
+ int width = mKeyboard.getMinWidth() + mPaddingH * 2;
|
|
|
+ if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
|
|
|
+ width = MeasureSpec.getSize(widthMeasureSpec);
|
|
|
+ }
|
|
|
+ setMeasuredDimension(width, mKeyboard.getHeight() + mPaddingV * 2 + mLegendHeight);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressLint("DrawAllocation")
|
|
|
+ @Override
|
|
|
+ public void onDraw(Canvas canvas) {
|
|
|
+ canvas.setDrawFilter(filter);
|
|
|
+ mPaint.setColor(mLegendBackground);
|
|
|
+ canvas.drawRect(0, 0, getWidth(), mLegendHeight, mPaint);
|
|
|
+ mPaint.setTypeface(source_han_sans_sc_medium);
|
|
|
+ mPaint.setTextSize(sp2px(20));
|
|
|
+ mPaint.setColor(mBtnCancelColor);
|
|
|
+ drawCenterText(canvas, mPaint, "取消", 0, 0, mLegendBtnWidth, mLegendHeight);
|
|
|
+ mPaint.setColor(mBtnConfirmColor);
|
|
|
+ drawCenterText(canvas, mPaint, "确定", getWidth() - mLegendBtnWidth, 0, getWidth(), mLegendHeight);
|
|
|
+ if (!TextUtils.isEmpty(mLegend)) {
|
|
|
+ mPaint.setTypeface(source_han_sans_sc_normal);
|
|
|
+ mPaint.setColor(Color.WHITE);
|
|
|
+ mPaint.setLetterSpacing(.2f);
|
|
|
+ drawCenterText(canvas, mPaint, mLegend, 0, 0, getWidth(), mLegendHeight);
|
|
|
+ }
|
|
|
+
|
|
|
+ mPaint.setTextSize(sp2px(25));
|
|
|
+ mPaint.setTypeface(roboto_normal);
|
|
|
+ for (Keyboard.Key key : mKeyboard.getKeys()) {
|
|
|
+ float left = mPaddingH + key.x;
|
|
|
+ float top = mLegendHeight + mPaddingV + key.y;
|
|
|
+ float right = mPaddingH + key.x + key.width;
|
|
|
+ float bottom = mLegendHeight + mPaddingV + key.y + key.height;
|
|
|
+
|
|
|
+
|
|
|
+ if (key.label.equals("del")) {
|
|
|
+ mPaint.setColor(mKeyBackgroundColor);
|
|
|
+ mBitmapPaint.setColorFilter(colorFilterWhite);
|
|
|
+ canvas.drawRoundRect(left, top, right, bottom, radius, radius, mPaint);
|
|
|
+ Bitmap bmCap = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_del);
|
|
|
+ Rect src = new Rect(0, 0, bmCap.getWidth(), bmCap.getHeight());
|
|
|
+ float bmLeft = left + (key.width - bmCap.getWidth()) / 2f;
|
|
|
+ float bmTop = top + (key.height - bmCap.getHeight()) / 2f;
|
|
|
+ RectF dst = new RectF(bmLeft, bmTop, bmLeft + bmCap.getWidth(), bmTop + bmCap.getHeight());
|
|
|
+ canvas.drawBitmap(bmCap, src, dst, mBitmapPaint);
|
|
|
+ } else if (key.label.equals("shift")) {
|
|
|
+ if (cap) {
|
|
|
+ mPaint.setColor(Color.WHITE);
|
|
|
+ mBitmapPaint.setColorFilter(colorFilterBlack);
|
|
|
+ } else {
|
|
|
+ mPaint.setColor(mKeyBackgroundColor);
|
|
|
+ mBitmapPaint.setColorFilter(colorFilterWhite);
|
|
|
+ }
|
|
|
+ canvas.drawRoundRect(left, top, right, bottom, radius, radius, mPaint);
|
|
|
+ Bitmap bmCap = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_cap);
|
|
|
+ Rect src = new Rect(0, 0, bmCap.getWidth(), bmCap.getHeight());
|
|
|
+ float bmLeft = left + (key.width - bmCap.getWidth()) / 2f;
|
|
|
+ float bmTop = top + (key.height - bmCap.getHeight()) / 2f;
|
|
|
+ RectF dst = new RectF(bmLeft, bmTop, bmLeft + bmCap.getWidth(), bmTop + bmCap.getHeight());
|
|
|
+ canvas.drawBitmap(bmCap, src, dst, mBitmapPaint);
|
|
|
+ } else {
|
|
|
+ mPaint.setColor(mKeyBackgroundColor);
|
|
|
+ canvas.drawRoundRect(left, top, right, bottom, radius, radius, mPaint);
|
|
|
+ mPaint.setColor(Color.WHITE);
|
|
|
+ drawCenterText(canvas, mPaint, cap ? key.label.toString().toUpperCase() : key.label.toString(), left, top, right, bottom);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean onTouchEvent(MotionEvent e) {
|
|
|
+ final long eventTime = e.getEventTime();
|
|
|
+
|
|
|
+ switch (e.getAction()) {
|
|
|
+ case MotionEvent.ACTION_UP:
|
|
|
+ if (e.getY() > mLegendHeight) {
|
|
|
+ int touchX = (int) (e.getX() - mPaddingH);
|
|
|
+ int touchY = (int) (e.getY() - mLegendHeight - mPaddingV);
|
|
|
+ int keyIndex = getKeyIndices(touchX, touchY, null);
|
|
|
+ mCurrentKey = keyIndex;
|
|
|
+ detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
|
|
|
+ } else {
|
|
|
+ if (e.getX() < mLegendBtnWidth) {
|
|
|
+ hide();
|
|
|
+ if (mKeyboardActionListener != null) {
|
|
|
+ mKeyboardActionListener.onKey(KEYCODE_CANCEL, new int[0]);
|
|
|
+ }
|
|
|
+ } else if (e.getX() > getWidth() - mLegendBtnWidth) {
|
|
|
+ if (mKeyboardActionListener != null) {
|
|
|
+ mKeyboardActionListener.onKey(KEYCODE_DONE, new int[0]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private int getKeyIndices(int x, int y, int[] allKeys) {
|
|
|
+ final Keyboard.Key[] keys = mKeys;
|
|
|
+ int primaryIndex = NOT_A_KEY;
|
|
|
+ int closestKey = NOT_A_KEY;
|
|
|
+ int closestKeyDist = mProximityThreshold + 1;
|
|
|
+ java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
|
|
|
+ int[] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
|
|
|
+ final int keyCount = nearestKeyIndices.length;
|
|
|
+ for (int i = 0; i < keyCount; i++) {
|
|
|
+ final Keyboard.Key key = keys[nearestKeyIndices[i]];
|
|
|
+ int dist = 0;
|
|
|
+ boolean isInside = key.isInside(x, y);
|
|
|
+ if (isInside) {
|
|
|
+ primaryIndex = nearestKeyIndices[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((mProximityCorrectOn
|
|
|
+ && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
|
|
|
+ || isInside)
|
|
|
+ && key.codes[0] > 32) {
|
|
|
+ // Find insertion point
|
|
|
+ final int nCodes = key.codes.length;
|
|
|
+ if (dist < closestKeyDist) {
|
|
|
+ closestKeyDist = dist;
|
|
|
+ closestKey = nearestKeyIndices[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (allKeys == null) continue;
|
|
|
+
|
|
|
+ for (int j = 0; j < mDistances.length; j++) {
|
|
|
+ if (mDistances[j] > dist) {
|
|
|
+ // Make space for nCodes codes
|
|
|
+ System.arraycopy(mDistances, j, mDistances, j + nCodes,
|
|
|
+ mDistances.length - j - nCodes);
|
|
|
+ System.arraycopy(allKeys, j, allKeys, j + nCodes,
|
|
|
+ allKeys.length - j - nCodes);
|
|
|
+ for (int c = 0; c < nCodes; c++) {
|
|
|
+ allKeys[j + c] = key.codes[c];
|
|
|
+ mDistances[j + c] = dist;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (primaryIndex == NOT_A_KEY) {
|
|
|
+ primaryIndex = closestKey;
|
|
|
+ }
|
|
|
+ return primaryIndex;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void detectAndSendKey(int index, int x, int y, long eventTime) {
|
|
|
+ if (index != NOT_A_KEY && index < mKeys.length) {
|
|
|
+ final Keyboard.Key key = mKeys[index];
|
|
|
+ if (key.text != null) {
|
|
|
+ mKeyboardActionListener.onText(key.text);
|
|
|
+ mKeyboardActionListener.onRelease(NOT_A_KEY);
|
|
|
+ } else {
|
|
|
+ int code = key.codes[0];
|
|
|
+ //TextEntryState.keyPressedAt(key, x, y);
|
|
|
+ int[] codes = new int[MAX_NEARBY_KEYS];
|
|
|
+ Arrays.fill(codes, NOT_A_KEY);
|
|
|
+ getKeyIndices(x, y, codes);
|
|
|
+ mKeyboardActionListener.onKey(code, codes);
|
|
|
+ mKeyboardActionListener.onRelease(code);
|
|
|
+
|
|
|
+ if (mKeys[index].label.equals("shift")) {
|
|
|
+ setCap(!cap);
|
|
|
+ postInvalidate();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ mLastSentIndex = index;
|
|
|
+ mLastTapTime = eventTime;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public int sp2px(float sp) {
|
|
|
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
|
|
|
+ getContext().getResources().getDisplayMetrics());
|
|
|
+ }
|
|
|
+
|
|
|
+ public int dp2px(float dp) {
|
|
|
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
|
|
|
+ getContext().getResources().getDisplayMetrics());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawCenterText(Canvas canvas, Paint paint, String text, float left, float top, float right, float bottom) {
|
|
|
+ float textWidth = paint.measureText(text);
|
|
|
+ float baseLineY = Math.abs(paint.ascent() + paint.descent()) / 2 + (top + bottom) / 2;
|
|
|
+ canvas.drawText(text, (left + right) / 2 - textWidth / 2, baseLineY, paint);
|
|
|
+ }
|
|
|
+}
|