SurfaceView的介绍和使用

来源:互联网 发布:c语言null 编辑:程序博客网 时间:2024/05/17 21:45

我们知道view的绘制一般是在主线程进行的,这就导致了如果view的绘制很复杂,主线程就会一直被view的绘制占用而导致卡死。因此surfaceView的出现很好的解决了这个问题。Surfaceview允许在子线程中进行view的绘制,因此可以使用它进行复杂的绘制,主线程还可以去处理其他的事情。这也是surfaceview最棒的优点。

SurfaceView中有一个显示的区域对象Surface,SurfaceView负责控制Surface显示的位置,Surface显示内容,那总得有一个对象来控制显示的内容的吧?没错,我们可以通过SurfaceView类中的getHolder()方法得到该SurfaceView中Surface的控制。我们要通过SurfaceViewHolder类中的addCallback()方法添加Surface的回调,这个SurfaceViewHolder.Callback回调接口里面有三个方法:

/**     * 当view创建的时候进行回调     * 一般在该方法中开启绘制view的子线程     * 该方法在ui线程执行     * @param holder 该SurfaceView的SurfaceViewHolder对象     */    @Override    public void surfaceCreated(SurfaceHolder holder) {    }    /**     * 该方法在ui线程执行     * 当SurfaceView的尺寸发生变化的时候进行回调(比如竖屏切换到横屏的时候)     * @param holder SurfaceView的SurfaceViewHolder对象     * @param format 格式     * @param width 宽度     * @param height 高度     */    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {    }    /**     * 该方法中ui线程执行     * 当SurfaceView被销毁的时候执行     * @param holder     */    @Override    public void surfaceDestroyed(SurfaceHolder holder) {}

下面大家首先看一下这个SurfaceViewTemplate模板


package test.com.lukypan;import android.content.Context;import android.graphics.Canvas;import android.util.AttributeSet;import android.view.SurfaceHolder;import android.view.SurfaceView;/** *surfaceView的模板 * Created by wangpeiyu on 2016/11/21. */public abstract class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {    protected SurfaceHolder mSurfaceHolder;    //surfaceView的画布,进行view的绘制    protected Canvas mCanvas;    //绘制的子线程    protected Thread mThread;    //子线程运行的Runnable    protected Runnable mRunnable;    //决定线程运行和结束    boolean isStart = false;    public SurfaceViewTemplate(Context context) {        this(context, null);    }    public SurfaceViewTemplate(Context context, AttributeSet attrs) {        super(context, attrs);        initNecessaryVariables();        initVariables();//调用子类的初始化变量方法    }    //初始化必要的变量    private void initNecessaryVariables() {        mRunnable = this;        mSurfaceHolder = getHolder();        mSurfaceHolder.addCallback(this);        setFocusable(true);        setFocusableInTouchMode(true);        setKeepScreenOn(true);        mThread = new Thread(mRunnable);    }    /**     * 当view创建的时候进行回调     * 一般在该方法中开启绘制view的子线程     * 该方法在ui线程执行     * @param holder 该SurfaceView的SurfaceViewHolder对象     */    @Override    public void surfaceCreated(SurfaceHolder holder) {        isStart = true;        mThread.start();        create();    }    /**     * 该方法在ui线程执行     * 当SurfaceView的尺寸发生变化的时候进行回调(比如竖屏切换到横屏的时候)     * @param holder SurfaceView的SurfaceViewHolder对象     * @param format 格式     * @param width 宽度     * @param height 高度     */    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {        change();    }    /**     * 该方法中ui线程执行     * 当SurfaceView被销毁的时候执行     * @param holder     */    @Override    public void surfaceDestroyed(SurfaceHolder holder) {        isStart = false;        destroy();    }    /**     * 进行view的绘制     */    @Override    public void run() {        while (isStart) {            try {                draw();            } catch (Exception e) {            } finally {                if (mCanvas != null) {                    mSurfaceHolder.unlockCanvasAndPost(mCanvas);                }            }        }    }    protected abstract void draw();    protected abstract void create();    protected abstract void change();    protected abstract void destroy();    protected abstract void initVariables();}

通过这个类我们可以看到有5个成员变量。mSurfaceHolder指向该SurfaceView中Surface的控制器,mCanvas为当前SurfaceView的画布。既然我们知道SurfaceView的绘制是在子线程中执行的,所以我们肯定要有一个线程对象mThread,mRunnable对象在这里没有显示出太大的作用,但是考虑到可以动态的指定其它的Runnable对象,所以还是保留吧。最后一个mStart对象为决定线程的开始开始结束。我们将这个模板类定义为抽象的,将各种实SurfaceView的子类一定有的部分都写在了模板类中,并将一些不同的实现定义成了抽象的方法,这样基于这个模板类实现的子类都必须实现这些方法。这些方法有

 protected abstract void draw();    protected abstract void create();    protected abstract void change();    protected abstract void destroy();protected abstract void initVariables();

下面是基于这个SurfaceViewTemplate模板类而开发的一个抽奖转盘。这个抽奖转盘的实现参考了张鸿祥老师的博客和慕课视频,但是代码的实现还是不一样的,大家可以参考一下。

package test.com.lukypan;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.Rect;import android.graphics.RectF;import android.util.AttributeSet;import android.util.Log;import android.util.TypedValue;/** * Created by wangpeiyu on 2016/11/21. */public class MyLuckyPan extends SurfaceViewTemplate {    private int mCount=6;    private int mBitmapIds[];    private Bitmap mBtp[];    private String mStr[];    private int mColor[];    private Bitmap mBtpBg;    private RectF mRectf;    private float mPadding;    private int mCenter;    private float mUnitOffset;    private float mStartAngle;    private Paint mPaintPan;    private Paint mPaintText;    private int mTxtSize;    private float mDiameter;    private float mSpeed;    private boolean isClickEnd;    public MyLuckyPan(Context context) {        this(context,null);    }    public MyLuckyPan(Context context, AttributeSet attrs) {        super(context, attrs);    }    /**     * 绘制,模板类的抽象类     */    @Override    protected void draw() {        long startTime=System.currentTimeMillis();        myDraw();        long endTime=System.currentTimeMillis();        if(endTime-startTime<100)        {            try {                mThread.sleep(100-(endTime-startTime));            } catch (InterruptedException e) {                e.printStackTrace();            }        }        if(isClickEnd)        {            mSpeed-=0.5;        }        if(mSpeed<0)        {            mSpeed = 0;            isClickEnd=false;        }        mStartAngle+=mSpeed;    }    /**     * 当前SurfaceView 的绘制     */    private void myDraw() {        mCanvas = mSurfaceHolder.lockCanvas();        mCanvas.drawColor(Color.WHITE);        mCanvas.drawBitmap(mBtpBg, null, mRectf, null);        drawArc();        drawBgPrize();        drawText();    }    /**     * 绘制抽奖转盘上的文本     */    private void drawText() {        Path path= new Path();        float divider=mDiameter/16;        RectF panRectf=new RectF(mPadding+divider,mPadding+divider,                getMeasuredWidth()-mPadding-divider,getMeasuredHeight()-mPadding-divider);        for(int i=0;i<mCount;i++)        {            path.reset();            path.addArc(panRectf,mStartAngle+mUnitOffset*i,mUnitOffset);            Rect textRect=new Rect();            mPaintText.getTextBounds(mStr[i],0,mStr[i].length(),textRect);            float textHeight=textRect.height();            float textWidth =  textRect.width();            float angle= (float) (mUnitOffset*Math.PI/180);            float radiu=(mDiameter-divider*2)/2;            float angleLendht=angle*radiu;            mCanvas.drawTextOnPath(mStr[i],path,angleLendht/2-textWidth/2,textHeight+10,mPaintText);        }    }    /**     * 绘制抽奖转盘上的图片     */    private void drawBgPrize() {        double unit=Math.PI/180;        float radiu=mDiameter/4;        float lenght=mDiameter/12;        RectF rectF=new RectF();        float x = 0;        float y=0;        for(int i=0;i<mCount;i++){            x = (float) (mCenter+radiu*Math.cos((mStartAngle+i*mUnitOffset+mUnitOffset/2)*unit));            y= (float) (mCenter+radiu*Math.sin((mStartAngle+i*mUnitOffset+mUnitOffset/2)*unit));            rectF.set(x-lenght,y-lenght,x+lenght,y+lenght);            mCanvas.drawBitmap(mBtp[i],null,rectF,null);        }    }    /**     * 绘制不同颜色的扇形区域     */    private void drawArc() {        float divider=mDiameter/16;        RectF panRectf=new RectF(mPadding+divider,mPadding+divider,                getMeasuredWidth()-mPadding-divider,getMeasuredHeight()-mPadding-divider);        for (int i = 0; i < mCount; i++) {            mPaintPan.setColor(mColor[i]);            mCanvas.drawArc(panRectf, mStartAngle + i * mUnitOffset,mUnitOffset,true, mPaintPan);        }    }    /**     * 判断是否在转动     * @return     */    public boolean isRolling(){        return mSpeed!=0;    }    /**     * 判断是否已经点击了停止转动     * @return     */    public boolean isClickEnd(){        return isClickEnd;    }    /**     * 开始转动     */    public void start(){        mSpeed=30;        isStart=true;    }    /**     * 停止转动     */    public void setClickEnd(){        isClickEnd = true;        //决定最后只能转移到什么位置        stopInEnd();    }    /**     * 这个方法是当用户点击停止转动后,判断转盘最后定在那个位置     * 如果停止的位置不是谢谢惠顾的位置,则应该对当前的位置进行一个     * 最小的微调,以致让这个转盘最后定在谢谢惠顾的扇形中     */    private void stopInEnd() {        double result = mStartAngle+60*(60-1)*0.5/2;        double offset = result%360;//偏移了多少        if((offset>0&&offset<30)||(offset>150&&offset<210)||(offset>330&&offset<360))        {            /**             * 表明最后停止的位置是在谢谢惠顾范围内,不用执行微调操作。             */            return;        }        /**         * 对于这里的微调,大家首先画图便可以很好的理解         */        if(offset<90)        {            mStartAngle= (float) (mStartAngle-(30*Math.random()+offset-30));        }else if(offset<150){            mStartAngle= (float) (mStartAngle+60*Math.random()+150-offset);        }else if(offset<270)        {            mStartAngle= (float) (mStartAngle-(60*Math.random()+offset-210));        }else{            mStartAngle= (float) (mStartAngle+30*Math.random()+330-offset);        }    }    @Override    protected void create() {    }    @Override    protected void change() {        initVariables();    }    @Override    protected void destroy() {    }    /**     * 初始化变量     * 因为该方法是在模板类中调用的,所以所有的变量都应该在这个方法中初始化     * 不能在声明变量的时候就直接初始化     */    @Override    protected void initVariables() {        mCount=6;        mBitmapIds = new int[]{R.drawable.ipad, R.drawable.thanks, R.drawable.iphone,                R.drawable.meinv, R.drawable.thanks, R.drawable.meizi};        mStr= new String[]{"ipad", "谢谢惠顾", "iphone", "妹子", "谢谢惠顾", "衣服"};        mBtpBg = BitmapFactory.decodeResource(getResources(), R.drawable.bg2);        mColor = new int[]{Color.parseColor("#ffff0000"), Color.parseColor("#ff00ff00"),                Color.parseColor("#ffff0000"), Color.parseColor("#ff00ff00"),                Color.parseColor("#ffff0000"), Color.parseColor("#ff00ff00")};        mStartAngle = 0;        mSpeed=0;        mBtp=new Bitmap[mCount];        for(int i=0;i<mCount;i++)        {            mBtp[i]=BitmapFactory.decodeResource(getResources(),mBitmapIds[i]);        }        mUnitOffset = (float) (360 / mCount);        mPaintPan = new Paint();        mPaintPan.setAntiAlias(true);        mPaintPan.setDither(true);        mPaintText = new Paint();        mPaintText.setColor(Color.BLACK);        mPaintText.setAntiAlias(true);        mPaintText.setDither(true);        mPaintText.setStrokeCap(Paint.Cap.ROUND);        mPaintText.setStrokeJoin(Paint.Join.ROUND);        mTxtSize= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,20,getResources().getDisplayMetrics());        mPaintText.setTextSize(mTxtSize);    }    /**     * 测量当前SurfaceView 的绘制区域     * @param widthMeasureSpec     * @param heightMeasureSpec     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int desireLenght = Math.min(getMeasuredHeight(), getMeasuredWidth());        setMeasuredDimension(desireLenght, desireLenght);        mPadding = getPaddingLeft();        mCenter = desireLenght / 2;        mDiameter = desireLenght - mPadding * 2;        mRectf = new RectF(mPadding, mPadding, desireLenght - mPadding, desireLenght - mPadding);    }}

这个是MainActivity的类:

package test.com.lukypan;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.ImageView;import android.widget.Toast;public class MainActivity extends AppCompatActivity {    MyLuckyPan luckyPan;    ImageView imageView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        luckyPan = (MyLuckyPan) findViewById(R.id.id_luckpan);        imageView= (ImageView) findViewById(R.id.id_img);        imageView.setImageResource(R.drawable.start);        imageView.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                if(!luckyPan.isRolling())                {                    luckyPan.start();                    imageView.setImageResource(R.drawable.stop);                }else{                    if(!luckyPan.isClickEnd())                    {                        luckyPan.setClickEnd();                        imageView.setImageResource(R.drawable.start);                    }else{                        //已经点击了                        Toast.makeText(MainActivity.this,"等待本次抽奖结束哦",Toast.LENGTH_SHORT).show();                    }                }            }        });    }}

               

大家复制这个三个类到自己的项目中,只需要修改图片的id便可以了,也就是修改在MyLuckyPan类中的initVariables方法中的mBitmapIds图片id的数组。这样就可以实现一个转盘了,大家试试吧。


2 0
原创粉丝点击