• Android开发——为EditText添加烟花效果的实现


    0. 前言

    Android开发中,很多酷炫的效果是增加用户粘性的必要条件。本篇介绍一个为EditText添加烟花效果的示例,效果展示如下。



    1.  烟花效果需要关注的点

    1爆炸的位置:光标所在位置。

    2火花飞出的方向:这里采用随机方向,0180度,即只向上。

    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.  光标位置的识别

    如何获得光标的位置呢?涉及到反射,需要自己查看TextViewEditText的父类是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

  • 相关阅读:
    团队工作第四次推进之——软件设计规格说明书
    失物找寻APP软件需求规格说明书——第三次团队作业
    你还在为校园内丢失东西无处可寻而发愁吗?速戳进来
    十分有趣却有些遗憾的结对编程——两位女程序员的挣扎
    结对编程初涉猎——结对伙伴的代码复审
    个人实战演练全过程——No.1 最大连续子数组求和
    小白出品 单元测试相关——入门级说明书
    写着写着停不下来的普通女程序员的总结
    vs2010 和vs2012的区别 副标题--Loaded事件走两次
    汽车防撞软件引发的一套软件系统思路
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461443.html
Copyright © 2020-2023  润新知