Android 开发精要系列文章

SurfaceView的开发精要

2017-09-18  本文已影响41人  ahking17
开发思路

SurfaceView extends View, 实际上它也是继承自View.
和普通View的区别就是:
普通View是在UI线程中对自己进行绘制的, 执行绘制的方法是:

    @Override
    protected void onDraw(Canvas canvas) {
    //调用canvas的方法进行绘制
    }

SurfaceView是在一个子线程中对自己进行绘制, 好处就是避免了UI线程阻塞.
本质上来说, SurfaceView中包含一个专门用于绘制的Surface, Surface中包含一个Canvas, 最终绘制出各种图形的工作也是调用canvas的API来完成的.

如何获得Canvas?

surfaceView.getHolder() -> 获得SurfaceHolder, surface的持有者.
surfaceHolder.lockCanvas() -> 获得canvas 对象.
surfaceHolder.addCallback(Callback callback). surfaceHolder除了可以获得Canvas以外, 还同时管理着surface的3个生命周期.
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
    //surface被创建的时候调用
    //一般做法是在这里面创建和启动子线程, 在子线程中调用canvas的API, 执行具体的绘制工作.
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    //surface被更新的时候调用
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    //surface被销毁的时候调用
    //一般做法是在这里把子线程结束掉.
    }
模板代码

SurfaceViewTemplate.java

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

    private SurfaceHolder mHolder;
    private Canvas mCanvas;

    //用于绘制线程
    private Thread t;

    //线程的控制开关
    private boolean isRunning;

    public SurfaceViewTempalte(Context context) {
        this(context, null);
    }

    public SurfaceViewTempalte(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHolder = getHolder();
        mHolder.addCallback(this);

        //可获得焦点
        setFocusable(true);
        setFocusableInTouchMode(true);

        //设置常量
        setKeepScreenOn(true);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRunning = true;
        t = new Thread(this);
        t.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){
            draw();
        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            if (mCanvas != null){
                //draw something
            }
        }catch (Exception e){
        }finally {
            if (mCanvas != null){
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }

    }
}
抽奖盘的实现

重点是学习它的实现思路, 不要拘泥于实现细节, 把重点的实现流程掌握住就可以了.

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

    private SurfaceHolder mHolder;
    private Canvas mCanvas;

    //用于绘制线程
    private Thread t;

    //线程的控制开关
    private boolean isRunning;

    //盘块的奖项
    private String[] mStrs = new String[]{"单反相机", "IPAD", "恭喜发财","IPHONE", "服装一套", "恭喜发财"};

    //奖项的图片
    private int[] mImgs = new int[]{R.mipmap.p_danfan, R.mipmap.p_ipad,R.mipmap.p_xiaolian,  R.mipmap.p_iphone, R.mipmap.p_meizi, R.mipmap.p_xiaolian};
    //与图片对应的bitmap数组
    private Bitmap[] mImgsBitmap;

    private Bitmap mBgBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.bg2);

    //盘块的颜色
    private int[] mColor = new int[]{0xFFFFC300, 0xFFF17E01,0xFFFFC300, 0xFFF17E01,0xFFFFC300, 0xFFF17E01};
    private int mItemCount = 6;

    //绘制盘块的画笔
    private Paint mArcPaint;

    //绘制文本的画笔
    private Paint mTextPaint;

    private float mTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics());

    //整个盘块的范围
    private RectF mRange = new RectF();

    //整个盘块的直径
    private int mRadius;

    //转盘的中心位置
    private int mCenter;

    //这里我们的padding直接以paddingLeft为准
    private int mPadding;

    //滚动的速度
    private double mSpeed ;

    //角度
    private volatile int mStartAngle = 0;

    //是否点击了停止按钮
    private boolean isShouldEnd;


    public LuckyPan(Context context) {
        this(context, null);
    }

    public LuckyPan(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHolder = getHolder();
        mHolder.addCallback(this);

        //可获得焦点
        setFocusable(true);
        setFocusableInTouchMode(true);

        //设置常量
        setKeepScreenOn(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = Math.min(getMeasuredWidth(), getMeasuredHeight());
        mPadding = getPaddingLeft();
        //直径
        mRadius = width - mPadding * 2;
        //中心点
        mCenter = width / 2;
        setMeasuredDimension(width, width);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

        //初始化绘制盘块的画笔
        mArcPaint = new Paint();
        mArcPaint.setAntiAlias(true);
        mArcPaint.setDither(true);

        mTextPaint = new Paint();
        mTextPaint.setColor(0xffffffff);
        mTextPaint.setTextSize(mTextSize);

        //初始化盘块绘制的范围
        mRange = new RectF(mPadding, mPadding, mPadding + mRadius, mPadding + mRadius);

        //初始化图片
        mImgsBitmap = new Bitmap[mItemCount];
        for (int i = 0; i < mItemCount; i++){
            mImgsBitmap[i] = BitmapFactory.decodeResource(getResources(), mImgs[i]);
        }

        isRunning = true;
        t = new Thread(this);
        t.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){
            long start = System.currentTimeMillis();
            draw();
            long end = System.currentTimeMillis();
            if (end - start < 50){
                try {
                    Thread.sleep(50-(end - start));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            if (mCanvas != null){
                //draw something
                //绘制背景
                drawBg();
                //绘制盘块
                float tmpAngle = mStartAngle;
                float sweepAngle = 360 / mItemCount;
                for (int i = 0; i < mItemCount; i++){
                    mArcPaint.setColor(mColor[i]);
                    //绘制盘块
                    mCanvas.drawArc(mRange, tmpAngle, sweepAngle, true, mArcPaint);

                    //绘制文本
                    drawText(tmpAngle, sweepAngle, mStrs[i]);

                    //绘制icon
                    drawIcon(tmpAngle, mImgsBitmap[i]);

                    tmpAngle += sweepAngle;
                }

                mStartAngle += mSpeed;

                //如果点击了停止按钮
                if (isShouldEnd){
                    mSpeed -= 1;
                }if (mSpeed <= 0){
                    mSpeed = 0;
                    isShouldEnd = false;
                }
            }
        }catch (Exception e){
        }finally {
            if (mCanvas != null){
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }

    }

    //点击启动旋转
    public void luckyStart(){
        mSpeed = 50;
        isShouldEnd = false;
    }
    public void luckyEnd(){
        isShouldEnd = true;
    }
    //转盘是否在旋转
    public boolean isStart(){
        return mSpeed != 0;
    }

    public boolean isShouldEnd(){
        return isShouldEnd;
    }

    /**
     * 绘制icon
     * @param tmpAngle
     * @param bitmap
     */
    private void drawIcon(float tmpAngle, Bitmap bitmap) {
        //设置图片的宽度为直径1/8
        int imgWidth = mRadius / 8;

        //Math.PI/180
        float angle = (float) ((tmpAngle + 360 / mItemCount/2) * Math.PI / 180);

        int x = (int) (mCenter + mRadius / 2 / 2 * Math.cos(angle));

        int y  = (int) (mCenter + mRadius / 2 / 2 * Math.sin(angle));

        //确定那个图片的位置
        Rect rect = new Rect(x - imgWidth / 2, y - imgWidth / 2, x + imgWidth/2, y + imgWidth / 2);

        mCanvas.drawBitmap(bitmap, null, rect, null);
    }


    /**
     * 绘制每个盘块的文本
     */

    private void drawText(float tmpAngle, float sweepAngle, String string) {
        Path path = new Path();
        path.addArc(mRange, tmpAngle, sweepAngle);

        //利用水平偏移量让文字居中
        float textWidth = mTextPaint.measureText(string);
        int hOffset = (int) (mRadius * Math.PI / mItemCount / 2 - textWidth/2);
        int vOffset = mRadius/2/6;//垂直偏移量
        mCanvas.drawTextOnPath(string, path, hOffset, vOffset, mTextPaint);
    }

    /**
     * 绘制背景
     */
    private void drawBg() {

        mCanvas.drawColor(0xffffffff);
        mCanvas.drawBitmap(mBgBitmap, null,
                new Rect(mPadding/2, mPadding/2, getMeasuredWidth() - mPadding/2, getMeasuredHeight() - mPadding/2), null);
    }
}


refer to:
http://www.imooc.com/learn/444
https://github.com/xuyunqiang/LuckyPan/tree/master/app

        //确定那个图片的位置
        Rect rect = new Rect(x - imgWidth / 2, y - imgWidth / 2, x + imgWidth/2, y + imgWidth / 2);

        mCanvas.drawBitmap(bitmap, null, rect, null);

用一个矩形, 去约束图片的位置.

Rect rect = new Rect(left, top, right, bottom); // 自动填充变量. 是怎么办到的.
ctrl + space, 应该是和输入法有冲突, 所以才不能使用.
多用ctrl+p,吧.

luckypan.png

---- DONE. ----

上一篇 下一篇

猜你喜欢

热点阅读