感谢 http://blog.youkuaiyun.com/xx23x/article/details/54617928 感觉很有用 ,转载记录一下,如有侵权请告知
首先说原理:
为activity的xml文件根布局添加setOnTouchListener。上下滑动和左右滑动的所有操作都是在OnTouchListener的onTouch方法中实现的,通过计算上下左右滑动的距离来操作View的。
一共有两个界面,第一个是LoginActivity,什么用也没有,就是在里面的button,点击跳转到MainActivity.MainActivity是主要的。
主要代码:
LoginActivity:
activity_main:
先获取activity_main.xml的根布局View,然后为它添加Touch事件监听,之后所有的操作都会在其中进行。
root_ll = (LinearLayout) findViewById(R.id.activity_main);
root_ll.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //代码在这里 return true; } });
注意ontouch方法一定要返回true。
先说上下滑动缩放图片:
分为两部分,
下滑时:图片会放大,宽度和高度都会变化,并且图片和下面的helloworld区域存在一种类似图层叠加透视的关系,仔细看效果图,图片和下面的区域并不是像铺地板一样,一块和另一块紧挨在一起。而是随着图片放大,这两部分看起来像两层,就好像下面的区域是覆盖在图片上一样。
上滑时,图片没有缩放,并且高度变小,宽度保持不变。
布局文件:
关于图片的缩放,只要在布局文件中设置android:scaleType="centerCrop"既可。这样我们在缩放的时候只要控制ImageView的宽高度就可以,图片会自动追随ImageView的变化而放大缩小。布局到这就结束了,回到代码。第一步,得到ImageView正常时的宽高度:
ImageView img; int width; int height;img = (ImageView) findViewById(R.id.img); ViewTreeObserver viewTreeObserver=img.getViewTreeObserver(); viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) @Override public void onGlobalLayout() { width = img.getMeasuredWidth(); height = img.getMeasuredHeight(); img.getViewTreeObserver().removeOnGlobalLayoutListener(this); } });应该知道这里不能通过img.getWidth()方法获得吧。因为这时候图片还没有绘制出来,得到的是0.img.getViewTreeObserver().removeOnGlobalLayoutListener(this);这一局要有,是在得到宽高度之后取消监听,不然之后每当img属性发生变化时都会执行这个方法。第二部,在onTouch中开始操作:
整个触摸屏幕的过程分为down,move,up三个阶段。在down的时候得到参考点:if (event.getAction()==MotionEvent.ACTION_DOWN) { starty = event.getY(); startx = event.getX(); }然后在滑动的时候实时获取x,y点,来缩放ImageViewif (event.getAction()==MotionEvent.ACTION_MOVE) { tw = (int) (width * ((event.getY() - starty) / 1000 + 1)); th = (int) (height * ((event.getY() - starty) / 1600 + 1)); if (th<=0) { th=1; } if (th < height) { layoutParams.width = width; layoutParams.height = th; } else { layoutParams.width = tw; layoutParams.height = th; } img.setLayoutParams(layoutParams); }这里是根据上下滑动的距离来缩放,所以(event.getY() - starty) / 1000是得到上下滑动的距离,除以1000,只是降低一下幅度而已,也可以换成其它的数值,这样得到的tw就是width放大了多少倍。特别需要注意,
th = (int) (height * ((event.getY() - starty) / 1600 + 1));这里高度除以的是1600,要比1000大,正是这个不一样实现了图片看起来是图层叠加的效果,如果我没有说清的话,你可以把高度也设置成除以1000.看看不同就明白了。
获得到缩放后的宽高后,和图片的原始高度比较一下,if (th < height)就知道是上滑还是下滑,然后设置一下宽高度就可以了。
第三步,抬起手指时的回弹效果:
if (event.getAction()==MotionEvent.ACTION_UP) { if (th < height / 2) { back2Origin(width, th, width, 1); } else if (th < height) { back2Origin(width, th, width, height); } else { back2Origin(tw, th, width, height); }这里也有一个简单的判断,如果此时高度小于原始高度一半了,那就让高度渐变到1,如果不是就回弹回原来的高度,这样就可以实现上滑下滑的动画效果,我觉得这里的功臣就是android:scaleType="centerCrop"这行。解决了图层叠加,遮挡的这种效果问题。回弹,我用到的是属性动画,后面拿出来。
这样,上下滑动的效果就实现了。
左右滑动:
第一步,设置activity主题为透明:
左右滑动时我们会看到下面露出了之前的activity的界面,这是因为设置了当前的activity主题为透明,然后通过设置TranslutionX来实现左右滑动效果。
自定义一个透明样式:
在styles.xml文件中
<style name="myTransparent" parent="@style/AppTheme"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowNoTitle">true</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item> <item name="windowNoTitle">true</item> </style>在manifest文件中为MainActivity使用自定义的样式:<activity android:name=".MainActivity" android:theme="@style/myTransparent" > </activity>设置完毕,回到onTouch代码中。
第二步,实现左右滑动:
if (event.getAction()==MotionEvent.ACTION_MOVE) {root_ll.setTranslationX(currentTranslationX+(event.getX() - startx)/2f);}(event.getX() - startx)/2f是得到横向的滑动距离,也是降低了一下幅度,currentTranslationX是root_ll当前的偏移数,就这么简单的一设置,就可以实现activity左右不停偏移,露出之前的activity了。此时还没有回弹效果,这里也是用到了属性动画。一会说。
这里还有一个小问题,看图,顶部的状态栏并没有随着一起运动,体验很差。
}这就涉及到一个问题,就是我们为activity setContentView的xml并不是activity的全部,这只是activity布局的一个子View而已,具体可以搜索详解。所以这里我们要得到activity真正的加载View,View root=this.getWindow().getDecorView();这个root就可以了。第三步,添加回弹效果:
if (event.getAction()==MotionEvent.ACTION_UP) { currentTranslationX = root.getTranslationX(); handleTranslationX(); }handleTranslationX()方法来处理的回弹动画,这样左右滑动也监听实现了。
组合:
单个实现上下滑或左右简单,但是组装在onTouch方法中,就会有问题了,我们可以简单的判断一下:if (event.getAction()==MotionEvent.ACTION_MOVE) { if (Math.abs(event.getY() - starty )>=Math.abs( event.getX() - startx)) { vertical=true; } else { vertical=false; }if (vertical) { Log.i("xx", "" + event.getY()); tw = (int) (width * ((event.getY() - starty) / 1000 + 1)); th = (int) (height * ((event.getY() - starty) / 1600 + 1)); if (th<=0) { th=1; } if (th < height) { layoutParams.width = width; layoutParams.height = th; } else { layoutParams.width = tw; layoutParams.height = th; } img.setLayoutParams(layoutParams); } else { root.setTranslationX(currentTranslationX+(event.getX() - startx)/2f); }vertical:boolean值,表示上下滑动,先根据方向的距离大小判断出是横向还是纵向,然后根据virtical来分离操作。这样会有问题的,可以实验一下,会发现因为move是在不断调用的,所有即使一开始是左右滑,一旦手势上下了virtical也会被置true,就会导致一会执行左右滑,一会执行上下滑。所以我们需要一个类似网络会话中session的东西,来告诉ontouch,一旦开始时判断出是哪个方向,就在这次不断的滑动当中,始终忽略另一个方向的滑动,直到抬起手指,这次滑动结束。
所以添加另一个boolean值thisTime判断:if (event.getAction()==MotionEvent.ACTION_MOVE) { if (!thisTime) { thisTime=true; if (Math.abs(event.getY() - starty )>=Math.abs( event.getX() - startx)) { vertical=true; } else { vertical=false; } }这一段代码,一旦判断出virtical的值,同时thisTime=true,这样在之后的滑动当中就不会再执行判断virtical的代码,virtical值就一直不会改变了。解决了一会上下滑 一会左右滑的问题。直到抬起手指:if (event.getAction()==MotionEvent.ACTION_UP) { thisTime=false;表示这个滑动结束了,这样当下次再按下手指滑动时,就又会重新判断一次virtical,一次新的单向滑动开始了。
回弹动画:
用的是属性动画中的ValueAnimator。我就直接贴代码了。解释在注释里。具体的属性动画详情也可以搜索。/** * * @param fromw 起始宽度 * @param fromh 起始高度 * @param tow 结束宽度 * @param toh 结束高度 */ public void back2Origin(final int fromw, final int fromh, final int tow, int toh) { final int ws=tow-fromw; final int hs=toh-fromh;//创建一个ValueAnimator,让他的值从0变化到100. ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);//为valueAnimator添加一个事件监听,每次数值变化时都会执行这个方法。 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) {//animation.getAnimatedValue(),取得此时的数值(0-100) layoutParams.height=fromh+(int)((int)animation.getAnimatedValue()/100f*hs); layoutParams.width=fromw+(int)((int)animation.getAnimatedValue()/100f*ws); img.setLayoutParams(layoutParams); } });//设置这个动画的时常 valueAnimator.setDuration(500);
//开始执行动画 valueAnimator.start(); } /** * 这里判断右滑的距离,如果超过宽度四分之一了,就向右滑到底,并结束 * 如果不是就回弹 */ public void handleTranslationX() { Point p = new Point();//取得屏幕的宽度,存放在point中 getWindowManager().getDefaultDisplay().getSize(p); if (Math.abs(currentTranslationX) > p.x / 4f) { slideWithFinish(p.x); } else { slideWithFinish(0f); } } /** * 左右滑动的动画 * @param endX 结束时的偏移量 */ public void slideWithFinish(final float endX) { ValueAnimator valueAnimator = ValueAnimator.ofFloat(currentTranslationX, endX); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { root.setTranslationX((float)animation.getAnimatedValue());//这里判断一下动画结束时是否是finish当前activity if ((float)animation.getAnimatedValue()==endX) { if (endX > currentTranslationX) { MainActivity.this.finish(); } else { currentTranslationX = endX; } } } }); valueAnimator.setDuration(500); valueAnimator.start(); }