前言
由於想研究關於UI方面的議題,所以最近試著寫一個客製化的FrameLayout,可以顯示撒圖的效果,這篇文章主要就是介紹概念與一些作法。這裡我製作了一個名為SnowEffectFrameLayout的客製化PercentFrameLayout,這個Layout主要是用來控制圖片的動畫與生成,當中還包括一些參數的設定。我們先來看一下實際效果,效果如下:
這邊我歸納幾個實現的步驟:
- 定義SnowEffectFrameLayout的屬性(declare-styleaqle)
 - 實作SnowEffectFrameLayout
 - 初始化資源檔內容
 - 初始化圖片物件池
 - 設定每張圖片的TranslateAnimation
 
定義SnowEffectFrameLayout的屬性(declare-styleaqle)
<resources>
    <declare-styleable name="SnowEffectFrameLayout">
        <attr name="snowBasicCount" format="integer" />
        <attr name="dropAverageDuration" format="integer" />
        <attr name="isRotation" format="boolean" />
    </declare-styleable>
</resources>
實作SnowEffectFrameLayout
首先讓SnowEffectFrameLayout繼承PercentFrameLayout,主要是讓每個圖片能夠利用PercentFrameLayout的百分比特性,均勻地分布在螢幕的X軸上,至於什麼是PercentFrameLayout,其實就是透過百分比來控制子視圖的大小,程式碼如下。
# Gradle: dependencies
compile 'com.android.support:percent:25.3.1'
# Java
public class SnowEffectFrameLayout extends PercentFrameLayout {
}
初始化資源檔內容
這邊則是在呼叫SnowEffectFrameLayout的建構子時,順便將我們在xml裡設定的屬性給帶進來(要是你有使用到的話!),可以透過AttributeSet這個參數獲取其在xml設定的內容,程式碼如下。private void init(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SnowEffectFrameLayout);
        this.snowBasicCount = typedArray.getInteger(R.styleable.SnowEffectFrameLayout_snowBasicCount, DEFAULT_SNOW_BASIC_COUNT);
        this.dropAverageDuration = typedArray.getInteger(R.styleable.SnowEffectFrameLayout_dropAverageDuration, DEFAULT_DROP_AVERAGE_DURATION);
        this.isRotation = typedArray.getBoolean(R.styleable.SnowEffectFrameLayout_isRotation, DEFAULT_IS_ROTATION);
        this.dropFrequency = DEFAULT_DROP_FREQUENCY;
        this.snowList = new ArrayList<>();
        if ((this.snowList == null || this.snowList.size() == 0)) {
            this.snowList.add(ContextCompat.getDrawable(this.getContext(), R.drawable.snow));
        }
}
初始化圖片物件池
為了要能重複利用圖片,這邊初始了一個物件池,用來存放這些大量的視圖,並且不用每次重新產生,造成資源的浪費,程式碼如下。private void initSnowPool() {
        final int snowCount = this.snowList.size();
        if (snowCount == 0) {
            throw new IllegalStateException("There are no drawables.");
        }
        this.cleanSnowPool();
        final int expectedMaxSnowCountOnScreen = (int) ((1 + RELATIVE_DROP_DURATION_OFFSET) * snowBasicCount * dropAverageDuration / ((float) dropFrequency));
        this.snowPool = new Pools.SynchronizedPool<>(expectedMaxSnowCountOnScreen);
        for (int i = 0; i < expectedMaxSnowCountOnScreen; i++) {
            final ImageView snow = this.generateSnowImage(this.snowList.get(i % snowCount));
            this.addView(snow, 0);
            this.snowPool.release(snow);
        }
        RandomTool.setSeed(10);
}
設定每張圖片的TranslateAnimation
最後就是透過TranslateAnimation讓每張圖片呈現撒落的感覺,再結合RotateAnimation在撒落同時能有旋轉的效果,程式碼如下。private void startDropAnimationForSingleSnow(final ImageView snow) {
        final int currentDuration = (int) (this.dropAverageDuration * RandomTool.floatInRange(1, RELATIVE_DROP_DURATION_OFFSET));
        final AnimationSet animationSet = new AnimationSet(false);
        final TranslateAnimation translateAnimation = new TranslateAnimation(
                Animation.RELATIVE_TO_SELF, 0,
                Animation.RELATIVE_TO_SELF, RandomTool.floatInRange(0, 5),
                Animation.RELATIVE_TO_PARENT, 0,
                Animation.ABSOLUTE, this.windowHeight);
        if (this.isRotation) {
            final RotateAnimation rotateAnimation = new RotateAnimation(
                    0,
                    RandomTool.floatInRange(0, 360),
                    Animation.RELATIVE_TO_SELF,
                    0.5f,
                    Animation.RELATIVE_TO_SELF,
                    0.5f);
            animationSet.addAnimation(rotateAnimation);
        }
        animationSet.addAnimation(translateAnimation);
        animationSet.setDuration(currentDuration);
        animationSet.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }
            @Override
            public void onAnimationEnd(Animation animation) {
                snowPool.release(snow);
            }
            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
        snow.startAnimation(animationSet);
    }
以上還有些小細節沒有交代,就是透過亂數的方式製造位置和動畫速度不一的落差,但這邊不多做贅言,實際的程式碼則在GitHub上,有興趣的人可以參考看看。
沒有留言:
張貼留言