Android EditText 添加烟花效果

作者: 猪_队友 | 来源:发表于2016-10-21 20:17 被阅读107次

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

    应用宝动态截屏2016102001.gif

    难点

    难点一:获取光标的坐标

    难点二:烟花动画实现

    光标坐标的计算

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

    cursor 追踪

    最终我们看到了

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

    终于找到了 这个方法invalidateRegion。

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

    字体测量

    光标的测量原理也是如此。我们需要得到光标的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<Element> elements = new ArrayList<Element>();
        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<FireWork> fireWorks = new LinkedList<FireWork>();
        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 ; i<fireWorks.size(); i++){
                  fireWorks.get(i).draw(canvas);
                }
                if (fireWorks.size()>0)
                    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 添加烟花效果

        本文链接:https://www.haomeiwen.com/subject/iqnfuttx.html