Android viewpager + 可缩放的imageview
http://files.cnblogs.com/files/liaolandemengxiang/PhotoWallFallsDemo.rar
http://files.cnblogs.com/files/liaolandemengxiang/ViewPager_imageview%E7%9A%84%E7%BC%A9%E6%94%BE%E4%BC%98%E5%8C%96%E5%90%8E.rar
第一个地址是中心缩放的demo,值得注意的是,这里面的位移是相对于(0,0)位置的偏移量,每次都是画布重新画图片
第二个是优化了以后的viewpager 和zoomimageview的组合的demo,是通过imageview的回调函数还控制viewpager是否可以缩放。并对原来的bug进行修改。bug主要的问题是存在于没有考虑高度大于屏幕高度的长图片的缩放,已经中心位置的计算错误。还有就是对于最大缩放倍数的理解,初始化缩放比例是X,可是视为1,最大缩放倍数应该是X的倍数。
引自http://www.cnblogs.com/linjzong/p/4212474.html
在上一篇Android:手把手教你打造可缩放移动的ImageView最后提出了一个注意点:当自定义的MatrixImageView如ViewPager、ListView等带有滑动效果的ViewGroup中时,ImageView自定义的拖动事件会和ViewGroup的滑动事件冲突,并且指出了冲突原因是由于ViewGroup拦截了Move事件的缘故。如果对于Touch事件的分发机制不甚了解的话,可以参考下这篇Android:30分钟弄明白Touch事件分发机制。
这篇文章将会在MatrixImageView的基础上,以ViewPager作为测试容器做进一步优化。
实现功能
- 当进行缩放操作时,手势不会同时触发ViewPager的滑动切换Item事件。
- 当进行拖动操作时,除非图片已经到达左右边界,否则不触发ViewPager的滑动切换Item事件。
- 当进行拖动操作时,若图片左边缘到达左边界,则可以向左滑动触发ViewPager切换至前一个Item;当图片右边缘到达右边界,则可以向右滑动触发ViewPager的切换至后一个Item。
- 每个down-up(cancel)事件周期内只执行一种类型的事务操作(缩放、拖动或者ViewPager切换Item),防止多重事务互相干扰。
- 将事务处理封装到MatrixImageView类内,提供状态接口给ViewPager使用,方便适配多种ViewGroup。
实现原理
当ViewPager内嵌MatrixImageView时,由于MatrixImgaeView在Down事件中返回了true,表明ViewPager将捕获本次完整的Touch事件(Move-Ponit_Down-UP等等),其中最重要的一个事件便是Move事件,因为ViewPager自身需要捕获Move事件在onTouch中进行切换Item操作,MatrixImageView的捕获意味着它将无法响应。不过,ViewPager本身控制着Touch事件的下发操作,每个Touch事件的下发都遵从从上至下层层递归,在MatrixImageView真正获得Move事件前,Move事件必须经过ViewPager的onInterceptTouchEvent和dispatchTouchEvent事件,前者执行拦截操作后者执行下发操作。ViewPager便是在onInterceptTouchEvent中对Move事件进行了过滤,当移动距离超过一定值时,它会拦截掉Move事件,阻止MatrixImageView继续处理Touch事件的权利,转而让自身的onTouch事件处理。于是,我们要做的便是重写onInterceptTouchEvent事件,通过判断MatrixImageView的状态决定是否拦截。
具体实现
由于容器ViewPager在满足条件的时候会拦截掉子View的touch事件,因此需要自定义个ViewPager修改拦截逻辑。当MatriImageView进行缩放和拖动时,我们不希望ViewPager拦截。具体代码如下:
public class AlbumViewPager extends ViewPager implements OnChildMovingListener {
/** 当前子控件是否处理拖动状态 */
private boolean mChildIsBeingDragged=false; @Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
if(mChildIsBeingDragged)
return false;
return super.onInterceptTouchEvent(arg0);
}
@Override
public void startDrag() {
// TODO Auto-generated method stub
mChildIsBeingDragged=true;
} @Override
public void stopDrag() {
// TODO Auto-generated method stub
mChildIsBeingDragged=false;
}
}
public interface OnChildMovingListener{
public void startDrag();
public void stopDrag();
}
通过判断变量mChildIsBeingDragged的值决定是否拦截,而mChildIsBeingDragged的值通过OnChildMovingListener接口由MatriImageView进行设置。别忘了在PagerAdapter的instantiateItem中给MatriImageView设置监听接口
MatrixImageView imageView = (MatrixImageView) imageLayout.findViewById(R.id.image);
imageView.setOnMovingListener(AlbumViewPager.this);
ViewPager的改造便完成了,只需要新增一个变量和实现一个接口,之后对于事件的拦截操作都转到了MatrixImageView中。
接下去看下改造后的MatrixImageView的onTouch方法。
/** 和ViewPager交互相关,判断当前是否可以左移、右移 */
boolean mLeftDragable;
boolean mRightDragable;
/** 是否第一次移动 */
boolean mFirstMove=false;
private PointF mStartPoint = new PointF();
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
//设置拖动模式
mMode=MODE_DRAG;
mStartPoint.set(event.getX(), event.getY());
isMatrixEnable();
startMove();
checkDragable();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
reSetMatrix();
stopMove();
break;
case MotionEvent.ACTION_MOVE:
if (mMode == MODE_ZOOM) {
setZoomMatrix(event);
}else if (mMode==MODE_DRAG) {
setDragMatrix(event);
}else {
stopMove();
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if(mMode==MODE_UNABLE) return true;
mMode=MODE_ZOOM;
mStartDis = distance(event);
break;
case MotionEvent.ACTION_POINTER_UP: break;
default:
break;
}
return mGestureDetector.onTouchEvent(event);
}
其中加红部分的代码都是修改后的代码,逐一分析。
/**
* 子控件开始进入移动状态,令ViewPager无法拦截对子控件的Touch事件
*/
private void startDrag(){
if(moveListener!=null) moveListener.startDrag(); }
/**
* 子控件开始停止移动状态,ViewPager将拦截对子控件的Touch事件
*/
private void stopDrag(){
if(moveListener!=null) moveListener.stopDrag();
}
startDrag和stopDrag方法很简单,就是调用ViewPager传递进来的OnChildMovingListener接口进行mChildIsBeingDragged的设置。当监听到down事件时,表示开始拖动,当接收到up和cancel事件时,表示结束拖动,以这个逻辑来说,ViewGroup将永远无法拦截touch事件,所以我们还需要在其他地方设置stopDrag事件,后面说明。
接下去是在down事件中执行checkDragable方法:
/**
* 根据当前图片左右边缘设置可拖拽状态
*/
private void checkDragable() {
mLeftDragable=true;
mRightDragable=true;
mFirstMove=true;
float[] values=new float[9];
getImageMatrix().getValues(values);
//图片左边缘离开左边界,表示不可右移
if(values[Matrix.MTRANS_X]>=0)
mRightDragable=false;
//图片右边缘离开右边界,表示不可左移
if((mImageWidth)*values[Matrix.MSCALE_X]+values[Matrix.MTRANS_X]<=getWidth()){
mLeftDragable=false;
}
}
该方法将会重置mLeftDragable、mRightDragable、mFirstMove三个参数的状态。mLeftDragable表示当前状态下的Matrix可以向左拖动,mRightDragable表示当前状态下的Matrix可以向右拖动,mFirstMove为每次完整touch事件(从down到up或cancel)中的第一次拖动操作标志。其中mLeftDragable和mRightDragable都是根据Matrix矩阵的数值计算出来的。
由于前面功能需求的时候说过"每个down-up(cancel)事件周期内只执行一种类型的事务操作(缩放、拖动或者ViewPager切换Item)",因此当进行缩放操作时,就不会再执行切换Item操作了,可以等缩放结束后执行up操作时stopDrag。而Move操作重点就是要识别是切换item还是拖动图片了。查看修改后的setDragMatrix代码
/**
* 设置拖拽状态下的Matrix
* @param event
*/
public void setDragMatrix(MotionEvent event) {
if(isZoomChanged()){
float dx = event.getX() - mStartPoint.x; // 得到x轴的移动距离
float dy = event.getY() - mStartPoint.y; // 得到x轴的移动距离
//避免和双击冲突,大于10f才算是拖动
if(Math.sqrt(dx*dx+dy*dy)>10f){
mStartPoint.set(event.getX(), event.getY());
//在当前基础上移动
mCurrentMatrix.set(getImageMatrix());
float[] values=new float[9];
mCurrentMatrix.getValues(values);
dy=checkDyBound(values,dy);
dx=checkDxBound(values,dx,dy); mCurrentMatrix.postTranslate(dx, dy);
setImageMatrix(mCurrentMatrix);
}
}else {
stopDrag();
}
} /**
* 和当前矩阵对比,检验dx,使图像移动后不会超出ImageView边界
* @param values
* @param dx
* @return
*/
private float checkDxBound(float[] values,float dx,float dy) {
float width=getWidth();
if(!mLeftDragable&&dx<0){
//加入和y轴的对比,表示在监听到垂直方向的手势时不切换Item
if(Math.abs(dx)*0.4f>Math.abs(dy)&&mFirstMove){
stopDrag();
}
return 0;
}
if(!mRightDragable&&dx>0){
//加入和y轴的对比,表示在监听到垂直方向的手势时不切换Item
if(Math.abs(dx)*0.4f>Math.abs(dy)&&mFirstMove){
stopDrag();
}
return 0;
}
mLeftDragable=true;
mRightDragable=true;
if(mFirstMove) mFirstMove=false;
if(mImageWidth*values[Matrix.MSCALE_X]<width){
return 0; }
if(values[Matrix.MTRANS_X]+dx>0){
dx=-values[Matrix.MTRANS_X];
}
else if(values[Matrix.MTRANS_X]+dx<-(mImageWidth*values[Matrix.MSCALE_X]-width)){
dx=-(mImageWidth*values[Matrix.MSCALE_X]-width)-values[Matrix.MTRANS_X];
}
return dx;
}
处理逻辑是这样的:
1.判断当前缩放级别是否是原始缩放级别(isZoomChanged()),如果未缩放过那将可以直接切换Item,在这直接stopDrag。
2.若进行了缩放,那判断是否累移动了10f,当移动了10f之后计算出x轴和y轴的移动量,并且通过checkDyBound方法计算出y轴的真实移动量
3.进入checkDxBound方法,首先判断当前是否能够左移,如果不能左移而实际的x轴偏移量是向左的,那就返回x的偏移量为0,防止左移。同时,如果当前是第一次移动,那就表示本次不是左移操作,而是向前切换item,于是执行stopDrag方法,令ViewPager拦截掉对MatrixImageView的事件分发。另外在这里加入和Y轴偏移量的对比,是为了防止执行的是垂直方向的滑动而导致stopDrag,ViewPager自身对于X轴偏移量/2小于Y轴偏移量的情况是不当成切换Item意图的,这里设置为*0.4可以保证不冲突。
4.右移同理。
5.当第一次左移和右移判断结果都不是切换Item后,将mLeftDragable和mRightDragable都设置为true,表示可以正常移动了。之后就和单个MatrixImageView的拖动处理一样了。
到此便完成了内嵌到ViewGroup内的MatriImageView的改造。下面还有两点显示优化。
首先在reSetMatrix中加入了新的功能:当缩放后的图片高度未达到ImageView高度时,在up和cancel之后会将其Y轴居中,防止“放大图片-Y轴移动图片-缩小图片”导致图片位置不对称。异常图效果如下:
/**
* 重置Matrix
*/
private void reSetMatrix() {
if(checkRest()){
mCurrentMatrix.set(mMatrix);
setImageMatrix(mCurrentMatrix);
}else {
//判断Y轴是否需要更正
float[] values=new float[9];
getImageMatrix().getValues(values);
float height=mImageHeight*values[Matrix.MSCALE_Y];
if(height<getHeight()){
//在图片真实高度小于容器高度时,Y轴居中,Y轴理想偏移量为两者高度差/2,
float topMargin=(getHeight()-height)/2;
if(topMargin!=values[Matrix.MTRANS_Y]){
mCurrentMatrix.set(getImageMatrix());
mCurrentMatrix.postTranslate(0, topMargin-values[Matrix.MTRANS_Y]);
setImageMatrix(mCurrentMatrix);
}
}
}
}
优化了缩放操作的缩放x轴对称轴选择问题。在"图片放大-移动X轴-缩小图片"时,若直接以ImageView中心点为缩放原点,可能会导致缩放后的图片边缘离开ImageView边界。
出错图效果如下:
/**
* 设置缩放Matrix
* @param event
*/
private void setZoomMatrix(MotionEvent event) {
//只有同时触屏两个点的时候才执行
if(event.getPointerCount()<2) return;
float endDis = distance(event);// 结束距离
if (endDis > 10f) { // 两个手指并拢在一起的时候像素大于10
float scale = endDis / mStartDis;// 得到缩放倍数
mStartDis=endDis;//重置距离
mCurrentMatrix.set(getImageMatrix());//初始化Matrix
float[] values=new float[9];
mCurrentMatrix.getValues(values);
scale = checkMaxScale(scale, values);
PointF centerF=getCenter(scale,values);
mCurrentMatrix.postScale(scale, scale,centerF.x,centerF.y);
setImageMatrix(mCurrentMatrix);
}
} /**
* 获取缩放的中心点。
* @param scale
* @param values
* @return
*/
private PointF getCenter(float scale,float[] values) {
//缩放级别小于原始缩放级别时或者为放大状态时,返回ImageView中心点作为缩放中心点
if(scale*values[Matrix.MSCALE_X]<mScale||scale>=1){
return new PointF(getWidth()/2,getHeight()/2);
}
float cx=getWidth()/2;
float cy=getHeight()/2;
//以ImageView中心点为缩放中心,判断缩放后的图片左边缘是否会离开ImageView左边缘,是的话以左边缘为X轴中心
if((getWidth()/2-values[Matrix.MTRANS_X])*scale<getWidth()/2)
cx=0;
//判断缩放后的右边缘是否会离开ImageView右边缘,是的话以右边缘为X轴中心
if((mImageWidth*values[Matrix.MSCALE_X]+values[Matrix.MTRANS_X])*scale<getWidth())
cx=getWidth();
return new PointF(cx,cy);
}
通过判断图片宽度,决定是以ImageView中点为X轴缩放原点,还是以左右边缘为缩放原点。
目前为止MatrixImageView的功能基本完善了,具体代码还是放在我的Github上的照相机Demo。该View如果有问题的可以在这篇文章下留言或私信我。
Android viewpager + 可缩放的imageview的更多相关文章
- Android——ViewPager多页面滑动切换以及动画效果
一.首先,我们来看一下效果图,这是新浪微博的Tab滑动效果.我们可以手势滑动,也可以点击上面的头标进行切换.与此同方式,白色横条会移动到相应的页卡头标下.这是一个动画效果,白条是缓慢滑动过去的.好了, ...
- Android ViewPager 用法
Android ViewPager 用法 场景:一般第一次打开应用程序时,程序会有一个提示页来给展现应用程序都有哪些功能:或者程序更新时,又更新哪些新特性,都可以使用ViewPager Demo 描述 ...
- android viewpager 图片翻页例子
使用ViewPager这个类可以轻松实现多个页面的滑动功能 viewpager默认在工具界面上是找不到的,需求添加android-support-v4.jar包: 如果没有找到,需要打开Android ...
- Android ViewPager再探:增加滑动指示条
上一篇:<Android ViewPager初探:让页面滑动起来> ViewPager只是左右滑动有些丑,也不知道当前位于第几页面. 可以在上方加入滑动指示条,来确定当前位置. 只需要修改 ...
- Android ViewPager使用详解
这是谷歌官方给我们提供的一个兼容低版本安卓设备的软件包,里面包囊了只有在安卓3.0以上可以使用的api.而viewpager就是其中之一利用它,我们可以做很多事情,从最简单的导航,到页面菜单等等.那如 ...
- 转:Android ViewPager多页面滑动切换以及动画效果
一.首先,我们来看一下效果图,这是新浪微博的Tab滑动效果.我们可以手势滑动,也可以点击上面的头标进行切换.与此同方式, 白色横条会移动到相应的页卡头标下.这是一个动画效果,白条是缓慢滑动过去的.好了 ...
- 【Android 界面效果21】Android ViewPager使用详解
这是谷歌官方给我们提供的一个兼容低版本安卓设备的软件包,里面包囊了只有在安卓3.0以上可以使用的api.而viewpager就是其中之一利用它,我们可以做很多事情,从最简单的导航,到页面菜单等等.那如 ...
- Android ViewPager欢迎页+引导页+进入首页
import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences ...
- Android ViewPager使用具体解释
这是谷歌官方给我们提供的一个兼容低版本号安卓设备的软件包,里面包囊了仅仅有在安卓3.0以上能够使用的api.而viewpager就是当中之中的一个利用它,我们能够做非常多事情,从最简单的导航,到页面菜 ...
随机推荐
- DevExpress之TreeList节点绑定图片
最近在项目中使用到了DX中的TreeList控件绑定数据源时在每个节点前显示特点的图片:查阅相关资料实现方法如下:1.首先打开VS2010新建一个WINFROM应用程序: 2.在WINFROM应用程序 ...
- Eclipse中logcat过滤器的使用
logcat里信息繁多,用过滤器可以方便快捷的找到我们要查找的信息. 我们可以在打开Eclipse之后,选择Window –> Show View ->Other菜单,然后在Android ...
- RDMA的基础概念
一张图可以简单明确的说明,目前RDMA的几种技术的差别: RDMA是remote Direct memory access的简称,有几个最基本的特点: CPU offload kernel bypas ...
- 探索未知种族之osg类生物---器官初始化一
我们把ViewerBase::frame()比作osg这类生物的肺,首先我们先来大概的看一下‘肺’长什么样子,有哪几部分组成.在这之前得对一些固定的零件进行说明,例如_done代表osg的viewer ...
- Ubuntu12.04软件安装指南
更新升级源 首先编辑软件源,在终端输入下面命令: sudo gedit /etc/apt/sources.list 较快速的升级源有163,台湾源,科大源,搜狐源等,大家将新的升级源全部覆盖原文件so ...
- LIbreOJ #6011. 「网络流 24 题」运输问题 最小费用最大流
#6011. 「网络流 24 题」运输问题 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据 题目描述 ...
- Spring 属性注入(三)AbstractNestablePropertyAccessor
Spring 属性注入(三)AbstractNestablePropertyAccessor Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117 ...
- Go环境下,编译运行etcd与goreman集群管理(1)
Go环境下编译运行etcd与goreman管理 近几年了Go在比特币.区块链.云服务等相关重要领域贡献突出,作为IT行业的传承“活到老.学到光头”,保持学习心态. 周末放假,补充一二 主题:在Go环境 ...
- spring学习 十八 spring的声明事物
1.编程式事务: 1.1 由程序员编程事务控制代码.commit与rollback都需要程序员决定在哪里调用,例如jdbc中conn.setAutoCimmit(false),conn.commit( ...
- 2019.01.19 codeforces896C.Willem, Chtholly and Seniorious(ODT)
传送门 ODTODTODT出处(万恶之源) 题目简述: 区间赋值 区间加 区间所有数k次方和 区间第k小 思路:直接上ODTODTODT. 不会的点这里 代码: #include<bits/st ...