介绍
通常情况程序的View和用户响应都是在同一个线程中处理的,这也是为什么处理长时间事件(例如访问网络)需要放到另外的线程中去(防止阻塞当前UI线程的操作和绘制)。但是在其他线程中却不能修改UI元素,例如用后台线程更新自定义View(调用View的在自定义View中的onDraw函数)是不允许的。
如果需要在另外的线程绘制界面、需要迅速的更新界面,或者渲染UI界面需要较长的时间,这种情况就要使用SurfaceView了。
使用
只要继承SurfaceView类并实现SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了,SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,SurfaceHolder.Callback具有如下的接口:
1.surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘制Surface。
2.surfaceChanged(SurfaceHolder holder, int format, int width,int height):当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次。
3.surfaceDestroyed(SurfaceHolder holder):当Surface被摧毁前会调用该函数,该函数被调用后就不能继续使用Surface了,一般在该函数中来清理使用的资源。
SurfaceView需要与SurfaceHolder使用。
SurfaceView和View的区别
SurfaceView和View最本质的区别在于,surfaceView是在一个新起的单独线程中可以重新绘制画面,而View必须在UI的主线程中更新画面。
画板实现
布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/my_SurfaceView"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#eee"
android:text="清空"
android:id="@+id/btn"/>
</RelativeLayout>
activity
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
SurfaceView surfaceView;
SurfaceHolder surfaceHolder;
Path path = new Path();
Button button;
@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= 23){
int i = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
if (i == PackageManager.PERMISSION_DENIED){
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE},101);
}
}
surfaceView = findViewById(R.id.my_SurfaceView);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(this);
button = findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
path.reset();
Toast.makeText(MainActivity.this, "清空", Toast.LENGTH_SHORT).show();
}
});
surfaceView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN){
path.moveTo(event.getX(),event.getY());
}
if (event.getAction() == MotionEvent.ACTION_MOVE){
path.lineTo(event.getX(),event.getY());
}
return true;
}
});
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void surfaceCreated(SurfaceHolder holder) {
new DrawThread().start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
class DrawThread extends Thread{
@Override
public void run() {
super.run();
Paint paint = new Paint();
paint.setStrokeWidth(10);
paint.setColor(Color.YELLOW);
paint.setStyle(Paint.Style.STROKE);
while (true){
Canvas canvas = surfaceHolder.lockCanvas();
if (canvas == null){
break;
}
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.drawPath(path,paint);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
视频播放及弹幕
布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:orientation="vertical"
android:background="#2C5FC4">
<SurfaceView
android:layout_width="match_parent"
android:layout_height="400dp"
android:id="@+id/my_SurfaceView"/>
<SurfaceView
android:layout_width="match_parent"
android:layout_height="400dp"
android:id="@+id/my_SurfaceView2"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:id="@+id/a"
android:layout_below="@id/my_SurfaceView">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="4.5"
android:id="@+id/current"/>
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/seek_bar"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="4.5"
android:id="@+id/duration"/>
</LinearLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:src="@mipmap/start"
android:id="@+id/start_pause"
android:layout_below="@id/a"
android:layout_centerHorizontal="true"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/start_pause"
android:id="@+id/my_EditText"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送"
android:layout_below="@id/my_EditText"
android:id="@+id/send"/>
</RelativeLayout>
弹幕类
public class Barrage {
private String string;
private int x = (int)(Math.random()*50);
private int y = (int)(Math.random()*500);
private int width = (int)(Math.random()*(80-50)+50);
private int size = (int)(Math.random()*(80-30)+30);
private int red = (int)(Math.random()*255);
private int green = (int)(Math.random()*255);
private int blue = (int)(Math.random()*255);
private int color = Color.argb(255,red,green,blue);
public Barrage(String string) {
this.string = string;
}
public String getString() {
return string;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getSize() {
return size;
}
public int getColor() {
return color;
}
public int getWidth() {
return width;
}
public void setX(int x) {
this.x = x;
}
}
视频服务
public class VideoSrevice extends Service {
static MediaPlayer mediaPlayer = new MediaPlayer();
@Override
public void onCreate() {
super.onCreate();
try {
mediaPlayer.setDataSource("/sdcard/Movies/刀郎 - 西海情歌.mp4");
mediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public IBinder onBind(Intent intent) {
return new MyBind();
}
class MyBind extends Binder{
//开始
public void start(){
if (mediaPlayer!=null){
mediaPlayer.start();
}
}
//暂停
public void pause(){
if (mediaPlayer!=null){
mediaPlayer.pause();
}
}
//总时长
public long getDuration(){
if (mediaPlayer!=null){
return mediaPlayer.getDuration();
}
return 0;
}
//当前时长
public long getCurrent(){
if (mediaPlayer!=null){
return mediaPlayer.getCurrentPosition();
}
return 0;
}
//进度条
public void seek(int p){
if (mediaPlayer!=null){
mediaPlayer.seekTo(p);
}
}
}
}
activity
public class MainActivity extends AppCompatActivity {
Intent intent;
VideoSrevice.MyBind myBind;
SurfaceView surfaceView;
SurfaceHolder surfaceHolder;
SurfaceView surfaceView2;
SurfaceHolder surfaceHolder2;
TextView current,duration;
ImageView imageView;
SeekBar seekBar;
Timer timer;
EditText editText;
Button button;
ArrayList<Barrage> arrayList = new ArrayList<>();
static boolean flag = false;
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBind = (VideoSrevice.MyBind) service;
Toast.makeText(MainActivity.this, "连接", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
myBind = null;
}
};
@SuppressLint("HandlerLeak")
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 201){
long d = myBind.getDuration();
int min = (int)(d/1000/60);
int second = (int)(d/1000%60);
if (second>9){
duration.setText("0"+min+":"+second);
}else {
duration.setText("0"+min+":0"+second);
}
long c = myBind.getCurrent();
int min1 = (int)(c/1000/60);
int second1 = (int)(c/1000%60);
if (second1>9){
current.setText("0"+min1+":"+second1);
}else {
current.setText("0"+min1+":0"+second1);
}
seekBar.setMax((int)d);
seekBar.setProgress((int)c);
VideoSrevice.mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
imageView.setImageResource(R.mipmap.start);
flag = false;
}
});
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT > 23){
int i = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
if (i == PackageManager.PERMISSION_DENIED){
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE},60);
}
}
initView();
intent = new Intent(MainActivity.this,VideoSrevice.class);
startService(intent);
bindService(intent,connection,BIND_AUTO_CREATE);
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
VideoSrevice.mediaPlayer.setDisplay(holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
surfaceView2.setZOrderOnTop(true);
surfaceHolder2.setFormat(PixelFormat.TRANSPARENT);
surfaceHolder2.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
new BarrageThread().start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
class BarrageThread extends Thread{
@Override
public void run() {
super.run();
Paint paint = new Paint();
while (true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
Canvas canvas = surfaceHolder2.lockCanvas();
if (canvas == null){
break;
}
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (int i = 0; i < arrayList.size(); i++){
Barrage barrage = arrayList.get(i);
paint.setTextSize(barrage.getSize());
paint.setColor(barrage.getColor());
paint.setStrokeWidth(barrage.getWidth());
int x = barrage.getX();
barrage.setX(x+=10);
canvas.drawText(barrage.getString(),barrage.getX(),barrage.getY(),paint);
}
surfaceHolder2.unlockCanvasAndPost(canvas);
}
}
}
});
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
myBind.seek(seekBar.getProgress());
}
});
}
private void initView() {
surfaceView = findViewById(R.id.my_SurfaceView);
surfaceView2 = findViewById(R.id.my_SurfaceView2);
current = findViewById(R.id.current);
duration = findViewById(R.id.duration);
imageView = findViewById(R.id.start_pause);
seekBar = findViewById(R.id.seek_bar);
surfaceHolder = surfaceView.getHolder();
surfaceHolder2 = surfaceView2.getHolder();
editText = findViewById(R.id.my_EditText);
button = findViewById(R.id.send);
timer = new Timer();
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!flag){
myBind.start();
imageView.setImageResource(R.mipmap.stop);
flag = true;
timer.schedule(new TimerTask() {
@Override
public void run() {
handler.sendEmptyMessage(201);
}
},0,1000);
}else {
myBind.pause();
imageView.setImageResource(R.mipmap.start);
flag = false;
}
}
});
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String str = editText.getText().toString().trim();
if (str.isEmpty()){
Toast.makeText(MainActivity.this, "输入不能为空", Toast.LENGTH_SHORT).show();
return;
}
arrayList.add(new Barrage(str));
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
stopService(intent);
}
}
画圆
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/my_SurfaceView"/>
</LinearLayout>
activity
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
SurfaceView surfaceView;
SurfaceHolder surfaceHolder;
float width;
float height;
float width1;
float height1;
int half;
int i = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
surfaceView = findViewById(R.id.my_SurfaceView);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(this);
width = getWindowManager().getDefaultDisplay().getWidth();
height = getWindowManager().getDefaultDisplay().getHeight();
half = getWindowManager().getDefaultDisplay().getWidth()/2+60;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
new MyThread().start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
class MyThread extends Thread{
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void run() {
super.run();
Paint paint = new Paint();
paint.setStrokeWidth(15);
while (true){
int red = (int)(Math.random()*255);
int green = (int)(Math.random()*255);
int blue = (int)(Math.random()*255);
paint.setColor(Color.argb(200,red,green,blue));
Canvas canvas = surfaceHolder.lockCanvas();
if (half<width){
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
float l = width-120;
float r = height-120;
canvas.drawOval(l,r-70,width,height-70,paint);
width-=11;
height-=20;
width1 = width;
height1 = height;
}else {
i++;
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
float l = width-120;
float r = height-120;
canvas.drawOval(l-(5*i),r-70,width-(5*i),height-70,paint);
canvas.drawOval(l+(5*i),r-70,width+(5*i),height-70,paint);
canvas.drawOval(l,r-70+(10*i),width1,height1-70+(10*i),paint);
canvas.drawOval(l,r-70-(10*i),width1,height1-70-(10*i),paint);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
音乐歌词
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/my_SurfaceView"/>
</LinearLayout>
歌词类
public class Music {
private String str;
private long time;
public Music(String str, long time) {
this.str = str;
this.time = time;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
activity
public class MainActivity extends AppCompatActivity {
SurfaceView surfaceView;
SurfaceHolder surfaceHolder;
ArrayList<Music> arrayList = new ArrayList<>();
MediaPlayer mediaPlayer = new MediaPlayer();
Timer timer = new Timer();
int positon=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= 23){
int i = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
if (i == PackageManager.PERMISSION_DENIED){
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE},101);
}
}
initView();
initLyric();
initMusic();
initTimer();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
new MusicThread().start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mediaPlayer != null){
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
});
}
private void initTimer() {
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
int currentPosition = mediaPlayer.getCurrentPosition();
if(arrayList.size()>0){
if(currentPosition>=arrayList.get(positon+1).getTime()){
positon++;
}
}
}
},0,200);
}
private void initMusic() {
if (mediaPlayer != null){
mediaPlayer.reset();
try {
mediaPlayer.setDataSource("/sdcard/Music/解忧邵帅 - 解忧.mp3");
mediaPlayer.prepare();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void initLyric() {
try {
FileInputStream fis = new FileInputStream("/sdcard/Music/解忧");
byte[] b = new byte[1024*8];
int len = 0;
StringBuffer sb = new StringBuffer();
while ((len = fis.read(b)) != -1){
sb.append(new String(b,0,len));
}
JSONObject jsonObject = new JSONObject(sb.toString());
JSONObject lrc = jsonObject.getJSONObject("lrc");
String lyric = lrc.getString("lyric");
String[] split = lyric.split("[\n]");
for (int i = 0; i <split.length; i++){
String[] arr = split[i].split("[\\[\\].:]");
if (arr.length == 6){
String min = arr[1];
String second = arr[2];
String millionSecond = arr[3];
String songs = arr[4]+":"+arr[5];
long time = Long.parseLong(min)*60*1000+Long.parseLong(second)*1000+Long.parseLong(millionSecond);
Music music = new Music(songs, time);
arrayList.add(music);
}
if (arr.length == 5){
String min = arr[1];
String second = arr[2];
String millionSecond = arr[3];
String songs = arr[4];
long time = Long.parseLong(min)*60*1000+Long.parseLong(second)*1000+Long.parseLong(millionSecond);
Music music = new Music(songs, time);
arrayList.add(music);
}
}
Log.e("####",arrayList.size()+"");
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
private void initView() {
surfaceView = findViewById(R.id.my_SurfaceView);
surfaceHolder = surfaceView.getHolder();
}
class MusicThread extends Thread{
@Override
public void run() {
super.run();
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setTextSize(50);
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Canvas canvas = surfaceHolder.lockCanvas();
if(canvas==null){
break;
}
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
//画一句歌词
if(arrayList.size()>0){
canvas.drawText(arrayList.get(positon).getStr(),200,500,paint);
}
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}