0%

View事件体系之滑动学习心得

最近学习《Android开发艺术探索》的View事件体系,对View有了跟多的理解和心得体会,遂记录,加深理解也便同人交流。

View一般有三种实现方式,ScrollTo/ScrollBy实现动画实现改变布局参数实现。注意其中除了动画以外,其余两种都是非弹性的滑动,即是“瞬移”。

ScrollTo/ScrollBy实现

这是View内部的两个方法,主要区别就是绝对滑动和相对滑动。即

ScrollTo(),基于所传递参数的绝对滑动
ScrollBy(),基于当前位置的相对滑动

这是书中的原话,但是我一开始却没有理解对“绝对滑动”。所谓相对滑动指的是相对于当前位置的滑动,所谓绝对滑动指的是content在View中的绝对位置,而非我一开始理解的相对于屏幕的绝对位置。关于这一点不理解的话,后边会有一个小例(keng)子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 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;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}

/**
* 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);
}

由源码很明显看出,ScrollBy()就是直接调用了ScrollTo(),ScrollTo()中就只是记录了一下当前滑动的坐标然后对View进行了重绘(postInvalidateOnAnimation中触发重绘)。且在重绘之前还会调用 onScrollChanged() 方法。
关于其中mScrollX,和mScrollY属性,直接画了一个示意图方便理解。
Content-View

关于ScrollTo方法十分需要注意的一点:
ScrollTo()方法只能对View中的Content进行操作移动而不能移动View的位置
View中的Content,例如TextView的Content即为文本,ImageView的Content即为Drawable。

动画实现

这种实现在效果上较于其他两种有所不同,动画可以通过设置移动时间实现弹性滑动的效果。动画在实质上其实是对translationX/translationY进行操作。

View动画

View动画需要在res下建立anim文件夹,通过xml文件定义动画形式。
由于View动画实际上是对View的影像进行操作,因此在使用上有许多需要注意的地方

  • 需要指定 android:fillAfter=”true”,否则动画完成以后其结果会消失,回到原状。
  • 由于操作的是影像,所以不能真正改变View的宽高。因此动画结束后,新的位置存在只是一个影像,View真正的位置其实还在原始位置。这在用户交互上显然是是一个很要命的问题,例如对一个设置了点击事件的Button进行了view动画,将无法在影响上响应点击事件,而是还要 回到原始位置触发

属性动画

属性动画则是直接在代码中对View进行动画操作。

1
2
// 对translationX进行操作 0->100
ObjectAnimator.ofFloat(tatrgetView,"translationX", 0, 100).setDuration(100).start();

android3.0以前,View动画需要使用nineoldandroids库实现,但其实现的属性动画,实质上还是View动画,因此也具有View动画的缺点。但Android3.0以上的属性动画则可以解决以上的所有问题。

《艺术探索》提供的解决(准确来说是规避)View动画缺点的参考方案

在终点位置设置一个与原始View外观与点击事件都相同的View,动画结束以后通过visibility显示。

改变布局参数实现

改变布局参数即设置LayoutParams。一般有两种情形,一是直接设置目标View的LayoutParams(margin)实现滑动,二是改变旁边布局的Layoutparams(width)实现滑动,而实际上View是被“挤开”的。书中也介绍了两种重新设置Layoutparams的方法。

1
2
3
4
// 我一般使用
view.setLayoutParams(lp);
// 涨见识
view.requestLayout();

一个小坑

学习了相关知识以后,我决定写一个小demo检验+加深一下学习成果。

1
2
3
4
5
6
7
8
9
10
11
12
btn_by.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator.ofFloat(viewP, "translationX", 0, 300).setDuration(1000).start();
}
});
btn_to.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewP.scrollTo(-100, -600);
}
});

逻辑十分简单,就是两个View用来滑动,为了体现滑动前后与用户的交互情况,给他们设置了点击事件并通过点击事件让自己滑动。其中viewP指的是两个view(Button)的父布局(LinearLayout),即两个view时viewP的content。
一开始一切实现效果都在我的预计之中,但当我将点击代码改为以上代码时,出现了与我想象完全不同的效果
演示1
首先btn_to使用ScrollTo(),不应该两次移动到两个不同的位置,其次btn_by并不是按动画中设置的从0位置出发。纠结半天后,设置了背景色,问题就无处遁形了。
演示2
可以很明显的发现,btn_by点击以后,动画是作用在整个viewP上的,所以点击btn_to之后,没有移动或者移动到指定位置为右边并没有问题,因为btn_to相对于其父布局来说确实是在指定位置了。
虽然这只是一个小乌龙,但提示了我两个问题:

  • 只有ScrollTo()方法是作用在content上的,动画和LayoutParams都能直接作用于View。这一点出问题其间是有意识到的,但我一直以为ScrollTo所谓的绝对滑动指的是绝对于屏幕。这也就是下一点要说的
  • ScrollTo()所谓的绝对滑动是相对于父布局(也就是调用ScrollTo()的View)而言,而不是我一开始认为的相对于屏幕的坐标。出了这个问题,再回头去看那个Content-View的图,一切就都很明了了。

总结:

  • ScrollTo/ScrollBy方法操作的是View的Content上,而其他两种方式则是直接作用在View上边的。且这种方式不影响内部元素的交互(点击事件)
  • 使用View动画时注意设置fillAfter以“保存”动画结果,注意View动画无法移动实体只能移动影像对交互的影响。因此View动画适用于不需要与用户交互的情况
  • Android3.0以前实现属性动画需要使用到的nineoldandroids库实质上也是实现了View动画
  • 一些复杂的效果只有动画可以实现

Ps:通过对View一些原理上的学习,很大程度上的提升了对View机制的理解,也对很多之前写代码时候使用到的东西理解更加清晰透彻。

知其然,也知其所以然!


完结 撒花 ฅ>ω<*ฅ