平时做开发的时候图片查看肯定是经常碰到的一个功能,在做社交App或内容浏览App的时候,图片查看的关闭方式做的人性化一点,肯定会给用户留下正面的印象。这里是仿照微信的图片关闭方式撸的代码,提供一点思路。

张效果图:
  1. 手指下拉图片,图片会被移动并且缩小,同时背景也有个透明度的变化;

  1. 正常状态按下,往上移动,是不能对图片进行移动的,但是往下拉一下之后就可以上下左右随便移动;

要实现上面的效果,我们需要操作图片查看Activity中的ViewPager的事件获取,所以这里自定义一个ScaleViewPager继承系统的ViewPager。并且把该viewpager的背景设置成黑色,一般图片查看背景都是黑色的。

public class ScaleViewPager extends ViewPager {public ScaleViewPager(Context context) {super(context);init(context);}public ScaleViewPager(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public void init(Context context){setBackgroundColor(Color.BLACK);}
}

当然,必不可少的是给图片查看Activity设置透明背景Theme:

<style name="ImageBrowseTransitionTheme" parent="AppTheme"><item name="android:windowIsTranslucent">true</item><item name="android:windowBackground">@android:color/transparent</item><item name="android:windowTranslucentStatus">true</item><item name="android:windowActivityTransitions">true</item><item name="android:windowSharedElementEnterTransition">@transition/changebounds</item><item name="android:windowSharedElementReturnTransition">@transition/changebounds</item>
</style>

下拉关闭图片的操作,这个MotionEvent是全屏都可以接收到的,而图片的缩放与移动,只是显示的图片自己的缩放与移动,所以我们要重写ViewPager的 OnTouchEvent方法对它进行处理

    @Overridepublic boolean onTouchEvent(MotionEvent ev) {switch (ev.getActionMasked()) {case MotionEvent.ACTION_DOWN:mDownX = ev.getRawX();mDownY = ev.getRawY();//记录按下的位置addIntoVelocity(ev);break;case MotionEvent.ACTION_MOVE:addIntoVelocity(ev);int deltaY = (int) (ev.getRawY() - mDownY);//计算手指移动距离,大于0表示手指往屏幕下方移动if (deltaY <= 0 && currentStatus!=STATUS_MOVING)return super.onTouchEvent(ev);if (deltaY>0||currentStatus==STATUS_MOVING){//如果往下移动,或者目前状态是缩放移动状态,那么传入移动坐标,进行对ImageView的操作setupMoving(ev.getRawX(),ev.getRawY());return super.onTouchEvent(ev);}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:if (currentStatus!=STATUS_MOVING)return super.onTouchEvent(ev);float vY = computeYVelocity();if (vY>=1500||Math.abs(mUpY-mDownY)>screenHeight/4){//速度有一定快,或者竖直方向位移超过屏幕1/4,那么释放//这里可以通过设置接口回调,外部Activity可以finish();}else {setupBack(mUpX,mUpY);}}return super.onTouchEvent(ev);}private void setupMoving(float movingX ,float movingY) {if (currentShowView == null)return;currentStatus = STATUS_MOVING;float deltaX = movingX - mDownX;float deltaY = movingY - mDownY;float scale = 1f;float alphaPercent = 1f;if(deltaY>0) {scale = 1 - Math.abs(deltaY) / screenHeight;alphaPercent = 1- Math.abs(deltaY) / (screenHeight/2);//这里是设置背景的透明度,我这是设置移动屏幕一半高度的距离就全透明了。}ViewHelper.setTranslationX(currentShowView, deltaX);ViewHelper.setTranslationY(currentShowView, deltaY);setupScale(scale);setupBackground(alphaPercent);}private void setupScale(float scale) {scale = Math.min(Math.max(scale, MIN_SCALE_WEIGHT), 1);//MIN_SCALE_WEIGHT是最小可缩小倍数,我这里设置的0.25fViewHelper.setScaleX(currentShowView, scale);ViewHelper.setScaleY(currentShowView, scale);}private void setupBackground(float percent){setBackgroundColor(convertPercentToBlackAlphaColor(percent));}//把0~1这透明度转换成相应的黑色背景透明度,应该有更好的方式private int convertPercentToBlackAlphaColor(float percent){percent = Math.min(1, Math.max(0,percent));int intAlpha = (int) (percent*255);String stringAlpha = Integer.toHexString(intAlpha).toLowerCase();String color = "#"+(stringAlpha.length()<2?"0":"")+stringAlpha+"000000";return Color.parseColor(color);}private void setupBack(final float mUpX, final float mUpY){currentStatus = STATUS_BACK;if (mUpY!=mDownY) {ValueAnimator valueAnimator = ValueAnimator.ofFloat(mUpY, mDownY);valueAnimator.setDuration(BACK_DURATION);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float mY = (float) animation.getAnimatedValue();float percent = (mY - mDownY) / (mUpY - mDownY);float mX = percent * (mUpX - mDownX) + mDownX;setupMoving(mX, mY);if (mY == mDownY) {mDownY = 0;mDownX = 0;currentStatus = STATUS_NORMAL;}}});valueAnimator.start();}else if (mUpX!=mDownX){ValueAnimator valueAnimator = ValueAnimator.ofFloat(mUpX, mDownX);valueAnimator.setDuration(BACK_DURATION);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float mX = (float) animation.getAnimatedValue();float percent = (mX - mDownX) / (mUpX - mDownX);float mY = percent * (mUpY - mDownY) + mDownY;setupMoving(mX, mY);if (mX == mDownX) {mDownY = 0;mDownX = 0;currentStatus = STATUS_NORMAL;}}});valueAnimator.start();}else {//按下点的x,y值跟松开点的x,y值一样,可以说明是点击事件。}}private void addIntoVelocity(MotionEvent event){if (mVelocityTracker==null)mVelocityTracker = VelocityTracker.obtain();mVelocityTracker.addMovement(event);}private float computeYVelocity(){float result = 0;if (mVelocityTracker!=null){mVelocityTracker.computeCurrentVelocity(1000);result = mVelocityTracker.getYVelocity();releaseVelocity();}return result;}private void releaseVelocity(){if (mVelocityTracker!=null){mVelocityTracker.clear();mVelocityTracker.recycle();mVelocityTracker = null;}}

这里获得触摸位置的坐标一定要使用:

MotionEvent.getRawX()
MotionEvent.getRawY()

如果使用:

MotionEvent.getX()
MotionEvent.getY()

会出现坐标闪动很严重的问题,不明白的修改下自己运行看看就知道了。关于ViewHelper这个类大家应该都知道的是一个view的操作库,引用方式为在build.gradle中加入:

compile 'com.nineoldandroids:library:2.4.0'

setupBack()方法是对手势放开之后,图片归位时的一些操作,简单地利用valueAnimator,通过传入按下点的mDownY值和放开点的mUpY值,模拟手势移动,在动画中不断调用setupMoving(x,y)方法来产生图片归位的现象,这里有个mUpX!=mDownX和mUpY!=mDownY的判断,是为了避免极限情况下比如只有竖直方向位移或者只有横向位移导致动画不正常。

使用ScaleViewPager的时候,在外部Activity或其他地方,每当ScaleViewPager切换了一张图片,那就需要给viewpager再次设置要操作的view。

    /*****你的图片查看Activity.java*****/scaleViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {View imageView = 根据position获得view;scaleViewPager.setCurrentShowView(imageView);}@Overridepublic void onPageSelected(int position) {newPageSelected = true;}@Overridepublic void onPageScrollStateChanged(int state) {}});/*****ScaleViewPager.java******/View currentShowView;public void setCurrentShowView(View currentShowView) {this.currentShowView = currentShowView;}

好了,到这里我们的关闭代码就完成了,可以运行下看看效果,看上去能正常工作。但是我们的图片浏览不可能这个viewpager只有一个item,当我们左右滑动的时候可以发现,我下拉的时候图片被缩小,此时我还未松手,此时往左或往右移动,会发现在当前图片缩放的同事,viewpager也跟着左右切换(drag)了,并且当我们在正常展示状态下左右滑动(drag)想切换图片时,当前的这个图片不合时宜的响应了我们的手势在进行缩放。这些现象是因为触摸事件冲突了,要解决这个问题很简单,首先,修改我们onTouchEvent中ACTION_MOVE事件的处理,当我们在move的时候,让viewpager不要动,下面是修改后的代码:

           case MotionEvent.ACTION_MOVE:addIntoVelocity(ev);int deltaY = (int) (ev.getRawY() - mDownY);//这个地方从原来的<=0改成了DRAG_GAP_PX,目前DRAG_GAP_PX=50,意思是imageView响应缩放的手势移动阈值,只有Y轴先移动了50px才能响应缩放,这个可以看情况改if (deltaY <= DRAG_GAP_PX && currentStatus!=STATUS_MOVING)return super.onTouchEvent(ev);//这个currentPageStatus在下面解释if (currentPageStatus!=SCROLL_STATE_DRAGGING && (deltaY>DRAG_GAP_PX||currentStatus==STATUS_MOVING)){setupMoving(ev.getRawX(),ev.getRawY());return true;//把这里改为true就好了,true代表scaleviewpager已经消耗了touch事件,不调用super就不会调用到viewpager中的代码,viewpager此时也就不会进行左右滑动了}break;

解决了缩放时viewpager不要滑动,我们还需要解决viewpager滑动时,当前view不要缩放,这就需要我们给viewpager额外设置滑动监听,修改ScaleViewPager.init()方法如下:

 public void init(Context context){setBackgroundColor(Color.BLACK);addOnPageChangeListener(new OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Overridepublic void onPageSelected(int position) {}@Overridepublic void onPageScrollStateChanged(int state) {currentPageStatus = state;}});}

通过监听viewpager,记录它的状态,结合上面修改过的ACTION_MOVE中的代码:

currentPageStatus!=SCROLL_STATE_DRAGGING

使得当viewpager滚动时,不调用setupMoving来解决冲突的问题。

下面放个完整代码:

public class ScaleViewPager extends ViewPager {public static final int STATUS_NORMAL = 0;public static final int STATUS_MOVING = 1;public static final int STATUS_BACK = 2;public static final String TAG = "ScaleViewPager";//最多可缩小比例public static final float MIN_SCALE_WEIGHT = 0.25f;public static final int BACK_DURATION = 300;//mspublic static final int DRAG_GAP_PX = 50;private int currentStatus = STATUS_NORMAL;private int currentPageStatus;float mDownX;float mDownY;float screenHeight = ScreenUtil.getDisplayHeight();View currentShowView;private VelocityTracker mVelocityTracker;IPictureDrag iPictureDrag;public void setiPictureDrag(IPictureDrag iPictureDrag) {this.iPictureDrag = iPictureDrag;}public ScaleViewPager(Context context) {super(context);init(context);}public ScaleViewPager(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public void init(Context context){setBackgroundColor(Color.BLACK);addOnPageChangeListener(new OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {Log.e(TAG,"in onPageScrolled positionOffset:"+positionOffset+" positionOffsetPixels:"+positionOffsetPixels);}@Overridepublic void onPageSelected(int position) {}@Overridepublic void onPageScrollStateChanged(int state) {currentPageStatus = state;}});}public void setCurrentShowView(View currentShowView) {this.currentShowView = currentShowView;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (currentStatus == STATUS_BACK)return false;switch (ev.getActionMasked()) {case MotionEvent.ACTION_DOWN:mDownX = ev.getRawX();mDownY = ev.getRawY();addIntoVelocity(ev);break;case MotionEvent.ACTION_MOVE:addIntoVelocity(ev);int deltaY = (int) (ev.getRawY() - mDownY);if (deltaY <= DRAG_GAP_PX && currentStatus!=STATUS_MOVING)return super.onTouchEvent(ev);if (currentPageStatus!=SCROLL_STATE_DRAGGING && (deltaY>DRAG_GAP_PX||currentStatus==STATUS_MOVING)){setupMoving(ev.getRawX(),ev.getRawY());return true;}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:if (currentStatus!=STATUS_MOVING)return super.onTouchEvent(ev);final float mUpX = ev.getRawX();//->mDownXfinal float mUpY = ev.getRawY();//->mDownYfloat vY = computeYVelocity();if (vY>=1500||Math.abs(mUpY-mDownY)>screenHeight/4){//速度有一定快,或者移动位置超过屏幕一半,那么释放if (iPictureDrag!=null)iPictureDrag.onPictureRelease(currentShowView);}else {setupBack(mUpX,mUpY);}break;}return super.onTouchEvent(ev);}private void setupBack(final float mUpX, final float mUpY){currentStatus = STATUS_BACK;if (mUpY!=mDownY) {ValueAnimator valueAnimator = ValueAnimator.ofFloat(mUpY, mDownY);valueAnimator.setDuration(BACK_DURATION);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float mY = (float) animation.getAnimatedValue();float percent = (mY - mDownY) / (mUpY - mDownY);float mX = percent * (mUpX - mDownX) + mDownX;setupMoving(mX, mY);if (mY == mDownY) {mDownY = 0;mDownX = 0;currentStatus = STATUS_NORMAL;}}});valueAnimator.start();}else if (mUpX!=mDownX){ValueAnimator valueAnimator = ValueAnimator.ofFloat(mUpX, mDownX);valueAnimator.setDuration(BACK_DURATION);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float mX = (float) animation.getAnimatedValue();float percent = (mX - mDownX) / (mUpX - mDownX);float mY = percent * (mUpY - mDownY) + mDownY;setupMoving(mX, mY);if (mX == mDownX) {mDownY = 0;mDownX = 0;currentStatus = STATUS_NORMAL;}}});valueAnimator.start();}else if (iPictureDrag!=null)iPictureDrag.onPictureClick();}private void setupMoving(float movingX ,float movingY) {if (currentShowView == null)return;currentStatus = STATUS_MOVING;float deltaX = movingX - mDownX;float deltaY = movingY - mDownY;float scale = 1f;float alphaPercent = 1f;if(deltaY>0) {scale = 1 - Math.abs(deltaY) / screenHeight;alphaPercent = 1- Math.abs(deltaY) / (screenHeight/2);}ViewHelper.setTranslationX(currentShowView, deltaX);ViewHelper.setTranslationY(currentShowView, deltaY);setupScale(scale);setupBackground(alphaPercent);}private void setupScale(float scale) {scale = Math.min(Math.max(scale, MIN_SCALE_WEIGHT), 1);ViewHelper.setScaleX(currentShowView, scale);ViewHelper.setScaleY(currentShowView, scale);}private void setupBackground(float percent){setBackgroundColor(convertPercentToBlackAlphaColor(percent));}private int convertPercentToBlackAlphaColor(float percent){percent = Math.min(1, Math.max(0,percent));int intAlpha = (int) (percent*255);String stringAlpha = Integer.toHexString(intAlpha).toLowerCase();String color = "#"+(stringAlpha.length()<2?"0":"")+stringAlpha+"000000";return Color.parseColor(color);}private void addIntoVelocity(MotionEvent event){if (mVelocityTracker==null)mVelocityTracker = VelocityTracker.obtain();mVelocityTracker.addMovement(event);}private float computeYVelocity(){float result = 0;if (mVelocityTracker!=null){mVelocityTracker.computeCurrentVelocity(1000);result = mVelocityTracker.getYVelocity();releaseVelocity();}return result;}private void releaseVelocity(){if (mVelocityTracker!=null){mVelocityTracker.clear();mVelocityTracker.recycle();mVelocityTracker = null;}}
}

源码地址:

https://github.com/sbLaughing/DragCloseDemo1

Android类似微信图片查看下拉缩放并移动相关推荐

  1. android中点击头像放大,Android头像下拉缩放动效

    头像下拉缩放动效 头像下拉缩放这个在IOS中很常见,最近在Github上也看到了类似的效果,所以决定把它集成到我现在做的项目中去. Github上的开源地址:https://github.com/Fr ...

  2. 打造Android微信朋友圈下拉刷新控件

    打造Android微信朋友圈下拉刷新控件> 转载于:https://www.cnblogs.com/zhujiabin/p/5707789.html

  3. android 自定义Scrollview实现淘宝二层楼效果新版微信小程序下拉效果

    android 自定义Scrollview实现淘宝二层楼效果新版微信小程序下拉效果 由于最近一段时间真的是太忙了,没有顾上即使更新博客,还请粉丝们见谅,最近要实现这样一个效果,这个效果跟淘宝二层楼和新 ...

  4. android 点击图片动画效果,Android仿微信图片点击全屏效果

    废话不多说,先看下Android图片点击全屏效果: 先是微信的 再是模仿的 先说下实现原理,再一步步分析 这里总共有2个Activity一个就是主页,一个就是显示我们图片效果的页面,参数通过Inten ...

  5. 微信小程序下拉框插件_微信小程序下拉框组件使用方法详解

    本文实例为大家分享了微信小程序下拉框组件的使用方法,供大家参考,具体内容如下 适用场景 1.省市三级联动 2.出生日期选择 3.性别选择 4.一般性的下拉选择等 一.省市三级联动使用 注意mode = ...

  6. Android 仿微信图片选择器

    版权声明:本文为博主原创文章,未经博主允许不得转载. 1.自我介绍 这是我写的第一篇博客,首先做一下自我介绍,我去年刚毕业,大学学的是计算机专业,期间也学了一门Android相关课程,但是你懂的,一个 ...

  7. 下拉多选框 微信小程序_微信小程序下拉框组件使用方法详解

    本文实例为大家分享了微信小程序下拉框组件的使用方法,供大家参考,具体内容如下 适用场景 1.省市三级联动 2.出生日期选择 3.性别选择 4.一般性的下拉选择等 一.省市三级联动使用 注意mode = ...

  8. Android自定义控件:仿美团下拉菜单及相关代码优化

    背景 最近的项目中用到了类似美团中的下拉多选菜单,在实际开发过程中,也发现了一些问题,主要归纳如下: 1.当菜单较为复杂时,如果不能设计好代码逻辑,将造成控件难于维护 2.美团菜单可以连续点击顶部ta ...

  9. 下拉多选框 微信小程序_微信小程序下拉框组件使用方法

    这篇文章主要为大家详细介绍了微信小程序下拉框组件的使用方法,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参考学习下.如有不足之处,欢迎批评指正.[推荐教程:小程序开发教程] 适用场景 ...

  10. html下拉刷新原理,微信小程序 下拉刷新及上拉加载原理解析

    这篇文章主要介绍了微信小程序 下拉刷新及上拉加载实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.下拉刷新的概念及应用场景. 概念: 下拉 ...

最新文章

  1. AssetBundle——外部加载资源Asset
  2. 数据中心柴油发电机组功率有哪几种?
  3. R:matlab交互,数据调用
  4. 线段树空间容纳且最上边的数(单点更新)
  5. 字符函数和内存函数模拟实现
  6. 主产品清单位于oracle,OPatch failed with error code 73(OracleHomeInventory gets null oracleHomeInfo)...
  7. php如何与数据库连接,PHP文章如何和数据库连接(1)
  8. 2017.9.5 DZY Loves Math 失败总结
  9. 关于技嘉主板使用win10操作系统关机自动重启的一种解决办法。其他厂家主板也可以尝试一下此方法。
  10. Exchange2003中只键入“http://服务器”来名访问OWA
  11. 大学计算机aoa学什么,浙江省高校计算机二级AOA考试excel试题及解析.xls
  12. DP(Nietzsche)的hu测 T1(状压dp)
  13. c jave等语言作用,编程语言的前世今生,看 Java、C、C++ 等语言的演变
  14. vue图片时间轴滑动_响应式垂直时间轴组件– vuetimeline
  15. html中如何实现倒计时
  16. centos7扫描新硬盘_跟大家讲讲硬盘基础知识
  17. HbuildX打包AndroidAPP使用教程
  18. 用python爬取网站_「自如网」关于用python爬取自如网信息的价格问题(已解决) - seo实验室...
  19. 面试题:MySQL优化
  20. 电脑被攻击的文件该如何恢复

热门文章

  1. 域名注册很复杂吗?一般去哪注册?
  2. 达芬奇密码 第七十六章
  3. 学习日语必须要掌握的日本文化基础知识14
  4. 山东计算机网络大赛,我院获全省技能大赛“计算机网络应用”赛项第一名
  5. 自媒体文章可以多平台分发吗?
  6. 武当山自由行--你的不二之选
  7. java这一年第几天_输入日期判断是这一年的第几天(JAVA)
  8. 三星市场上传应用问题
  9. 号称下一代监控系统,来看看它有多强
  10. (转)Java中的String为什么是不可变的? -- String源码分析