Android 利用Surfaceview实现自定义位移动画

少说话,上图。。。
看了效果图不高兴,就绕道了。。。哈哈

2016-12-15 20_08_16.gif
2016-12-15 20_14_06.gif

利用Surfaceview实现位移动画效果,可以根据具体需求,自己编写位移动画执行的算法。比如,电商APP中加入购物车的动画、特殊曲线的动画等等。

Github地址:https://github.com/qizhenghao/AnimationSurfaceView

以第一个抛物线的动画为例:
在demoActivity中初始化动画:

 private void initParabolaAnimation() {
        animationSurfaceView = (AnimationSurfaceView) findViewById(R.id.animation_surfaceView);
        animationSurfaceView.setOnAnimationStausChangedListener(this);
        // 设置起始Y轴高度和终止X轴位移
        iAnimationStrategy = new ParabolaAnimationStrategy(animationSurfaceView, dp2px(320), dp2px(320));
        animationSurfaceView.setStrategy(iAnimationStrategy);
        animationSurfaceView.setIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
        animationSurfaceView.startAnimation();
    }

AnimationSurfaceView 继承自 SurfaceView:

package com.bruce.open.animationsurfaceview.lib;

import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * @author zhenghao.qi
 * @version 1.0
 * @time 2015年11月09日15:24:15
 */
public class AnimationSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    private static final String TAG = "AnimationSurfaceView";
    private static final long REFRESH_INTERVAL_TIME = 15l;//每间隔15ms刷一帧
    private SurfaceHolder mSurfaceHolder;
    private Bitmap mBitmap;                               //动画图标
    private IAnimationStrategy mIAnimationStrategy;       //动画执行算法策略
    private OnStausChangedListener mStausChangedListener; //动画状态改变监听事件

    private int marginLeft;
    private int marginTop;

    private boolean isSurfaceDestoryed = true;            //默认未创建,相当于Destory
    private Thread mThread;                               //动画刷新线程

    public AnimationSurfaceView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

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

    public AnimationSurfaceView(Context context) {
        super(context);
        init();
    }

    //初始化
    private void init() {
        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
        setZOrderOnTop(true);//设置画布背景透明
        mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT);
        mThread = new Thread(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isSurfaceDestoryed = false;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        isSurfaceDestoryed = true;
        if (mIAnimationStrategy != null)//如果surfaceView创建后,没有执行setStrategy,就被销毁,会空指针异常
            mIAnimationStrategy.cancel();
    }

    //执行
    private void executeAnimationStrategy() {
        Canvas canvas = null;

        Paint tempPaint = new Paint();
        tempPaint.setAntiAlias(true);
        tempPaint.setColor(Color.TRANSPARENT);

        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.CYAN);
        if (mStausChangedListener != null) {
            mStausChangedListener.onAnimationStart(this);
        }
        mIAnimationStrategy.start();
        while (mIAnimationStrategy.doing()) {
            try {
                mIAnimationStrategy.compute();

                canvas = mSurfaceHolder.lockCanvas();
                canvas.drawColor(Color.TRANSPARENT, android.graphics.PorterDuff.Mode.CLEAR);// 设置画布的背景为透明

                // 绘上新图区域
                float x = (float) mIAnimationStrategy.getX() + marginLeft;
                float y = (float) mIAnimationStrategy.getY() + marginTop;

                canvas.drawRect(x, y, x + mBitmap.getWidth(), y + mBitmap.getHeight(), tempPaint);
                canvas.drawBitmap(mBitmap, x, y, paint);

                mSurfaceHolder.unlockCanvasAndPost(canvas);
                Thread.sleep(REFRESH_INTERVAL_TIME);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // clear屏幕内容
        if (isSurfaceDestoryed == false) {// 如果直接按Home键回到桌面,这时候SurfaceView已经被销毁了,lockCanvas会返回为null。
            canvas = mSurfaceHolder.lockCanvas();
            canvas.drawColor(Color.TRANSPARENT, android.graphics.PorterDuff.Mode.CLEAR);
            mSurfaceHolder.unlockCanvasAndPost(canvas);
        }

        if (mStausChangedListener != null) {
            mStausChangedListener.onAnimationEnd(this);
        }
    }

    /**
     * 开始播放动画
     */
    public void startAnimation() {
        if (mThread.getState() == Thread.State.NEW) {
            mThread.start();
        } else if (mThread.getState() == Thread.State.TERMINATED) {
            mThread = new Thread(this);
            mThread.start();
        }
    }

    /**
     * 是否正在播放动画
     */
    public boolean isShow() {
        return mIAnimationStrategy.doing();
    }

    /**
     * 结束动画
     */
    public void endAnimation() {
        mIAnimationStrategy.cancel();
    }

    /**
     * 设置要播放动画的bitmap
     *
     * @param bitmap
     */
    public void setIcon(Bitmap bitmap) {
        this.mBitmap = bitmap;
    }

    /**
     * 获取要播放动画的bitmap
     */
    public Bitmap getIcon() {
        return mBitmap;
    }

    /**
     * 设置margin left 像素
     *
     * @param marginLeftPx
     */
    public void setMarginLeft(int marginLeftPx) {
        this.marginLeft = marginLeftPx;
    }

    /**
     * 设置margin left 像素
     *
     * @param marginTopPx
     */
    public void setMarginTop(int marginTopPx) {
        this.marginTop = marginTopPx;
    }

    /**
     * 设置动画状态改变监听器
     */
    public void setOnAnimationStausChangedListener(OnStausChangedListener listener) {
        this.mStausChangedListener = listener;
    }

    @Override
    public void run() {
        executeAnimationStrategy();
    }

    public interface OnStausChangedListener {
        void onAnimationStart(AnimationSurfaceView view);

        void onAnimationEnd(AnimationSurfaceView view);
    }

    /**
     * 设置动画执行算法策略
     *
     * @param strategy
     */
    public void setStrategy(IAnimationStrategy strategy) {
        this.mIAnimationStrategy = strategy;
    }

}

具体执行的动画算法策略:

package com.bruce.open.animationsurfaceview.strategies;

import android.util.Log;

import com.bruce.open.animationsurfaceview.lib.AnimationSurfaceView;
import com.bruce.open.animationsurfaceview.lib.IAnimationStrategy;

/**
 * @author zhenghao.qi
 * @version 2015年11月10日10:40:03
 */
public class ParabolaAnimationStrategy implements IAnimationStrategy {
    /**
     * 重力加速度值。
     */
    private static final float GRAVITY = 400.78033f;
    /**
     * 与X轴碰撞后,重力势能损失掉的百分比。
     */
    private static final float WASTAGE = 0.3f;
    /**
     * 起始下降高度。
     */
    private int height;
    /**
     * 起始点到终点的X轴位移。
     */
    private int width;
    /**
     * 水平位移速度。
     */
    private double velocity;
    /**
     * X Y坐标。
     */
    private double x, y;
    /**
     * 动画开始时间。
     */
    private long startTime;
    /**
     * 首阶段下载的时间。 单位:毫秒。
     */
    private double t1;
    /**
     * 第二阶段上升与下载的时间。 单位:毫秒。
     */
    private double t2;
    /**
     * 动画正在进行时值为true,反之为false。
     */
    private boolean doing;

    private AnimationSurfaceView animationSurfaceView;

    public ParabolaAnimationStrategy(AnimationSurfaceView animationSurfaceView, int h, int w) {
        this.animationSurfaceView = animationSurfaceView;
        setParams(h, w);
    }

    public void start() {
        startTime = System.currentTimeMillis();
        doing = true;
    }

    /**
     * 设置起始下落的高度及水平位移宽度;以此计算水平初速度、计算小球下落的第一阶段及第二阶段上升耗时。
     */
    private void setParams(int h, int w) {
        height = h;
        width = w;

        t1 = Math.sqrt(2 * height * 1.0d / GRAVITY);
        t2 = Math.sqrt((1 - WASTAGE) * 2 * height * 1.0d / GRAVITY);
        velocity = width * 1.0d / (t1 + 2 * t2);
        Log.d("Bruce1", "t1=" + t1 + " t2=" + t2);
    }

    /**
     * 根据当前时间计算小球的X/Y坐标。
     */
    public void compute() {
        double used = (System.currentTimeMillis() - startTime) * 1.0d / 1000;
        x = velocity * used;
        if (0 <= used && used < t1) {
            y = height - 0.5d * GRAVITY * used * used;
        } else if (t1 <= used && used < (t1 + t2)) {
            double tmp = t1 + t2 - used;
            y = (1 - WASTAGE) * height - 0.5d * GRAVITY * tmp * tmp;
        } else if ((t1 + t2) <= used && used < (t1 + 2 * t2)) {
            double tmp = used - t1 - t2;
            y = (1 - WASTAGE) * height - 0.5d * GRAVITY * tmp * tmp;
        } else {
            Log.d("Bruce1", "used:" + used + " set doing false");
            x = velocity * (t1 + 2 * t2);
            y = 0;
            doing = false;
        }
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return getMirrorY(animationSurfaceView.getHeight(), animationSurfaceView.getIcon().getHeight());
    }

    /**
     * 反转Y轴正方向。适应手机的真实坐标系。
     */
    public double getMirrorY(int parentHeight, int bitHeight) {
        int half = parentHeight >> 1;
        double tmp = half + (half - y);
        tmp -= bitHeight;
        return tmp;
    }

    public boolean doing() {
        return doing;
    }

    public void cancel() {
        doing = false;
    }
}

这个小的demo是在15年时候写的了,如有错误。。。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,185评论 25 707
  • 开启改变的三大法宝 1.动力 自律不足他律来凑: 外界监督,公开承诺,同行伙伴 2.方向 从诚实记录开始,先做到自...
    字母球球阅读 320评论 0 0
  • “醒醒,快醒醒,你做恶梦了。”珂轩的妻子凤怡摇着珂轩,这时珂轩满头大汗,嘴里不知说着什么。“什么时候是头啊!...
    河边人家阅读 264评论 0 0
  • 没想到会是这样。 我似乎被自己套住了,总觉得不满意,总觉得不开心。 删除简书已经很久了。旧手机内存小,不是必...
    阿里斯托妮阅读 259评论 0 0
  • 前几天,Monk在深圳开年会。可能是年会上酒没喝好吧,他在年会结束后回成都,特意绕道北京,找哥几个喝酒来着。 1....
    吴益军子阅读 1,152评论 0 2