先上效果图:

android 快速上下滑动列表Glide加载的图片混乱 android上下滑动布局_touch事件监听

android 快速上下滑动列表Glide加载的图片混乱 android上下滑动布局_touch事件监听_02

android 快速上下滑动列表Glide加载的图片混乱 android上下滑动布局_touch事件监听_03

android 快速上下滑动列表Glide加载的图片混乱 android上下滑动布局_touch事件监听_04

上传图片不能超过2M,费了好大劲。每一张gif动的有点快,将就看。

首先说原理:

为activity的xml文件根布局添加setOnTouchListener。上下滑动和左右滑动的所有操作都是在OnTouchListener的onTouch方法中实现的,通过计算上下左右滑动的距离来操作View的。

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


主要代码:

LoginActivity:

mButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
               startActivity(new Intent(this,MainActivity.class));

            }
        });






activity_main:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout

    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/img"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="centerCrop"
        android:src="@drawable/xx"
        android:layout_gravity="center_horizontal"
        />
    <LinearLayout android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:background="@color/colorPrimary"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
    </LinearLayout>
</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();
}
到这就结束了,其实很简单吧。代码写的不完善,比如左右滑时可以向左滑,一些小情况没有判断。写的不对的地方请大家指教,如果帮到你了,