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的数组。这样就可以实现一个转盘了,大家试试吧。
- SurfaceView的介绍和使用
- SurfaceView的介绍和应用
- surfaceview的详解和使用
- SurfaceView使用介绍
- surfaceview 和surfaceholder的理解和使用
- android的surfaceView介绍
- surfaceView的简单介绍
- surfaceview的介绍
- SurfaceView的介绍
- SurfaceView的简单介绍
- SurfaceView的概念和优点及使用
- android进阶-surfaceView的分析和使用
- SurfaceView介绍和通用模板
- SurfaceView、SurfaceHolder和SurfaceHolder.CallBack的简单介绍
- SurfaceView的基本使用
- Android的SurfaceView使用
- SurfaceView的基本使用
- SurfaceView的简单使用
- 计算机网络(六) 网关--内部网关协议RIP和OSPF
- 图像缩放——双线性插值算法
- log4j配置
- 关于STM32 定时器PWM输出,关闭PWM时,输出引脚电平控制。
- 通过文件名利用NSClassFromString, 取到Class类别,通过setValue:forKey:传递值
- SurfaceView的介绍和使用
- Matlab之Treeplot
- Java和PHP在Web开发方面的比较
- 学习笔记之C++为什么将函数声明或者类的定义放在.h文件中,而将其实现放在原文件中
- sock5系列之centos6.5 搭建!(一)
- Camera Projection (相机投影)
- js从零开始第二天
- VS2013常用的一些快捷键
- LeetCode 117. Populating Next Right Pointers in Each Node II