4

Я пытаюсь найти способ реализовать эффект пульсации клавиш KeyboardView при нажатии. Звучит достаточно просто, но я пробовал все способы добавления пульсации, которые работают с другими типами представлений (listview, button и т. Д.) Без каких-либо успехов.Как реализовать эффект пульсации в Android KeyboardView?

Моя цель состоит в том, чтобы создать цифровую клавиатуру, которая выглядит как клавиатуры в приложении запас калькулятор, который поставляется по умолчанию в Lollipop ОС:

Stock Lollipop calculator

Там Похожая приложение калькулятор в GitHub (https://github.com/numixproject/com.numix.calculator), который имеет эффект пульсации на клавиатуре, но поскольку я читаю код, похоже, что он использует кнопки для цифровых клавиш вместо KeyboardView.

Надеюсь, эффект пульсации можно выполнить с помощью 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> 

и затем numeric_keypad_ripple.xml в вытяжке-v21 папка:

<?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, но это не должно иметь никакого значения, я полагаю.

Любая помощь приветствуется, спасибо заранее!

+0

Рябь только работать правильно, если установить в качестве вида фоны (и несколько других экземпляров, например, переднего плана FrameLayout и селектора AbsListView). Они не будут работать в KeyboardView из-за того, как они обрабатывают рендеринг и касаются взаимодействия. – alanv

+0

А я вижу, так что действительно невозможно использовать пульсацию на клавиатуре. Я подозревал это, но надеялся, что это все равно может быть сделано в некотором смысле. Спасибо за подтверждение, @alanv! – ABVincita

ответ

4

Просто отвечая на мой собственный вопрос, чтобы он мог помочь другим.

Как отметил в своем комментарии @alanv, Ripples не будет работать в KeyboardView из-за его различного способа обработки рендеринга и касания взаимодействия.

Так что ответ отрицательный, использовать эффект пульсации в Android KeyboardView невозможно.

Надеется, что это может спасти других от тратить время, пытаясь выяснить, как добавить рябь в KeyboardView :)

1

Моего ответ на это: есть не уже реализован способ (в других словах: простой способ) для этого. Но мы говорим об использовании технологии с открытым исходным кодом, поэтому ...

Если у вас есть время и терпение, вы можете создать пользовательский KeyboardView от original, чтобы переопределить способ создания его представлений по умолчанию с помощью макета, совместимого с рябью.

«Если вы не можете решить проблему, измените проблему» (Генри Форд).

0

может быть, это, кстати, с оттенком влияния на вводимого коэффициента, если кто-то нуждается в нем:

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> 
+0

где находятся чертежи –

+0

R.drawable.sym_keyboard_delete –

+0

вы помещаете, например, pngs в свою папку. убедитесь, что у вас есть правильное имя. например sym_keyboard_delete.png – th3matr1x

Смежные вопросы