0. 前言
在Android开发中,很多酷炫的效果是增加用户粘性的必要条件。本篇介绍一个为EditText添加烟花效果的示例,效果展示如下。
1. 烟花效果需要关注的点
(1)爆炸的位置:光标所在位置。
(2)火花飞出的方向:这里采用随机方向,0~180度,即只向上。
(3)发射速度:每个火花发射的速度是不一样的,在一定范围内随机。发射后速度衰减。
(4)风速固定:风向根据文字的增长或减少决定。
(5)重力:烟花飞出的应该是一条抛物线。
(6)火花的颜色:单次次发射的所有火花颜色一样,每次发射颜色随机。
(7)什么时候发射烟花:监听EditText的文字改变,获取文字数量的变化以确定风的方向,还有获取光标的位置确定爆炸的位置。光标的位置没有具体的方法确定坐标,要通过反射自己计算。
2. 主要实现类
库里包含三个类:
Element(int color, Double direction, floatspeed)
烟花的小火花,存放颜色,飞行方向,飞行速度这三个变量。
Firework(Location location, int windDirection)
烟花,控制整个烟花的动画,计算小火花的位置并绘制小火花。
FireworkView()
View类,监听EditText中文字的改变,获取光标的位置,并在该位置生成Firework。
3. 一些实现细节
首先我们看看FireworkView的使用方法:
mFireworkView = (FireworkView) findViewById(R.id.fire_work); mFireworkView.bindEditText(mEditText);
是不是很简单,只要绑定需要呈现烟花效果的EditText就行了。
//Class FireworkView: @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { /** *i为EditText里的字符数,i1为减少的字符数,i2为增加的字符数。 *关于launch的第三个参数,决定风的方向,1为吹向右边,-1为左边。 */ float [] coordinate = getCursorCoordinate(); launch(coordinate[0], coordinate[1], i1 ==0?-1:1); } @Override public void afterTextChanged(Editable editable) { } private void launch(float x, float y, int direction){ final Firework firework = new Firework(new Firework.Location(x, y), direction); firework.addAnimationEndListener(new Firework.AnimationEndListener() { @Override public void onAnimationEnd() { //动画结束后把firework移除,当没有firework时不会刷新页面 fireworks.remove(firework); } }); fireworks.add(firework); firework.fire(); invalidate(); }
在bindEditText()中我们监听EditText。当文字有改变时,首先计算文字是增多还是减少,以确定风的方向。然后getCursorCoordinate()获得光标的坐标。最后就可以发射烟花了。
用LinkedList<Firework>保存正在动画的Firework,如果里面Firework的数量不为0就不断地重绘view以实现动画,为0时不重绘。
//Class Firework public void fire(){ animator = ValueAnimator.ofFloat(1,0); animator.setDuration(duration); animator.setInterpolator(new AccelerateInterpolator(2)); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { animatorValue = (float) valueAnimator.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 + gravity*(1-animatorValue)); } } }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { listener.onAnimationEnd(); } }); animator.start(); }
用一个ValueAnimator实现动画。由于发射速度是衰减的,所以animator = ValueAnimator.ofFloat(1,0);同时设定一个new AccelerateInterpolator(2),即加速度是增长的。如果对Interpolator不熟悉可以看http://my.oschina.net/banxi/blog/135633 。
Class Firework: public void draw(Canvas canvas){ mPaint.setAlpha((int) (225*animatorValue)); for (Element element : elements){ canvas.drawCircle(location.x + element.x, location.y + element.y, elementSize, mPaint); } }
最后只要不断地绘制小火花就行了。
4. 光标位置的识别
如何获得光标的位置呢?涉及到反射,需要自己查看TextView(EditText的父类是TextView)的源码并理清绘制过程。下面注释说的很清楚了,这里就不再反复说明。
//Class FireworkView 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(); 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 ){ if (drawable[0] != 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(); } float x = mEditText.getX() + xOffset; float y = mEditText.getY() + yOffset; return new float[]{ x , y}; }
Github地址:https://github.com/covetcode/EditTextFirework-Demo
原文地址:http://www.jianshu.com/p/a001192aaa4b