为你的EditText添加一个烟花效果

关键点:
爆炸的位置:光标所在位置。

火花飞出的方向:我采用随机方向,0~180度,即只向上。

发射速度:每个火花发射的速度是不一样的,在一定范围内随机。发射后速度衰减。

风:风速固定,方向根据文字的增长或减少决定。

重力:烟花飞出的应该是一条抛物线。

火花的颜色:单次次发射的所有火花颜色一样,每次从颜色库随机挑选。

什么时候发射烟花:监听edittext,当文字改变时,获取文字数量的变化以确定风的方向。获取光标的位置确定爆炸的位置。

难点:光标的位置;反射;没有具体的方法确定坐标,要自己计算。
布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" tools:context="com.example.demoeiter.MainActivity">

    <TextView
        android:layout_marginTop="100dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#6e6e6e"
        android:gravity="center"
        android:text="EditText-Firework"/>
    <EditText
        android:layout_marginTop="150dp"
        android:layout_centerHorizontal="true"
        android:textColor="#6e6e6e"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:id="@+id/edit_text"/>
    <com.example.demoeiter.FireworkView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/fire_work"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="day"
        android:padding="10dp"
        android:textColor="#FFFFFF"
        android:id="@+id/day"
        android:layout_alignTop="@+id/night"
        android:layout_alignRight="@+id/edit_text"
        android:layout_alignEnd="@+id/edit_text" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="night"
        android:textColor="#000000"
        android:id="@+id/night"
        android:layout_marginBottom="40dp"
        android:layout_alignParentBottom="true"
        android:layout_alignLeft="@+id/edit_text"
        android:layout_alignStart="@+id/edit_text" />

</RelativeLayout>

Element

package com.example.demoeiter;

/**烟花的小火花,存放颜色,飞行方向,飞行速度这三个变量。
 * Created by 李倩 on 2017/12/8.
 */

public class Element {
    public int color;
    public Double direction;
    public float speed;
    public float x = 0;
    public float y = 0;
//Element(int color, Double direction, float speed)
//    烟花的小火花,存放颜色,飞行方向,飞行速度这三个变量。
    public Element(int color, Double direction, float speed){
        this.color = color;
        this.direction = direction;
        this.speed = speed;
    }
}

Firework

package com.example.demoeiter;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.animation.AccelerateInterpolator;

import java.util.ArrayList;
import java.util.Random;


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 ;     //count of element
    private int duration;
    private int[] colors;
    private int color;
    private float launchSpeed;
    private float windSpeed;
    private float gravity;
    private int windDirection;      //1 or -1
    private Location location;
    private float elementSize;
    private ValueAnimator animator;
    private float animatorValue;
    private ArrayList<Element> elements = new ArrayList<>();
    private AnimationEndListener listener;

    /**
     * Firework(Location location, int windDirection)
      烟花,控制整个烟花的动画,计算小火花的位置并绘制小火花。
     * @param location
     * @param windDirection
     */
    public Firework(Location location, int windDirection){
        this.location = location;
        this.windDirection = windDirection;

        colors = baseColors;
        duration = DEFAULT_DURATION;
        gravity = DEFAULT_GRAVITY;
        elementSize = 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);
        //  BlurMaskFilter maskFilter = new BlurMaskFilter(2, BlurMaskFilter.Blur.NORMAL);
        //  mPaint.setMaskFilter(maskFilter);

    }

    /**
     * 用一个 ValueAnimator 实现动画。
     * 由于发射速度是衰减的,
     * 所以需要设定一个 new AccelerateInterpolator(2)。
     */
    public void fire(){
        animator = ValueAnimator.ofFloat(1,0);
        animator.setDuration(duration);
        animator.setInterpolator(new AccelerateInterpolator());
        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();
    }

    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;
    }

    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);
        }
    }

    private static final int[] baseColors = {0xFFFF43,0x00E500,0x44CEF6,0xFF0040,0xFF00FFB7,0x008CFF
            ,0xFF5286,0x562CFF,0x2C9DFF,0x00FFFF,0x00FF77,0x11FF00,0xFFB536,0xFF4618,0xFF334B,0x9CFA18};

    interface AnimationEndListener{
        void onAnimationEnd();
    }

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

FireworkView

package com.example.demoeiter;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;

/**FireworkView()
    View类,监听 EditText 中文字的改变,并获取光标的位置。在该位置生成 Firework。
 * Created by wayww on 2016/9/8.
 */
public class FireworkView extends View {

    private final String TAG = this.getClass().getSimpleName();
    private EditText mEditText;
    private LinkedList<Firework> fireworks = new LinkedList<>();
    private int windSpeed;
    private TextWatcher mTextWatcher;

    public FireworkView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

    public FireworkView(Context context) {
        super(context);
    }

   // 在 bindEditText() 中我们监听 EditText。当文字有改变时,首先计算文字是增多还是减少,
    //以确定风的方向。然后 getCursorCoordinate() 获得光标的坐标。最后就可以发射烟花了
    public void bindEditText(EditText editText) {
        this.mEditText = editText;
        mEditText.addTextChangedListener( mTextWatcher = new TextWatcher() {
            @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) {

            }

        });
    }
    public void removeBind(){
        mEditText.removeTextChangedListener(mTextWatcher);
        mEditText = null;
    }
    //~~~~~~~~~~~~~private method~~~~~~~~~~~~~~~~~~~

    /**
     * 用 LinkedList<Firework> 保存正在动画的 Firework,如果里面 Firework
     * 的数量不为0就不断地 重绘view 以实现动画,为0时不重绘。
     * @param x
     * @param y
     * @param direction
     */
    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();
    }
    /**获得光标
     * @return the coordinate of cursor. x=float[0]; y=float[1];
     */
    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){
                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;

        //当EditText的父view与FireworkView的坐标(左上角的坐标值)不一致时进行修正
        int[] positionE = new int[2];
        if (mEditText.getParent() != null) {
            ((ViewGroup)mEditText.getParent()).getLocationInWindow(positionE);
        }
        int[] positionF = new int[2];
        this.getLocationInWindow(positionF);
        x = x+positionE[0]-positionF[0];
        y = y+positionE[1]-positionF[1];

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

    @Override
    protected void onDraw(Canvas canvas) {
        for (int i =0 ; i<fireworks.size(); i++){
            fireworks.get(i).draw(canvas);
        }
        if (fireworks.size()>0)
            invalidate();
    }

}

MainActivity

package com.example.demoeiter;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private EditText mEditText;
    private FireworkView mFireworkView;
    private RelativeLayout layout;
    private TextView day;
    private TextView night;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        layout = (RelativeLayout) findViewById(R.id.layout);
        day = (TextView) findViewById(R.id.day);
        day.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                layout.setBackgroundColor(0xFFFFFFFF);
            }
        });
        night = (TextView) findViewById(R.id.night);
        night.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                layout.setBackgroundColor(0xFF000000);
            }
        });
        mEditText = (EditText) findViewById(R.id.edit_text);


        mFireworkView = (FireworkView) findViewById(R.id.fire_work);
        mFireworkView.bindEditText(mEditText);

    }
}

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值