前言:这几天能好好地把自己想学的知识好好整理一遍,感觉真是好极了。时间不多了,是要到最后拼搏的时间了。相信自己,你永远是最棒的。
相关文章:
1、《PullScrollView详解(一)——自定义控件属性》
2、《PullScrollView详解(二)——Animation、Layout与下拉回弹》
3、《PullScrollView详解(三)——PullScrollView实现》
4、《PullScrollView详解(四)——完全使用listview实现下拉回弹(方法一)》
5、《PullScrollView详解(五)——完全使用listview实现下拉回弹(方法二)》
6、《PullScrollView详解(六)——延伸拓展(listview中getScrollY()一直等于0、ScrollView中的overScrollBy)》
这篇先给大家梳理下有关TranslateAnimation和Layout的知识点,然后我们初步做一个下拉回弹的效果出来。
public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)其中
TranslateAnimation animation = new TranslateAnimation(0, 0, 0, -100); animation.setDuration(200); view.startAnimation(animation);这段代码的意思是,将view从(0,0)位置移动的(0,-100)的位置,也就是向上移动100像素。
view.startAnimation(animation);这个animation是运用在view中的,当然是以view的左上角为坐标系的原点来计算运动坐标的。理解不了也没关系,下面我们会用个例子来详细讲一讲。
public void layout(int left, int top, int right, int bottom)对应的使用方法:
view.layout(view.getLeft(),view.getTop()+100,view.getRight(),view.getBottom()+100);ayout函数有四个参数,分别是上、下、左、右,四个点的坐标。很明显,这是用来定位view所有位置的。
首先,点击animation_up按钮,底部的TextView的移动动画为:
TranslateAnimation(0, 0, 0, -100);即从(0,0)点移动到(0,-100)点;从图中也确实可以看到TextView从原始位置向上移动了;从图中也可以看到开始运动的(0,0)点,就是控件所有的原始位置。因为如果不在它原始位置的话,TextView会先移动到这个位置开始动画。
mRootView.layout(mRootView.getLeft(),mRootView.getTop()+100,mRootView.getRight(),mRootView.getBottom()+100);这时候再点击animation_up按钮,同样是让它从(0,0)点运动到(0,-100)点;但它已经不是从上次的原始位置来开始运动了。而是从当前的控件位置向上移动;从这个示例可以看出:layout()函数,不仅能通过指定上、下、左、右四个点坐标来指定控件的大小和位置。同时,该控件坐标系的(0,0)原点,也随着左上角位置的变动而变动。即控件的左上角始终是该控件坐标系的(0,0)原点;
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/layout_down" android:text="layout_down" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/animation_up" android:text="animation_up" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv" android:layout_width="200dp" android:layout_height="400dp" android:layout_gravity="center_horizontal" android:background="#ff00ff"/> </LinearLayout>2、MainActivity.java
public class MainActivity extends Activity implements View.OnClickListener{ private TextView mRootView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRootView = (TextView)findViewById(R.id.tv); Button btnMoveDwn = (Button)findViewById(R.id.layout_down); Button btnAnimationUp = (Button)findViewById(R.id.animation_up); btnMoveDwn.setOnClickListener(this); btnAnimationUp.setOnClickListener(this); } /** * 两个点: * 首先mRootView.startAnimation()中Animation的移动坐标是以mRootView的布局为原点的,如果使用mRootView.layout()将其改变,那么原点位置也将会改变. * Animation不会改变布局的坐标;layout则不一样,在改变位置的同时,layout的左上角始终是这个布局的(0,0)原点 * * @param v */ @Override public void onClick(View v) { switch (v.getId()){ case R.id.layout_down:{ mRootView.layout(mRootView.getLeft(),mRootView.getTop()+100,mRootView.getRight(),mRootView.getBottom()+100); } break; case R.id.animation_up:{ TranslateAnimation animation = new TranslateAnimation(0, 0, 0, -100); animation.setDuration(200); mRootView.startAnimation(animation); } break; } } }核心的代码我们都讲过了,这里也没什么好讲的了;
从图中可以看到,当我们下拉猫咪图片之后,图片自动反弹回去。
这里涉及两个问题:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <com.harvic.com.touchscrollview.CustomScrollView android:layout_width="fill_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="fitXY" android:src="@drawable/pic1"/> <TextView android:text="extra item 1" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16dp"/> <TextView android:text="extra item 2" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16dp"/> <TextView android:text="extra item 3" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16dp"/> <TextView android:text="extra item 4" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16dp"/> </LinearLayout> </com.harvic.com.touchscrollview.CustomScrollView> </LinearLayout>其中CustomScrollView是下面我们将要自定义的控件,派生自ScrollView;上面的布局代码很容易看懂,就不再细讲。
private View mRootView; @Override protected void onFinishInflate() { // TODO Auto-generated method stub mRootView = getChildAt(0); super.onFinishInflate(); }由于ScrollView只能有一个子布局,所以,我们通过getChildAt(0)直接获取ScrollView下的子布局即可;
private int mpreY=0; public boolean onTouchEvent(MotionEvent event) { float curY = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_MOVE:{ int delta = (int)((curY - mpreY)*0.25); if (delta>0) { mRootView.layout(mRootView.getLeft(), mRootView.getTop()+delta, mRootView.getRight(), mRootView.getBottom()+delta); } } break; } mpreY = (int) curY; return super.onTouchEvent(event); }首先,通过mpreY来保存每次手指移动的最新位置。
float curY = event.getY(); int delta = (int)((curY - mpreY)*0.25);为什么这里要乘以0.25?
mRootView.layout(mRootView.getLeft(), mRootView.getTop()+delta, mRootView.getRight(), mRootView.getBottom()+delta);由于,我们不改变大小,也不改变左右的位置,只让它向下移动,所以只需要在top和bottom上直接加上要移动的距离即可。
public class CustomScrollView extends ScrollView { // 根布局视图 private View mRootView; private int mpreY = 0; public CustomScrollView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } @Override protected void onFinishInflate() { // TODO Auto-generated method stub mRootView = getChildAt(0); super.onFinishInflate(); } @Override public boolean onTouchEvent(MotionEvent event) { float curY = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_MOVE: { int delta = (int) ((curY - mpreY) * 0.25); if (delta > 0) { mRootView.layout(mRootView.getLeft(), mRootView.getTop() + delta, mRootView.getRight(), mRootView.getBottom() + delta); } } break; } mpreY = (int) curY; return super.onTouchEvent(event); } }
//原始根视图对应的位置 private Rect mNormalRect; public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN:{ if (mRootView != null) { mNormalRect = new Rect(mRootView.getLeft(), mRootView.getTop(), mRootView.getRight(), mRootView.getBottom()); } } break; ………… }由于我们布局的初始化位置是不变的,所以,我们完全可以在其它地方获取原始布局的上、下、左、右位置,但由于布局的位置只有在真正被渲染出来以后才能得到,所以在 onFinishInflate()中是得不到的,因为这时候布局还没有被渲染出来。而当运行到onTouchEvent()时,布局肯定已经被渲染出来了。所以我放在了用户点击时获取初始化位置。
public boolean onTouchEvent(MotionEvent event) { float curY = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN:{ if (mRootView != null) { mNormalRect = new Rect(mRootView.getLeft(), mRootView.getTop(), mRootView.getRight(), mRootView.getBottom()); } } break; ………… case MotionEvent.ACTION_UP:{ int curTop = mRootView.getTop(); mRootView.layout( mNormalRect.left, mNormalRect.top, mNormalRect.right, mNormalRect.bottom); TranslateAnimation animation = new TranslateAnimation(0, 0, curTop - mNormalRect.top, 0); animation.setDuration(200); mRootView.startAnimation(animation); } break; } mpreY = (int) curY; return super.onTouchEvent(event); }非常注意在MotionEvent.ACTION_UP时的代码处理步骤:
首先,我们假定mRoot布局的初始化位置为A,即如图(2)中的位置所示;跟随手指移动到的位置为B,如图(1)中的位置所示;
整个代码的流程是这样的:
图示(1)显示的是,在MotionEvent.ACTION_MOVE中,让布局跟随手指移动后的状态。
图示(2),(3)显示的是反弹时的代码操作,即在MotionEvent.ACTION_UP中的代码。
这里非常要注意的一点是,我们反弹时,并不是只利用TranslateAnimation来将mRoot布局动画返回回去。
在操作动画之前,我们利用layout()函数,将布局首先还原到了初始化位置。
为什么要这样做呢?
我们在开头讲过,Animation动画的坐标是以它自己布局的左上角为顶点来计算的。如果我们只是利用动画将mRoot布局返回回去,那它本身的坐标系仍然没有还原。这跟初始化状态是不一样的。由于跟初始状态不一样,那以下次再下拉的时候,根据初始状态来做的各种操作就肯定会出问题。所以,无论怎样,我们都需要利用layout()将布局还原。
第二个问题,你先还原布局,然后再利用TranslateAnimation让布局返回到跟随手指移动到的位置(B位置),这样做,不会闪屏吗?
由于layout()函数移动布局是瞬间完成的,并不会像Animation一样,它没有过程,直接到达目的地。而TranslateAnimation移动到动画开始移动的点的过程中(到B点),也是瞬间完成的;TranslateAnimation的动画,只存在于用户定义的动画开始位置到动画结束位置的过程中。其它地方都是瞬间完成的。所以由于从B到A是瞬间完成的(图示1到图示2的过程),从B到A也是瞬间完成的(从图示2到图示3的过程),所以从图1到图3,用户看起来就是没有改变的。因为人眼要存在图象,必须图像的存在时间长于1/25秒,这才能被人眼采集进去,形成图像。而现在手机从图示(1)到图示(3)的处理时间,远低于我们人眼所能看到的时间,所以用户是根本看不到的。
TranslateAnimation ta = new TranslateAnimation(0, 0, curTop,normal.top); ta.setDuration(200); inner.startAnimation(ta); // 设置回到正常的布局位置 inner.layout(normal.left, normal.top, normal.right, normal.bottom);这段代码是不正确的,首先给大家说他的一个BUG,如果
TranslateAnimation ta = new TranslateAnimation(0, 0, curTop,normal.top);中的normal.top不等于0,也就是说,你可以给mRootView添加上一个参数andoird:marginTop=”100dp”,你会明显的发现动画会出现问题。 (源码会在文章底部给出)
我们来对比原添加前后的效果图:
添加前:
添加后
明显看到,在添加以后的示图中,当手指松开以后,mRoot视图会先往下运动一下,然后再向上运动。我们后面会讲它的这个BUG要怎么解,但我们先来看一个更重要的问题。
TranslateAnimation ta = new TranslateAnimation(0, 0, curTop,normal.top); ta.setDuration(200); inner.startAnimation(ta); // 设置回到正常的布局位置 inner.layout(normal.left, normal.top, normal.right, normal.bottom);首先,我们要明白的一个问题是,在MotionEvent.ACTION_MOVE中,我们已经利用layout()函数,将布局随手指移动到了指定位置(B位置)
图(1)显示的是控件初始化位置。所示的O点是mRoot的原点的坐标。对应的值是(normal.left,normal.top,normal.right,normal.bottom)
当mRoot布局跟着手指下移,到图(2)的位置,由于我们移动布局使用的是layout()函数,所以在图(2)中,mRoot的左上角原点O,所对应的位置就是(mRoot.getLeft(),mRoot.getTop(),mRoot.getRight(),mRoot.getBottom),
如果在抬起手指时,先运行:
TranslateAnimation ta = new TranslateAnimation(0, 0, mRoot.getTop(),normal.top);上面我们讲过Animation的动画坐标是以控件本身左上角来计算的,所以它移动的位置应该如图(3)所示,而我们为什么没有看出下先向下移动然后再向上移动呢?
TranslateAnimation ta = new TranslateAnimation(0, 0, mRoot.getTop(),normal.top); ta.setDuration(200); mRoot.startAnimation(ta); // 设置回到正常的布局位置 mRoot.layout(normal.left, normal.top, normal.right, normal.bottom);注意,他在动画以后,直接利用mRoot.layout()将动画还原。我觉得,由于mRoot.layout()的操作是一瞬间的事情,而动画的渲染速度要比mRoot.layout()慢,所以当开始动画的时候,mRoot就已经回到了初始化的布局状态。此时的原点,依然是初始化状态的原点。所以看起来没有什么问题,但这完全是投机的算法。不是正规流程。
这个问题依然很难讲,我都有点想放弃讲解了,还是说说吧,大家能看得懂就最好了,看不懂就算了……还是不要为难自己了……
最关键的问题还是在松手时,Animation动画的坐标是以本身的左上角为原点来计算的。
TranslateAnimation ta = new TranslateAnimation(0, 0, mRoot.getTop(),normal.top); ta.setDuration(200); mRoot.startAnimation(ta); // 设置回到正常的布局位置 mRoot.layout(normal.left, normal.top, normal.right, normal.bottom);还是来看图吧:
图示1:显示的是mRoot的初始化位置,控件上面的空白,就是margin_top的高度
图示2:显示的是跟随手指移动的最终位置。此时利用mRoot.getTop()是在父控件上面的高度,即除了手指的移动距离还多了个margin_top的高度。
图示3:这个就显示了,当我们真正使用TranslateAnimation来做动画的时候,它的初始化移动位置,是要比图示2的位置要靠下,因为我们传给TranslateAnimation的TOP值是mRoot.getTop(),而TranslateAnimation的计算的原点不是在父控件的原点,而是控件初始化位置的原点,即多了个margin_top的高度:
TranslateAnimation ta = new TranslateAnimation(0, 0, mRoot.getTop(),normal.top);如果要修改的话也比较容易,想办法把目的位置改成手指的移动距离就好了;
TranslateAnimation ta = new TranslateAnimation(0, 0, mRoot.getTop()- normal.top, 0);
好了,到这里,这篇文章就结束了,大家对于第三部分,讲解网上代码所存在问题的部分,可以忽略,有兴趣的朋友可以仔细看看,感觉讲解起来难度很大啊,也不知道大家能不能看得懂,不行就只有自己根据理解画画出看。
源码内容:
1、TryAnimation:第一部分《TranslateAnimation和Layout》对应的源码
2、touchScrollView:第二部分《下拉回弹》对应源码
3、MyScrollView:第三部分《网上解决方案及存在问题》对应源码
如果本文有帮到你,记得加关注哦
源码下载地址:http://download.csdn.net/detail/harvic880925/8862361
请大家尊重原创者版权,转载请标明出处: http://blog.csdn.net/harvic880925/article/details/46543859 谢谢