前言
由於想研究關於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上,有興趣的人可以參考看看。
沒有留言:
張貼留言