讲讲Android为自定义view提供的SurfaceView

简介: 本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。

系列文章目录

讲讲Android为自定义view提供的SurfaceView

前言

前几天发表了几篇在自定义view中通过修改值实现动态效果的文章。起到主要作用的是调用刷新界面的方法。但是假设绘制的过程逻辑比较复杂,并且界面更新频繁,这时候就会造成界面的卡顿。十分影响用户体验感。

灵感来源于,Android官方demo(效果图如下)

1.gif

一、Android为什么会提供SurfaceView

View是通过刷新来重绘视图,并且有一个刷新的间隔,当绘制过程逻辑很复杂加上界面更新还非常频繁时,就可能无法在间隔内完成绘制,就会造成界面效果的卡顿,影响用户体验,为此Android提供了SurfaceView来解决这一问题。

二、先看看Android Demo的实现

1.实现接口以及接口定义的方法

....implements SurfaceHolder.Callback2
public void surfaceCreated(SurfaceHolder holder) {
   
    synchronized (mDrawingThread) {
   
        mDrawingThread.mSurface = holder;
        mDrawingThread.notify();
    }
}

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
   
    //这里不需要做任何事情;绘制线程将从画布中获取
}

public void surfaceRedrawNeeded(SurfaceHolder holder) {
   
}

public void surfaceDestroyed(SurfaceHolder holder) {
   
    //我们需要告诉绘图线程停止
    synchronized (mDrawingThread) {
   
        mDrawingThread.mSurface = holder;
        mDrawingThread.notify();
        while (mDrawingThread.mActive) {
   
            try {
   
                mDrawingThread.wait();
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }
    }
}

2.与Activity生命周期进行绑定

@Override
protected void onPause() {
   
    super.onPause();

    //当我们暂停时,确保绘制线程没有运行。
    synchronized (mDrawingThread) {
   
        mDrawingThread.mRunning = false;
        mDrawingThread.notify();
    }
}

@Override
protected void onResume() {
   
    super.onResume();

    //让绘图线程继续运行。
    synchronized (mDrawingThread) {
   
        mDrawingThread.mRunning = true;
        mDrawingThread.notify();
    }
}

@Override
protected void onDestroy() {
   
    super.onDestroy();

    //确保绘图线程消失。
    synchronized (mDrawingThread) {
   
        mDrawingThread.mQuit = true;
        mDrawingThread.notify();
    }
}

3.完成初始化操作

4.实现

通过lockCanvas()方法获得Canvas对象

//锁定画布进行绘图。
Canvas canvas = mSurface.lockCanvas();
if (canvas == null) {
   
    Log.i("WindowSurface", "Failure locking canvas");
    continue;
}

在子线程中使用Canvas对象进行绘制

// 更新图形
if (!mInitialized) {
    mInitialized = true;
    mPoint1.init(canvas.getWidth(), canvas.getHeight(), mMinStep);
    mPoint2.init(canvas.getWidth(), canvas.getHeight(), mMinStep);
    mColor.init(127, 127, 1);
} else {
    mPoint1.step(canvas.getWidth(), canvas.getHeight(),
            mMinStep, mMaxStep);
    mPoint2.step(canvas.getWidth(), canvas.getHeight(),
            mMinStep, mMaxStep);
    mColor.step(127, 127, 1, 3);
}
//颜色的效果
mBrightLine+=2;
if (mBrightLine > (NUM_OLD*2)) {
    mBrightLine = -2;
}

// 清理背景
canvas.drawColor(mBackground.getColor());

// 画旧线
for (int i=mNumOld-1; i>=0; i--) {
    mForeground.setColor(mOldColor[i] | makeGreen(i));
    mForeground.setAlpha(((NUM_OLD-i) * 255) / NUM_OLD);
    int p = i*4;
    canvas.drawLine(mOld[p], mOld[p+1], mOld[p+2], mOld[p+3], mForeground);


}

// 画新线
int red = (int)mColor.x + 128;
if (red > 255) red = 255;
int blue = (int)mColor.y + 128;
if (blue > 255) blue = 255;
int color = 0xff000000 | (red<<16) | blue;
mForeground.setColor(color | makeGreen(-2));
canvas.drawLine(mPoint1.x, mPoint1.y, mPoint2.x, mPoint2.y, mForeground);




// 添加新的线条
if (mNumOld > 1) {
    System.arraycopy(mOld, 0, mOld, 4, (mNumOld-1)*4);
    System.arraycopy(mOldColor, 0, mOldColor, 1, mNumOld-1);
}
if (mNumOld < NUM_OLD) mNumOld++;
mOld[0] = mPoint1.x;
mOld[1] = mPoint1.y;
mOld[2] = mPoint2.x;
mOld[3] = mPoint2.y;
mOldColor[0] = color;
  • 使用unlockCanvasAndPost()方法将画布内容进行提交
//全部完成
mSurface.unlockCanvasAndPost(canvas);

5.运行

//告诉活动的窗口,我们想做我们自己的绘制
getWindow().takeSurface(this);
//这是将绘制到我们的表面的线程。
mDrawingThread = new DrawingThread();
mDrawingThread.start();

三、继承SurfaceView实现

1.自定义类继承自SurfaceView,并且实现两个接口以及接口定义的方法。

public class MyView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    //创建
    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }
    //改变
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }
    //销毁
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
    //子线程
    @Override
    public void run() {
    //子线程中执行的绘图逻辑
    }
}

2.初始化

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

3.步骤与Android Demo的实现-4.实现类似

  • 通过lockCanvas()方法获得Canvas对象
  • 在子线程中使用Canvas对象进行绘制(run())
  • 使用unlockCanvasAndPost()方法将画布内容进行提交
        try {
            mCanvas = mSurfaceHolder.lockCanvas();
            mCanvas.drawColor(Color.WHITE);
            //绘制的逻辑
            .....
        }catch (Exception e){

        }finally {
            if (mCanvas != null){
                //释放canvas对象并提交画布
                mSurfaceHolder.unlockCanvasAndPost(mCanvas);
            }
        }

四、放一个使用案例源码

效果见:Android自定义view之线条等待动画(灵感来源:金铲铲之战)

public class MyView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
   
    private SurfaceHolder mSurfaceHolder;
    private Canvas mCanvas;
    private int mWidth;
    private int mHeight;
    private int useWidth, minwidth;
    private boolean viewContinue=true,viewContinue1=true;
    private float mSweep,mSweep1;
    private boolean runDrawing;
    private Paint mPaint;
    public MyView(Context context) {
   
        super(context);
        initView();
    }

    public MyView(Context context, AttributeSet attrs) {
   
        super(context, attrs);
        initView();
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
   
        super(context, attrs, defStyleAttr);
    }
    //创建
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
   
        runDrawing = true;
        new Thread(this).start();
    }
    //改变
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
   

    }
    //销毁
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
   
        runDrawing=false;
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        useWidth = mWidth;
        if (mWidth > mHeight) {
   
            useWidth = mHeight;
        }

    }
    //子线程
    @Override
    public void run() {
   
        while (runDrawing){
   
            draw();
        }
    }

    //绘制
    private void draw() {
   
        try {
   
            //获得canvas对象
            mCanvas = mSurfaceHolder.lockCanvas();
            //绘制背景颜色
            mCanvas.drawColor(Color.WHITE);

            minwidth = useWidth / 10;

            mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5+mSweep,minwidth*5+mSweep,mPaint);
            mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5-mSweep,minwidth*5-mSweep,mPaint);
            mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5+mSweep,minwidth*5-mSweep,mPaint);
            mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5-mSweep,minwidth*5+mSweep,mPaint);

            mCanvas.drawLine(minwidth*5+mSweep,minwidth*5+mSweep,minwidth*5+mSweep,minwidth*5+mSweep-mSweep1,mPaint);
            mCanvas.drawLine(minwidth*5-mSweep,minwidth*5-mSweep,minwidth*5-mSweep,minwidth*5-mSweep+mSweep1,mPaint);
            mCanvas.drawLine(minwidth*5+mSweep,minwidth*5-mSweep,minwidth*5+mSweep-mSweep1,minwidth*5-mSweep,mPaint);
            mCanvas.drawLine(minwidth*5-mSweep,minwidth*5+mSweep,minwidth*5-mSweep+mSweep1,minwidth*5+mSweep,mPaint);


            if (viewContinue&&viewContinue1){
   
                mSweep += 2;
                if (mSweep > minwidth*2) {
   
                    viewContinue=false;

                }

            }
            if (!viewContinue&&viewContinue1){
   
                mSweep1 += 4;
                if (mSweep1 > 4*minwidth) {
   
                    viewContinue1=false;
                    viewContinue=true;

                }
            }
            if (viewContinue&&!viewContinue1){
   
                if (mSweep1 <=0) {
   
                    mSweep-=4;
                    if (mSweep<0){
   
                        viewContinue=true;
                        viewContinue1=true;
                    }

                }else{
   
                    mSweep1 -= 2;
                }
            }
            //刷新View
            invalidate();
        }catch (Exception e){
   

        }finally {
   
            if (mCanvas != null){
   
                //释放canvas对象并提交画布
                mSurfaceHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }
    private void initView(){
   
        mSurfaceHolder = getHolder();
        //注册回调方法
        mSurfaceHolder.addCallback(this);
        setFocusable(true);
        setKeepScreenOn(true);
        setFocusableInTouchMode(true);
        //初始化画笔
        initPaint();
    }

    private void initPaint() {
   
        mPaint = new Paint();        //创建画笔对象
        mPaint.setColor(Color.BLACK);    //设置画笔颜色
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(4f);     //设置画笔宽度为10px
        mPaint.setAntiAlias(true);     //设置抗锯齿
        mPaint.setAlpha(255);        //设置画笔透明度
    }
}

五、拓展一下(以下内容来源于网络)

View和SurfaceView的区别:

  • View适用于主动更新的情况,而SurfaceView则适用于被动更新的情况,比如频繁刷新界面。
  • View在主线程中对页面进行刷新,而SurfaceView则开启一个子线程来对页面进行刷新。
  • View在绘图时没有实现双缓冲机制,SurfaceView在底层机制中就实现了双缓冲机制。
相关文章
|
29天前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
268 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
29天前
|
Android开发
Android自定义view之利用PathEffect实现动态效果
本文介绍如何在Android自定义View中利用`PathEffect`实现动态效果。通过改变偏移量,结合`PathEffect`的子类(如`CornerPathEffect`、`DashPathEffect`、`PathDashPathEffect`等)实现路径绘制的动态变化。文章详细解析了各子类的功能与参数,并通过案例代码展示了如何使用`ComposePathEffect`组合效果,以及通过修改偏移量实现动画。最终效果为一个菱形图案沿路径运动,源码附于文末供参考。
|
29天前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
143 65
Android自定义view之网易云推荐歌单界面
|
29天前
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
387 84
|
8月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
8月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
74 2
|
8月前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
154 3
|
10月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
View的绘制和事件处理是两个重要的主题,上一篇《图解 Android事件分发机制》已经把事件的分发机制讲得比较详细了,这一篇是针对View的绘制,View的绘制如果你有所了解,基本分为measure、layout、draw 过程,其中比较难理解就是measure过程,所以本篇文章大幅笔地分析measure过程,相对讲得比较详细,文章也比较长,如果你对View的绘制还不是很懂,对measure过程掌握得不是很深刻,那么耐心点,看完这篇文章,相信你会有所收获的。
208 3
|
11月前
|
消息中间件 前端开发 Android开发
Android面试题自定义View之Window、ViewRootImpl和View的三大流程
Android开发中,View的三大核心流程包括measure(测量)、layout(布局)和draw(绘制)。MeasureSpec类在测量过程中起到关键作用,它结合尺寸大小和模式(EXACTLY、AT_MOST、UNSPECIFIED)来指定View应如何测量。onMeasure方法用于自定义View的测量,布局阶段,ViewGroup调用onLayout确定子元素位置,而draw阶段按照特定顺序绘制背景、内容、子元素和装饰。整个流程始于ViewRootImpl的performTraversals,该方法触发测量、布局和绘制。
227 0
OSZAR »