Android EditText 添加烟花效果

来源:互联网 发布:汇鑫通网络支付 编辑:程序博客网 时间:2024/05/16 12:21

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

应用宝动态截屏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);    }

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

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

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 毕业证被学校扣了怎么办 自考本科档案没有密封怎么办 艺术生统考没过怎么办 本科科目没考过怎么办 军校体检条丢了怎么办 学美术考了大专怎么办 音基试唱音不准怎么办 音基证书丢了怎么办 职称计算机证书丢了怎么办 30岁了 开始怀旧怎么办 31岁了 缺乏运动怎么办 30多了还一事无成未来怎么办 导师说名额已满怎么办 在中国想当大官怎么办 站久了小腿变粗怎么办 苹果x锁屏延迟怎么办 被扇了巴掌耳痛怎么办 被扇了巴掌耳鸣怎么办 水兵舞不会扭胯怎么办 我的字写得很丑怎么办 二年级学生不会造句怎么办 w10下载种子文件失败怎么办 宝宝挂水手肿了怎么办 lol有英雄皮肤没英雄怎么办 qq聊天图标粉色钥匙怎么办 和舍友相处不来怎么办 被舍友偷了东西怎么办 体育生没过线怎么办 户主去世房产不能过户怎么办 苍蝇飞到嘴唇上怎么办 苍蝇不小心碰到嘴唇了怎么办 苍蝇老往身上飞怎么办 单位乒乓球比赛有领导参加怎么办 意外看到别人打野战怎么办 骨盆低想顺产要怎么办 右胯比左胯突出怎么办 一岁宝宝骨盆不对称怎么办 入盆了又出来了怎么办 大腿前突小腿后怎么办 英语不好高二了怎么办 断奶后又复吸怎么办