Освой программирование играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
SurfaceView с поддержкой касаний
Гроза эпилептиков
Поймай меня
Отскакивающие значки
Класс SurfaceView предоставляет объект Surface, который поддерживает рисование в фоновом потоке и дает возможность использовать OpenGL для трехмерной графики. Это отличный вариант для насыщенных графикой элементов, которые нуждаются в частых обновлениях или должны отображать сложную графическую информацию, как в случае с играми и трехмерной визуализацией.
Найти данный элемент можно в разделе Advanced.
В основе SurfaceView объект Surface, а не Canvas. Это важно, потому как Surface поддерживает рисование из фоновых потоков. Данное отличие особенно полезно для ресурсоемких операций или быстрых обновлений, а также когда необходимо обеспечить высокую частоту изменения кадров (использование трехмерной графики, создание игр или предпросмотр видеопотока с камеры в режиме реального времени.
Возможность рисовать вне зависимости от графического потока ведет к повышенному потреблению памяти. Таким образом, хоть это и полезный (а иногда просто необходимый) способ создания нестандартных представлений, будьте осторожны, используя его.
SurfaceView используется точно таким же образом, как любые производные от View классы. Вы можете применять анимацию и размещать их внутри разметки так же, как и другие компоненты.
Применяя OpenGL, вы можете рисовать на Surface любые поддерживаемые двумерные или трехмерные объекты, получая при этом все выгоды от аппаратного ускорения (если таковое имеется). Таким образом, вы значительно повышаете производительность, если сравнивать с теми же операциями, выполненными на двумерном Canvas.
Объекты SurfaceView особенно пригодятся для отображения динамических трехмерных изображений, к примеру, в интерактивных играх, их можно назвать лучшим выбором для отображения предварительного просмотра видеопотоков с камеры в режиме реального времени.
Чтобы создать данный тип, наследуйте класс SurfaceView и реализуйте интерфейс SurfaceHolder.Callback, описывающий функцию обратного вызова. Он уведомляет представление о том, что исходный объект Surface был создан/уничтожен/модифицирован и передает в объект SurfaceHolder ссылку, содержащую допустимый экземпляр Surface.
Типичный шаблон проектирования SurfaceView предусматривает классы, производные от Thread, которые принимают ссылку на текущий объект SurfaceHolder и немедленно его обновляют.
Напишем простой пример использования SurfaceView, на котором можно рисовать линии.
package ru.alexanderklimov.surfacedemo;
import java.util.Random;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
public class TestActivity extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new MySurfaceView(this));
}
class MySurfaceView extends SurfaceView {
Path path;
Thread thread = null;
SurfaceHolder surfaceHolder;
volatile boolean running = false;
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Random random;
public MySurfaceView(Context context) {
super(context);
surfaceHolder = getHolder();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
path = new Path();
path.moveTo(event.getX(), event.getY());
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
path.lineTo(event.getX(), event.getY());
} else if (event.getAction() == MotionEvent.ACTION_UP) {
path.lineTo(event.getX(), event.getY());
}
if (path != null) {
Canvas canvas = surfaceHolder.lockCanvas();
canvas.drawPath(path, paint);
surfaceHolder.unlockCanvasAndPost(canvas);
}
return true;
}
}
}
Запустив проект, вы можете рисовать пальцем по экрану.
Попробовал данный пример. При рисовании рисуются окружности, которые постоянно мигают. Жуткое зрелище.
В примере реализовано:
package ru.alexanderklimov.test;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
public class TestActivity extends Activity {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float initX, initY, radius;
private boolean drawing = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new MySurfaceView(this));
}
public class MySurfaceThread extends Thread {
private SurfaceHolder myThreadSurfaceHolder;
private MySurfaceView myThreadSurfaceView;
private boolean myThreadRun = false;
public MySurfaceThread(SurfaceHolder surfaceHolder,
MySurfaceView surfaceView) {
myThreadSurfaceHolder = surfaceHolder;
myThreadSurfaceView = surfaceView;
}
public void setRunning(boolean b) {
myThreadRun = b;
}
@Override
public void run() {
// super.run();
while (myThreadRun) {
Canvas c = null;
try {
c = myThreadSurfaceHolder.lockCanvas(null);
synchronized (myThreadSurfaceHolder) {
myThreadSurfaceView.onDraw(c);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
myThreadSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
public class MySurfaceView extends SurfaceView implements
SurfaceHolder.Callback {
private MySurfaceThread thread;
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
if (drawing) {
canvas.drawCircle(initX, initY, radius, paint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// return super.onTouchEvent(event);
int action = event.getAction();
if (action == MotionEvent.ACTION_MOVE) {
float x = event.getX();
float y = event.getY();
radius = (float) Math.sqrt(Math.pow(x - initX, 2)
+ Math.pow(y - initY, 2));
} else if (action == MotionEvent.ACTION_DOWN) {
initX = event.getX();
initY = event.getY();
radius = 1;
drawing = true;
} else if (action == MotionEvent.ACTION_UP) {
drawing = false;
}
return true;
}
public MySurfaceView(Context context) {
super(context);
init();
}
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MySurfaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
getHolder().addCallback(this);
setFocusable(true); // make sure we get key events
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);
}
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,
int arg3) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new MySurfaceThread(getHolder(), this);
thread.setRunning(true);
thread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
}
}
Тот автор на основе предыдущего примера сделал новый пример. На экране рисуется белая точка, которая начинает двигаться в место касания экрана пальцем пользователя.
package ru.alexanderklimov.test;
import ...
public class TestActivity extends Activity {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float initX, initY;
private float targetX, targetY;
private boolean drawing = true;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new MySurfaceView(this));
}
public class MySurfaceThread extends Thread {
private SurfaceHolder myThreadSurfaceHolder;
private MySurfaceView myThreadSurfaceView;
private boolean myThreadRun = false;
public MySurfaceThread(SurfaceHolder surfaceHolder,
MySurfaceView surfaceView) {
myThreadSurfaceHolder = surfaceHolder;
myThreadSurfaceView = surfaceView;
}
public void setRunning(boolean b) {
myThreadRun = b;
}
@Override
public void run() {
// super.run();
while (myThreadRun) {
Canvas c = null;
try {
c = myThreadSurfaceHolder.lockCanvas(null);
synchronized (myThreadSurfaceHolder) {
myThreadSurfaceView.onDraw(c);
}
sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
myThreadSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
public class MySurfaceView extends SurfaceView implements
SurfaceHolder.Callback {
private MySurfaceThread thread;
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
if (drawing) {
canvas.drawRGB(0, 0, 0);
canvas.drawCircle(initX, initY, 3, paint);
if ((initX == targetX) && (initY == targetY)) {
drawing = false;
} else {
initX = (initX + targetX) / 2;
initY = (initY + targetY) / 2;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// return super.onTouchEvent(event);
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
targetX = event.getX();
targetY = event.getY();
drawing = true;
}
return true;
}
public MySurfaceView(Context context) {
super(context);
init();
}
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MySurfaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
getHolder().addCallback(this);
thread = new MySurfaceThread(getHolder(), this);
setFocusable(true); // make sure we get key events
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);
initX = targetX = 0;
initY = targetY = 0;
}
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,
int arg3) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
}
}
Этот же пример, но с использованием разметки FrameLayout, внутри которой находится SurfaceView, находится здесь. Не буду приводить код.
Продолжение серии примеров - Exercise of SurfaceView: SurfaceView overlap with a LinearLayout. Здесь появляется возможность выбрать цвет точки.
Ещё один пример - при касании экрана в области SurfaceView будет появляться новая картинка, которая будет двигаться внутри контейнера, отскакивая от стенок.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/button_erase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:onClick="onClick"
android:text="Очистить" />
<SurfaceView
android:id="@+id/surface"
android:layout_width="350dp"
android:layout_height="250dp"
android:layout_gravity="center" />
</LinearLayout>
Код:
package ru.alexanderklimov.test;
import ...
public class TestActivity extends Activity implements View.OnTouchListener,
SurfaceHolder.Callback {
private SurfaceView mSurface;
private DrawingThread mThread;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSurface = (SurfaceView) findViewById(R.id.surface);
mSurface.setOnTouchListener(this);
mSurface.getHolder().addCallback(this);
}
public void onClick(View v) {
mThread.clearItems();
}
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mThread.addItem((int) event.getX(), (int) event.getY());
}
return true;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mThread = new DrawingThread(holder, BitmapFactory.decodeResource(
getResources(), R.drawable.ic_launcher));
mThread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
mThread.updateSize(width, height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mThread.quit();
mThread = null;
}
private static class DrawingThread extends HandlerThread implements
Handler.Callback {
private static final int MSG_ADD = 100;
private static final int MSG_MOVE = 101;
private static final int MSG_CLEAR = 102;
private int mDrawingWidth, mDrawingHeight;
private SurfaceHolder mDrawingSurface;
private Paint mPaint;
private Handler mReceiver;
private Bitmap mIcon;
private ArrayList<DrawingItem> mLocations;
private class DrawingItem {
// Current location marker
int x, y;
// Direction markers for motion
boolean horizontal, vertical;
public DrawingItem(int x, int y, boolean horizontal,
boolean vertical) {
this.x = x;
this.y = y;
this.horizontal = horizontal;
this.vertical = vertical;
}
}
public DrawingThread(SurfaceHolder holder, Bitmap icon) {
super("DrawingThread");
mDrawingSurface = holder;
mLocations = new ArrayList<DrawingItem>();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mIcon = icon;
}
@Override
protected void onLooperPrepared() {
mReceiver = new Handler(getLooper(), this);
// Start the rendering
mReceiver.sendEmptyMessage(MSG_MOVE);
}
@Override
public boolean quit() {
// Clear all messages before dying
mReceiver.removeCallbacksAndMessages(null);
return super.quit();
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_ADD:
// Create a new item at the touch location,
// with a randomized start direction
DrawingItem newItem = new DrawingItem(msg.arg1, msg.arg2,
Math.round(Math.random()) == 0, Math.round(Math
.random()) == 0);
mLocations.add(newItem);
break;
case MSG_CLEAR:
// Remove all objects
mLocations.clear();
break;
case MSG_MOVE:
// Render a frame
Canvas c = mDrawingSurface.lockCanvas();
if (c == null) {
break;
}
// Clear Canvas first
c.drawColor(Color.BLACK);
// Draw each item
for (DrawingItem item : mLocations) {
// Update location
item.x += (item.horizontal ? 5 : -5);
if (item.x >= (mDrawingWidth - mIcon.getWidth())) {
item.horizontal = false;
} else if (item.x <= 0) {
item.horizontal = true;
}
item.y += (item.vertical ? 5 : -5);
if (item.y >= (mDrawingHeight - mIcon.getHeight())) {
item.vertical = false;
} else if (item.y <= 0) {
item.vertical = true;
}
// Draw to the Canvas
c.drawBitmap(mIcon, item.x, item.y, mPaint);
}
// Release to be rendered to the screen
mDrawingSurface.unlockCanvasAndPost(c);
break;
}
// Post the next frame
mReceiver.sendEmptyMessage(MSG_MOVE);
return true;
}
public void updateSize(int width, int height) {
mDrawingWidth = width;
mDrawingHeight = height;
}
public void addItem(int x, int y) {
// Pass the location into the Handler using Message arguments
Message msg = Message.obtain(mReceiver, MSG_ADD, x, y);
mReceiver.sendMessage(msg);
}
public void clearItems() {
mReceiver.sendEmptyMessage(MSG_CLEAR);
}
}
}
Класс MediaPlayer - воспроизведение видео.
Предварительный просмотр записываемого видео.