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

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

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

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


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


1.使用了双缓冲技术


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


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


使用缓冲的Canvas绘图


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

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


我们这里直接通过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);
    }
}


上面也是每个自定义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;//每次移动的距离


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


其次,我们需要监控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;//设置线程不可以执行绘图操作
        }
    });
}


接着,我们需要将背景图片宽度放大到屏幕的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();
}


这段代码就是放大图片,然后执行左右移动,这里使用到了本文第二个知识点,如何使用缓冲画布,而我们将绘制的操作放在了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;
    }
}


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

相关文章
|
3天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义控件
【9月更文挑战第5天】在安卓开发的海洋中,自定义控件如同一艘精致的小船,让开发者能够乘风破浪,创造出既独特又高效的用户界面。本文将带你领略自定义控件的魅力,从基础概念到实战应用,一步步深入理解并掌握这一技术。
|
12天前
|
API Android开发
Android P 性能优化:创建APP进程白名单,杀死白名单之外的进程
本文介绍了在Android P系统中通过创建应用进程白名单并杀死白名单之外的进程来优化性能的方法,包括设置权限、获取运行中的APP列表、配置白名单以及在应用启动时杀死非白名单进程的代码实现。
34 1
|
16天前
|
IDE Java 开发工具
探索安卓开发之旅:打造你的第一款App
【8月更文挑战第24天】在这篇文章中,我们将一起踏上激动人心的安卓开发之旅。不论你是编程新手还是希望扩展技能的老手,本文将为你提供一份详尽指南,帮助你理解安卓开发的基础知识并实现你的第一个应用程序。从搭建开发环境到编写“Hello World”,每一步都将用浅显易懂的语言进行解释。那么,让我们开始吧!
|
27天前
|
开发工具 Android开发
|
27天前
|
Android开发
将AAB(Android App Bundle)转换为APK
将AAB(Android App Bundle)转换为APK
31 1
|
9天前
|
Android开发 iOS开发 C#
Xamarin:用C#打造跨平台移动应用的终极利器——从零开始构建你的第一个iOS与Android通用App,体验前所未有的高效与便捷开发之旅
【8月更文挑战第31天】Xamarin 是一个强大的框架,允许开发者使用单一的 C# 代码库构建高性能的原生移动应用,支持 iOS、Android 和 Windows 平台。作为微软的一部分,Xamarin 充分利用了 .NET 框架的强大功能,提供了丰富的 API 和工具集,简化了跨平台移动应用开发。本文通过一个简单的示例应用介绍了如何使用 Xamarin.Forms 快速创建跨平台应用,包括设置开发环境、定义用户界面和实现按钮点击事件处理逻辑。这个示例展示了 Xamarin.Forms 的基本功能,帮助开发者提高开发效率并实现一致的用户体验。
16 0
|
9天前
|
Android开发 UED 开发者
安卓开发中的自定义控件基础
【8月更文挑战第31天】在安卓应用开发过程中,自定义控件是提升用户界面和用户体验的重要手段。本文将通过一个简易的自定义按钮控件示例,介绍如何在安卓中创建和使用自定义控件,包括控件的绘制、事件处理以及与布局的集成。文章旨在帮助初学者理解自定义控件的基本概念,并能够动手实践,为进一步探索安卓UI开发打下坚实的基础。
|
9天前
|
存储 缓存 前端开发
安卓开发中的自定义控件实现及优化策略
【8月更文挑战第31天】在安卓应用的界面设计中,自定义控件是提升用户体验和实现特定功能的关键。本文将引导你理解自定义控件的核心概念,并逐步展示如何创建一个简单的自定义控件,同时分享一些性能优化的技巧。无论你是初学者还是有一定经验的开发者,这篇文章都会让你对自定义控件有更深的认识和应用。
|
12天前
|
存储 XML Linux
深入理解操作系统:进程管理与调度策略探索安卓应用开发:从零开始构建你的第一个App
【8月更文挑战第28天】在数字世界里航行,操作系统是掌控一切的舵手。本文将带你领略操作系统的精妙设计,特别是进程管理和调度策略这两大核心领域。我们将从基础概念出发,逐步深入到复杂的实现机制,最后通过实际代码示例,揭示操作系统如何高效协调资源,确保多任务顺畅运行的秘密。准备好了吗?让我们启航,探索那些隐藏在日常电脑使用背后的奥秘。 【8月更文挑战第28天】在这个数字时代,拥有一款自己的移动应用程序不仅是技术的展示,也是实现创意和解决问题的一种方式。本文将引导初学者了解安卓开发的基础知识,通过一个简单的待办事项列表App项目,逐步介绍如何利用安卓开发工具和语言来创建、测试并发布一个基本的安卓应用
|
13天前
|
Java 程序员 Android开发
探索安卓开发:构建你的第一个App
【8月更文挑战第27天】在数字化时代的浪潮中,移动应用成为人们生活不可或缺的一部分。对于渴望进入软件开发领域的新手而言,掌握如何构建一款简单的安卓App是开启技术之旅的关键一步。本文旨在通过浅显易懂的语言和步骤分解,引导初学者了解安卓开发的基础知识,并跟随示例代码,一步步实现自己的第一个安卓App。从环境搭建到界面设计,再到功能实现,我们将一同揭开编程的神秘面纱,让每个人都能体会到创造软件的乐趣。