android进阶4step1:Android动画处理与自定义View——SurfaceView

SurfaceView简介

  • 1、SurfaceView与View的区别
  • 2、SurfaceView的具体使用场景
  • 3、如何使用SurfaceView

一、SurfaceView与View的区别

  • 1、不使用onDraw
  • 2、非UI线程绘制
  • 3、独立的Surface

二、SurfaceView的具体使用场景

  • 1、视频播放
  • 2、一些炫酷的动画效果 (一直在动的动画)
  • 3、小游戏

三、如何使用SurfaceView

  • 1、获取SurfaceHolder对象
  • 2、监听Surface的创建
  • 3、开启异步线程进行while循环(子线程可以不停地绘制)
  • 4、通过SurfaceHolder获取Canvas进行绘制

固定模板:

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * <p>文件描述:<p>
 * <p>作者:Mr-Donkey<p>
 * <p>创建时间:2018/12/10 21:09<p>
 * <p>更改时间:2018/12/10 21:09<p>
 * <p>版本号:1<p>
 */
public class SurfaceViewTemplate extends SurfaceView implements Runnable {
    private Thread mThread;
    //多线程的同步
    private volatile boolean isRunning;

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

        /**
         * 如何使用SurfaceView
         1、获取SurfaceHolder对象
         2、监听Surface的创建
         3、开启异步线程进行while循环(子线程可以不停地绘制)
         4、通过SurfaceHolder获取Canvas进行绘制
         */
        //1.获取SurfaceHolder对象
        SurfaceHolder holder = getHolder();
        //2.监听Surface创建
        holder.addCallback(new SurfaceHolder.Callback() {

            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                //监听Surface创建完毕
                mThread = new Thread(SurfaceViewTemplate.this);
                //执行run方法
                mThread.start();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                isRunning = false;
            }
        });
    }

    @Override
    public void run() {
        while (isRunning) {
            drawSelf();
        }
    }

    /**
     * 3、开启异步线程进行while循环(子线程可以不停地绘制)
     * 4、通过SurfaceHolder获取Canvas进行绘制
     */
    private void drawSelf() {
        Canvas canvas = null;
        try {
            canvas = getHolder().lockCanvas();
            if (canvas != null) {
                //draw
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (canvas != null) {
                //释放canvas
                getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }
}

surface实例:

在子线程开启while循环 保证子线程一直运行

(一直在动态绘制圆(动态改变半径)( 1.先绘制白色背景(覆盖之前绘制的圆) 2.再绘制圆)) 重复1、2步骤

完整代码:

public class SurfaceViewTemplate extends SurfaceView implements Runnable {
    private Thread mThread;
    //多线程的同步
    private volatile boolean isRunning;
    //画笔
    private Paint mPaint;

    //定义圆的属性
    private int mMinRadius;
    private int mMaxRadius;
    private int mRadius;
    private int mFlag;

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

        /**
         * 三、如何使用SurfaceView
         1、获取SurfaceHolder对象
         2、监听Surface的创建
         3、开启异步线程进行while循环(子线程可以不停地绘制)
         4、通过SurfaceHolder获取Canvas进行绘制
         */
        //1.获取SurfaceHolder对象
        SurfaceHolder holder = getHolder();
        //2.监听Surface创建
        holder.addCallback(new SurfaceHolder.Callback() {


            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                //让线程开启
                isRunning = true;
                //监听Surface创建完毕
                mThread = new Thread(SurfaceViewTemplate.this);
                //执行run方法
                mThread.start();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                isRunning = false;
            }
        });

        //SurfaceView设置属性
        setFocusable(true);
        setFocusableInTouchMode(true);
        setKeepScreenOn(true);
        //初始化画笔
        initPaint();

    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        mPaint = new Paint();
        mPaint.setDither(true);
        //抗锯齿
        mPaint.setAntiAlias(true);
        //空心圆
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(6);
        mPaint.setColor(Color.GREEN);
    }

    /**
     * 控件大小发生改变时调用 系统调用
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //-20 随意写的
        mMaxRadius = Math.min(w, h) / 2 - 20;
        mRadius = mMinRadius = 30;
    }

    @Override
    public void run() {
        while (isRunning) {
            //不断的绘制
            drawSelf();
        }
    }

    /**
     * 3、开启异步线程进行while循环(子线程可以不停地绘制)
     * 4、通过SurfaceHolder获取Canvas进行绘制
     */
    private void drawSelf() {
        Canvas canvas = null;
        try {
            canvas = getHolder().lockCanvas();
            if (canvas != null) {
                drawCircle(canvas);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (canvas != null) {
                //释放canvas
                getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }

    /**
     * 画圆的方法
     * 因为放到while里要不断循环绘制视图
     * 1.第一次先画个白色背景
     * 2.背景之上画个圆
     * 循环1.2 动态改变 圆的半径
     *
     * @param canvas
     */
    private void drawCircle(Canvas canvas) {
        //绘制背景(覆盖之前的圆)
        canvas.drawColor(Color.WHITE);
        //绘制圆
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, mPaint);
        //动态改变 圆的半径
        if (mRadius >= mMaxRadius) {
            mFlag = -1;
        } else if (mRadius <= mMinRadius) {
            mFlag = 1;
        }
        //达到最大值递减 2个像素 达到最小值递减2个像素
        mRadius += mFlag * 2;
    }
}

实现飞享的小鸟的案例  

先了解一下canvas画矩形的方法:

public voiddrawRect(float left, float top, float right, float bottom,Paint paint)

           说明:绘制一个矩型。需要注明的是绘制矩形的参数和Java中的方法不一样。

              该方法的参数图解说明如下:

        各位看官请注意:图中X、Y轴方向标记错误。 自己也懒得重新修正了。

           

           那么,矩形的高 height = bottom  - right

                      矩形的宽 width  = right – left

       PS :假如drawRect的参数有误,比如right < left ,Android是不会给我们检查的,也不会提示相应的错误信息,

           但它会绘画出一个高或宽很小的矩形,可能不是你希望的。

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">

    <!--让自定义的surfaceview 占满 并使用NoActionbar主题 xml中设置-->
    <com.demo.surfaceviewdemo.game.FlappyBird
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
FlappyBird.java   主要逻辑 循环绘制动画 和动态改变鸟的高度 和 地板的x方向的值
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.support.annotation.DrawableRes;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.demo.surfaceviewdemo.R;

/**
 * <p>文件描述:<p>
 * <p>作者:Mr-Donkey<p>
 * <p>创建时间:2018/12/10 21:09<p>
 * <p>更改时间:2018/12/10 21:09<p>
 * <p>版本号:1<p>
 */
public class FlappyBird extends SurfaceView implements Runnable {
    private Thread mThread;
    //多线程的同步
    private volatile boolean isRunning;

    //绘制的矩形区域
    private RectF mDestRect;
    //res
    private Bitmap mBg;
    private Bitmap mBirdbm;
    private Bitmap mFloorbm;

    //定义Floor对象
    private Floor mFloor;
    private Bird mBrid;

    //定义 底部x的移动速度
    private int mSpeed;

    //点击屏幕鸟上升的距离
    private static final int TOUCH_UP_SIZE = -16;
    private int mBirdUpDis;
    //鸟自由下落的大小
    private static final int SIZE_AUTO_DOWN = 2;
    private int mAutoDownDis;

    //中间值
    private int mTempBirdDis;


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

        /**
         * 三、如何使用SurfaceView
         1、获取SurfaceHolder对象
         2、监听Surface的创建
         3、开启异步线程进行while循环(子线程可以不停地绘制)
         4、通过SurfaceHolder获取Canvas进行绘制
         */
        //1.获取SurfaceHolder对象
        SurfaceHolder holder = getHolder();
        //2.监听Surface创建
        holder.addCallback(new SurfaceHolder.Callback() {


            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                //让线程开启
                isRunning = true;
                //监听Surface创建完毕
                mThread = new Thread(FlappyBird.this);
                //执行run方法
                mThread.start();
                //当用户点击home的时候,会重新绘制 定位鸟的位置
                //让鸟重写在屏幕中央
                mBrid.reset();
                mTempBirdDis = 0;
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                isRunning = false;
            }

        });

        //SurfaceView设置属性
        setFocusable(true);
        setFocusableInTouchMode(true);
        setKeepScreenOn(true);
        //初始化资源
        initRes();
        //设置底部x的移动速度
        mSpeed = Util.dp2px(getContext(), 2);
        //每次点击上升的距离
        mBirdUpDis = Util.dp2px(getContext(), TOUCH_UP_SIZE);
        //每次自由下落的大小
        mAutoDownDis = Util.dp2px(getContext(), SIZE_AUTO_DOWN);

    }

    /**
     * 初始化资源
     */
    private void initRes() {
        mBg = loadBitmapByResId(R.drawable.bg1);
        mBirdbm = loadBitmapByResId(R.drawable.b1);
        mFloorbm = loadBitmapByResId(R.drawable.floor_bg);

    }

    //封装初始化资源
    //@DrawableRes 注解 来增强代码的健壮性 必须要传入一个 DrawableRes值
    private Bitmap loadBitmapByResId(@DrawableRes int resId) {
        return BitmapFactory.decodeResource(getResources(), resId);
    }

    /**
     * 控件大小发生改变时调用 系统调用
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //矩形区域
        mDestRect = new RectF(0, 0, w, h);
        mFloor = new Floor(getContext(), w, h, mFloorbm);
        mBrid = new Bird(getContext(), w, h, mBirdbm);
    }

    @Override
    public void run() {
        while (isRunning) {
            long start = System.currentTimeMillis();
            drawSelf();
            long end = System.currentTimeMillis();
            //为了避免浪费资源
            //限制每隔50ms绘制一次 自己定义
            if (end - start < 50) {
                try {
                    Thread.sleep(50 - (end - start));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 3、开启异步线程进行while循环(子线程可以不停地绘制)
     * 4、通过SurfaceHolder获取Canvas进行绘制
     */
    private void drawSelf() {
        Canvas canvas = null;
        try {
            canvas = getHolder().lockCanvas();
            if (canvas != null) {
                //进行绘制工作
                logic();
                drawBg(canvas);
                drawBird(canvas);
                drawFloor(canvas);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (canvas != null) {
                //释放canvas
                getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }

    /***
     *执行一定的逻辑
     *
     */
    private void logic() {
        //动态改变Floor地板 x的位置让它有动起来的效果
        mFloor.setX(mFloor.getX() - mSpeed);

        //动态改变鸟的下降的速度y越大 越靠近底部
        mTempBirdDis += mAutoDownDis;
        mBrid.set(mBrid.getY() + mTempBirdDis);
    }

    //点击屏幕的世界
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            //赋值给中间变量 让 y减小 即上升
            mTempBirdDis = mBirdUpDis;
        }
        return true;
    }

    //绘制背景
    private void drawBg(Canvas canvas) {
        //参数1 bitmap,matrix,绘制矩形区域
        canvas.drawBitmap(mBg, null, mDestRect, null);
    }

    /**
     * 对于下面有具体逻辑的绘制
     * 自定义类实现具体逻辑方便些
     *
     * @param canvas
     */
    //绘制地板
    private void drawFloor(Canvas canvas) {
        mFloor.draw(canvas);
    }

    //绘制小鸟
    private void drawBird(Canvas canvas) {
        mBrid.draw(canvas);
    }


}

工具类 Util.java 将dp转换成px

public class Util {
    //将dp转成 px
    public static int dp2px(Context context, int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal,
                context.getResources().getDisplayMetrics());
    }
}

Bird和Floor的基类  抽取的公共方法

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;

/**
 * <p>文件描述:<p>
 * <p>作者:Mr-Donkey<p>
 * <p>创建时间:2018/12/10 23:09<p>
 * <p>更改时间:2018/12/10 23:09<p>
 * <p>版本号:1<p>
 */

/**
 * 地板和鸟的公共类
 */
public abstract class DrawablePart {
    protected Context mContext;
    protected int mGameWidth;
    protected int mGameHeight;
    protected Bitmap mBitmap;

    public DrawablePart(Context context, int gameW, int gameH, Bitmap bitmap) {
        mContext = context;
        mGameWidth = gameW;
        mGameHeight = gameH;
        mBitmap = bitmap;
    }

    public abstract void draw(Canvas canvas);

}

Bird.java   实现鸟的绘制

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RectF;

/**
 * <p>文件描述:<p>
 * <p>作者:Mr-Donkey<p>
 * <p>创建时间:2018/12/10 23:12<p>
 * <p>更改时间:2018/12/10 23:12<p>
 * <p>版本号:1<p>
 */
public class Bird extends DrawablePart {
    private int x;
    private int y;

    private static final float RADIO_Y_POS = 1 / 2F;

    // 30dp
    private static final int WIDTH_BIRD = 30;

    private int mWidth;
    private int mHeight;

    //绘制的矩形范围
    private RectF mRect = new RectF();

    public Bird(Context context, int gameW, int gameH, Bitmap bitmap) {
        super(context, gameW, gameH, bitmap);
        //鸟所在的y的位置
        y = (int) (gameH * RADIO_Y_POS);
        //设置鸟的宽度为30dp
        mWidth = Util.dp2px(context, WIDTH_BIRD);
        //在和宽度等比例下 鸟的高度
        mHeight = (int) (mWidth * 1.0f / bitmap.getWidth() * bitmap.getHeight());
        //绘制鸟的初始左边x位置 屏幕的一半减去鸟本身的宽度的一半
        x = gameW / 2 - mWidth / 2;
    }

    @Override
    public void draw(Canvas canvas) {
        mRect.set(x, y, x + mWidth, y + mHeight);
        canvas.drawBitmap(mBitmap, null, mRect, null);
    }

    /**
     * 重置bird位置
     * 改变高度
     */
    public void reset() {
        y = (int) (mHeight * RADIO_Y_POS);
    }

    /**
     * 暴露方法点击时动态改变y的值
     *
     * @return
     */
    public int getY() {
        return y;
    }

    public void set(int y) {
        this.y = y;
    }
}

Floor.java 实现地板的绘制

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Shader;

/**
 * <p>文件描述:<p>
 * <p>作者:Mr-Donkey<p>
 * <p>创建时间:2018/12/10 23:08<p>
 * <p>更改时间:2018/12/10 23:08<p>
 * <p>版本号:1<p>
 */
public class Floor extends DrawablePart {

    //设置动画的位置
    private int x;
    private int y;
    private static final float RADIO_Y_POS = 4 / 5F;
    private Paint mPaint;
    private BitmapShader mBitmapShader;


    public Floor(Context context, int gameW, int gameH, Bitmap bitmap) {
        super(context, gameW, gameH, bitmap);
        //让高度位于视图的4/5左右
        y = (int) (gameH * RADIO_Y_POS);
        //初始化画笔
        mPaint = new Paint();
        //设置抗锯齿
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        //参数1 bitmap 参数2:横向的模式(设置为重复) 参数3:纵向的模式(设置为拉伸)
        mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.save();
        //将画步canvas移动到指定的位置
        canvas.translate(x, y);
        mPaint.setShader(mBitmapShader);
        canvas.drawRect(x, 0, mGameWidth - x, mGameHeight - y, mPaint);
        canvas.restore();
        //绘制完成之后 回收shader
        mPaint.setShader(null);
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
        //如果x的大于屏幕宽度 则取余
        if (-x > mGameWidth) {
            this.x = x % mGameWidth;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_17846019/article/details/84945894