Android自定义控件(十)——SurfaceView实战实现天气APP背景移动效果

举报
择城终老 发表于 2021/07/26 23:50:35 2021/07/26
【摘要】 本文目录 SurfaceView与View区别使用缓冲的Canvas绘图SurfaceView生命周期实现天气APP背景自动左右循环移动效果 SurfaceView与View区别 前面我们所有的讲解基本都是自定义View来实现各种Android的自定义控件,但编写过相机的Android程序员,肯定对SurfaceView不陌生,那什么时候该用Sur...


实现效果

SurfaceView与View区别

前面我们所有的讲解基本都是自定义View来实现各种Android的自定义控件,但编写过相机的Android程序员,肯定对SurfaceView不陌生,那什么时候该用SurfaceView呢?

我们先来看一个概念,在Android中屏幕的刷新时间为16ms,如果View能够在16ms内完成所有的执行的绘图操作,那么在视觉上,界面是流畅的;否则APP就会卡顿,我们经常会看到如果View的逻辑非常复杂,Android Studio都会提示以下日志:

Skipped 60 frames! The application maybe doing too much work on its main thread

  
 
  • 1

之所以会提示这个警告,是因为我们在自定义View的绘图操作中,执行了非常复杂的逻辑运算,导致16s内并没有完成绘制,所以当出现在自定义View中非常复杂的耗时的逻辑运算时,就需要使用SurfaceView。

SurfaceView在两个方面改进了View的绘图操作:

1.使用了双缓冲技术

2.自带画布,支持在子线程中更新画布内容

这里说的双缓冲技术,就是多加了一块缓冲画布,当需要执行绘图操作的时候,先在缓冲画布上绘制,绘制好后直接将缓冲画布的内部更新到主画布之中。这样,在屏幕更新的时候,只需要把缓冲画布上的内容照搬过来就可以了,就不会存在耗时的逻辑问题,也解决了超时绘制。

使用缓冲的Canvas绘图

前面我们已经介绍了,SurfaceView时自带画布的,具有双缓冲技术,那么问题来了,我们怎么才能拿到这块画布呢?直接先上代码:

SurfaceHolder surfaceHolder=getHolder();
Canvas canvas=surfaceHodler.lockCanvas();
//中间执行绘图操作
surfaceHolder.unlockCanvasAndPost(canvas);

  
 
  • 1
  • 2
  • 3
  • 4

我们这里直接通过surfaceHolder.lockCanvas()获取到了缓冲画布,并且将画布上锁,防止被其他线程篡改,当绘图完成之后释放锁,通过surfaceHolder.unlockCanvasAndPost(canvas)进行释放,这段代码不仅释放锁,还将缓冲画布的内容更新到主线程的画布上,从而显示到屏幕中。

这里上锁是防止其他线程同时更新缓冲画布,造成缓冲画布乱七八糟,所以我们需要加锁,至于什么是线程锁,死锁,释放锁等知识,这是Java多线程的知识,详情参考Java多线程书籍或者操作系统,这属于基础,篇幅有限,这里就不赘述了。

SurfaceView生命周期

在讲解SurfaceView生命周期之前,我们先要理解三个概念:Surface,SurfaceView,SurfaceHolder。有过MVC开发经验的小伙伴应该会非常熟悉,SurfaceView就是视图V,Surface中保存了缓冲画布和绘制内容相关的各种数据,也就是模型M,SurfaceHolder很明显就是MVC中的C控制器。

所以,当我们需要操作SurfaceView的时候,必然需要Surface存在,所以Android专门提供了监听Surface生命周期的函数:

public class DemoSurfaceView extends SurfaceView { private SurfaceHolder surfaceHolder; public DemoSurfaceView(Context context) { super(context); } public DemoSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); this.surfaceHolder=getHolder(); this.surfaceHolder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }); } public DemoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

上面也是每个自定义SurfaceView的基本使用方式,下面小编解释以下Surface的生命周期。

1.surfaceCreated:当Surface对象被创建后,该函数就会调用。

2.surfaceChanged:当surface发生任何结构性变化时,可以时格式,或者大小变化,该函数就会被立即调用。

3.surfaceDestroyed:当surface将要被销毁时调用。

一般来说,我们需要在类初始化时就立即绘图,那么一般放在surfaceCreated中来开启子线程的绘图操作,以防止没被创建时,缓冲画布时空的,在surfaceDestroyed中观察线程是否执行完成,如果没有执行完成,但surface将要被销毁,必须强制取消线程执行。

实现天气APP背景自动左右循环移动效果

为了实现常用的天气APP自动移动背景效果,我们来看看我们首先需要定义哪些成员变量,根据刚才讲的我们需要观察线程在销毁时,线程是否在执行,所以必须定义个线程是否执行的布尔变量,surfaceHolder控制器当然也需要,左右移动只需要X坐标变化,所以也需要定义变化的X坐标值,代码如下:

private SurfaceHolder surfaceHolder;//控制器
private boolean flag=false;//线程是否能执行
private Bitmap bgBitmap;//背景图片
private float screenWidht,screenHeight;//屏幕宽高
private int mBgX;//绘制的X坐标
private Canvas canvas;//画布
private Thread thread;//线程
//定义一个枚举类型,判断移动的方向
private enum State{
	LEFT,RIGHT
}
private State state=State.LEFT;//开始向左运动
private final int MOVE_SIZE=1;//每次移动的距离

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

因为时左右循环啊移动,送所以我们还定义了枚举类型判断现在时向左还是向右,同时定义画布,屏幕宽高,以及当前运动方向,线程。

其次,我们需要监控Surface的生民周期,所以在其构造函数中调用如下方法进行监控:

public BgAnimSurfaceView(Context context, @Nullable AttributeSet attrs) {
	super(context, attrs);
	this.surfaceHolder=getHolder(); this.surfaceHolder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { flag=true;//设置线程可以执行绘图操作 startAnim();//执行动画 } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { flag=false;//设置线程不可以执行绘图操作 } });
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

接着,我们需要将背景图片宽度放大到屏幕的3/2,高度为屏幕高度,所以,我们首先必须将图片定义到指定的大小,用到前面的Bitmap知识,代码如下:

/***
* 执行动画
*/
private void startAnim(){ this.screenWidht=getWidth();//获取屏幕宽度 this.screenHeight=getHeight();//获取屏幕高度 int enlargeWidht=(int) getWidth()*3/2;//放大的倍数 Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.drawable.background);//获取源图片 this.bgBitmap=Bitmap.createScaledBitmap(bitmap,enlargeWidht,(int)this.screenHeight,true);//将源图片宽度放大3/2倍,生成新的图片 this.thread=new Thread(new Runnable() { @Override public void run() { while (flag){//如果线程可以执行 canvas=surfaceHolder.lockCanvas(); drawView();//绘制 surfaceHolder.unlockCanvasAndPost(canvas); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } }); this.thread.start();
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这段代码就是放大图片,然后执行左右移动,这里使用到了本文第二个知识点,如何使用缓冲画布,而我们将绘制的操作放在了drawView()函数中,这里我们50ms执行一次绘图操作,不设置间隔时间,移动可能很快,达不到慢慢移动的效果,接着我们看看drawView()代码实现:

/***
* 开始绘制
*/
private void drawView(){ this.canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);//先清空屏幕 this.canvas.drawBitmap(this.bgBitmap,this.mBgX,0,null);//绘制图片 switch (this.state){//判断现在是向左还是向右移动 case LEFT: this.mBgX-=this.MOVE_SIZE;//向左移动 break; case RIGHT: this.mBgX+=this.MOVE_SIZE;//向右移动 break; default: break; } //如果向左移动了1/2,那么更改为向右移动,本身图片宽度只有3/2都移动了1/2显然已经移动完了 if(this.mBgX<=-this.screenWidht/2){ this.state=State.RIGHT; } //如果X坐标大于0,向左移动 if(this.mBgX>=0){ this.state=State.LEFT; }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

这样我们就实现了天气APP背景自动移动的效果,代码中的注释已经够详细了,这里就不再赘述了,本文Github下载地址:点击下载

文章来源: liyuanjinglyj.blog.csdn.net,作者:李元静,版权归原作者所有,如需转载,请联系作者。

原文链接:liyuanjinglyj.blog.csdn.net/article/details/103315311

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。