加入購物車動(dòng)畫效果實(shí)現(xiàn)

不知道大家有沒有發(fā)現(xiàn)璃饱,主流的電商類APP添加商品到購物車時(shí),都會(huì)伴隨一個(gè)小的“添加”動(dòng)畫肪康。你有沒有想過它是怎么實(shí)現(xiàn)的呢荚恶?今天我們就一起來學(xué)習(xí)下。

其實(shí)實(shí)現(xiàn)這個(gè)效果很簡(jiǎn)單磷支,主要涉及到兩個(gè)知識(shí)點(diǎn):貝塞爾曲線和自定義Evaluator估值器谒撼。下面我先簡(jiǎn)單介紹下這兩個(gè)知識(shí)點(diǎn),如果你之前有了解過這兩塊的話齐唆,可以直接翻到下面看下實(shí)現(xiàn)代碼嗤栓。

貝塞爾曲線是數(shù)值分析領(lǐng)域的重要參數(shù)曲線冻河,在我們的生活中隨處都可以看到它的影子箍邮,比如:QQ聊天氣泡的拖拽效果、直播室送花點(diǎn)贊效果叨叙、電量水波紋效果等等锭弊。貝塞爾曲線可以分為一階貝塞爾曲線、二階貝塞爾曲線擂错、三階貝塞爾曲線 .....味滞。在這里我們用到了二階貝塞爾曲線,下面我們先來看下它的計(jì)算公式:

公式.jpg
公式中钮呀,B(t) 的值隨時(shí)間 t 變化剑鞍,B(t)運(yùn)動(dòng)點(diǎn)的運(yùn)動(dòng)軌跡就形成了二階貝塞爾曲線,P0是起始點(diǎn)爽醋,P1是控制點(diǎn)蚁署,P2是終點(diǎn)。這里我找了一個(gè)二階貝塞爾曲線的動(dòng)畫蚂四,更直觀一些:https://img-blog.csdn.net/20160328202508739光戈。

相信大家在開發(fā)過程中都有用到過屬性動(dòng)畫吧哪痰,屬性動(dòng)畫中有兩個(gè)很重要的知識(shí)點(diǎn),那就是差值器Interpolator和估值器Evaluator久妆。簡(jiǎn)單來講晌杰,差值器就類似于我們物理中所學(xué)的“加速度”,比如我們的執(zhí)行動(dòng)畫需要先加速再減速筷弦。Android系統(tǒng)為我們內(nèi)置了幾種常見的差值器肋演,在某些特定情況下不能滿足需要的話,我們就需要實(shí)現(xiàn)TimeInterpolator接口實(shí)現(xiàn)自定義差值器烂琴。估值器Evaluator其實(shí)就是一個(gè)轉(zhuǎn)換器惋啃,他能把小數(shù)進(jìn)度轉(zhuǎn)換成對(duì)應(yīng)的具體數(shù)值,我們可以實(shí)現(xiàn)TypeEvaluator接口來自定義估值器监右。

我們先思考一下边灭,加入購物車動(dòng)畫效果就相當(dāng)于數(shù)學(xué)中常見的拋物線,可以借助二階貝塞爾曲線來實(shí)現(xiàn)健盒,那我們?cè)趺创_定起始點(diǎn)P0绒瘦、控制點(diǎn)P1、終點(diǎn)P2的位置呢扣癣?起始點(diǎn)P0的位置就是我們的商品添加按鈕所在位置惰帽,終點(diǎn)P2位置就是界面左下角購物車Icon圖標(biāo)所在的位置,控制點(diǎn)的位置要怎么選取呢父虑?在這里我們可以沿添加按鈕水平向左该酗,沿購物車Icon圖標(biāo)豎直向上,兩條線的交點(diǎn)處正可以作為我們的控制點(diǎn)坐標(biāo)士嚎,到此三個(gè)點(diǎn)正好構(gòu)成一個(gè)倒立的直角三角形呜魄。控制點(diǎn)P1的橫坐標(biāo)等于終點(diǎn)P2的橫坐標(biāo)莱衩,控制點(diǎn)P1的縱坐標(biāo)等于起始點(diǎn)P0的縱坐標(biāo)爵嗅。下面我們就可以進(jìn)行編碼工作了。

首先自定義估值器CartEvaluator:

public class CartEvaluator implements TypeEvaluator<PointF>{

    private PointF pointCur;
    private PointF mControlPoint;

    public CartEvaluator(PointF mControlPoint) {

        this.mControlPoint = mControlPoint;
        pointCur = new PointF();
    }

    @Override
    public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
       // 將二階貝塞爾曲線的計(jì)算公式直接代入即可
        pointCur.x = (1 - fraction) * (1 - fraction) * startValue.x
                + 2 * fraction * (1 - fraction) * mControlPoint.x + fraction * fraction * endValue.x;
        pointCur.y = (1 - fraction) * (1 - fraction) * startValue.y
                + 2 * fraction * (1 - fraction) * mControlPoint.y + fraction * fraction * endValue.y;

        return pointCur;
    }
}

在這里我們創(chuàng)建了一個(gè)pointCur對(duì)象笨蚁,專門用來存儲(chǔ)當(dāng)前移動(dòng)點(diǎn)的坐標(biāo)睹晒。

下面看下Demo中的布局文件效果,代碼就不貼出來了括细,就是布局左下角放置了一個(gè)購物車圖標(biāo)伪很,右上角放置了三個(gè)添加按鈕,用來模擬添加商品操作:
布局文件

最后看下Activity中的代碼實(shí)現(xiàn):

public class MainActivity extends AppCompatActivity {

    private ImageView mAddOne;
    private ImageView mAddTwo;
    private ImageView mAddThree;
    private ImageView mCart;
    private ViewGroup mRootView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();

        initListener();
    }

    // 初始化控件
    private void initView(){
        mRootView = (ViewGroup) getWindow().getDecorView();
        mCart = findViewById(R.id.mCart);
        mAddOne = findViewById(R.id.mAddOne);
        mAddTwo = findViewById(R.id.mAddTwo);
        mAddThree = findViewById(R.id.mAddThree);
    }

    // 初始化監(jiān)聽
    private void initListener(){
        mAddOne.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                playAnim(view);
            }
        });

        mAddTwo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                playAnim(view);
            }
        });

        mAddThree.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                playAnim(view);
            }
        });
    }
    
    // 執(zhí)行動(dòng)畫
    private void playAnim(final View view){

        //創(chuàng)建int數(shù)組奋单,用來接收貝塞爾起始點(diǎn)坐標(biāo)和終點(diǎn)坐標(biāo)值
        int[] startPosition = new int[2];
        int[] endPosition = new int[2];

        view.getLocationInWindow(startPosition);
        mCart.getLocationInWindow(endPosition);

        PointF startF = new PointF();        //起始點(diǎn) startF
        PointF endF = new PointF();          //終點(diǎn) endF
        PointF controlF = new PointF();      //控制點(diǎn) controlF

        startF.x = startPosition[0];
        startF.y = startPosition[1] ;
        endF.x = endPosition[0]+mCart.getWidth()/2-view.getWidth()/2;             //微調(diào)處理锉试,確保動(dòng)畫執(zhí)行完畢“添加”圖標(biāo)中心點(diǎn)與購物車中心點(diǎn)重合
        endF.y = endPosition[1]+mCart.getHeight()/2 - view.getHeight()/2;
        controlF.x = endF.x;
        controlF.y = startF.y;
 
        // 創(chuàng)建執(zhí)行動(dòng)畫的“添加”圖標(biāo)
        final ImageView imageView = new ImageView(this);           
        mRootView.addView(imageView);
        imageView.setImageResource(R.mipmap.cartadd);
        imageView.getLayoutParams().width = view.getMeasuredWidth();
        imageView.getLayoutParams().height = view.getMeasuredHeight();

        ValueAnimator valueAnimator = ValueAnimator.ofObject(new CartEvaluator(controlF), startF, endF);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF pointF = (PointF) animation.getAnimatedValue();
                imageView.setX(pointF.x);
                imageView.setY(pointF.y);
            }
        });

        valueAnimator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                // 動(dòng)畫執(zhí)行完畢,將執(zhí)行動(dòng)畫的“添加”圖標(biāo)移除掉
                mRootView.removeView(imageView);
                
                // 執(zhí)行購物車縮放動(dòng)畫
                AnimatorSet animatorSet = new AnimatorSet();
                ObjectAnimator animatorX = ObjectAnimator.ofFloat(mCart, "scaleX", 1f, 1.2f, 1f);
                ObjectAnimator animatorY = ObjectAnimator.ofFloat(mCart, "scaleY", 1f, 1.2f, 1f);
                animatorSet.play(animatorX).with(animatorY);
                animatorSet.setDuration(400);
                animatorSet.start();
            }
        });

        valueAnimator.setDuration(800);
        valueAnimator.start();
    }
}

代碼中相關(guān)地方都標(biāo)上注釋了辱匿,相信大家都能夠理解键痛,整體代碼量還是很少的炫彩。最后我們看下實(shí)現(xiàn)效果:
luping.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市絮短,隨后出現(xiàn)的幾起案子江兢,更是在濱河造成了極大的恐慌,老刑警劉巖丁频,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杉允,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡席里,警方通過查閱死者的電腦和手機(jī)叔磷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奖磁,“玉大人改基,你說我怎么就攤上這事】” “怎么了秕狰?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)躁染。 經(jīng)常有香客問我鸣哀,道長(zhǎng),這世上最難降的妖魔是什么吞彤? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任我衬,我火速辦了婚禮,結(jié)果婚禮上饰恕,老公的妹妹穿的比我還像新娘挠羔。我一直安慰自己,他們只是感情好懂盐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布褥赊。 她就那樣靜靜地躺著糕档,像睡著了一般莉恼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上速那,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天俐银,我揣著相機(jī)與錄音,去河邊找鬼端仰。 笑死捶惜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的荔烧。 我是一名探鬼主播吱七,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼汽久,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了踊餐?” 一聲冷哼從身側(cè)響起景醇,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吝岭,沒想到半個(gè)月后三痰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窜管,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年散劫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幕帆。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡获搏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出失乾,到底是詐尸還是另有隱情颜凯,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布仗扬,位于F島的核電站症概,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏早芭。R本人自食惡果不足惜彼城,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望退个。 院中可真熱鬧募壕,春花似錦、人聲如沸语盈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刀荒。三九已至代嗤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缠借,已是汗流浹背干毅。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泼返,地道東北人硝逢。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親渠鸽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叫乌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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

  • 前言 近段時(shí)間我在工作中實(shí)現(xiàn)了一個(gè)動(dòng)畫功能,其中涉及到動(dòng)畫元素要按一定的軌跡在屏幕上移動(dòng)徽缚,運(yùn)動(dòng)軌跡的生成我使用了P...
    Alexyz123閱讀 6,558評(píng)論 0 11
  • Android自定義曲線路徑動(dòng)畫框架 最近在一個(gè)項(xiàng)目中需要一個(gè)像QQ打開個(gè)人愛好那樣的動(dòng)畫效果如下圖: 可以看出每...
    飛渡浮舟閱讀 14,661評(píng)論 7 47
  • 貝塞爾曲線開發(fā)的藝術(shù) 一句話概括貝塞爾曲線:將任意一條曲線轉(zhuǎn)化為精確的數(shù)學(xué)公式综芥。 很多繪圖工具中的鋼筆工具,就是典...
    eclipse_xu閱讀 27,694評(píng)論 38 370
  • 本文主要內(nèi)容為貝塞爾曲線原理解析并用 SurfaceView 實(shí)現(xiàn)其展示動(dòng)畫 關(guān)于SurfaceView 的使用猎拨,...
    滌生_Woo閱讀 13,403評(píng)論 5 94
  • 請(qǐng)點(diǎn)擊藍(lán)字收聽朗讀音頻 依靠屋頂?shù)奶?停頓在故事的最后一個(gè)標(biāo)點(diǎn) 光陰提裙而去 余暉滑進(jìn)踮著腳尖的舞鞋 藏在童話里...
    云中飄舞閱讀 258評(píng)論 14 29