可自定義神奇動效的卡片切換視圖

前言

面對衆多卡片層疊效果,咱們的產品童鞋也突發奇想,搞出了另外一種卡片層疊切換展現的交互,並且產品狗們竟然要求多作幾種動效給他們看,好讓他們選擇,這簡直就是要搞事情啊,what are you 弄啥咧?!java

「哥哥我作不到啊.....啊.....呸」,作爲一名有節操的程序猿,天然是不能說出這麼沒有出息的話,哥就知足大家,因而,出了個可自定義動效的卡片切換視圖,效果以下所示git

Github地址:https://github.com/BakerJQ/An...
本文博客:http://bakerjq.com/github

思路

首先,要展現出卡片層疊的視覺效果。在這裏,咱們經過方塊的縮放大小差別以及在Y方向上的位置差別,來展示這種視覺效果。ide

其次,要可以方便的定義卡片視圖內容。咱們經過都很熟悉的設置Adapter的方式來構建內容視圖。動畫

最後,要可以自定義動效。咱們經過定義一個由0到1的ValueAnimator,即每一個動畫的過程,其實就是該ValueAnimator在一個動畫週期內,從0變化到1的過程,0表示動畫剛開始,1表示動畫結束了,0.5則表示這一輪動畫已經執行到了一半。這樣,經過轉換器以及插值器,咱們就能夠根據ValueAnimator實時的值,來設置當前正在執行動畫的卡片應該有的「樣子」。spa

總覽

咱們給出三種基本的動畫模式code

/*
 * ANIM_TYPE_FRONT:被選中的卡片經過自定義動效移至第一,其餘的卡片經過通用動效補位
 * ANIM_TYPE_SWITCH:選中的卡片和第一張卡片互換位置,並都是自定義動效
 * ANIM_TYPE_FRONT_TO_LAST:第一張圖片經過自定義動效移至最後,其餘卡片經過通用動效補位
 */
public static final int ANIM_TYPE_FRONT = 0, ANIM_TYPE_SWITCH = 1, ANIM_TYPE_FRONT_TO_LAST = 2;

並經過Helper類來處理全部的動畫邏輯,以及Adapter來生成卡片視圖orm

private CardAnimationHelper mAnimationHelper;
private BaseAdapter mAdapter;

在onMeasure時根據卡片寬高比來設置卡片的尺寸,在此請注意,當前狀況下卡片寬度與總體容器寬度一致,後續經過自定義的方式,經過縮放來產生卡片的視覺效果。圖片

private float mCardRatio = CARD_SIZE_RATIO;//寬高比:卡片高 / 卡片寬
private int mCardWidth, mCardHeight;//卡片寬高

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    if (mCardWidth == 0 || mCardHeight == 0) {
        setCardSize(true);
    }
}

/*
 * 根據卡片比例計算卡片寬高,並傳入Helper
 */
private void setCardSize(boolean resetAdapter) {
    mCardWidth = getMeasuredWidth();
    mCardHeight = (int) (mCardWidth * mCardRatio);
    mAnimationHelper.setCardSize(mCardWidth, mCardHeight);
    mAnimationHelper.initAdapterView(mAdapter, resetAdapter);
}

那麼如此以後,天然Helper中就保存了視圖的主要數據與參數rem

//卡片列表
private LinkedList<CardItem> mCards;
//當前正在向後以及向前移動的卡片
private CardItem mCardToBack, mCardToFront;
//正在向後以及向前移動卡片的位置
private int mPositionToBack = 0, mPositionToFront = 0;
//動畫運行的ValueAnimator
private ValueAnimator mValueAnimator;
//當前的動畫係數
private float mCurrentFraction = 1;
...//以及一系列的轉換器與插值器

細節

那麼,動畫究竟是如何實現的,以及如何自定義的呢,咱們以通用動畫爲例,來看看動畫的主要流程

首先,在ValueAnimator更新的時候,得到當前的動畫係數,依次來執行動畫

/**
 * ValueAnimator動畫更新
 */
@Override
public void onAnimationUpdate(ValueAnimator animation) {
    //獲取當前動畫係數
    mCurrentFraction = (float) animation.getAnimatedValue();
    //經過插值器獲取插值係數
    fractionInterpolated = mAnimInterpolator.getInterpolation(mCurrentFraction);
    doAnimationCommon(mCurrentFraction, fractionInterpolated);
    ...
}

接着,對須要執行動畫的卡片,執行動畫,以ANIM_TYPE_FRONT動畫模式爲例,當選中的卡片移動到最前的時候,原來在這張卡片以前的全部卡片,都要向後移動一位,來留出第一個的位置

/**
 * 執行通用動畫
 */
private void doAnimationCommon(float fraction, float fractionInterpolated) {
    //若是當前動畫模式爲選中的卡片移到最前
    if (mAnimType == InfiniteCardView.ANIM_TYPE_FRONT) {
        //則遍歷在選中卡片以前的卡片
        for (int i = 0; i < mPositionToFront; i++) {
            CardItem card = mCards.get(i);
            //對卡片執行動畫,從當前位置移動到後一個位置
            doAnimationCommonView(card.view, fraction, fractionInterpolated, i, i + 1);
            ...
        }
    }...
}

最後,經過轉換器,對卡片進行自定義動畫處理

/**
 * 對視圖執行通用動畫
 * @param view                 卡片視圖
 * @param fromPosition         從該位置
 * @param toPosition           移動到該位置
 */
private void doAnimationCommonView(View view, float fraction, float fractionInterpolated, int
        fromPosition, int toPosition) {
    //通用轉換器轉換動畫
    mTransformerCommon.transformAnimation(view, fraction, mCardWidth,
            mCardHeight, fromPosition, toPosition);
    if (mAnimInterpolator != null) {
        //通用轉換器轉換插值動畫
        mTransformerCommon.transformInterpolatedAnimation(view, fractionInterpolated,                        mCardWidth,mCardHeight, fromPosition, toPosition);
    }
}

而轉換器的具體實現則以DefaultCommonTransformer爲例

@Override
public void transformAnimation(View view, float fraction, int cardWidth, int cardHeight, int fromPosition, int toPosition) {
   //須要跨越的卡片數量
   int positionCount = fromPosition - toPosition;
   //以0.8作爲第一張的縮放尺寸,每向後一張縮小0.1
   //(0.8f - 0.1f * fromPosition) = 當前位置的縮放尺寸
   //(0.1f * fraction * positionCount) = 移動過程當中須要改變的縮放尺寸
   float scale = (0.8f - 0.1f * fromPosition) + (0.1f * fraction * positionCount);
   ViewHelper.setScaleX(view, scale);
   ViewHelper.setScaleY(view, scale);
   //在Y方向的偏移量,每向後一張,向上偏移卡片寬度的0.02
   //-cardHeight * (0.8f - scale) * 0.5f 對卡片作總體居中處理
   ViewHelper.setTranslationY(view, -cardHeight * (0.8f - scale) * 0.5f - cardWidth * (0.02f *
          fromPosition - 0.02f * fraction * positionCount));
}

對於向第一位移動的選中卡片,也是同理,只不過是根據該卡片對應的轉換器來進行自定義動畫的轉換。

最後的效果,就像演示圖中第一次點擊,圖片向前翻轉到第一位的效果同樣。

總結

對於產品狗突如其來的想法,我們程序猿不善於口水仗的,就只能用代碼來讓他們來服氣了。畢竟,你們還都是伐木累嘛,哈哈。

當實現某個東西遇到困難時,不妨想一想Android系統自身的一些實現方式,好比參考ListView的Adapter,ViewPager定義翻頁動畫的Transformer等等,總會有意想不到的啓發。

再次貼一下Github地址:https://github.com/BakerJQ/An...

相關文章
相關標籤/搜索