引用:http://www.cnblogs.com/xuling/archive/2011/06/06/android.html
SurfaceView是视图(View)的继承类,这个视图里内嵌了一个专门用于绘制的Surface。你可以控制这个Surface的格式和尺寸。Surfaceview控制这个Surface的绘制位置。
surface是纵深排序(Z-ordered)的,这表明它总在自己所在窗口的后面。surfaceview提供了一个可见区域,只有在这个可见区域内 的surface部分内容才可见,可见区域外的部分不可见。surface的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。这意味者 surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)。注意,如果surface上面 有透明控件,那么它的每次变化都会引起框架重新计算它和顶层控件的透明效果,这会影响性能。你可以通过SurfaceHolder接口访问这个surface,getHolder()方法可以得到这个接口。
surfaceview变得可见时,surface被创建;surfaceview隐藏前,surface被销毁。这样能节省资源。如果你要查看 surface被创建和销毁的时机,可以重载surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder)。
surfaceview的核心在于提供了两个线程:UI线程和渲染线程。这里应注意:
1> 所有SurfaceView和SurfaceHolder.Callback的方法都应该在UI线程里调用,一般来说就是应用程序主线程。渲染线程所要访问的各种变量应该作同步处理。
2> 由于surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之间有效,所以要确保渲染线程访问的是合法有效的surface。
——————————————————————————————————————————————————————————————————————————
SurfaceView的使用多用于游戏开发中。且SurfaceView的一个通用模板为如下代码:
SurfaceTemplate.java
package com.example.surfaceviewdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
public class SurfaceTemplate extends SurfaceView implements Callback, Runnable {
private SurfaceHolder mHolder;
private Canvas mCanvas;
/**
* 用于绘制的线程
*/
private Thread t;
/**
* 线程的控制开关
*/
private boolean isRunning;
public SurfaceTemplate(Context context) {
this(context,null);
}
public SurfaceTemplate(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;
//在Surface创建之后初始化线程,开启线程。
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{
//获取Canvas
mCanvas = mHolder.lockCanvas();
if(mCanvas != null){
}
}catch(Exception e){
}finally{
if(mCanvas != null){
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
下面是SurfaceView的一个案例,是按照慕课网上的教程自己敲了一遍代码弄上去的。这是一个转盘抽奖的自定义View,视频地址在:http://www.imooc.com/learn/444
下图是工程目录结构图:
MainActivity.java的代码如下:
</pre><pre name="code" class="java">package com.example.surfaceviewdemo;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
public class MainActivity extends Activity {
private LuckyPan mLuckyPan;
private ImageView mStartBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLuckyPan = (LuckyPan) findViewById(R.id.id_luckypan);
mStartBtn = (ImageView) findViewById(R.id.id_start_btn);
mStartBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(! mLuckyPan.isStart()){
mLuckyPan.luckyStart();
mStartBtn.setImageResource(R.drawable.stop);
}else{
if(! mLuckyPan.isShouldEnd()){
mLuckyPan.luckyEnd();
mStartBtn.setImageResource(R.drawable.start);
}
}
}
});
}
}
LuckyPan.java的代码如下:
package com.example.surfaceviewdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.widget.Toast;
public class LuckyPan extends SurfaceView implements 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.drawable.p_danfan,R.drawable.p_ipad,
R.drawable.p_xiaolian,R.drawable.p_iphone,R.drawable.p_meizi,
R.drawable.p_xiaolian};
/**
* 每个盘块对应的颜色,其中只有两种颜色,但是相互交替。
*/
private int[] mColors = new int[]{0xFFFFC300,0xFFF17E01,0xFFFFC300,0xFFF17E01,0xFFFFC300,0xFFF17E01};
/**
* 与盘块对应的数量。
*/
private int mItemCount = 6;
/**
* 与图片对应的Bitmap数组,在后面将会用mImgs数组中的drawable将Bitmap初始化。
*/
private Bitmap[] mImgsBitmap ;
/**
* 背景图片的Bitmap
*/
private Bitmap mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg2);
/**
* 整个盘块的范围,用一个矩形来表示。
*/
private RectF mRange = new RectF();
/**
* 整个盘块的直径
*/
private int mRadius;
/**
* 绘制盘块的画笔
*/
private Paint mArcPaint;
/**
* 绘制文本的画笔
*/
private Paint mTextPaint;
/**
* 字体的大小
*/
private float mTextSize = TypedValue.applyDimension
(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics());
/**
* 盘块旋转的速度
*/
private double mSpeed;
/**
*
*/
private volatile int mStartAngle = 0;//为了保证线程间的可见性,需要用volatile进行声明。
/**
* 判断是否点击了停止按钮
*/
private boolean isShouldEnd ;
/**
* 转盘的中心位置
*/
private int mCenter;
/**
* 这里的mPadding直接取四个padding中的最小值,或者直接以PaddingLeft为准
*/
private int mPadding ;
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());
mRadius = width - getPaddingLeft() * 2;
mPadding = getPaddingLeft();
Toast.makeText(getContext(), "" + mPadding, Toast.LENGTH_SHORT).show();
//直径
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;
//在Surface创建之后初始化线程,开启线程。
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{
//获取Canvas
mCanvas = mHolder.lockCanvas();
if(mCanvas != null){
//绘制背景
drawBg();
//绘制盘块
float tmpAngle = mStartAngle;
float sweepAngle = 360/mItemCount;
for(int i = 0; i < mItemCount; i++){
mArcPaint.setColor(mColors[i]);
//绘制一个盘块
mCanvas.drawArc(mRange, tmpAngle, sweepAngle, true, mArcPaint);
//绘制文本
drawText(tmpAngle,sweepAngle,mStrs[i]);
//绘制盘块上的图片
drawIcons(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;
}
/**
* 绘制盘块上的图片
* @param bitmap
* @param tmpAngle
*/
private void drawIcons(float tmpAngle, Bitmap bitmap) {
//设置图片的宽度为直径的 1/8;
int imgWidth = mRadius / 8;
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);
}
/**
* 绘制每个盘块的文本
* @param tmpAngle
* @param sweepAngle
* @param mStrs2
*/
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);
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.example.surfaceviewdemo.LuckyPan
android:id="@+id/id_luckypan"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:padding="30dp"
/>
<ImageView
android:id="@+id/id_start_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/start"
/>
</RelativeLayout>
源码地址为: http://pan.baidu.com/s/1eSFMwzK