目录
- 一、前言
- 二、创意名
- 三、效果展示
- 四、实现步骤
- 五、编码实现
- 总结
一、前言
这个冬天,老家一直没有下雨, 正好圣诞节,就想着制作一个下雪的特效。
圣诞祝福:平安夜,舞翩阡。雪花飘,飞满天。心与心,永相伴。
圣诞节是传统的宗教节日,对于基 督徒,那是庆祝耶稣的诞生,纪念耶稣和发扬基督精神。现在整个西方社会都在过圣诞节,像许多宗教节日一样,它已经越来越民俗化了。
尽管如此,圣诞节依然倍受尊重。人们在圣诞快乐中怀有对耶稣的敬仰,欢乐的节庆里含有庄严肃穆的神念。欢度圣诞佳节的人都不拒绝耶稣的教诲,要仁爱、善良、诚实、忍耐、感恩……在信神的国度,不是基 督徒的人们,也都知道人应该感恩,心存谢意。对需要帮助的人给予关爱;对他人的帮助给予感谢。这是西方社会价值观的一部份,而不是说圣诞夜就只是一家坐在壁炉前,共进有火鸡或烤鹅的圣诞大餐或是冬季里开的一个最热闹的大派对。
二、创意名
Android实现雪花特效自定义view
三、效果展示
四、实现步骤
1.创建一个view,里面加载雪花类的集合,有一个死循环线程,一直执行动画
public class myRunnable implements Runnable { | |
public void run() { | |
while (true){ | |
Canvas canvas =null; | |
try { | |
synchronized (holder){ | |
canvas = holder.lockCanvas(); | |
//清除画布 | |
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); | |
for (Snowflake snowflake :list){ | |
snowflake.draw(canvas); | |
snowflake.update(); | |
} | |
} | |
}catch (Exception e){ | |
e.printStackTrace(); | |
}finally { | |
if (canvas!=null){ | |
holder.unlockCanvasAndPost(canvas); | |
} | |
} | |
} | |
} | |
} |
这样的话,可以让所有的雪花图片动起来
2.创建雪花类,其实就是一个bitmap,然后设置不同尺寸和动画
这步相对来说简单一些,其实就是将bitmap绘制到画布上面
public void reset(){ | |
size = randomizer.randomInt(sizeMinInPx, sizeMaxInPx, true); | |
if (image!=null){ | |
if (bitmap==null){ | |
bitmap = Bitmap.createScaledBitmap(image, size, size, false); | |
} | |
} | |
float speed = (float)(size - sizeMinInPx) / (sizeMaxInPx - sizeMinInPx) * (speedMax - speedMin) + speedMin; | |
double angle = Math.toRadians(randomizer.randomDouble(angleMax) * randomizer.randomSignum()); | |
speedX = speed* Math.sin(angle); | |
speedY = speed* Math.cos(angle); | |
alpha = randomizer.randomInt(alphaMin, alphaMax, false); | |
paint.setAlpha(alpha); | |
positionX = randomizer.randomDouble(parentWidth); | |
this.positionY=randomizer.randomDouble(parentHeight); | |
if (!alreadyFalling){ | |
this.positionY = this.positionY-parentHeight-size; | |
} | |
} |
3.界面展示
实现manifest加载视图即可
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:background="#" | |
tools:context="com.marvin.snowfall_master.MainActivity"> | |
<com.itbird.SnowfallView | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:id="@+id/sf_snow" | |
app:snowflakesNum="" | |
app:snowflakeAlphaMin="" | |
app:snowflakeAlphaMax="" | |
app:snowflakeAngleMax="" | |
app:snowflakeSizeMin="dp" | |
app:snowflakeSizeMax="dp" | |
app:snowflakeSpeedMin="" | |
app:snowflakeSpeedMax="" | |
app:snowflakesFadingEnabled="true" | |
app:snowflakesAlreadyFalling="false" | |
app:snowflakeImage="@mipmap/snowflake" | |
/> | |
</android.support.constraint.ConstraintLayout> | |
五、编码实现
界面类SnowfallView
public class SnowfallView extends SurfaceView implements SurfaceHolder.Callback { | |
private int DEFAULT_SNOWFLAKES_NUM =; | |
private int DEFAULT_SNOWFLAKE_ALPHA_MIN =; | |
private int DEFAULT_SNOWFLAKE_ALPHA_MAX =; | |
private int DEFAULT_SNOWFLAKE_ANGLE_MAX =; | |
private int DEFAULT_SNOWFLAKE_SIZE_MIN_IN_DP =; | |
private int DEFAULT_SNOWFLAKE_SIZE_MAX_IN_DP =; | |
private int DEFAULT_SNOWFLAKE_SPEED_MIN =; | |
private int DEFAULT_SNOWFLAKE_SPEED_MAX =; | |
private boolean DEFAULT_SNOWFLAKES_FADING_ENABLED = false; | |
private boolean DEFAULT_SNOWFLAKES_ALREADY_FALLING = false; | |
private int snowflakesNum; | |
private Bitmap snowflakeImage; | |
private int snowflakeAlphaMin; | |
private int snowflakeAlphaMax; | |
private int snowflakeAngleMax; | |
private int snowflakeSizeMinInPx; | |
private int snowflakeSizeMaxInPx; | |
private int snowflakeSpeedMin; | |
private int snowflakeSpeedMax; | |
private boolean snowflakesFadingEnabled; | |
private boolean snowflakesAlreadyFalling; | |
//雪花类集合 | |
private ArrayList<Snowflake> list =new ArrayList<>(); | |
private SnowfallView.myRunnable myRunnable = new myRunnable(); | |
private Thread myThread; | |
private SurfaceHolder holder; | |
public SnowfallView(Context context, { AttributeSet attrs) | |
super(context, attrs); | |
init(context,attrs); | |
} | |
private void init(Context context, AttributeSet attributeSet) { | |
TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.SnowfallView); | |
snowflakesNum = typedArray.getInt(R.styleable.SnowfallView_snowflakesNum, DEFAULT_SNOWFLAKES_NUM); | |
snowflakeImage = drawableBitmap(typedArray.getDrawable(R.styleable.SnowfallView_snowflakeImage)); | |
snowflakeAlphaMin = typedArray.getInt(R.styleable.SnowfallView_snowflakeAlphaMin, DEFAULT_SNOWFLAKE_ALPHA_MIN); | |
snowflakeAlphaMax = typedArray.getInt(R.styleable.SnowfallView_snowflakeAlphaMax, DEFAULT_SNOWFLAKE_ALPHA_MAX); | |
snowflakeAngleMax = typedArray.getInt(R.styleable.SnowfallView_snowflakeAngleMax, DEFAULT_SNOWFLAKE_ANGLE_MAX); | |
snowflakeSizeMinInPx = typedArray.getDimensionPixelSize(R.styleable.SnowfallView_snowflakeSizeMin, dpPx(DEFAULT_SNOWFLAKE_SIZE_MIN_IN_DP)); | |
snowflakeSizeMaxInPx = typedArray.getDimensionPixelSize(R.styleable.SnowfallView_snowflakeSizeMax, dpPx(DEFAULT_SNOWFLAKE_SIZE_MAX_IN_DP)); | |
snowflakeSpeedMin = typedArray.getInt(R.styleable.SnowfallView_snowflakeSpeedMin, DEFAULT_SNOWFLAKE_SPEED_MIN); | |
snowflakeSpeedMax = typedArray.getInt(R.styleable.SnowfallView_snowflakeSpeedMax, DEFAULT_SNOWFLAKE_SPEED_MAX); | |
snowflakesFadingEnabled = typedArray.getBoolean(R.styleable.SnowfallView_snowflakesFadingEnabled, DEFAULT_SNOWFLAKES_FADING_ENABLED); | |
snowflakesAlreadyFalling = typedArray.getBoolean(R.styleable.SnowfallView_snowflakesAlreadyFalling, DEFAULT_SNOWFLAKES_ALREADY_FALLING); | |
typedArray.recycle(); | |
holder = this.getHolder(); | |
holder.addCallback(this); | |
//设置背景为透明 | |
setZOrderOnTop(true); | |
holder.setFormat(PixelFormat.TRANSPARENT); | |
} | |
protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |
super.onSizeChanged(w, h, oldw, oldh); | |
//获取雪花集合 | |
for (int i=;i<snowflakesNum;i++){ | |
list.add(new Snowflake(w, h, snowflakeImage, snowflakeAlphaMin, snowflakeAlphaMax | |
, snowflakeAngleMax, snowflakeSizeMinInPx, snowflakeSizeMaxInPx, snowflakeSpeedMin, snowflakeSpeedMax | |
, snowflakesFadingEnabled, snowflakesAlreadyFalling)); | |
} | |
} | |
protected void onVisibilityChanged(int visibility) { View changedView, | |
super.onVisibilityChanged(changedView, visibility); | |
if (changedView==this&&visibility==GONE){ | |
//初始化雪花类 | |
try { | |
for (Snowflake snowflake :list){ | |
snowflake.reset(); | |
} | |
}catch (Exception e){ | |
e.printStackTrace(); | |
} | |
} | |
} | |
protected void onDraw(Canvas canvas) { | |
super.onDraw(canvas); | |
if (isInEditMode()){ | |
return; | |
} | |
} | |
/** | |
* dp转px | |
* @param dp | |
* @return | |
*/ | |
private int dpPx(int dp){ | |
return (int) (dp*getResources().getDisplayMetrics().density); | |
} | |
/** | |
* drawble转Bitmap | |
* @param drawable | |
* @return | |
*/ | |
private Bitmap drawableBitmap(Drawable drawable){ | |
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_); | |
Canvas canvas = new Canvas(bitmap); | |
drawable.setBounds(,0,drawable.getIntrinsicWidth(),drawable.getIntrinsicHeight()); | |
drawable.draw(canvas); | |
return bitmap; | |
} | |
public void surfaceCreated(SurfaceHolder surfaceHolder) { | |
if (myThread==null){ | |
myThread = new Thread(myRunnable); | |
} | |
if(!myThread.isAlive()){ | |
myThread.start(); | |
} | |
} | |
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i, int i2) { | |
} | |
public void surfaceDestroyed(SurfaceHolder surfaceHolder) { | |
if (myThread!=null){ | |
myThread.interrupt(); | |
} | |
} | |
public class myRunnable implements Runnable { | |
public void run() { | |
while (true){ | |
Canvas canvas =null; | |
try { | |
synchronized (holder){ | |
canvas = holder.lockCanvas(); | |
//清除画布 | |
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); | |
for (Snowflake snowflake :list){ | |
snowflake.draw(canvas); | |
snowflake.update(); | |
} | |
} | |
}catch (Exception e){ | |
e.printStackTrace(); | |
}finally { | |
if (canvas!=null){ | |
holder.unlockCanvasAndPost(canvas); | |
} | |
} | |
} | |
} | |
} | |
} |
雪花类Snowflake
public class Snowflake { | |
private int parentWidth; | |
private int parentHeight; | |
private int alphaMin; | |
private int alphaMax; | |
private int angleMax; | |
private int sizeMinInPx; | |
private int sizeMaxInPx; | |
private int speedMin; | |
private int speedMax; | |
private Bitmap image; | |
private boolean fadingEnabled; | |
private boolean alreadyFalling; | |
private int size = ; | |
private int alpha =; | |
private Bitmap bitmap = null; | |
private double speedX=.0; | |
private double speedY =.0; | |
private double positionX =.0; | |
private double positionY =.0; | |
private final Randomizer randomizer; | |
private Paint paint; | |
public Snowflake(int parentWidth, int parentHeight, Bitmap image | |
,int alphaMin,int alphaMax,int angleMax,int sizeMinInPx,int sizeMaxInPx, | |
int speedMin,int speedMax,boolean fadingEnabled,boolean alreadyFalling ){ | |
this.parentWidth = parentWidth; | |
this.parentHeight = parentHeight; | |
this.alphaMin = alphaMin; | |
this.alphaMax = alphaMax; | |
this.angleMax = angleMax; | |
this.sizeMinInPx = sizeMinInPx; | |
this.sizeMaxInPx = sizeMaxInPx; | |
this.speedMin = speedMin; | |
this.speedMax = speedMax; | |
this.image = image; | |
this.fadingEnabled=fadingEnabled; | |
this.alreadyFalling=alreadyFalling; | |
randomizer = new Randomizer(); | |
initPaint(); | |
reset(); | |
} | |
/** | |
* 初始化画笔 | |
*/ | |
private void initPaint() { | |
paint = new Paint(Paint.ANTI_ALIAS_FLAG); | |
paint.setColor(Color.rgb(,255,255)); | |
paint.setStyle(Paint.Style.FILL); | |
} | |
public void reset(double positionY){ | |
size = randomizer.randomInt(sizeMinInPx, sizeMaxInPx, true); | |
if (image!=null){ | |
if (bitmap==null){ | |
bitmap = Bitmap.createScaledBitmap(image, size, size, false); | |
} | |
} | |
float speed = (float)(size - sizeMinInPx) / (sizeMaxInPx - sizeMinInPx) * (speedMax - speedMin) + speedMin; | |
double angle = Math.toRadians(randomizer.randomDouble(alphaMax) * randomizer.randomSignum()); | |
if (angle<-||angle>1){ | |
angle =; | |
} | |
speedX = speed* Math.sin(angle); | |
speedY = speed* Math.cos(angle); | |
alpha = randomizer.randomInt(alphaMin, alphaMax, false); | |
paint.setAlpha(alpha); | |
positionX = randomizer.randomDouble(parentWidth); | |
this.positionY = positionY; | |
} | |
public void reset(){ | |
size = randomizer.randomInt(sizeMinInPx, sizeMaxInPx, true); | |
if (image!=null){ | |
if (bitmap==null){ | |
bitmap = Bitmap.createScaledBitmap(image, size, size, false); | |
} | |
} | |
float speed = (float)(size - sizeMinInPx) / (sizeMaxInPx - sizeMinInPx) * (speedMax - speedMin) + speedMin; | |
double angle = Math.toRadians(randomizer.randomDouble(angleMax) * randomizer.randomSignum()); | |
speedX = speed* Math.sin(angle); | |
speedY = speed* Math.cos(angle); | |
alpha = randomizer.randomInt(alphaMin, alphaMax, false); | |
paint.setAlpha(alpha); | |
positionX = randomizer.randomDouble(parentWidth); | |
this.positionY=randomizer.randomDouble(parentHeight); | |
if (!alreadyFalling){ | |
this.positionY = this.positionY-parentHeight-size; | |
} | |
} | |
public void update(){ | |
positionX = positionX+speedX; | |
positionY = positionY+speedY; | |
if (positionY>parentHeight){ | |
positionY = -(double)size; | |
reset(positionY); | |
} | |
if (fadingEnabled){ | |
paint.setAlpha((int) (alpha * ((float) (parentHeight - positionY) / parentHeight))); | |
} | |
} | |
public void draw(Canvas canvas){ | |
if (bitmap!=null){ | |
canvas.drawBitmap(bitmap,(float)positionX,(float)positionY,paint); | |
}else { | |
canvas.drawCircle((float)positionX,(float)positionY,(float)size,paint); | |
} | |
} | |
} |