public FloatLeafLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// 四张不同形状的叶子
mLeafs = new Drawable[]{getResources().getDrawable(R.mipmap.leaf_1),
getResources().getDrawable(R.mipmap.leaf_2),
getResources().getDrawable(R.mipmap.leaf_3),
getResources().getDrawable(R.mipmap.leaf_4)};
// 四个不同的补间器
mInterpolator = new Interpolator[]{new AccelerateDecelerateInterpolator(),
new AccelerateInterpolator(),
new DecelerateInterpolator(),
new LinearInterpolator()};
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(mWidthSize = measure(widthMeasureSpec), mHeightSize = measure(heightMeasureSpec));
if (getChildCount() == 0) {
addTree(mWidthSize, mHeightSize);
}
}
private int measure(int measureSpec) {
int result = 0;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = dip2px(getContext(), 300);
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
// 添加树的图片
private void addTree(int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.mipmap.tree, options);
final int outWidth = options.outWidth;
final int outHeight = options.outHeight;
int inSampleSize = 1;
if (outWidth > reqWidth || outHeight > reqHeight) {
final int widthRatio = outWidth / reqWidth;
final int heightRatio = outHeight / reqHeight;
inSampleSize = Math.min(widthRatio, heightRatio);
}
options.inSampleSize = inSampleSize == 0 ? 1 : inSampleSize;
options.inJustDecodeBounds = false;
ImageView mTree = new ImageView(getContext());
final Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.tree, options);
mTree.setBackgroundDrawable(new BitmapDrawable(bitmap));
addView(mTree, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
添加树叶:addLeaf()
和播放树叶:playLeaf()
两个方法首先addLeaf()
开始随机添加一片树叶,起点X坐标随机取,然后算出Y坐标
public void addLeaf() {
ImageView mLeaf = new ImageView(getContext());
Random random = new Random();
// 设置随机一片落叶
mLeaf.setImageDrawable(mLeafs[random.nextInt(4)]);
// 随机设置落叶的起点x坐标
float leafX = random.nextInt(mWidthSize);
float leafY;
// 根据x坐标算出y坐标,因为树叶的范围呈三角形,并且约占高度一半,所以要控制y坐标
if (leafX > mWidthSize / 2) {
leafY = mHeightSize * 1.0f / mWidthSize * leafX - mHeightSize / 2;
} else {
leafY = -mHeightSize * 1.0f / mWidthSize * leafX + mHeightSize / 2;
}
// 设置落叶起点,添加到布局
ViewCompat.setX(mLeaf, leafX);
ViewCompat.setY(mLeaf, leafY);
addView(mLeaf);
// 设置树叶刚开始出现的动画
ObjectAnimator alpha = ObjectAnimator.ofFloat(mLeaf, "alpha", 0.1f, 1);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(mLeaf, "scaleX", 0.1f, 1);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(mLeaf, "scaleY", 0.1f, 1);
AnimatorSet set = new AnimatorSet();
set.playTogether(alpha, scaleX, scaleY);
set.setDuration(300);
// 树叶落下经过的第二个点
final PointF pointF1 = new PointF(leafX + random.nextInt((int) (mWidthSize - leafX)), leafY + random.nextInt((int) (mHeightSize - leafY)));
// 树叶落下经过的第三个点
final PointF pointF2 = new PointF(leafX + random.nextInt((int) (mWidthSize - leafX)), leafY + random.nextInt((int) (mHeightSize - leafY)));
// 树叶落下的起点
final PointF pointF0 = new PointF(ViewCompat.getX(mLeaf), ViewCompat.getY(mLeaf));
// 树叶落下的终点
final PointF pointF3 = new PointF(random.nextInt(mWidthSize), mHeightSize);
// 通过自定义的贝塞尔估值器算出途经的点的想x,y坐标
final BazierTypeEvaluator bazierTypeEvaluator = new BazierTypeEvaluator(pointF1, pointF2);
// 设置值动画
ValueAnimator bazierAnimator = ValueAnimator.ofObject(bazierTypeEvaluator, pointF0, pointF3);
bazierAnimator.setTarget(mLeaf);
bazierAnimator.addUpdateListener(new BazierUpdateListener(mLeaf));
bazierAnimator.setDuration(2000);
// 将以上动画添加到动画集合
AnimatorSet allSet = new AnimatorSet();
allSet.play(set).before(bazierAnimator);
// 随机设置一个补间器
allSet.setInterpolator(mInterpolator[random.nextInt(4)]);
allSet.addListener(new AnimatorEndListener(mLeaf));
allSet.start();
属性动画用到了两个集合,开始是一个树叶生成时缩放透明度的动画,接下来就是值动画的使用,使用到了一个自定义的估值器BazierTypeEvaluator
,此货运用了三次方贝塞尔公式算出落叶途经的坐标。贝塞尔是啥呢?我反正不想知道 凸(⊙▂⊙✖ ) ,想简单了解的可以看下爱哥的自定义控件其实很简单5/12,这里直接拿公式套上去就OK了,通过evaluate()
的t值变化,算出途经的坐标值。
public class BazierTypeEvaluator implements TypeEvaluator {
/**
* 三次方贝塞尔曲线
* B(t)=P0*(1-t)^3+3*P1*t*(1-t)^2+3*P2*t^2*(1-t)+P3*t^3,t∈[0,1]
* P0,是我们的起点,
* P3是终点,
* P1,P2是途径的两个点
* 而t则是我们的一个因子,取值范围是0-1
*/
private PointF pointF1;
private PointF pointF2;
public BazierTypeEvaluator(PointF pointF1, PointF pointF2) {
this.pointF1 = pointF1;
this.pointF2 = pointF2;
}
@Override
public PointF evaluate(float t, PointF startValue, PointF endValue) {
PointF pointF = new PointF();
pointF.x = (float) (startValue.x * Math.pow(1 - t, 3) + 3 * pointF1.x * t * Math.pow(1 - t, 2) + 3 * pointF2.x * Math.pow(t, 2) * (1 - t) + endValue.x * Math.pow(t, 3));
pointF.y = (float) (startValue.y * Math.pow(1 - t, 3) + 3 * pointF1.y * t * Math.pow(1 - t, 2) + 3 * pointF2.y * Math.pow(t, 2) * (1 - t) + endValue.y * Math.pow(t, 3));
return pointF;
}
}
上面bazierAnimator.addUpdateListener(new BazierUpdateListener(mLeaf))
,继承ValueAnimator.AnimatorUpdateListener后不断去取算出的坐标值设置给落叶即可,还做了个透明度的变化
// 值动画更新监听
private class BazierUpdateListener implements ValueAnimator.AnimatorUpdateListener {
View target;
public BazierUpdateListener(View target) {
BazierUpdateListener.this.target = target;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 获取坐标,设置落叶的位置
final PointF pointF = (PointF) animation.getAnimatedValue();
ViewCompat.setX(target, pointF.x);
ViewCompat.setY(target, pointF.y);
ViewCompat.setAlpha(target, 1 - animation.getAnimatedFraction());
}
}
allSet.addListener(new AnimatorEndListener(mLeaf));
动画集合添加动画停止的监听,用于移除落叶节约资源
// 动画更新适配器,用于动画停止的时候移除落叶
private class AnimatorEndListener extends AnimatorListenerAdapter {
View target;
public AnimatorEndListener(View target) {
this.target = target;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
removeView(target);
Log.e(TAG, "child:" + getChildCount());
}
}
播放落叶无非开启子线程不断调用addLeaf()
生成落叶
// 播放落叶,播放15片
public void playLeaf() {
new Thread() {
@Override
public void run() {
if (mIsDestoryed)
// 页面销毁直接返回
return;
for (int i = 0; i < 15; i++) {
if (mIsDestoryed)
// 页面销毁直接返回
return;
((Activity) getContext()).runOnUiThread(new Runnable() {
@Override
public void run() {
addLeaf();
}
});
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
onDestory()
做清理工作 // 销毁的时候做清理工作
public void onDestroy() {
Log.e(TAG, "Activity被销毁了");
mIsDestoryed = true;
if (mAnimatorSets == null) return;
for (int i = 0; i < mAnimatorSets.size(); i++) {
mAnimatorSets.get(i).cancel();
}
mAnimatorSets.clear();
}
ヽ(^o^)ρ┳┻┳°σ(^o^)/