Android 利用Surfaceview實現(xiàn)自定義位移動畫

少說話,上圖似忧。渣叛。。
看了效果圖不高興盯捌,就繞道了淳衙。。饺著。哈哈

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

利用Surfaceview實現(xiàn)位移動畫效果箫攀,可以根據(jù)具體需求,自己編寫位移動畫執(zhí)行的算法幼衰。比如靴跛,電商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;       //動畫執(zhí)行算法策略
    private OnStausChangedListener mStausChangedListener; //動畫狀態(tài)改變監(jiān)聽事件

    private int marginLeft;
    private int marginTop;

    private boolean isSurfaceDestoryed = true;            //默認未創(chuàng)建梢睛,相當于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創(chuàng)建后,沒有執(zhí)行setStrategy,就被銷毀识椰,會空指針異常
            mIAnimationStrategy.cancel();
    }

    //執(zhí)行
    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);// 設置畫布的背景為透明

                // 繪上新圖區(qū)域
                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屏幕內(nèi)容
        if (isSurfaceDestoryed == false) {// 如果直接按Home鍵回到桌面绝葡,這時候SurfaceView已經(jīng)被銷毀了,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();
    }

    /**
     * 結(jié)束動畫
     */
    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;
    }

    /**
     * 設置動畫狀態(tài)改變監(jiān)聽器
     */
    public void setOnAnimationStausChangedListener(OnStausChangedListener listener) {
        this.mStausChangedListener = listener;
    }

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

    public interface OnStausChangedListener {
        void onAnimationStart(AnimationSurfaceView view);

        void onAnimationEnd(AnimationSurfaceView view);
    }

    /**
     * 設置動畫執(zhí)行算法策略
     *
     * @param strategy
     */
    public void setStrategy(IAnimationStrategy strategy) {
        this.mIAnimationStrategy = strategy;
    }

}

具體執(zhí)行的動畫算法策略:

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

    /**
     * 根據(jù)當前時間計算小球的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());
    }

    /**
     * 反轉(zhuǎn)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年時候?qū)懙牧耍缬绣e誤碱妆。肉盹。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疹尾,一起剝皮案震驚了整個濱河市上忍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纳本,老刑警劉巖窍蓝,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異繁成,居然都是意外死亡它抱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門朴艰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來观蓄,“玉大人,你說我怎么就攤上這事祠墅∥甏” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵毁嗦,是天一觀的道長亲茅。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么克锣? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任茵肃,我火速辦了婚禮,結(jié)果婚禮上袭祟,老公的妹妹穿的比我還像新娘验残。我一直安慰自己,他們只是感情好巾乳,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布您没。 她就那樣靜靜地躺著,像睡著了一般胆绊。 火紅的嫁衣襯著肌膚如雪氨鹏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天压状,我揣著相機與錄音仆抵,去河邊找鬼。 笑死种冬,一個胖子當著我的面吹牛镣丑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播碌廓,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼剩盒!你這毒婦竟也來了谷婆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辽聊,失蹤者是張志新(化名)和其女友劉穎纪挎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跟匆,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡异袄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了玛臂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烤蜕。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖迹冤,靈堂內(nèi)的尸體忽然破棺而出讽营,到底是詐尸還是另有隱情,我是刑警寧澤泡徙,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布橱鹏,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏莉兰。R本人自食惡果不足惜挑围,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望糖荒。 院中可真熱鬧杉辙,春花似錦、人聲如沸寂嘉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泉孩。三九已至硼端,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寓搬,已是汗流浹背珍昨。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留句喷,地道東北人镣典。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像唾琼,于是被迫代替她去往敵國和親兄春。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,283評論 25 707
  • 開啟改變的三大法寶 1.動力 自律不足他律來湊: 外界監(jiān)督锡溯,公開承諾赶舆,同行伙伴 2.方向 從誠實記錄開始,先做到自...
    字母球球閱讀 322評論 0 0
  • “醒醒祭饭,快醒醒芜茵,你做惡夢了〕”珂軒的妻子鳳怡搖著珂軒九串,這時珂軒滿頭大汗,嘴里不知說著什么寺鸥≈砼ィ“什么時候是頭啊胆建!...
    河邊人家閱讀 264評論 0 0
  • 沒想到會是這樣躬贡。 我似乎被自己套住了,總覺得不滿意眼坏,總覺得不開心拂玻。 刪除簡書已經(jīng)很久了酸些。舊手機內(nèi)存小,不是必...
    阿里斯托妮閱讀 259評論 0 0
  • 前幾天市栗,Monk在深圳開年會】榷蹋可能是年會上酒沒喝好吧填帽,他在年會結(jié)束后回成都,特意繞道北京咙好,找哥幾個喝酒來著篡腌。 1....
    吳益軍子閱讀 1,154評論 0 2