SurfaceView 中的缩放和拖动功能
Zooming and drag feature in SurfaceView
我正在尝试创建一个可以缩放和拖动的 SurfaceView。它实现了直接绘制到 canvas
的 HTTP 图像流
我试过下面的代码,它有点工作...但它给我带来了边界问题。不知道为什么。有帮助吗?
全流:
放大后的图片:
在第二张图片中,您可以看到多条不需要存在的绿线。
这是处理此流的 class:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SurfaceView;
import android.view.WindowManager;
/**
* Created by fil on 07/12/15.
*/
public class ZoomSurfaceView extends SurfaceView {
//These two constants specify the minimum and maximum zoom
private static float MIN_ZOOM = 1f;
private static float MAX_ZOOM = 5f;
private float scaleFactor = 1.f;
private ScaleGestureDetector detector;
//These constants specify the mode that we're in
private static int NONE = 0;
private static int DRAG = 1;
private static int ZOOM = 2;
private boolean dragged = false;
private float displayWidth;
private float displayHeight;
private int mode;
//These two variables keep track of the X and Y coordinate of the finger when it first
//touches the screen
private float startX = 0f;
private float startY = 0f;
//These two variables keep track of the amount we need to translate the canvas along the X
//and the Y coordinate
private float translateX = 0f;
private float translateY = 0f;
//These two variables keep track of the amount we translated the X and Y coordinates, the last time we
//panned.
private float previousTranslateX = 0f;
private float previousTranslateY = 0f;
private final Paint p = new Paint();
private void init(Context context){
detector = new ScaleGestureDetector(getContext(), new ScaleListener());
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
displayWidth = display.getWidth();
displayHeight = display.getHeight();
}
public ZoomSurfaceView(Context context) {
super(context);
init(context);
}
public ZoomSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public ZoomSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
public void resetZoom() {
}
public void drawBitmap(Canvas canvas, Bitmap b, Rect rect){
canvas.save();
//If translateX times -1 is lesser than zero, letfs set it to zero. This takes care of the left bound
if((translateX * -1) > (scaleFactor - 1) * displayWidth)
{
translateX = (1 - scaleFactor) * displayWidth;
}
if(translateY * -1 > (scaleFactor - 1) * displayHeight)
{
translateY = (1 - scaleFactor) * displayHeight;
}
//We need to divide by the scale factor here, otherwise we end up with excessive panning based on our zoom level
//because the translation amount also gets scaled according to how much we've zoomed into the canvas.
canvas.translate(translateX / scaleFactor, translateY / scaleFactor);
//We're going to scale the X and Y coordinates by the same amount
canvas.scale(scaleFactor, scaleFactor);
canvas.drawBitmap(b, null, rect, p);
/* The rest of your canvas-drawing code */
canvas.restore();
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
{
@Override
public boolean onScale(ScaleGestureDetector detector)
{
scaleFactor *= detector.getScaleFactor();
scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
return true;
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
switch (event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
mode = DRAG;
//We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
//amount for each coordinates This works even when we are translating the first time because the initial
//values for these two variables is zero.
startX = event.getX() - previousTranslateX;
startY = event.getY() - previousTranslateY;
break;
case MotionEvent.ACTION_MOVE:
translateX = event.getX() - startX;
translateY = event.getY() - startY;
//We cannot use startX and startY directly because we have adjusted their values using the previous translation values.
//This is why we need to add those values to startX and startY so that we can get the actual coordinates of the finger.
double distance = Math.sqrt(Math.pow(event.getX() - (startX + previousTranslateX), 2) +
Math.pow(event.getY() - (startY + previousTranslateY), 2));
if(distance > 0)
{
dragged = true;
distance *= scaleFactor;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_UP:
mode = NONE;
dragged = false;
//All fingers went up, so letfs save the value of translateX and translateY into previousTranslateX and
//previousTranslate
previousTranslateX = translateX;
previousTranslateY = translateY;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = DRAG;
//This is not strictly necessary; we save the value of translateX and translateY into previousTranslateX
//and previousTranslateY when the second finger goes up
previousTranslateX = translateX;
previousTranslateY = translateY;
break;
}
detector.onTouchEvent(event);
//We redraw the canvas only in the following cases:
//
// o The mode is ZOOM
// OR
// o The mode is DRAG and the scale factor is not equal to 1 (meaning we have zoomed) and dragged is
// set to true (meaning the finger has actually moved)
if ((mode == DRAG && scaleFactor != 1f && dragged) || mode == ZOOM)
{
invalidate();
}
return true;
}
}
将框架添加到表面的代码如下:
if (!b.isRecycled()){
try {
Rect rect = new Rect(0, 0, frame.getWidth(), frame.getHeight());
Canvas canvas = frame.getHolder().lockCanvas();
synchronized (frame.getHolder()) {
if (!b.isRecycled()) {
frame.drawBitmap(canvas, b, rect);
b.recycle();
}
}
frame.getHolder().unlockCanvasAndPost(canvas);
} catch (java.lang.RuntimeException exc){
Dbg.d("ERROR", exc);
}
lastBitmap = b;
}
您发布的代码不完整,所以很难说是什么问题。我确实将您的代码放到了一个快速演示项目中,但没有发现任何边框问题。
只要看一下屏幕截图:您的图像数据是否有可能以某种方式环绕?第二张截图看起来像是在图像顶部绘制了底部边框。如果没有可重现的代码,又很难说。
可能会尝试在重绘位图之前重绘背景
canvas.drawRect(rect, backgroundPaint);
frame.drawBitmap(canvas, b, rect);
我正在尝试创建一个可以缩放和拖动的 SurfaceView。它实现了直接绘制到 canvas
的 HTTP 图像流我试过下面的代码,它有点工作...但它给我带来了边界问题。不知道为什么。有帮助吗?
全流:
放大后的图片:
在第二张图片中,您可以看到多条不需要存在的绿线。
这是处理此流的 class:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SurfaceView;
import android.view.WindowManager;
/**
* Created by fil on 07/12/15.
*/
public class ZoomSurfaceView extends SurfaceView {
//These two constants specify the minimum and maximum zoom
private static float MIN_ZOOM = 1f;
private static float MAX_ZOOM = 5f;
private float scaleFactor = 1.f;
private ScaleGestureDetector detector;
//These constants specify the mode that we're in
private static int NONE = 0;
private static int DRAG = 1;
private static int ZOOM = 2;
private boolean dragged = false;
private float displayWidth;
private float displayHeight;
private int mode;
//These two variables keep track of the X and Y coordinate of the finger when it first
//touches the screen
private float startX = 0f;
private float startY = 0f;
//These two variables keep track of the amount we need to translate the canvas along the X
//and the Y coordinate
private float translateX = 0f;
private float translateY = 0f;
//These two variables keep track of the amount we translated the X and Y coordinates, the last time we
//panned.
private float previousTranslateX = 0f;
private float previousTranslateY = 0f;
private final Paint p = new Paint();
private void init(Context context){
detector = new ScaleGestureDetector(getContext(), new ScaleListener());
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
displayWidth = display.getWidth();
displayHeight = display.getHeight();
}
public ZoomSurfaceView(Context context) {
super(context);
init(context);
}
public ZoomSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public ZoomSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
public void resetZoom() {
}
public void drawBitmap(Canvas canvas, Bitmap b, Rect rect){
canvas.save();
//If translateX times -1 is lesser than zero, letfs set it to zero. This takes care of the left bound
if((translateX * -1) > (scaleFactor - 1) * displayWidth)
{
translateX = (1 - scaleFactor) * displayWidth;
}
if(translateY * -1 > (scaleFactor - 1) * displayHeight)
{
translateY = (1 - scaleFactor) * displayHeight;
}
//We need to divide by the scale factor here, otherwise we end up with excessive panning based on our zoom level
//because the translation amount also gets scaled according to how much we've zoomed into the canvas.
canvas.translate(translateX / scaleFactor, translateY / scaleFactor);
//We're going to scale the X and Y coordinates by the same amount
canvas.scale(scaleFactor, scaleFactor);
canvas.drawBitmap(b, null, rect, p);
/* The rest of your canvas-drawing code */
canvas.restore();
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
{
@Override
public boolean onScale(ScaleGestureDetector detector)
{
scaleFactor *= detector.getScaleFactor();
scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
return true;
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
switch (event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
mode = DRAG;
//We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
//amount for each coordinates This works even when we are translating the first time because the initial
//values for these two variables is zero.
startX = event.getX() - previousTranslateX;
startY = event.getY() - previousTranslateY;
break;
case MotionEvent.ACTION_MOVE:
translateX = event.getX() - startX;
translateY = event.getY() - startY;
//We cannot use startX and startY directly because we have adjusted their values using the previous translation values.
//This is why we need to add those values to startX and startY so that we can get the actual coordinates of the finger.
double distance = Math.sqrt(Math.pow(event.getX() - (startX + previousTranslateX), 2) +
Math.pow(event.getY() - (startY + previousTranslateY), 2));
if(distance > 0)
{
dragged = true;
distance *= scaleFactor;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_UP:
mode = NONE;
dragged = false;
//All fingers went up, so letfs save the value of translateX and translateY into previousTranslateX and
//previousTranslate
previousTranslateX = translateX;
previousTranslateY = translateY;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = DRAG;
//This is not strictly necessary; we save the value of translateX and translateY into previousTranslateX
//and previousTranslateY when the second finger goes up
previousTranslateX = translateX;
previousTranslateY = translateY;
break;
}
detector.onTouchEvent(event);
//We redraw the canvas only in the following cases:
//
// o The mode is ZOOM
// OR
// o The mode is DRAG and the scale factor is not equal to 1 (meaning we have zoomed) and dragged is
// set to true (meaning the finger has actually moved)
if ((mode == DRAG && scaleFactor != 1f && dragged) || mode == ZOOM)
{
invalidate();
}
return true;
}
}
将框架添加到表面的代码如下:
if (!b.isRecycled()){
try {
Rect rect = new Rect(0, 0, frame.getWidth(), frame.getHeight());
Canvas canvas = frame.getHolder().lockCanvas();
synchronized (frame.getHolder()) {
if (!b.isRecycled()) {
frame.drawBitmap(canvas, b, rect);
b.recycle();
}
}
frame.getHolder().unlockCanvasAndPost(canvas);
} catch (java.lang.RuntimeException exc){
Dbg.d("ERROR", exc);
}
lastBitmap = b;
}
您发布的代码不完整,所以很难说是什么问题。我确实将您的代码放到了一个快速演示项目中,但没有发现任何边框问题。
只要看一下屏幕截图:您的图像数据是否有可能以某种方式环绕?第二张截图看起来像是在图像顶部绘制了底部边框。如果没有可重现的代码,又很难说。
可能会尝试在重绘位图之前重绘背景
canvas.drawRect(rect, backgroundPaint);
frame.drawBitmap(canvas, b, rect);