简单实现满屏表情下落的动画效果,你也可以

博文出处:简单实现满屏表情下落的动画效果,你也可以,欢迎大家关注我的博客,谢谢!

首先我相信大家一定玩过微信吧。之前在玩微信的时候,给好友发一句“圣诞快乐”就会有满屏的圣诞树往下掉,当时觉得这个动画好酷。正好在公司的项目中需要用到这样的动画效果。于是写了一个小Demo,就有了这篇文章。

下图是做出的相关效果:

表情下落动画效果gif

看完上面的效果图,大家一定都迫不及待地想要试一试了,那就让我们来动手吧。

首先我们定义一个实体类DropLook:

/**
 * 下落的表情
 */
public class DropLook {

    // x轴坐标
    private float x;
    // y轴坐标
    private float y;
    // 初始旋转角度
    private float rotation;
    // 下落速度
    private float speed;
    // 旋转速度
    private float rotationSpeed;
    // 宽度
    private int width;
    // 高度
    private int height;
    // 图片
    private Bitmap bitmap;

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }

    public float getRotationSpeed() {
        return rotationSpeed;
    }

    public void setRotationSpeed(float rotationSpeed) {
        this.rotationSpeed = rotationSpeed;
    }

    public float getRotation() {
        return rotation;
    }

    public void setRotation(float rotation) {
        this.rotation = rotation;
    }

    public float getSpeed() {
        return speed;
    }

    public void setSpeed(float speed) {
        this.speed = speed;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public Bitmap getBitmap() {
        return bitmap;
    }

    public void setBitmap(Bitmap bitmap) {
        this.bitmap = bitmap;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

}

我们定义的实体类很简单,只是设置了如宽高、x,y坐标、下落速度等。接下来我们再创建一个DropLookFactory类,用来创建DropLook对象。

public class DropLookFactory {

    private DropLookFactory() {

    }

    public static DropLook createDropLook(int width, int height,Bitmap originalBitmap) {
        DropLook look = new DropLook();
        if (originalBitmap == null) {
            throw new NullPointerException("originalBitmap cannot be null");
        }
        // 设置与图片等宽
        look.setWidth(originalBitmap.getWidth());
        // 设置与图片等高
        look.setHeight(originalBitmap.getHeight());
        // 设置起始位置的X坐标
        look.setX((float) Math.random() * (width - look.getWidth()));
        // 设置起始位置的Y坐标
        look.setY((float) Math.random() * (height - look.getHeight()));
        // 设置速度
        look.setSpeed(20 + (float) Math.random() * 40);
        // 设置初始旋转角度
        look.setRotation((float) Math.random() * 180 - 90);
        // 设置旋转速度
        look.setRotationSpeed((float) Math.random() * 90 - 60);
        // 设置图片
        look.setBitmap(originalBitmap);
        return look;
    }

}

其中createDropLook(Context context, float xRange, Bitmap originalBitmap)的第一个参数代表着下落表情在x轴上的范围,第二个参数代表在y轴上的范围,第三个参数是表情的图片。在createDropLook方法中相信大家都看得懂,主要就是用随机数初始化DropLook的坐标及下落速度等。

好了,下面就是今天的重头戏DropLookView,先来看看onMeasure():

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    if (widthMode == MeasureSpec.EXACTLY) {
        mWidth = widthSize;
    } else {
        mWidth = Tools.dip2px(getContext(),200);
    }
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    if (heightMode == MeasureSpec.EXACTLY) {
        mHeight = heightSize;
    } else {
        mHeight = Tools.dip2px(getContext(),200);
    }
    setMeasuredDimension(mWidth, mHeight);
    if (looks.size() == 0) {
        for (int i = 0; i < DEFAULT_DROP_LOOK_NUMS; ++i) {
            looks.add(DropLookFactory.createDropLook(mWidth, mHeight, mBitmap));
        }
        Log.i(TAG, "num = " + looks.size());
    }
}

onMeasure里主要是对View的测量,如果是wrap_content的话设置一个默认的宽高度200dp。然后就是初始化DropLook,looks是DropLook类的集合,用于管理DropLook。而DEFAULT_LOOK_NUMS是默认的looks集合的数量。

接下来就是最关键的onDraw():

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.save();
    long nowTime = System.currentTimeMillis();
    if (nowTime - startTime < 100) {
        try {
            Thread.sleep(100 + startTime - nowTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    for (int i = 0; i < DEFAULT_DROP_LOOK_NUMS; i++) {

        DropLook look = looks.get(i);
        mMatrix.setTranslate(-look.getWidth() / 2, -look.getHeight() / 2);
        mMatrix.postRotate(look.getRotation());
        mMatrix.postTranslate(look.getWidth() / 2 + look.getX(), look.getHeight() / 2 + look.getY());
        canvas.drawBitmap(look.getBitmap(), mMatrix, mPaint);

        look.setY(look.getY() + look.getSpeed());
        if (look.getY() > getHeight()) {
            look.setY((float) (0 - Math.random() * look.getHeight()));
        }

        look.setRotation(look.getRotation() + look.getRotationSpeed());
    }

    canvas.restore();
    startTime = System.currentTimeMillis();
    invalidate();
}

一开始判断时间间隔如果没有超过100ms,就让线程睡眠一会。然后就是用drawBitmap的方法把looks里面逐个绘制出来。并且再把look的y轴坐标加上下落速度等,旋转的角度也是如此。最后就是调用invalidate()不断地重绘。总体上并没有什么难点。

以下是DropLookView的完整代码:

/**
 * 表情下落view
 */
public class DropLookView extends View {

    // 表情
    private Bitmap mBitmap;
    // 所有表情集合
    List<DropLook> looks = new ArrayList();
    // view开始时间
    private long startTime;
    // view宽度
    private int mWidth;
    // view高度
    private int mHeight;
    // 画笔
    private Paint mPaint;
    // 默认表情下落数
    private static final int DEFAULT_DROP_LOOK_NUMS = 35;

    private static final String TAG = "DropLookView";

    private Matrix mMatrix = new Matrix();

    public DropLookView(Context context) {
        this(context, null);
    }

    public DropLookView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DropLookView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 图片
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.d_5_xiaoku);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            mWidth = widthSize;
        } else {
            mWidth = Tools.dip2px(getContext(),200);
        }
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode == MeasureSpec.EXACTLY) {
            mHeight = heightSize;
        } else {
            mHeight = Tools.dip2px(getContext(),200);
        }
        setMeasuredDimension(mWidth, mHeight);
        if (looks.size() == 0) {
            for (int i = 0; i < DEFAULT_DROP_LOOK_NUMS; ++i) {
                looks.add(DropLookFactory.createDropLook(mWidth, mHeight, mBitmap));
            }
            Log.i(TAG, "num = " + looks.size());
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        long nowTime = System.currentTimeMillis();
        if (nowTime - startTime < 100) {
            try {
                Thread.sleep(100 + startTime - nowTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        for (int i = 0; i < DEFAULT_DROP_LOOK_NUMS; i++) {

            DropLook look = looks.get(i);
            mMatrix.setTranslate(-look.getWidth() / 2, -look.getHeight() / 2);
            mMatrix.postRotate(look.getRotation());
            mMatrix.postTranslate(look.getWidth() / 2 + look.getX(), look.getHeight() / 2 + look.getY());
            canvas.drawBitmap(look.getBitmap(), mMatrix, mPaint);

            look.setY(look.getY() + look.getSpeed());
            if (look.getY() > getHeight()) {
                look.setY((float) (0 - Math.random() * look.getHeight()));
            }

            look.setRotation(look.getRotation() + look.getRotationSpeed());
        }

        canvas.restore();
        startTime = System.currentTimeMillis();
        invalidate();
    }

}

该讲的也差不多讲完了,其实并没有想象中的那么有难度,实现起来也比较容易。当然DropLookView也有需要改进的地方。比如说可以在布局文件中自定义表情下落的数量等。这些就需要自己根据需求来更改了,那今天就先这样吧。

下面是本Demo的完整代码:
DropLookView.rar

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容