安卓SurfaceView 实现下雨效果

安卓SurfaceView 实现下雨效果 先来一张效果图


我的思路

下雨每个雨滴用一条线来实现,生成一条线,X轴坐标随机,Y轴不断增加,就形成了下雨的效


1.首先定义mSurfaceView类,继承Serfaceview 并实现SurfaceHolder.Callbac以及Runnable接口

 
 
public class mSurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable{


    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {

    }


    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        Flag=false;

    } 
    
    @Override 
    public void run() { }
}
 
 
 
 

2.定义如下成员变量

 
//用于标注线程是否继续
private boolean Flag=true;

//SurfaceHolder
SurfaceHolder surfaceHolder;

//定义画笔
Paint paint=new Paint();

//雨滴的集合
public List<Line> lines=new ArrayList<>();

//Random对象 用于随机生成雨滴的X轴坐标
Random random=new Random();


3.实现构造方法,此处注意,构造方法用两个参数的

public mSurfaceView(Context context, AttributeSet attrs) {
    super(context, attrs);

    surfaceHolder=getHolder();
    surfaceHolder.addCallback(this);

    //设置背景透明
    this.setZOrderOnTop(true);
    surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
}


4.实现init方法 用于初始化画笔等操作

private void init() {
    //设置画笔颜色
    paint.setColor(Color.WHITE);
    //抗锯齿
    paint.setAntiAlias(true);
    //设置画笔颜色
    paint.setColor(Color.WHITE);
    //设置画笔模式为填充
    paint.setStyle(Paint.Style.FILL);
    //设置画笔宽度为2
    paint.setStrokeWidth(2f);
}

定义雨滴的类

前面说过,每个雨滴都是一条线 那么下面咱们实现雨滴的类:Line

//雨的类,每一条线代表一个雨滴
class Line{
    private int startx;//线的起始X坐标
    private int starty;//线的起始Y坐标
    private int stopx;//线的结束X坐标
    private int stopy;//线的结束Y坐标

    public int getStartx() {
        return startx;
    }

    public void setStartx(int startx) {
        this.startx = startx;
    }

    public int getStarty() {
        return starty;
    }

    public void setStarty(int starty) {
        this.starty = starty;
    }

    public int getStopx() {
        return stopx;
    }

    public void setStopx(int stopx) {
        this.stopx = stopx;
    }

    public int getStopy() {
        return stopy;
    }

    public void setStopy(int stopy) {
        this.stopy = stopy;
    }


}


每一条雨滴都是存在雨滴的集合内的,咱们需要实现添加雨滴的方法,生成雨滴并且把雨滴添加到集合中

//添加雨滴
private void addline() {
        Line line=new Line();
        //随机生成雨滴的起始X坐标
        line.startx=random.nextInt(getWidth());
        //设置雨滴的起始y坐标为-60  从屏幕外开始运动
        line.starty=-60;
        //雨滴偏移3个像素 看起来不会太直
        line.stopx=line.getStartx()+3;
        //雨滴的长度
        line.stopy=line.starty+60;
        //添加到集合
        lines.add(line);
}

好了,准备工作做完了,下面实现开始实现Run方法,绘制每一条雨滴

@Override
public void run() {
    Canvas canvas=null;
    Line line=null;
    while (Flag){
        try {
            canvas=surfaceHolder.lockCanvas();
            //清空画布
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        }catch (Exception e){
            break;
        }



        //遍历雨滴集合
        for (int i = 0; i < lines.size(); i++) {
            line=lines.get(i);
            //绘制雨滴
            canvas.drawLine(line.getStartx(),line.getStarty(),line.getStopx(),line.getStopy(),paint);

            //绘制雨滴之后 更改雨滴的Y轴坐标 下次绘制时即可更新位置 使雨滴下落
            //取三个随机数 每个随机数代表3种不同的长度以及下落速度
            int c=random.nextInt(3);
            if (c==0){
                line.setStarty(line.getStarty()+30);
                line.setStopy(line.getStarty()+50);
            }
            if (c==1){
                line.setStarty(line.getStarty()+40);
                line.setStopy(line.getStarty()+60);
            }
            if (c==2){
                line.setStarty(line.getStarty()+50);
                line.setStopy(line.getStarty()+40);
            }
            if (c==3){
                line.setStarty(line.getStarty()+60);
                line.setStopy(line.getStarty()+45);
            }

        }

        //解锁画布
        surfaceHolder.unlockCanvasAndPost(canvas);

        //添加雨滴
        addline();
        //当雨滴大于100条时 删除第一个 让雨滴保持在100条
        if (lines.size()>100){
            lines.remove(0);
        }
        Log.d("log", "run: "+lines.size());
    }
}
 

在surfaceCreated方法中启动线程:

@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
    //初始化画笔等
    init();

    Flag=true;

    //启动线程绘制雨滴
    new Thread(this).start();
}


大工告成,在布局中添加:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent" android:background="@mipmap/bg">

    <maill.shop.com.volley.mtest.mSurfaceView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>



github完整代码地址:SurfaceView实现下雨效果

csdn资源下载:安卓SurfaceView实现下雨效果源码

PS:如有不正确的地方欢迎指出!

转载请注明出处:https://blog.csdn.net/weixin_41549915/article/details/80257734

效果开始前先做个热身( ˘•灬•˘ )自己实现比较容易,但是到了要出博客整理思路,总结要点的时候就挠头,不知云所以,所以最简单的还是 Read the fucking source code如果对安卓UI有兴趣的朋友可以加我好友互相探讨,这里有很多自定义view可以参考思路思路比较简单,整个view无非两样东西云雨滴这里又包含两部分动画,一部分是云的左右移动动画,一部分是雨滴移动动画 那我们这里可以自定义一些属性,如果对自定义属性还不太了解的同学,搜下百度哈<resources>     <declare-styleable name="RainyView">         <!--雨滴的颜色-->         <attr name="raindrop_color" format="color"></attr>         <!--左边云的颜色-->         <attr name="left_cloud_color" format="color"></attr>         <!--右边云的颜色-->         <attr name="right_cloud_color" format="color"></attr>         <!-可同时存在的雨滴的最大数量-->         <attr name="raindrop_max_number" format="integer"></attr>         <!--每个雨滴之间创建的时间间隔-->         <attr name="raindrop_creation_interval" format="integer"></attr>         <!--每个雨滴的最小长度-->         <attr name="raindrop_min_length" format="integer"></attr>         <!--每个雨滴的最大长度-->         <attr name="raindrop_max_length" format="integer"></attr>         <!--雨滴的大小-->         <attr name="raindrop_size" format="integer"></attr>         <!--雨滴的最小移动速度-->         <attr name="raindrop_min_speed" format="float"></attr>         <!--雨滴的最大移动速度-->         <attr name="raindrop_max_speed" format="float"></attr>         <!--雨滴的斜率-->         <attr name="raindrop_slope" format="float"></attr>     </declare-styleable> </resources>画云云怎么画?云的形状不可胜举,我这里只实现了一种简单的形状:那我们如何通过画笔将其画出来:1.首先,我们先画底部,底部是一个圆角的矩形,通过下面方法绘制添加圆角矩形path.addRoundRect(RectF rect, float rx, float ry, Direction dir) 2.在该圆角的矩形的基础上,再画两个圆,左边的为小圆,右边的为大圆,这样就产生了一个最简单的云的图形, 在设置了以下代码之后paint.setStyle(Paint.Style.FILL);云的效果如下:我们把这个云作为左边的云,那么右边的云怎么画?很简单,因为我们这里用path来装载了这个云的路径,通过以下方法,mComputeMatrix.preTranslate(rightCloudTranslateX, -calculateRect.height() * (1 - CLOUD_SCALE_RATIO) / 2); mComputeMatrix.postScale(CLOUD_SCALE_RATIO, CLOUD_SCALE_RATIO, rightCloudCenterX, leftCloudEndY); mLeftCloudPath.transform(mComputeMatrix, mRightCloudPath);将这个云的path移动,缩小,并将其路径转换到mRightCloudPath即可在onDraw()的时候,调用以下方法就可以描绘路径了canvas.drawPath()接下来我们来实现云的动画,我们由上面已经了解到:/**  * Transform the points in this path by matrix, and write the answer  * into dst. If dst is null, then the the original path is modified.  *  * @param matrix The matrix to apply to the path  * @param dst    The transformed path is written here. If dst is null,  *               then the the original path is modified  */ public void transform(Matrix matrix, Path dst) {     long dstNative = 0;     if (dst != null) {         dst.isSimplePath = false;         dstNative = dst.mNativePath;     }     nTransform(mNativePath, matrix.native_instance, dstNative); }该方法可以将一个path进行matrix转换,即矩阵转换,因此我们可以通过方法matrix.postTranslate来实现平移动画,即创建一个循环动画,通过postTranslate来设置动画值就可以了,这里左边的云在右边的云之上,因此先画右边的云。mComputeMatrix.reset(); mComputeMatrix.postTranslate((mMaxTranslationX / 2) * mRightCloudAnimatorValue, 0); mRightCloudPath.transform(mComputeMatrix, mComputePath); canvas.drawPath(mComputePath, mRightCloudPaint); mComputeMatrix.reset(); mComputeMatrix.postTranslate(mMaxTranslationX * mLeftCloudAnimatorValue, 0); mLeftCloudPath.transform(mComputeMatrix, mComputePath); canvas.drawPath(mComputePath, mLeftCloudPaint);画雨滴首先我们要知道一点是,所有的雨滴都是随机产生的,而产生的值,可以根据上面的自定义属性指定,也可以使用自定义的值,我们先定义一个雨滴类private class RainDrop{     float speedX;  //雨滴x轴移动速度     float speedY;   //雨滴y轴移动速度     float xLength; //雨滴的x轴长度     float yLength; //雨滴的y轴长度     float x;        //雨滴的x轴坐标     float y;        //雨滴的y轴坐标     float slope; //雨滴的斜率 }关于上面参数,这里画张图来示例:关于斜率 我这里开放了一个设置斜率的接口,代表雨滴的一个倾斜度,可以看到下图的雨滴都是倾斜的,就是通过斜率来设置这个倾斜度 斜率:表示一条直线(或曲线的切线)关于(横)坐标轴倾斜程度的量。它通常用直线(或曲线的切线)与(横)坐标轴夹角的正切,或两点的纵坐标之差与横坐标之差的比来表示。该直线的斜率为k=(y1-y2)/(x1-x2)我这里使用了固定的斜率,使所有的雨滴方向一致,如果想将其改为随机值的同学,可以下载源码自行修改。在创建雨滴对象的时候,以下步骤使我们需要做的:斜率赋值(我这里是指定的,因此不用计算随机斜率)计算x轴、y轴移动速度随机值计算雨滴长度随机值(同时计算x轴,y轴长度值)计算x,y坐标随机值(为了营造雨滴更好的出场效果,这里设置了y轴的起点坐标为y-雨滴y轴长度)创建雨滴对象后,我们有了想要的参数,我们可以canvas.drawLine来画雨滴canvas.drawLine(rainDrop.x, rainDrop.y,             rainDrop.slope > 0 ? rainDrop.x   rainDrop.xLength : rainDrop.x - rainDrop.xLength,             rainDrop.y   rainDrop.yLength,             mRainPaint);这里需要注意以下,为什么canvas.drawLine中的stopX参数要设置为rainDrop.slope > 0 ? rainDrop.x   rainDrop.xLength : rainDrop.x - rainDrop.xLength这是因为,我们的雨滴是一直往下移动即y是增加的,我们上面知道斜率公式为:k=(y1-y2)/(x1-x2)即y1-y2肯定是大于0的,因此当斜率小于0的时候,雨滴是这样的,即x1-x2 < 0 当斜率大于0的时候,雨滴是这样的,即x1-x2 > 0 雨滴动画,由于每一个雨滴对象参数已经定义,在进行动画的时候,只需要根据速度,设置x、y轴的下一个点的坐标就行了if (rainDrop.slope >= 0) {         rainDrop.x  = rainDrop.speedX;     }else{         rainDrop.x -= rainDrop.speedX;     } rainDrop.y  = rainDrop.speedY;优化我们知道,在频繁的创建雨滴的时候,如果每次都创建新对象的话, 可能会增加不必要的内存使用,而且很容易引起频繁的gc,甚至是内存抖动。因此这里我增加了一个回收功能//首先判断栈中是否存在回收的对象,若存在,则直接复用,若不存在,则创建一个新的对象 private RainDrop obtainRainDrop(){      if (mRecycler.isEmpty()){          return new RainDrop();      }      return mRecycler.pop();  } //回收到一个栈里面,若这个栈数量超过最大可显示数量,则pop private void recycle(RainDrop rainDrop){     if (rainDrop == null){         return;     }     if (mRecycler.size() >= mRainDropMaxNumber){         mRecycler.pop();     }     mRecycler.push(rainDrop); }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值