如何在 Android KeyboardView 中实现涟漪效果?
How to implement ripple effect in Android KeyboardView?
我一直在尝试寻找一种方法来在按下 KeyboardView 键时实现涟漪效应。听起来很简单,但我已经尝试了所有添加适用于其他类型视图(列表视图、按钮等)的波纹的方法,但都没有成功。
我的目标是创建一个数字小键盘,它看起来像 Lollipop 默认自带的标准计算器应用程序中的小键盘 OS:
GitHub (https://github.com/numixproject/com.numix.calculator) 中有一个类似的计算器应用程序,它在键盘上有涟漪效应,但当我阅读代码时,它似乎使用按钮作为数字键而不是键盘视图。
我希望 KeyboardView 可以实现涟漪效应,因为我的应用已经使用 KeyboardView 实现了自定义数字键盘,我不想将其更改为使用按钮。
我试过将波纹添加为 keyBackground
样式的属性,如下所示:
<android.inputmethodservice.KeyboardView
android:id="@+id/numeric_keypad"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:focusable="true"
android:focusableInTouchMode="true"
style="@style/my_numeric_keypad" />
然后在 themes.xml:
<style name="my_numeric_keypad">
<item name="android:keyTextSize">30dp</item>
<item name="android:fontFamily">roboto</item>
<item name="android:keyBackground">@drawable/numeric_keypad_ripple</item>
<item name="android:keyTextColor">@android:color/white</item>
</style>
然后在 drawable-v21 文件夹中 numeric_keypad_ripple.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:drawable="@drawable/numeric_keypad_states"/>
</ripple>
numeric_keypad_states.xml 是带有按下状态的旧选择器(它曾经被直接声明为 keyBackground
属性):
<?xml version="1.0" encoding="UTF-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/numeric_keypad_pressed" />
<item android:drawable="@drawable/numeric_keypad_normal" />
</selector>
numeric_keypad_pressed.xml 和 numeric_keypad_normal.xml 只是每个特定状态的颜色的可绘制对象,像这样(两者完全相同,只是颜色属性不同):
<?xml version="1.0" encoding="UTF-8" ?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="rectangle">
<solid android:color="@color/numeric_keypad_pressed_color"/>
</shape>
</item>
</layer-list>
我认为我上面的方法行得通;但事实并非如此。在我的 Lollipop 设备中,当我按下键盘时,它只显示正常按下的颜色,根本没有任何波纹,这与我没有波纹的旧实现没有区别。我试过移除该层,因为我认为它以某种方式与波纹重叠,但仍然不起作用。为波纹添加遮罩也不起作用。
我也尝试过使用 rippledrawable 作为选择器中的按下状态可绘制对象而不是包装选择器,但它仍然无法正常工作。还尝试使用 ?android:attr/selectableItemBackground
而不是 rippledrawable 但也不起作用。
哦,我实际上是在 Xamarin 而不是本机上开发应用程序 Android,但我认为这应该没有任何区别。
只是回答我自己的问题,希望对其他人有所帮助。
正如@alanv 在他的评论中提到的,Ripples 在 KeyboardView 中不起作用,因为它处理渲染和触摸交互的方式不同。
所以答案是否定的,无法在 Android KeyboardView 中使用波纹效果。
希望这可以避免其他人浪费时间试图找出如何向 KeyboardView 添加波纹:)
我对此的回应是:没有一种已经实现的方法(换句话说:一种简单的方法)来做到这一点。但是我们说的是开源技术,所以...
如果您有时间和耐心,可以从 original 创建一个自定义的 KeyboardView,以重新定义默认组件创建其具有波纹兼容布局的视图的方式。
"If you can't solve a problem, change the problem"(亨利·福特)。
也许这个,顺便说一句,如果有人需要的话,对可绘制对象有色调效果:
import java.util.List;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.animation.ValueAnimator;
import com.nineoldandroids.animation.Animator.AnimatorListener;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.Keyboard.Key;
import android.os.Build;
import android.inputmethodservice.KeyboardView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.graphics.Region.Op;
public class RippleKeyboardView extends KeyboardView
{
private Bitmap tintedBitmap,tintedBitmap2;
private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private Paint mPaint = new Paint();
private int tintcolor = 0xffff0000;
private BitmapDrawable mDrawable,mDrawable2;
private int invalidatekeyindex = -1;
private static final int NOT_A_KEY = -1;
private float animationcircleProgress;
private Paint circlePaint = new Paint();
private ValueAnimator circleAnimator;
private static final int ANIMATION_TIME_ID = android.R.integer.config_shortAnimTime;
private float ripplex,rippley;
private float textoffsety;
private int keytextsize;
private int mLabelTextSize;
private Rect mDirtyRect = new Rect();
private boolean mDrawPending;
private Bitmap mBuffer;
private boolean mKeyboardChanged;
private Canvas mCanvas;
private Keyboard mKeyboard;
private List<Key> mkeys;
private Rect tobeinvalidated=new Rect();
@SuppressWarnings("deprecation")
@TargetApi(21)
public RippleKeyboardView(Context context, AttributeSet attrs)
{
super(context, attrs);
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
tintedBitmap = generateIconBitmaps(getBitmapFromDrawable(getResources().getDrawable(R.drawable.sym_keyboard_delete,null)));
tintedBitmap2 = generateIconBitmaps(getBitmapFromDrawable(getResources().getDrawable(R.drawable.sym_keyboard_feedback_return,null)));
}
else
{
tintedBitmap = generateIconBitmaps(getBitmapFromDrawable(getResources().getDrawable(R.drawable.sym_keyboard_delete)));
tintedBitmap2 = generateIconBitmaps(getBitmapFromDrawable(getResources().getDrawable(R.drawable.sym_keyboard_feedback_return)));
}
mDrawable = new BitmapDrawable(getResources(), tintedBitmap);
mDrawable2 = new BitmapDrawable(getResources(), tintedBitmap2);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.RippleKeyboardView, 0, 0);
keytextsize = a.getDimensionPixelSize(R.styleable.RippleKeyboardView_keytxtsize,60);
mLabelTextSize = a.getDimensionPixelSize(R.styleable.RippleKeyboardView_lblsize, 40);
a.recycle();
animationcircleProgress = 0;
final int pressedAnimationTime = getResources().getInteger(ANIMATION_TIME_ID);
circleAnimator = ObjectAnimator.ofFloat(this, "animationlayerProgress", 100, 0f);
circleAnimator.setDuration(pressedAnimationTime);
circleAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mPaint.setColor(0xff000000);
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Align.CENTER);
mPaint.setAlpha(255);
circlePaint.setColor(0x77989898);
super.setPreviewEnabled(false);
invalidateAllKeys();
}
private Bitmap getBitmapFromDrawable(Drawable drawable)
{
if (drawable == null)
return null;
if (drawable instanceof BitmapDrawable)
return ((BitmapDrawable) drawable).getBitmap();
try
{
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
catch (OutOfMemoryError e)
{
return null;
}
}
private Bitmap generateIconBitmaps(Bitmap origin)
{
if (origin == null)
return null;
Bitmap bmp = origin.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(bmp);
canvas.drawColor(tintcolor & 0x00ffffff | 0xff000000 , PorterDuff.Mode.SRC_IN);
origin.recycle();
return bmp;
}
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
mBuffer = null;
}
@Override
public void setKeyboard(Keyboard keyboard)
{
super.setKeyboard(keyboard);
mKeyboardChanged = true;
mKeyboard = keyboard;
invalidateAllKeys();
}
@Override
public void onDraw(Canvas canvas)
{
if (mDrawPending || mBuffer == null || mKeyboardChanged)
onBufferDraw();
canvas.drawBitmap(mBuffer, 0, 0, null);
}
public void onBufferDraw()
{
if (mBuffer == null || mKeyboardChanged)
{
if (mBuffer == null || mKeyboardChanged && (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight()))
{
final int width = Math.max(1, getWidth());
final int height = Math.max(1, getHeight());
mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBuffer);
mkeys = getKeyboard().getKeys();
}
invalidateAllKeys();
mKeyboardChanged = false;
}
final Canvas canvas = mCanvas;
canvas.clipRect(mDirtyRect, Op.REPLACE);
canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
if (mKeyboard == null)
return;
List<android.inputmethodservice.Keyboard.Key> keys = mkeys;
for (android.inputmethodservice.Keyboard.Key key : keys)
{
canvas.translate(key.x , key.y );
if (key.codes[0] == 67)
{
final int drawableX = (key.width - 0 - key.icon.getIntrinsicWidth()) / 2 + 0;
final int drawableY = (key.height - 0 - key.icon.getIntrinsicHeight()) / 2 + 0;
canvas.translate(drawableX, drawableY);
mDrawable.setBounds(0, 0, key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
mDrawable.draw(canvas);
canvas.translate(-drawableX, -drawableY);
}
else if (key.codes[0] == 66)
{
final int drawableX = (key.width - 0 - key.icon.getIntrinsicWidth()) / 2 + 0;
final int drawableY = (key.height - 0 - key.icon.getIntrinsicHeight()) / 2 + 0;
canvas.translate(drawableX, drawableY);
mDrawable2.setBounds(0, 0, key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
mDrawable2.draw(canvas);
canvas.translate(-drawableX, -drawableY);
}
else
{
String label = key.label.toString();
if (label.length()>1 && key.codes.length < 2)
{
mPaint.setTextSize(mLabelTextSize);
mPaint.setTypeface(Typeface.DEFAULT_BOLD);
}
else
{
mPaint.setTextSize(keytextsize);
mPaint.setTypeface(Typeface.DEFAULT);
}
textoffsety= (mPaint.getTextSize() - mPaint.descent()) / 2;
canvas.drawText(label,(key.width ) / 2,(key.height ) / 2+ textoffsety, mPaint);
}
canvas.translate(-key.x , -key.y );
}
if (invalidatekeyindex!=-1)
canvas.drawCircle(ripplex , rippley, getAnimationlayerProgress(), circlePaint);
mDrawPending = false;
mDirtyRect.setEmpty();
}
@Override
public void invalidateAllKeys()
{
mDirtyRect.union(0, 0, getWidth(), getHeight());
mDrawPending = true;
}
@Override
public void invalidateKey(int keyIndex)
{
List<android.inputmethodservice.Keyboard.Key> mKeys = mkeys;
if (mKeys == null) return;
if (keyIndex < 0 || keyIndex >= mKeys.size())
return;
final Key key = mKeys.get(keyIndex);
mDirtyRect.union(key.x , key.y , key.x + key.width , key.y + key.height );
invalidate(key.x , key.y , key.x + key.width , key.y + key.height );
}
@Override
public boolean performClick()
{
super.performClick();
return true;
}
@Override
public boolean onTouchEvent(MotionEvent me)
{
boolean ret = super.onTouchEvent(me);
final int action = me.getAction();
int primaryIndex = NOT_A_KEY;
if (action == MotionEvent.ACTION_UP)
{
int [] nearestKeyIndices=getKeyboard().getNearestKeys((int)me.getX(),(int) me.getY());
final int keyCount = nearestKeyIndices.length;
List<android.inputmethodservice.Keyboard.Key> keys = mkeys;
for (int i = 0; i < keyCount; i++)
{
final android.inputmethodservice.Keyboard.Key key = keys.get(nearestKeyIndices[i]);
boolean isInside = key.isInside((int)me.getX(),(int)me.getY());
if (isInside)
primaryIndex = nearestKeyIndices[i];
}
}
if (primaryIndex!=NOT_A_KEY)
{
DrawCustomRipple(primaryIndex);
}
performClick();
return ret;
}
private void DrawCustomRipple(int keyindex)
{
if (circleAnimator.isRunning())
{
tobeinvalidated.set((int)(ripplex-animationcircleProgress-4),
(int)(rippley-animationcircleProgress-4),
(int)(ripplex+animationcircleProgress+4),
(int)(rippley+animationcircleProgress+4));
circleAnimator.removeAllListeners();
invalidatekeyindex = -1;
mDrawPending = true;
invalidatekeyindex = -1;
mDirtyRect.union(tobeinvalidated);
invalidate(tobeinvalidated);
}
invalidatekeyindex=keyindex;
List<android.inputmethodservice.Keyboard.Key> keys = mkeys;
final android.inputmethodservice.Keyboard.Key cKey=keys.get(invalidatekeyindex);
if (cKey.label == null && cKey.codes[0] != 67 && cKey.codes[0] != 66)
return;
circleAnimator.setFloatValues(30.0f,100.0f);
circleAnimator.addListener(new AnimatorListener()
{
@Override
public void onAnimationStart(Animator animation)
{
final Rect bounds=new Rect();
if (cKey.label!=null)
{
String label = cKey.label.toString();
mPaint.getTextBounds(label, 0, 1, bounds);
}
ripplex =cKey.x+(cKey.width ) / 2+bounds.width()/2;
rippley = cKey.y + (cKey.height ) / 2;
}
@Override
public void onAnimationEnd(Animator animation)
{
invalidatekeyindex = -1;
mDrawPending = true;
tobeinvalidated.set((int)(ripplex-animationcircleProgress-4),
(int)(rippley-animationcircleProgress-4),
(int)(ripplex+animationcircleProgress+4),
(int)(rippley+animationcircleProgress+4));
mDirtyRect.union(tobeinvalidated);
onBufferDraw();
invalidate(tobeinvalidated);
}
@Override
public void onAnimationCancel(Animator animation)
{
}
@Override
public void onAnimationRepeat(Animator animation)
{
}
});
circleAnimator.start();
}
public float getAnimationlayerProgress()
{
return animationcircleProgress;
}
public void setAnimationlayerProgress(float animationlayerProgress)
{
this.animationcircleProgress = animationlayerProgress;
tobeinvalidated.set((int)(ripplex-animationcircleProgress-4),
(int)(rippley-animationcircleProgress-4),
(int)(ripplex+animationcircleProgress+4),
(int)(rippley+animationcircleProgress+4));
mDirtyRect.union((int)(ripplex-animationcircleProgress-4),
(int)(rippley-animationcircleProgress-4),
(int)(ripplex+animationcircleProgress+4),
(int)(rippley+animationcircleProgress+4) );
mDrawPending = true;
invalidate( (int)(ripplex-animationcircleProgress-4),
(int)(rippley-animationcircleProgress-4),
(int)(ripplex+animationcircleProgress+4),
(int)(rippley+animationcircleProgress+4));
}
}
并添加此 attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources >
<resources>
<declare-styleable name="RippleKeyboardView">
<attr name="keytxtsize" format="dimension" />
<attr name="lblsize" format="dimension" />
</declare-styleable>
</resources>
我一直在尝试寻找一种方法来在按下 KeyboardView 键时实现涟漪效应。听起来很简单,但我已经尝试了所有添加适用于其他类型视图(列表视图、按钮等)的波纹的方法,但都没有成功。
我的目标是创建一个数字小键盘,它看起来像 Lollipop 默认自带的标准计算器应用程序中的小键盘 OS:
GitHub (https://github.com/numixproject/com.numix.calculator) 中有一个类似的计算器应用程序,它在键盘上有涟漪效应,但当我阅读代码时,它似乎使用按钮作为数字键而不是键盘视图。
我希望 KeyboardView 可以实现涟漪效应,因为我的应用已经使用 KeyboardView 实现了自定义数字键盘,我不想将其更改为使用按钮。
我试过将波纹添加为 keyBackground
样式的属性,如下所示:
<android.inputmethodservice.KeyboardView
android:id="@+id/numeric_keypad"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:focusable="true"
android:focusableInTouchMode="true"
style="@style/my_numeric_keypad" />
然后在 themes.xml:
<style name="my_numeric_keypad">
<item name="android:keyTextSize">30dp</item>
<item name="android:fontFamily">roboto</item>
<item name="android:keyBackground">@drawable/numeric_keypad_ripple</item>
<item name="android:keyTextColor">@android:color/white</item>
</style>
然后在 drawable-v21 文件夹中 numeric_keypad_ripple.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:drawable="@drawable/numeric_keypad_states"/>
</ripple>
numeric_keypad_states.xml 是带有按下状态的旧选择器(它曾经被直接声明为 keyBackground
属性):
<?xml version="1.0" encoding="UTF-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/numeric_keypad_pressed" />
<item android:drawable="@drawable/numeric_keypad_normal" />
</selector>
numeric_keypad_pressed.xml 和 numeric_keypad_normal.xml 只是每个特定状态的颜色的可绘制对象,像这样(两者完全相同,只是颜色属性不同):
<?xml version="1.0" encoding="UTF-8" ?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="rectangle">
<solid android:color="@color/numeric_keypad_pressed_color"/>
</shape>
</item>
</layer-list>
我认为我上面的方法行得通;但事实并非如此。在我的 Lollipop 设备中,当我按下键盘时,它只显示正常按下的颜色,根本没有任何波纹,这与我没有波纹的旧实现没有区别。我试过移除该层,因为我认为它以某种方式与波纹重叠,但仍然不起作用。为波纹添加遮罩也不起作用。
我也尝试过使用 rippledrawable 作为选择器中的按下状态可绘制对象而不是包装选择器,但它仍然无法正常工作。还尝试使用 ?android:attr/selectableItemBackground
而不是 rippledrawable 但也不起作用。
哦,我实际上是在 Xamarin 而不是本机上开发应用程序 Android,但我认为这应该没有任何区别。
只是回答我自己的问题,希望对其他人有所帮助。
正如@alanv 在他的评论中提到的,Ripples 在 KeyboardView 中不起作用,因为它处理渲染和触摸交互的方式不同。
所以答案是否定的,无法在 Android KeyboardView 中使用波纹效果。
希望这可以避免其他人浪费时间试图找出如何向 KeyboardView 添加波纹:)
我对此的回应是:没有一种已经实现的方法(换句话说:一种简单的方法)来做到这一点。但是我们说的是开源技术,所以...
如果您有时间和耐心,可以从 original 创建一个自定义的 KeyboardView,以重新定义默认组件创建其具有波纹兼容布局的视图的方式。
"If you can't solve a problem, change the problem"(亨利·福特)。
也许这个,顺便说一句,如果有人需要的话,对可绘制对象有色调效果:
import java.util.List;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.animation.ValueAnimator;
import com.nineoldandroids.animation.Animator.AnimatorListener;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.Keyboard.Key;
import android.os.Build;
import android.inputmethodservice.KeyboardView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.graphics.Region.Op;
public class RippleKeyboardView extends KeyboardView
{
private Bitmap tintedBitmap,tintedBitmap2;
private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private Paint mPaint = new Paint();
private int tintcolor = 0xffff0000;
private BitmapDrawable mDrawable,mDrawable2;
private int invalidatekeyindex = -1;
private static final int NOT_A_KEY = -1;
private float animationcircleProgress;
private Paint circlePaint = new Paint();
private ValueAnimator circleAnimator;
private static final int ANIMATION_TIME_ID = android.R.integer.config_shortAnimTime;
private float ripplex,rippley;
private float textoffsety;
private int keytextsize;
private int mLabelTextSize;
private Rect mDirtyRect = new Rect();
private boolean mDrawPending;
private Bitmap mBuffer;
private boolean mKeyboardChanged;
private Canvas mCanvas;
private Keyboard mKeyboard;
private List<Key> mkeys;
private Rect tobeinvalidated=new Rect();
@SuppressWarnings("deprecation")
@TargetApi(21)
public RippleKeyboardView(Context context, AttributeSet attrs)
{
super(context, attrs);
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
tintedBitmap = generateIconBitmaps(getBitmapFromDrawable(getResources().getDrawable(R.drawable.sym_keyboard_delete,null)));
tintedBitmap2 = generateIconBitmaps(getBitmapFromDrawable(getResources().getDrawable(R.drawable.sym_keyboard_feedback_return,null)));
}
else
{
tintedBitmap = generateIconBitmaps(getBitmapFromDrawable(getResources().getDrawable(R.drawable.sym_keyboard_delete)));
tintedBitmap2 = generateIconBitmaps(getBitmapFromDrawable(getResources().getDrawable(R.drawable.sym_keyboard_feedback_return)));
}
mDrawable = new BitmapDrawable(getResources(), tintedBitmap);
mDrawable2 = new BitmapDrawable(getResources(), tintedBitmap2);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.RippleKeyboardView, 0, 0);
keytextsize = a.getDimensionPixelSize(R.styleable.RippleKeyboardView_keytxtsize,60);
mLabelTextSize = a.getDimensionPixelSize(R.styleable.RippleKeyboardView_lblsize, 40);
a.recycle();
animationcircleProgress = 0;
final int pressedAnimationTime = getResources().getInteger(ANIMATION_TIME_ID);
circleAnimator = ObjectAnimator.ofFloat(this, "animationlayerProgress", 100, 0f);
circleAnimator.setDuration(pressedAnimationTime);
circleAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mPaint.setColor(0xff000000);
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Align.CENTER);
mPaint.setAlpha(255);
circlePaint.setColor(0x77989898);
super.setPreviewEnabled(false);
invalidateAllKeys();
}
private Bitmap getBitmapFromDrawable(Drawable drawable)
{
if (drawable == null)
return null;
if (drawable instanceof BitmapDrawable)
return ((BitmapDrawable) drawable).getBitmap();
try
{
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
catch (OutOfMemoryError e)
{
return null;
}
}
private Bitmap generateIconBitmaps(Bitmap origin)
{
if (origin == null)
return null;
Bitmap bmp = origin.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(bmp);
canvas.drawColor(tintcolor & 0x00ffffff | 0xff000000 , PorterDuff.Mode.SRC_IN);
origin.recycle();
return bmp;
}
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
mBuffer = null;
}
@Override
public void setKeyboard(Keyboard keyboard)
{
super.setKeyboard(keyboard);
mKeyboardChanged = true;
mKeyboard = keyboard;
invalidateAllKeys();
}
@Override
public void onDraw(Canvas canvas)
{
if (mDrawPending || mBuffer == null || mKeyboardChanged)
onBufferDraw();
canvas.drawBitmap(mBuffer, 0, 0, null);
}
public void onBufferDraw()
{
if (mBuffer == null || mKeyboardChanged)
{
if (mBuffer == null || mKeyboardChanged && (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight()))
{
final int width = Math.max(1, getWidth());
final int height = Math.max(1, getHeight());
mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBuffer);
mkeys = getKeyboard().getKeys();
}
invalidateAllKeys();
mKeyboardChanged = false;
}
final Canvas canvas = mCanvas;
canvas.clipRect(mDirtyRect, Op.REPLACE);
canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
if (mKeyboard == null)
return;
List<android.inputmethodservice.Keyboard.Key> keys = mkeys;
for (android.inputmethodservice.Keyboard.Key key : keys)
{
canvas.translate(key.x , key.y );
if (key.codes[0] == 67)
{
final int drawableX = (key.width - 0 - key.icon.getIntrinsicWidth()) / 2 + 0;
final int drawableY = (key.height - 0 - key.icon.getIntrinsicHeight()) / 2 + 0;
canvas.translate(drawableX, drawableY);
mDrawable.setBounds(0, 0, key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
mDrawable.draw(canvas);
canvas.translate(-drawableX, -drawableY);
}
else if (key.codes[0] == 66)
{
final int drawableX = (key.width - 0 - key.icon.getIntrinsicWidth()) / 2 + 0;
final int drawableY = (key.height - 0 - key.icon.getIntrinsicHeight()) / 2 + 0;
canvas.translate(drawableX, drawableY);
mDrawable2.setBounds(0, 0, key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
mDrawable2.draw(canvas);
canvas.translate(-drawableX, -drawableY);
}
else
{
String label = key.label.toString();
if (label.length()>1 && key.codes.length < 2)
{
mPaint.setTextSize(mLabelTextSize);
mPaint.setTypeface(Typeface.DEFAULT_BOLD);
}
else
{
mPaint.setTextSize(keytextsize);
mPaint.setTypeface(Typeface.DEFAULT);
}
textoffsety= (mPaint.getTextSize() - mPaint.descent()) / 2;
canvas.drawText(label,(key.width ) / 2,(key.height ) / 2+ textoffsety, mPaint);
}
canvas.translate(-key.x , -key.y );
}
if (invalidatekeyindex!=-1)
canvas.drawCircle(ripplex , rippley, getAnimationlayerProgress(), circlePaint);
mDrawPending = false;
mDirtyRect.setEmpty();
}
@Override
public void invalidateAllKeys()
{
mDirtyRect.union(0, 0, getWidth(), getHeight());
mDrawPending = true;
}
@Override
public void invalidateKey(int keyIndex)
{
List<android.inputmethodservice.Keyboard.Key> mKeys = mkeys;
if (mKeys == null) return;
if (keyIndex < 0 || keyIndex >= mKeys.size())
return;
final Key key = mKeys.get(keyIndex);
mDirtyRect.union(key.x , key.y , key.x + key.width , key.y + key.height );
invalidate(key.x , key.y , key.x + key.width , key.y + key.height );
}
@Override
public boolean performClick()
{
super.performClick();
return true;
}
@Override
public boolean onTouchEvent(MotionEvent me)
{
boolean ret = super.onTouchEvent(me);
final int action = me.getAction();
int primaryIndex = NOT_A_KEY;
if (action == MotionEvent.ACTION_UP)
{
int [] nearestKeyIndices=getKeyboard().getNearestKeys((int)me.getX(),(int) me.getY());
final int keyCount = nearestKeyIndices.length;
List<android.inputmethodservice.Keyboard.Key> keys = mkeys;
for (int i = 0; i < keyCount; i++)
{
final android.inputmethodservice.Keyboard.Key key = keys.get(nearestKeyIndices[i]);
boolean isInside = key.isInside((int)me.getX(),(int)me.getY());
if (isInside)
primaryIndex = nearestKeyIndices[i];
}
}
if (primaryIndex!=NOT_A_KEY)
{
DrawCustomRipple(primaryIndex);
}
performClick();
return ret;
}
private void DrawCustomRipple(int keyindex)
{
if (circleAnimator.isRunning())
{
tobeinvalidated.set((int)(ripplex-animationcircleProgress-4),
(int)(rippley-animationcircleProgress-4),
(int)(ripplex+animationcircleProgress+4),
(int)(rippley+animationcircleProgress+4));
circleAnimator.removeAllListeners();
invalidatekeyindex = -1;
mDrawPending = true;
invalidatekeyindex = -1;
mDirtyRect.union(tobeinvalidated);
invalidate(tobeinvalidated);
}
invalidatekeyindex=keyindex;
List<android.inputmethodservice.Keyboard.Key> keys = mkeys;
final android.inputmethodservice.Keyboard.Key cKey=keys.get(invalidatekeyindex);
if (cKey.label == null && cKey.codes[0] != 67 && cKey.codes[0] != 66)
return;
circleAnimator.setFloatValues(30.0f,100.0f);
circleAnimator.addListener(new AnimatorListener()
{
@Override
public void onAnimationStart(Animator animation)
{
final Rect bounds=new Rect();
if (cKey.label!=null)
{
String label = cKey.label.toString();
mPaint.getTextBounds(label, 0, 1, bounds);
}
ripplex =cKey.x+(cKey.width ) / 2+bounds.width()/2;
rippley = cKey.y + (cKey.height ) / 2;
}
@Override
public void onAnimationEnd(Animator animation)
{
invalidatekeyindex = -1;
mDrawPending = true;
tobeinvalidated.set((int)(ripplex-animationcircleProgress-4),
(int)(rippley-animationcircleProgress-4),
(int)(ripplex+animationcircleProgress+4),
(int)(rippley+animationcircleProgress+4));
mDirtyRect.union(tobeinvalidated);
onBufferDraw();
invalidate(tobeinvalidated);
}
@Override
public void onAnimationCancel(Animator animation)
{
}
@Override
public void onAnimationRepeat(Animator animation)
{
}
});
circleAnimator.start();
}
public float getAnimationlayerProgress()
{
return animationcircleProgress;
}
public void setAnimationlayerProgress(float animationlayerProgress)
{
this.animationcircleProgress = animationlayerProgress;
tobeinvalidated.set((int)(ripplex-animationcircleProgress-4),
(int)(rippley-animationcircleProgress-4),
(int)(ripplex+animationcircleProgress+4),
(int)(rippley+animationcircleProgress+4));
mDirtyRect.union((int)(ripplex-animationcircleProgress-4),
(int)(rippley-animationcircleProgress-4),
(int)(ripplex+animationcircleProgress+4),
(int)(rippley+animationcircleProgress+4) );
mDrawPending = true;
invalidate( (int)(ripplex-animationcircleProgress-4),
(int)(rippley-animationcircleProgress-4),
(int)(ripplex+animationcircleProgress+4),
(int)(rippley+animationcircleProgress+4));
}
}
并添加此 attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources >
<resources>
<declare-styleable name="RippleKeyboardView">
<attr name="keytxtsize" format="dimension" />
<attr name="lblsize" format="dimension" />
</declare-styleable>
</resources>