感谢   感觉很有用 ,


首先说原理:

setOnTouchListener。上下滑动和左右滑动的所有操作都是在OnTouchListener的onTouch方法中实现的,通过计算上下左右滑动的距离来操作View的。

一共有两个界面,第一个是LoginActivity,什么用也没有,就是在里面的button,点击跳转到MainActivity.MainActivity是主要的。


主要代码:

LoginActivity:



    1. mButton.setOnClickListener(new OnClickListener() {  
    2. @Override  
    3. public void onClick(View view) {  
    4. new Intent(this,MainActivity.class));  
    5.   
    6.             }  
    7.         });






    activity_main:




    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <LinearLayout  
    3.   
    4. xmlns:android="http://schemas.android.com/apk/res/android"  
    5. xmlns:tools="http://schemas.android.com/tools"  
    6.   
    7. android:id="@+id/activity_main"  
    8. android:layout_width="match_parent"  
    9. android:layout_height="match_parent"  
    10. android:background="#ffffff"  
    11. android:orientation="vertical">  
    12.   
    13. <ImageView  
    14. android:id="@+id/img"  
    15. android:layout_width="match_parent"  
    16. android:layout_height="200dp"  
    17. android:scaleType="centerCrop"  
    18. android:src="@drawable/xx"  
    19. android:layout_gravity="center_horizontal"  
    20. />  
    21. <LinearLayout android:layout_width="match_parent"  
    22. android:layout_height="match_parent"  
    23. android:gravity="center_horizontal"  
    24. android:orientation="vertical"  
    25. android:background="@color/colorPrimary"  
    26. >  
    27. <TextView  
    28. android:layout_width="wrap_content"  
    29. android:layout_height="wrap_content"  
    30. android:textSize="18sp"  
    31. android:text="Hello World!" />  
    32.   
    33. <TextView  
    34. android:layout_width="wrap_content"  
    35. android:layout_height="wrap_content"  
    36. android:textSize="18sp"  
    37. android:text="Hello World!" />  
    38. <TextView  
    39. android:layout_width="wrap_content"  
    40. android:layout_height="wrap_content"  
    41. android:textSize="18sp"  
    42. android:text="Hello World!" />  
    43. <TextView  
    44. android:layout_width="wrap_content"  
    45. android:layout_height="wrap_content"  
    46. android:textSize="18sp"  
    47. android:text="Hello World!" />  
    48. <TextView  
    49. android:layout_width="wrap_content"  
    50. android:layout_height="wrap_content"  
    51. android:textSize="18sp"  
    52. android:text="Hello World!" />  
    53. <TextView  
    54. android:layout_width="wrap_content"  
    55. android:layout_height="wrap_content"  
    56. android:textSize="18sp"  
    57. android:text="Hello World!" />  
    58. <TextView  
    59. android:layout_width="wrap_content"  
    60. android:layout_height="wrap_content"  
    61. android:textSize="18sp"  
    62. android:text="Hello World!" />  
    63. <TextView  
    64. android:layout_width="wrap_content"  
    65. android:layout_height="wrap_content"  
    66. android:textSize="18sp"  
    67. android:text="Hello World!" />  
    68. <TextView  
    69. android:layout_width="wrap_content"  
    70. android:layout_height="wrap_content"  
    71. android:textSize="18sp"  
    72. android:text="Hello World!" />  
    73. <TextView  
    74. android:layout_width="wrap_content"  
    75. android:layout_height="wrap_content"  
    76. android:textSize="18sp"  
    77. android:text="Hello World!" />  
    78. <TextView  
    79. android:layout_width="wrap_content"  
    80. android:layout_height="wrap_content"  
    81. android:textSize="18sp"  
    82. android:text="Hello World!" />  
    83. </LinearLayout>  
    84. </LinearLayout>



    先获取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点,来缩放ImageView
    if (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();
    }