`
yiheng
  • 浏览: 155484 次
社区版块
存档分类

使用Observer模式解决图片拖动与缩放

阅读更多

1、java内置的观察者模式:

由Java 中的Observable 类和 Observer 接口组成

(1) Observable 类代表 被观察者 (java.util.Observable )

主要方法有:

void setChanged() : 设置被观察者的状态已经被改变
void addObserver(Observer observer) : 添加观察者
int countObservers() : 返回所有观察者的数目
void deleteObserver(Observer observer) :删除指定观察者
void deleteObservers() : 删除所有观察者
boolean hasChanged() : 被观察者的状态是否被改变,如果是则返回true,否则返回false
void notifyObservers() : 通知所有观察者

void notifyObservers(Object arg) : 通知所有观察者(参数一般设定为被改变的属性)
void clearChanged() :清除被观察者状态的改变

(2) Observer 接口代表 观察者 (java.util.Observer )

void update(Observable observable, Object arg) :当 被观察者 调用 notifyObservers() 方法
时,会根据被观察者的 hasChanged() 方法 来判断 它的状态是否被改变, 如果被观察者的状态被改变了,则
会调用 观察者 的 update 方法,参数 observable 为 被观察者对象, arg 为调用 notifyObservers( Object arg ) 时传入的参数 arg ,如果调用的是 notifyObservers() 方法, 则 arg 为 null。

值得注意的是:在Observable里,在notify()时必须先调用setChanged()方法,此方式表明状态更新...

贴上代码:

ImageZoomState类:

package hfut.gmm;

import java.util.Observable;

public class ImageZoomState extends Observable {
	private float mZoom = 1.0f;// 控制图片缩放的变量,表示缩放倍数,值越大图像越大
	private float mPanX = 0.5f;// 控制图片水平方向移动的变量,值越大图片可视区域的左边界距离图片左边界越远,图像越靠左,值为0.5f时居中
	private float mPanY = 0.5f;// 控制图片水平方向移动的变量,值越大图片可视区域的上边界距离图片上边界越远,图像越靠上,值为0.5f时居中

	public float getmZoom() {
		return mZoom;
	}

	public void setmZoom(float mZoom) {
		if (this.mZoom != mZoom) {
			this.mZoom = mZoom < 1.0f ? 1.0f : mZoom;// 保证图片最小为原始状态
			if (this.mZoom == 1.0f) {// 返回初始大小时,使其位置也恢复原始位置
				this.mPanX = 0.5f;
				this.mPanY = 0.5f;
			}
			this.setChanged();
		}
	}

	public float getmPanX() {
		return mPanX;
	}

	public void setmPanX(float mPanX) {
		if (mZoom == 1.0f) {// 使图为原始大小时不能移动
			return;
		}
		if (this.mPanX != mPanX) {
			this.mPanX = mPanX;
			this.setChanged();
		}
	}

	public float getmPanY() {
		return mPanY;
	}

	public void setmPanY(float mPanY) {
		if (mZoom == 1.0f) {// 使图为原始大小时不能移动
			return;
		}
		if (this.mPanY != mPanY) {
			this.mPanY = mPanY;
			this.setChanged();
		}
	}

	public float getZoomX(float aspectQuotient) {
		return Math.min(mZoom, mZoom * aspectQuotient);
	}

	public float getZoomY(float aspectQuotient) {
		return Math.min(mZoom, mZoom / aspectQuotient);
	}
}

SimpleImageZoomListener类:

package hfut.gmm;

import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class SimpleImageZoomListener implements View.OnTouchListener {
	private ImageZoomState mState;// 图片缩放和移动状态
	private final float SENSIBILITY = 0.8f;// 图片移动时的灵敏度

	/**
	 * 变化的起始点坐标
	 */
	private float sX;
	private float sY;
	/**
	 * 不变的起始点坐标,用于判断手指是否进行了移动,从而在UP事件中判断是否为点击事件
	 */
	private float sX01;
	private float sY01;
	/**
	 * 两触摸点间的最初距离
	 */
	private float sDistance;

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		int action = event.getAction();
		int pointNum = event.getPointerCount();// 获取触摸点数
		if (pointNum == 1) {// 单点触摸,用来实现图像的移动和相应点击事件
			Log.d("Infor", "一个触点");
			float mX = event.getX();// 记录不断移动的触摸点x坐标
			float mY = event.getY();// 记录不断移动的触摸点y坐标
			switch (action) {
			case MotionEvent.ACTION_DOWN:
				// 记录起始点坐标
				sX01 = mX;
				sY01 = mY;
				sX = mX;
				sY = mY;
				return false;// 必须return false 否则不响应点击事件
			case MotionEvent.ACTION_MOVE:
				float dX = (mX - sX) / v.getWidth();
				float dY = (mY - sY) / v.getHeight();
				mState.setmPanX(mState.getmPanX() - dX * SENSIBILITY);
				mState.setmPanY(mState.getmPanY() - dY * SENSIBILITY);
				mState.notifyObservers();
				// 更新起始点坐标
				sX = mX;
				sY = mY;
				break;
			case MotionEvent.ACTION_UP:
				if (event.getX() == sX01 && event.getY() == sY01) {
					return false;// return false 执行点击事件
				}
				break;
			}
		}

		if (pointNum == 2) {// 多点触摸,用来实现图像的缩放
			// 记录不断移动的一个触摸点坐标
			Log.d("Infor", "二个触点");
			float mX0 = event.getX(event.getPointerId(0));
			float mY0 = event.getY(event.getPointerId(0));
			// 记录不断移动的令一个触摸点坐标
			float mX1 = event.getX(event.getPointerId(1));
			float mY1 = event.getY(event.getPointerId(1));

			float distance = this.getDistance(mX0, mY0, mX1, mY1);
			switch (action) {
			case MotionEvent.ACTION_POINTER_2_DOWN:
			case MotionEvent.ACTION_POINTER_1_DOWN:
				sDistance = distance;
				break;
			case MotionEvent.ACTION_POINTER_1_UP:
				// 注意:松开第一个触摸点后的手指滑动就变成了以第二个触摸点为起始点的移动,所以要以第二个触摸点坐标值为起始点坐标赋值
				sX = mX1;
				sY = mY1;
				break;
			case MotionEvent.ACTION_POINTER_2_UP:
				// 注意:松开第二个触摸点后的手指滑动就变成了以第二个触摸点为起始点的移动,所以要以第一个触摸点坐标值为起始点坐标赋值
				sX = mX0;
				sY = mY0;
				break;
			case MotionEvent.ACTION_MOVE:
				// float dDistance = (distance - sDistance) / sDistance;
				// mState.setmZoom(mState.getmZoom()
				// * (float) Math.pow(5, dDistance));
				mState.setmZoom(mState.getmZoom() * distance / sDistance);
				mState.notifyObservers();
				sDistance = distance;
				break;
			}
		}
		return true;// 必须返回true,具体请查询Android事件拦截机制的相关资料
	}

	/**
	 * //返回( mX0, mY0)与(( mX1, mY1)两点间的距离
	 * 
	 * @param mX0
	 * @param mX1
	 * @param mY0
	 * @param mY1
	 * @return
	 */
	private float getDistance(float mX0, float mY0, float mX1, float mY1) {
		double dX2 = Math.pow(mX0 - mX1, 2);// 两点横坐标差的平法
		double dY2 = Math.pow(mY0 - mY1, 2);// 两点纵坐标差的平法
		return (float) Math.pow(dX2 + dY2, 0.5);
	}

	public void setZoomState(ImageZoomState state) {
		mState = state;
	}
}

ImageZoomView类:

package hfut.gmm;

import java.util.Observable;
import java.util.Observer;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

public class ImageZoomView extends View implements Observer {
	private Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
	private Rect mRectSrc = new Rect();
	private Rect mRectDst = new Rect();
	private float mAspectQuotient;

	private Bitmap mBitmap;
	private ImageZoomState mZoomState;

	public ImageZoomView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	public void update(Observable observable, Object data) {
		this.invalidate();
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		if (mBitmap != null && mZoomState != null) {
			int viewWidth = this.getWidth();
			int viewHeight = this.getHeight();
			int bitmapWidth = mBitmap.getWidth();
			int bitmapHeight = mBitmap.getHeight();

			float panX = mZoomState.getmPanX();
			float panY = mZoomState.getmPanY();
			float zoomX = mZoomState.getZoomX(mAspectQuotient) * viewWidth
					/ bitmapWidth;// 相当于viewHeight/bitmapHeight*mZoom
			float zoomY = mZoomState.getZoomY(mAspectQuotient) * viewHeight
					/ bitmapHeight;// 相当于viewWidth/bitmapWidth*mZoom

			// Setup source and destination rectangles
			// 这里假定图片的高和宽都大于显示区域的高和宽,如果不是在下面做调整
			mRectSrc.left = (int) (panX * bitmapWidth - viewWidth / (zoomX * 2));
			mRectSrc.top = (int) (panY * bitmapHeight - viewHeight
					/ (zoomY * 2));
			mRectSrc.right = (int) (mRectSrc.left + viewWidth / zoomX);
			mRectSrc.bottom = (int) (mRectSrc.top + viewHeight / zoomY);

			mRectDst.left = this.getLeft();
			mRectDst.top = this.getTop();
			mRectDst.right = this.getRight();
			mRectDst.bottom = this.getBottom();

			// Adjust source rectangle so that it fits within the source image.
			// 如果图片宽或高小于显示区域宽或高(组件大小)或者由于移动或缩放引起的下面条件成立则调整矩形区域边界
			if (mRectSrc.left < 0) {
				mRectDst.left += -mRectSrc.left * zoomX;
				mRectSrc.left = 0;
			}
			if (mRectSrc.right > bitmapWidth) {
				mRectDst.right -= (mRectSrc.right - bitmapWidth) * zoomX;
				mRectSrc.right = bitmapWidth;
			}

			if (mRectSrc.top < 0) {
				mRectDst.top += -mRectSrc.top * zoomY;
				mRectSrc.top = 0;
			}
			if (mRectSrc.bottom > bitmapHeight) {
				mRectDst.bottom -= (mRectSrc.bottom - bitmapHeight) * zoomY;
				mRectSrc.bottom = bitmapHeight;
			}

			// 把bitmap的一部分(就是src所包括的部分)绘制到显示区中dst指定的矩形处.关键就是dst,它确定了bitmap要画的大小跟位置
			// 注:两个矩形中的坐标位置是相对于各自本身的而不是相对于屏幕的。
			canvas.drawBitmap(mBitmap, mRectSrc, mRectDst, mPaint);
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		// TODO Auto-generated method stub
		super.onLayout(changed, left, top, right, bottom);
		this.calculateAspectQuotient();
	}

	public void setImageZoomState(ImageZoomState zoomState) {
		if (mZoomState != null) {
			mZoomState.deleteObserver(this);
		}
		mZoomState = zoomState;
		mZoomState.addObserver(this);
		invalidate();
	}

	public void setImage(Bitmap bitmap) {
		mBitmap = bitmap;
		this.calculateAspectQuotient();
		invalidate();

	}

	private void calculateAspectQuotient() {
		if (mBitmap != null) {
			mAspectQuotient = (float) (((float) mBitmap.getWidth() / mBitmap
					.getHeight()) / ((float) this.getWidth() / this.getHeight()));
		}
	}
}

入口MainActivity类:

package hfut.gmm;

import android.app.Activity;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.ZoomControls;

public class MainActivity extends Activity {
    /** Called when the activity is first created. */
	private ZoomControls zoomCtrl;// 系统自带的缩放控制组件
	private ImageZoomView zoomView;// 自定义的图片显示组件
	private ImageZoomState zoomState;// 图片缩放和移动状态类
	private SimpleImageZoomListener zoomListener;// 缩放事件监听器
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.main);
        zoomState = new ImageZoomState();

		zoomListener = new SimpleImageZoomListener();
		zoomListener.setZoomState(zoomState);

		zoomCtrl = (ZoomControls) findViewById(R.id.zoomCtrl);
		this.setImageController();

	   zoomView = (ImageZoomView) findViewById(R.id.zoomView);
	 //	zoomView.setImage(bitmap);
	    zoomView.setImage(BitmapFactory.decodeResource(this.getResources(), R.drawable.index));
		zoomView.setImageZoomState(zoomState);
		zoomView.setOnTouchListener(zoomListener);
		zoomView.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				setFullScreen();
			}
		});

	}

	private void setImageController() {
		zoomCtrl.setOnZoomInClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				float z = zoomState.getmZoom() + 0.25f;
				zoomState.setmZoom(z);
				zoomState.notifyObservers();
			}
		});
		zoomCtrl.setOnZoomOutClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				float z = zoomState.getmZoom() - 0.25f;// 图像大小减少原来的0.25倍
				zoomState.setmZoom(z);
				zoomState.notifyObservers();
			}
		});
	}

	/**
	 * 隐藏处ImageZoomView外地其他组件,全屏显示
	 */
	private void setFullScreen() {
		if (zoomCtrl != null) {
			if (zoomCtrl.getVisibility() == View.VISIBLE) {
				// zoomCtrl.setVisibility(View.GONE);
				zoomCtrl.hide(); // 有过度效果

			} else if (zoomCtrl.getVisibility() == View.GONE) {
				// zoomCtrl.setVisibility(View.VISIBLE);
				zoomCtrl.show();// 有过渡效果

			}
		} 

}
}


XML文件:

<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <hfut.gmm.ImageZoomView
        android:id="@+id/zoomView"
        android:layout_width="fill_parent"
        
        android:layout_height="fill_parent" >
    </hfut.gmm.ImageZoomView>

    <ZoomControls
        android:id="@+id/zoomCtrl"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
       
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true" >
    </ZoomControls>

</RelativeLayout>




结果截图如下:


参考英文文献:http://developer.sonymobile.com/wp/2010/05/18/android-one-finger-zoom-tutorial-part-1/

英语水平太菜、英文文献看的相当费劲啊....



3
5
分享到:
评论

相关推荐

    android实现拖拽图片的操作

    本教程将探讨如何在Android应用中实现拖拽图片的功能,同时也会涉及到`Observable`与`Observer`模式的运用,这是Java编程中一种重要的设计模式,用于实现对象间的数据同步和事件通知。 首先,拖拽图片的核心在于...

    android实现单指拖动放大缩小图片

    总结来说,这个项目展示了如何利用`Observable与Observer`模式在Android中实现一个交互式的图片查看器,通过手势识别进行拖动和缩放操作。这不仅涉及到了Android的基础组件和手势识别机制,还涵盖了设计模式的应用和...

    图片可任意拖动并带有滑动效果的JS特效

    - 使用Intersection Observer API检测图片是否进入视口,以决定何时开始加载或执行动画。 6. **响应式设计**: 确保图片墙和滑动效果在不同设备和屏幕尺寸上都能正常工作,需要考虑媒体查询(media queries)和...

    仿淘宝详情页继续拖动,查看图片详情

    "仿淘宝详情页继续拖动,查看图片详情"这一课题,主要是针对商品详情页中的图片浏览功能进行优化,实现用户可以滚动页面时,图片依然能持续显示,提供更流畅的浏览体验。 首先,我们需要理解淘宝详情页的核心功能。...

    js仿Discuz弹出图片随鼠标滚动放大缩小代码

    例如,可以使用`Intersection Observer API`来监听图片是否进入视口,或者使用Web Worker处理复杂的计算任务。 以上就是实现"js仿Discuz弹出图片随鼠标滚动放大缩小代码"涉及的主要技术点。在实际开发中,开发者...

    百度图片展示js效果

    在放大模式下,可以实现平滑的拖动效果,让用户能自由查看图片的各个部分。 4. **翻页功能**:如果图片数量较多,可以提供分页功能,让用户可以浏览更多图片。这通常涉及计算每一页显示的图片数量,以及处理页码...

    javascript图片效果

    13. 图片编码与解码:比如使用 canvas API,可以实现对图片的编码和解码,用于处理图片数据或实现一些高级效果。 14. 实现图片的裁剪、旋转、翻转等功能,如使用 Fabric.js 这样的图形库,可以创建一个可编辑的画布...

    基于Android自定义图表组件的关键技术研究.pdf

    可以使用Observer模式或者LiveData等数据绑定框架来实现这一功能。 7. **交互性与动画效果**:良好的图表组件不仅展示数据,还应提供用户交互,如点击事件处理、长按事件、拖动缩放等。同时,动画效果能提升用户...

    HTML5全屏图册

    在全屏图册中,`&lt;canvas&gt;`可以用来实现图片的动态展示,如平滑缩放、拖动和旋转等效果。 2. `&lt;img&gt;`元素与`srcset`属性:在响应式设计中,`srcset`属性可以为不同分辨率的设备提供不同的图片资源,确保在全屏展示时...

    ArcGIS三维培训手册(草稿)

    为了优化全屏显示的效果,用户可以通过 **Customize &gt; ArcGlobe Options &gt; General** 菜单下的 **Full View Observer Position** 设置项来自定义全屏模式下的观察者位置信息。 #### 七、启用动画设置 除了以上基础...

    VTK DICOM 图像显示以及距离角度测量

    总的来说,这个项目展示了如何使用VTK处理DICOM图像,创建交互式的距离和角度测量工具,以及如何通过观察者模式来响应用户操作,为医学影像分析提供了一种实用的可视化解决方案。通过这种方式,医生和研究人员可以更...

    java绘图java绘图java绘图

    你可以加载图片,然后使用`drawImage(Image img, int x, int y, ImageObserver observer)`方法在画布上显示它们。 6. **字体和文本**: 通过`Font`类,你可以创建和控制字体样式。`drawString(String str, int x, ...

    ios总结1

    13. **iOS中的手势识别(Gesture Recognizers)**:iOS提供了一系列手势识别器,如UIPanGestureRecognizer(拖动)、UITapGestureRecognizer(点击)、UIPinchGestureRecognizer(捏合缩放)等,用于捕捉用户的触摸...

    Android自定义泡泡效果源码.zip

    可能会使用到`Builder`模式来构建气泡实例,`Observer`模式来实现气泡状态变化的通知,以及`Repository`模式来管理数据源。 以上是根据“Android自定义泡泡效果源码”可能涉及的一些关键知识点。通过学习和理解这些...

    javascript94个网页特效

    例如,使用`new Image()`对象预加载图片,使用`Intersection Observer API`实现懒加载,或者结合CSS3动画实现图片轮播。 4. **表单交互**:JavaScript可以实时验证用户输入,提供错误提示,甚至创建自定义的表单...

    移动端滑动视觉差效果

    同时,可以考虑使用Intersection Observer API来只处理可视区域内的元素,减少计算负担。 6. **兼容性处理**:考虑到浏览器兼容性问题,可能需要引入polyfill库或者使用CSS3的`transform`和`transition`属性来实现...

    js实现移动端时间轴插件

    可以使用`Intersection Observer API`来实现无限滚动加载。另外,通过CSS3的`transition`和`animation`属性,可以为时间轴项添加平滑的动画效果,比如淡入淡出、滑动等。 在提供的"timeline"压缩包中,包含了示例...

    Android应用源码之环形的调节条,用于工程中特殊的调值控件,拟物控件的制作.zip

    环形调节条的实现原理与此类似,它会扩展一个基础的View类,并在其中实现旋转、缩放、触摸事件处理等功能,以实现环形的拖动和旋转效果。 在源码中,我们可以看到以下关键知识点: 1. **自定义View的绘制**:`...

    快速启动一个根据浏览器滚动位置变化页面的H5项目

    在JavaScript开发中,创建一个基于浏览器滚动位置...在实际开发中,还可以考虑性能优化,比如使用`requestAnimationFrame`来避免频繁的滚动事件处理,或者利用Intersection Observer API来只处理进入或离开视口的元素。

    特炫的javascript效果

    - 轮播图是展示多张图片或内容的常见方式,通常包含自动切换、导航箭头和点状指示器等功能。 - 使用JavaScript控制定时器和DOM操作,如`next()`和`prev()`函数,可以实现轮播图的自动切换。 - 结合CSS3的`...

Global site tag (gtag.js) - Google Analytics