Android手机因为屏幕小所以为了呈现更多的内容就要选择使用滑动来显示和隐藏一些内容,而如果想要做出绚丽的自定义控件也是避不开实现View的滑动的。
实现View的滑动可以通过以下三种方式:
(一) 通过View本身提供的scrollTo和scrollBy方法实现滑动
这两个方法是View提供专门用于实现滑动的,但上一篇博文说过它有一个缺点就是他的滑动是非弹性的。
scrollTo有一句关键代码是:onScrollChanged(mScrollX,mScrollY,oldX,oldY);这说明scrollTo实现的是基于所传参数的绝对滑动。
scrollBy只有一句方法体:scrollTo(mScrollX+x,mScrollY+y);scrollBy调用了scrollTo方法,实现了基于当前位置的相对滑动。所以从他们的方法名大概也能看出确实是这样,to:到达,by:经过。
mScrollX和mScrollY是View的两个内部属性,下面要重点讲一下他们是什么以及改变规律。结合上一篇文章讲的“视图坐标”,它不受物理屏幕限制,可以延伸到无限远。我们也说了View有几个坐标属性,比如left,top等他们是相对于父容器而言的,比如下图所示,以父容器左上角建立坐标系称为”布局坐标”,他是有边界的,超过了该显示区域的子View将不能显示到父视图的区域中。所以说这个View在父容器中的移动也是有边界的,我们称之为View边缘,把View称作父容器的内容,而父容器也是一个View。所以就可以明白了一个View可以有若干内容,scrollTo和scrollBy改变的是某一个内容的位置,View在布局中的位置是不变的。当View左边缘在View内容左边缘的右侧时,scrollX为正,当View上边缘在View内容上边缘的下侧时,scrollY为正。所以如果从左向右滑动,scrollX为负;从上向下滑动,scrollY为正。
(二) 通过动画给View施加平移效果来实现动画
使用动画来移动View,主要操作的是View的translationX和tranlationY,这里既可以采取传统的View动画,也可以采用属性动画。但是要很.注意,使用属性动画的时候,为了兼容Android3.0以下的版本,要采用开源动画库nineoldandroid。
采用View动画需要定义在res下的anim文件夹下,格式是xml文件。如下所示,这个动画实现了100ms内将一个View向右下角移动100像素:
<?xml version="1.0" encoding="utf-8"?>
<setxmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:zAdjustment="normal" >
<translate
android:duration="100"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="100"
android:toYDelta="100" />
</set>
如果采用属性动画直接定义在java代码中,下面的代码实现了一个View在100ms内向右移平移100像素:
ObjectAnimator.ofFloat(targetView,”translationX”,0,100).setDuration(100).start();
关于动画无法三言两语说清楚的,会在以后的博文中更新讲解Android的动画机制。
使用View动画实现滑动时要注意两点:
(1)如果希望动画结束后View的状态得到保留而不是自动回到原处,需要将fillAfter属性设为true,否则动画完成后View会自动恢复到动画前的状态。
(2)View动画只是对View影像的移动,但View的实体还停留在原处。比如我们移动了Button后点击新位置却没有响应,但是原来的位置虽然看不到Button但点击后依然正常响应。也就是说新位置上只是View的影像,所以我们不能简单的给一个View做平移还需要他在新位置上能够继续被触发一系列监听。
Android3.0以后就可以使用属性动画解决上面的两个问题了,除此之外还有一个解决办法:我们可以预先在新位置创建一个和原View一模一样的View,而且连监听也一样,当目标View完成平移动画后就把目标View隐藏,然后把预先设置的View显示出来。
(三)通过改变View的LayoutParams使得View重新布局实现滑动
第三种方法是改变布局参数,即改变LayoutParams。
比如我们要把一个button向右平移100px,我们可以把他的LayoutParams中的marginLeft增加100px即可;也可以采用下面的方式(假设Button的父容器是水平方向的LinearLayout),我们可以事先在Button左边放置一个空View,默认宽度是0,当我们滑动结束的时候,重新设置View的宽度,当空View宽度增大时,Button自然被挤到了右边。那么如何设置一个View的LayoutParames呢?如下所示:
MarginLayoutParams params =(MarginLayoutParams)button.getLayoutParams();
params.width += 100;
params.leftMargin +=100;
button.requestLayout();
//或者button.setLayoutParams(params);
(四)各种滑动方式对比
先看scrollTo和scrollBy,前面已经说过,他们是View提供的原生方法,用它可以方便的实现滑动效果而且不影响内部元素的监听事件。但他的缺点是只能滑动View的内容,不能滑动View本身。所以他适用于对View内容的滑动。
在Android3.0以上使用属性动画没有明显的缺点。但是使用View动画或者Android3.0以下使用属性动画,均不能改变View实体的属性,这在前面说过,但是如果没有在View上添加监听,使用它也是不错的。因为很多复杂的效果必须使用动画才能实现。
改变布局参数的方式没有明显的缺点,主要适用于一些具有交互性的View。
下面是一个实现跟手滑动的自定义View的关键代码,只需要在onTouchEvent中处理ACTION_MOVE事件即可。
public boolean onTouchEvent(MotionEventevent) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "move, deltaX:" +deltaX + " deltaY:" + deltaY);
int translationX =(int)ViewHelper.getTranslationX(this) + deltaX;
int translationY =(int)ViewHelper.getTranslationY(this) + deltaY;
ViewHelper.setTranslationX(this,translationX);
ViewHelper.setTranslationY(this,translationY);
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
在上面的代码中先通过getRawX和getRawY获取手指当前坐标,其次得到两次滑动之间的位移,移动的时候采用动画兼容库nineoldandroids中ViewHelper提供的setTranslationX 和setTranslationY。View中也有这两个方法,但是View的这两个方法只能兼容Android3.0及其以上的版本。此外还有一系列方法,不在一一列举。