
有了这四个参数,计算View的宽高就很容易了:width = right - left;height = bottom - top。关于View还有两个参数需要我们注意:translationX代表View平移的水平距离,translationY代表View平移的竖直距离;x、y分别为View的左上角的横纵坐标。View若经过了平移,改变的是它的x、y(代表当前View的左上角位置),它的四个位置参数代表了View的原始位置信息,是始终不变的。View在平移的过程中始终满足如下关系:
x = left + translationX; y = top + translationY。
2. 实现View滑动的几种方式
我们在使用View的过程中,经常需要实现View的滑动效果。比如ListView、跟随手指而移动的自定义View等等,前者的滑动效果是SDK为我们提供的,而对于我们自定义View的滑动效果就需要我们自己来实现。下面我们来详细介绍以下实现View滑动的几种方式。
(1)使用scrollTo/scrollBy实现View的滑动
实现滑动的最朴素直接的方式就是使用View类自带的scrollTo/scrollBy方法了。scrollBy方法是滑动指定的位移量,而scrollTo方法是滑动到指定位置。这两个方法的源码如下:
/*** Set the scrolled position of your view. This will cause a call to* {@link #onScrollChanged(int, int, int, int)} and the view will be* invalidated.* @param x the x position to scroll to* @param y the y position to scroll to*/ public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) {int oldX = mScrollX;int oldY = mScrollY;mScrollX = x;mScrollY = y;onScrollChanged(mScrollX, mScrollY, oldX, oldY);if (!awakenScrollBars()) { invalidate();} } } /*** Move the scrolled position of your view. This will cause a call to* {@link #onScrollChanged(int, int, int, int)} and the view will be* invalidated.* @param x the amount of pixels to scroll by horizontally* @param y the amount of pixels to scroll by vertically */ public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); } 通过以上代码的33~35行我们可以看到,scrollBy方法内部也是调用了scrollTo方法来实现。以上源码中我们注意到了mScrollX和mScrollY成员变量,前者是View的左边缘减去View的内容的左边缘,后者是View的上边缘减去View的内容的上边缘。示意图如下:
上图中,黑色边框代表View在屏幕上对应的矩形区域,蓝色边框代表View的内容。在上图中,我们调用scrollTo/scrollBy把View向右滚动了一定距离。实际上,调用scrollBy/scrollTo方法只能实现View的内容的滚动,而View的四个位置参数是保持不变的。想一下我们平常使用ListView时,滚动的就是ListView的内容,而ListView本身在屏幕上的位置是不变的。上图中,黑色左边(即View的左边缘)减去蓝色左边(即View的内容的左边缘)即可得到mScrollX。由此我们还可以知道,向右滚动时mScrollX负的,向左滚动时mScrollX是正的。同理我们可以知道,向下滚动时,mScrollY是负的,向上滚动时,mScrollY是正的。
经过以上的分析,我们了解到使用scrollTo/scrollBy方法实现View的滑动是很简单直接的,那么简单的背后有什么代价呢?代价就是滑动不是“弹性的”,弹性滑动指的是View的滑动应该是一个先加速再逐渐减速到停止的过程,这样看起来很平滑,不会很突兀。scrollTo/scrollBy方法实现的滑动看起来就会很突兀,这样的用户体验很不好。在解决这个问题之前,我们先来看看实现View滑动的其他方法。
(2)使用动画来实现View的滑动
使用动画来实现View的滑动主要通过改变View的translationX和translationY参数来实现,使用动画的好处在于滑动效果是平滑的。上面我们提到过,View的x、y参数决定View的当前位置,通过改变translationX和translationY,我们就可以改变View的当前位置。我们可以使用属性动画或者补间动画来实现View的平移。
首先,我们先来看一下如何使用补间动画来实现View的平移。补间动画资源定义如下(anim.xml):
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true"> <translateandroid:duration="100"android:fromXDelta="0"android:fromYDelta="0"android:interpolator="@android:anim/linear_interpolator"android:toXDelta="100"android:toYDelta="100"/></set>然后在onCreat方法中调用startAnimation方法即可。使用补间动画实现View的滑动有一个缺陷,那就是移动的知识View的“影像”,这意味着其实View并未真正的移动,只是我们看起来它移动了而已。拿Button来举例,假若我们通过补间动画移动了一个Button,我们会发现,在Button的原来位置点击屏幕会出发点击事件,而在移动后的Button上点击不会触发点击事件。
MarginLayoutParams params = (MarginLayoutParams) mButton.getLayoutParams();params.leftMargin += 100;mButton.requestLayout();以上代码即实现了把mButton向右滑动100px。通过改变布局参数来实现的滑动效果也不是平滑的。
Scroller scroller = new Scroller(mContext);private void smoothScrollTo(int dstX, int dstY) { int scrollX = getScrollX(); int delta = dstX - scrollX; scroller.startScroll(scrollX, 0, delta, 0, 1000); invalidate();}@Overridepublic void computeScroll() { if (scroller.computeScrollOffset()) {scrollTo(scroller.getCurrX(), scroller.getCurY());postInvalidate(); }} 我们来看一下以上的代码。第4行中,我们获取到View的mScrollX参数并存到scrollX变量中。然后在第5行计算要滑动的位移量。第6行调用了startScroll方法,我们来看看startScroll方法的源码:public void startScroll(int startX, int startY, int dx, int dy, int duration) {mMode = SCROLL_MODE;mFinished = false;mDuration = duration;mStartTime = AnimationUtils.currentAnimationTimeMillis();mStartX = startX;mStartY = startY;mFinalX = startX + dx;mFinalY = startY + dy;mDeltaX = dx;mDeltaY = dy;mDurationReciprocal = 1.0f / (float) mDuration; mViscousFluidScale = 8.0f; mViscousFluidNormalize = 1.0f;mViscousFluidNormalize = 1.0f / viscousFluid(1.0f); } 从以上的源码我们可以看到,startScroll方法中并没有进行实际的滚动操作,而是把startX、startY、deltaX、deltaY等参数都保存了下来。那么究竟怎么实现View的滑动的呢?我们先回到Scroller惯用代码。我们看到第7行调用了invalidate方法,这个方法会请求重绘View,这会导致View的draw的方法被调用,draw的方法内部会调用computeScroll方法。我们来看看第13行,调用了scrollTo方法,并传入mScroller.getCurrX()和mScroller.getCurrY()方法作为参数。那么获取到的这两个参数是什么呢?这两个参数是在第12行调用的computeScrollOffset方法中设置的,我们来看看这个方法中设置这两个参数的相关代码:public boolean computeScrollOffset() { ... int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime); if (timePassed < mDuration) {switch (mMode) { case SCROLL_MODE:final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);mCurrX = mStartX + Math.round(x * mDeltaX);mCurrY = mStartY + Math.rounc(y * mDeltaY);break;...} } return true;} 以上代码中第8行和第9行设置的mCurrX和mCurrY即为以上scrollTo的两个参数,表示本次滑动的目标位置。computeScrollOffset方法返回true表示滑动过程还未结束,否则表示结束。