先上效果图:
上传图片不能超过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();
}
到这就结束了,其实很简单吧。代码写的不完善,比如左右滑时可以向左滑,一些小情况没有判断。写的不对的地方请大家指教,如果帮到你了,