Android EditText 添加烟花效果

摆脱枯燥的文字输入,让输入更加炫彩。 老规矩先上图。

Android EditText 添加烟花效果_第1张图片
应用宝动态截屏2016102001.gif

难点

难点一:获取光标的坐标

难点二:烟花动画实现

光标坐标的计算

我们发现 api里并没有可以直接获取光标坐标的方法。api没有并不是说就没有。源码里肯定有,不然他光标是怎么画出来的呢。对吧。打开EditView的源码,只有一百多行,里面并没有关于光标的代码,那只好找他爸爸了---TextView。打开吓一跳,一万多行的代码,看源码讲究根据蛛丝马迹来推算。光标的英文是cursor。

Android EditText 添加烟花效果_第2张图片
cursor 追踪

最终我们看到了

invalidateCursorPath()->invalidateCursor()->invalidateCursor(where, where, where)->invalidateRegion(start, end,true/* Also invalidates blinking cursor */);

终于找到了 这个方法invalidateRegion。

普及一下 android 字体的测量知识。

Android EditText 添加烟花效果_第3张图片
字体测量

光标的测量原理也是如此。我们需要得到光标的left和top的值,在加上padding的left和top值,就是我们光标在EditView里的偏移量了。

invalidate(bounds.left+ horizontalPadding, bounds.top+ verticalPadding,
bounds.right+ horizontalPadding, bounds.bottom+ verticalPadding);

我们寻找的偏移量

XOffset = bounds.left+ horizontalPadding=bounds.left+getCompoundPaddingLeft();
YOffset = bounds.bottom+ verticalPadding=bounds.bottom+getExtendedPaddingTop() + getVerticalOffset(true);

反射取值

Class clazz = EditText.class;

clazz = clazz.getSuperclass();

try{

Field editor = clazz.getDeclaredField("mEditor");

editor.setAccessible(true);

Object mEditor = editor.get(mEditText);

Class editorClazz = Class.forName("android.widget.Editor");

Field drawables = editorClazz.getDeclaredField("mCursorDrawable");

drawables.setAccessible(true);

Drawable[] drawable= (Drawable[]) drawables.get(mEditor);

Method getVerticalOffset = clazz.getDeclaredMethod("getVerticalOffset",boolean.class);

Method getCompoundPaddingLeft = clazz.getDeclaredMethod("getCompoundPaddingLeft");

Method getExtendedPaddingTop = clazz.getDeclaredMethod("getExtendedPaddingTop");

getVerticalOffset.setAccessible(true);

getCompoundPaddingLeft.setAccessible(true);

getExtendedPaddingTop.setAccessible(true);

if(drawable !=null){

Rect bounds = drawable[0].getBounds();

Log.d(TAG,bounds.toString());

xOffset = (int) getCompoundPaddingLeft.invoke(mEditText) + bounds.left;

yOffset = (int) getExtendedPaddingTop.invoke(mEditText) + (int)getVerticalOffset.invoke(mEditText,false)+bounds.bottom;

}

}catch(NoSuchMethodException e) {

e.printStackTrace();

}catch(InvocationTargetException e) {

e.printStackTrace();

}catch(IllegalAccessException e) {

e.printStackTrace();

}catch(NoSuchFieldException e) {

e.printStackTrace();

}catch(ClassNotFoundException e) {

e.printStackTrace();

}

floatx =mEditText.getX() + xOffset;

floaty =mEditText.getY() + yOffset;
到目前位置 我们已经解决第一个难题了。好接下是烟花动画绘制部分。

烟花动画

  • 烟花粒子
  • 烟花
  • 自定义View

烟花粒子

public class Element {
public int color;//颜色
public Double direction;//方向
public float speed;//速度
public float x;//坐标
public float y;
public Element(int color, Double direction, float speed) {
    super();
    this.color = color;
    this.direction = direction;
    this.speed = speed;
     
}

烟花

public class FireWork {

    private final String TAG = this.getClass().getSimpleName();
    private final static int DEFAULT_ELEMENT_COUNT = 12;// 默认 粒子的数量
    private final static float DEFAULT_ELEMENT_SIZE = 8;// 默认 粒子的尺寸
    private final static int DEFAULT_DURATION = 400;// 默认 动画间隔时间
    private final static float DEFAULT_LAUNCH_SPEED = 18;// 默认 粒子 加载时的 速度
    private final static float DEFAULT_WIND_SPEED = 6;// 默认 风的 素的
    private final static float DEFAULT_GRAVITY = 6;// 默认 重力大小

    private Paint mPaint;// 画笔

    private int count;// 粒子数量
    private int duration;// 间隔时间
    private int[] colors;// 颜色库
    private int color;

    private float launchSpeed;
    private int windDirection;// 1 or -1
    private float windSpeed;
    private float grivaty;
    private Location location;
    private float elemetSize;

    private ValueAnimator animator;
    private float animatorValue;

    private ArrayList elements = new ArrayList();
    private AnimationEndListener listener;

    public FireWork(Location location, int windDirection) {
        this.location = location;
        this.windDirection = windDirection;
        colors = baseColors;
        duration = DEFAULT_DURATION;
        grivaty = DEFAULT_GRAVITY;
        elemetSize = DEFAULT_ELEMENT_SIZE;
        launchSpeed = DEFAULT_LAUNCH_SPEED;
        windSpeed = DEFAULT_WIND_SPEED;
        count = DEFAULT_ELEMENT_COUNT;
        init();

    }

    private void init() {

        Random random = new Random();
        color = colors[random.nextInt(colors.length)];
        // 给每一个火花 设定一个随机的方向 0 - 180
        for (int i = 0; i < count; i++) {
            elements.add(new Element(color, Math.toRadians(random.nextInt(180)), random.nextFloat() * launchSpeed));
        }
        mPaint = new Paint();
        mPaint.setColor(color);

    }

    public void fire() {
        animator = ValueAnimator.ofInt(1, 0);
        animator.setDuration(duration);
        animator.setInterpolator(new AccelerateInterpolator());
        animator.addUpdateListener(new AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
             
                  animatorValue =   Float.parseFloat(animation.getAnimatedValue()+"") ;
                // 重点 计算每一个 火花的位置
                for(Element element :elements){
                     element.x = (float) (element.x + Math.cos(element.direction)*element.speed*animatorValue + windSpeed*windDirection);
                   element.y = (float) (element.y - Math.sin(element.direction)*element.speed*animatorValue + grivaty*(1-animatorValue));
                }
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                listener.onAinmationEnd();
            }
        });
        animator.start();
    }
    
    public void draw(Canvas canvas){
         mPaint.setAlpha((int) (225*animatorValue));
         for(Element element :elements){
             canvas.drawCircle(location.x + element.x, location.y + element.y, elemetSize, mPaint);
         }
        
    }
    
     public void setCount(int count){
            this.count = count;
        }

        public void setColors(int colors[]){
            this.colors = colors;
        }

        public void setDuration(int duration){
            this.duration = duration;
        }
        
        public void addAnimationEndListener(AnimationEndListener listener){
            this.listener = listener;
        }
    private static final int[] baseColors = { 0xFFFF43, 0x00E500, 0x44CEF6, 0xFF0040, 0xFF00FFB7, 0x008CFF, 0xFF5286,
            0x562CFF, 0x2C9DFF, 0x00FFFF, 0x00FF77, 0x11FF00, 0xFFB536, 0xFF4618, 0xFF334B, 0x9CFA18 };

    interface AnimationEndListener {
        void onAinmationEnd();
    }

    static class Location {
        public float x;
        public float y;

        public Location(float x, float y) {
            this.x = x;
            this.y = y;
        }
    }

自定义view

public class FireWorkView extends View {

    private final String TAG = this.getClass().getSimpleName();
    private EditText mEditText;
    private LinkedList fireWorks = new LinkedList();
    private int windSpeed;

    public FireWorkView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void bindEditText(EditText editText) {
        this.mEditText = editText;
        mEditText.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                float[] coordinate = getCursorCoordinate();
                 launch(coordinate[0], coordinate[1], before ==0?-1:1);
            }

            private void launch(float f, float g, int i) {
                  final FireWork firework = new FireWork(new FireWork.Location(f, g), i);
                    firework.addAnimationEndListener(new FireWork.AnimationEndListener() {
                        @Override
                        public void onAinmationEnd() {
                            //动画结束后把firework移除,当没有firework时不会刷新页面
                            fireWorks.remove(firework);
                        }
                    });
                    fireWorks.add(firework);
                    firework.fire();
                    invalidate();
                
            }

            private float[] getCursorCoordinate() {
                /*
                 * 以下通过反射获取光标cursor的坐标。
                 * 首先观察到TextView的invalidateCursorPath()方法,它是光标闪动时重绘的方法。
                 * 方法的最后有个invalidate(bounds.left + horizontalPadding, bounds.top
                 * + verticalPadding, bounds.right + horizontalPadding,
                 * bounds.bottom + verticalPadding); 即光标重绘的区域,由此可得到光标的坐标
                 * 具体的坐标在TextView.mEditor.mCursorDrawable里,
                 * 获得Drawable之后用getBounds()得到Rect。 之后还要获得偏移量修正,通过以下三个方法获得:
                 * getVerticalOffset(),getCompoundPaddingLeft(),
                 * getExtendedPaddingTop()。
                 *
                 */

                int xOffset = 0;
                int yOffset = 0;
                Class clazz = EditText.class;
                clazz = clazz.getSuperclass();// 获得 TextView 这个类
                try {
                    Field editor = clazz.getDeclaredField("mEditor");
                    editor.setAccessible(true);
                    Object mEditor = editor.get(mEditText);
                    Class editorClazz = Class.forName("android.widget.Editor");
                    Field drawables = editorClazz.getDeclaredField("mCursorDrawable");
                    drawables.setAccessible(true);
                    Drawable[] drawable = (Drawable[]) drawables.get(mEditor);
                    Method getVerticalOffset = clazz.getDeclaredMethod("getVerticalOffset", boolean.class);
                    Method getCompoundPaddingLeft = clazz.getDeclaredMethod("getCompoundPaddingLeft");
                    Method getExtendedPaddingTop = clazz.getDeclaredMethod("getExtendedPaddingTop");
                    getVerticalOffset.setAccessible(true);
                    getCompoundPaddingLeft.setAccessible(true);
                    getExtendedPaddingTop.setAccessible(true);

                    if (drawable != null) {
                        Rect bounds = drawable[0].getBounds();
                        xOffset = Integer.parseInt(getCompoundPaddingLeft.invoke(mEditText) + "") + bounds.left;
                        yOffset = Integer.parseInt(getExtendedPaddingTop.invoke(mEditText) + "")
                                + Integer.parseInt(getVerticalOffset.invoke(mEditText, false) + "") + bounds.bottom;

                    }
                } catch (NoSuchFieldException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                float x = mEditText.getX()+xOffset ;
                float y = mEditText.getY()+yOffset ;

                return new float[] { x, y };
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                // TODO Auto-generated method stub

            }

            @Override
            public void afterTextChanged(Editable s) {
                // TODO Auto-generated method stub

            }
        });

    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        
          for (int i =0 ; i0)
                invalidate();
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

见证奇迹的时刻

public class MainActivity extends Activity{
    
    private EditText mEditText;
    private FireWorkView mFireworkView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mEditText = (EditText) findViewById(R.id.edit_text);
        mFireworkView = (FireWorkView) findViewById(R.id.fireworkview);
        
mFireworkView.bindEditText(mEditText);
    }

到此我们烟花效果便是全部实现完毕。欢迎指正品评。最后,也是 最重要的 特别感谢 郭霖大神的技术支持。

射虎不成重练箭,斩龙不断再磨刀

你可能感兴趣的:(Android EditText 添加烟花效果)