前言:当你感到不舒服的时候就是成长的时候。入职阿里时学长跟我说的这句话,一直记得。到死时,人们往往不会因为自己做过什么而后悔,而常常会因为没做什么而后悔。趁你还有激情,加油!
相关文章:
1、《PullScrollView详解(一)——自定义控件属性》
2、《PullScrollView详解(二)——Animation、Layout与下拉回弹》
3、《PullScrollView详解(三)——PullScrollView实现》
4、《PullScrollView详解(四)——完全使用listview实现下拉回弹(方法一)》
5、《PullScrollView详解(五)——完全使用listview实现下拉回弹(方法二)》
6、《PullScrollView详解(六)——延伸拓展(listview中getScrollY()一直等于0、ScrollView中的overScrollBy)》
言归正转,这篇就是终极篇了,一般来讲,终极篇总是最有难度的,已经折磨大家四篇文章,这篇文章也终于千呼万唤始出来了。在listview中实现下拉回弹是比较有难度的,因为在listview中有关scroll各方面的获取都需要自己来做,所以整体难度还是比较大。废话不多说,现在开整吧。
先来看看效果图:(与PullScrollView的效果是一样一样的)
一、搭框架
同前几篇一样,先出框架。这篇将是前几篇的集大成者,一些细小的部分,就不再细讲了,如果有疑问,我会在后面列出每个忽略未细讲的知识点所在的博客位置,大家可以先移步过去看看。在这部分,我们要完成下面这个效果:
1、创建PullScrollListView 派生自ListView
- public class PullScrollListView extends ListView {
-
- private int mContentMaxMoveHeight = 0;
-
- public PullScrollListView(Context context) {
- super(context);
- }
-
- public PullScrollListView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(context, attrs);
- }
-
- public PullScrollListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context, attrs);
- }
-
- private void init(Context context, AttributeSet attrs) {
- if (null != attrs) {
- TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PullScrollView);
- if (ta != null) {
- mContentMaxMoveHeight = (int) ta.getDimension(R.styleable.PullScrollView_maxMoveHeight, -1);
- ta.recycle();
-
- }
- }
- }
- }
这里可能难理解的部分,就是init()函数里的部分,这是通过declare-styleable来自定义控件属性的知识,在《PullScrollView详解(一)——自定义控件属性》
中有详细讲述。2、main.xml 主布局
这个布局每篇文章都会讲一遍,没什么难度- <?xml version="1.0" encoding="utf-8"?>
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
-
- <ImageView
- android:id="@+id/background_img"
- android:layout_width="match_parent"
- android:layout_height="400dp"
- android:layout_marginTop="-100dp"
- android:scaleType="fitXY"
- android:src="@drawable/pic3"/>
-
- <com.harvic.PullScrollListViewDemo.PullScrollListView
- android:id="@+id/pull_list_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:divider="@null"
- android:dividerPadding="0dp"
- android:dividerHeight="0dp"
- app:maxMoveHeight="200dp"
- app:headerTopHeight = "100dp"
- app:headerVisibleHeight="150dp"/>
- </FrameLayout>
这里可能难理解的部分也就app:XXXX这些自定义属性了,在上面的博客中都有的。同样,注意ImageView要定义android:layout_marginTop="-100dp",即初始向上移100px,以便向下移动。3、填充布局
(1)、Item布局(item_layout.xml)
- <TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/text1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:minHeight="40dp"
- android:background="#ffffff"/>
(2)、MainActivity填充数据有关构造数据,填充数据的部分也不再细讲了,每篇文章都会讲,这是listview构造的基础知识,大家应该能看明白。- public class MainActivity extends Activity {
- private String[] mStrings = {"Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
- "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
- "Allgauer Emmentaler", "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
- "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
- "Allgauer Emmentaler"};
- private LinkedList<String> mListItems;
- private PullScrollListView mListView;
- private ArrayAdapter<String> mAdapter;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- mListView = (PullScrollListView) findViewById(R.id.pull_list_view);
- mListItems = new LinkedList<String>();
- mListItems.addAll(Arrays.asList(mStrings));
- mAdapter = new ArrayAdapter<String>(this,R.layout.item_layout, mListItems);
-
- mListView.setAdapter(mAdapter);
- }
- }
到现在,我们数据填充已经完成了,但如何将底部的小狗图片显示出来呢?在上篇,我们讲过,不能用padding和marginTop,只能通过给ListView添加透明Header来实现。4、添加Header
(1)、headerview的布局(headerview.xml)
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="300dp"
- android:clickable="false">
-
-
- </LinearLayout>
(2)、在MainActivity中添加headerview一定要注意的是,要通过listview的addHeaderView(View v, Object data, boolean isSelectable)函数来添加不可点击的headview,如果直接使用addHeaderView(View v)来添加,默认是可以点击的,当用户点击时,会变白。代码如下:- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- mListView = (PullScrollListView) findViewById(R.id.pull_list_view);
- mListItems = new LinkedList<String>();
- mListItems.addAll(Arrays.asList(mStrings));
- mAdapter = new ArrayAdapter<String>(this,R.layout.item_layout, mListItems);
-
- LayoutInflater inflater = getLayoutInflater();
- View view = inflater.inflate(R.layout.headerview,mListView,false);
- //设置headerview不可点击
- mListView.addHeaderView(view,null,false);
-
- mListView.setAdapter(mAdapter);
- }
这样,整个架构就出来了
**源码在文章底部给出**
二、下拉回弹
1、添加topView
与前几篇一样,我们首先要讲小狗的图片对应的ImageView传给PullScrollListView,在它下拉的时候跟着一起下拉,在松开的时候,一起回弹。PullScrollListView.java中:-
- private View mTopView;
- public void setmTopView(View view) {
- mTopView = view;
- }
在MainActivity.java中:将图片ImageView设置进去- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- mListView = (PullScrollListView) findViewById(R.id.pull_list_view);
- ………………
- ImageView headerView = (ImageView)findViewById(R.id.background_img);
- mListView.setmTopView(headerView);
- }
2、点击时保存初始化位置
我们这里尽量保持《PullScrollView详解(三)——PullScrollView实现》
的逻辑,在用户点击时做变量的初始化操作,初始化操作包括topView位置的初始化:用于在反弹时,回到初始化位置contentView位置的初始化:同样用于在反弹时,回到初始化位置,但这里的contentVIew是listview本身,所以就是this变量。下面还会再讲。点击位置的初始化:点击位置的初始化主要是用来计算当前用户移动了多少的。这里还有其它变量的初始化,先列出完整代码,下面再细讲。-
- private View mContentView = this;
-
- private Point mTouchPoint = new Point();
-
- private Rect mHeadInitRect = new Rect();
-
- private Rect mContentInitRect = new Rect();
-
- boolean mIsMoving = false;
-
- private boolean mEnableTouch = false;
-
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
-
- mTouchPoint.set((int) event.getX(), (int) event.getY());
- mHeadInitRect.set(mTopView.getLeft(), mTopView.getTop(), mTopView.getRight(), mTopView.getBottom());
- mContentInitRect.set(mContentView.getLeft(), mContentView.getTop(), mContentView.getRight(), mContentView.getBottom());
- mIsMoving = false;
- mEnableTouch = false;
- }
- return super.onInterceptTouchEvent(event);
- }
这里与博客三最大的不同在于,没有拦截ACTION_MOVE事件!为什么呢?因为listview本身就是一个控件,内部不会再有其它控件,顶部是不会有其它控件在OnTouchEvent中消费ACTION_MOVE事件的。所以不必拦截。因为在拦截之后,有关的ACTION_MOVE事件就不会再向上传递的,比如我们的HeaderView如果是一个viewPager呢?那它永远也不会滑动了,除非我们做到精确判断来拦截,即确定是在顶部下拉时才进行拦截,其它时候不拦截ACTION_MOVE消息。这样,只有在顶部下拉时才会被我们拦截起来,其它的滑动事件,比如左右滑,是会继续传递给子控件的。这里就不再进行拦截,因为没有拦截的意义。在PullScrollView中对ScrollView的ACTION_MOVE事件进行了拦截,是因为ScrollView本身还是有子控件的。有可能在OnTouchEvent中把ACTION_MOVE事件消费掉而传不到ScrollView里。(1)、变量定义在定义的变量里,其实没什么难度,与博客三不相同的一点是:
- private View mContentView = this;
mContentView的意义表示,向下拉动的内容部分,这里对应的是整个的listview,所以这里直接用this给它赋值。而不是像博客三一样从外部赋值。(2)、初始化上面也都说了,各个初始化的意义
- mTouchPoint.set((int) event.getX(), (int) event.getY());
- mHeadInitRect.set(mTopView.getLeft(), mTopView.getTop(), mTopView.getRight(), mTopView.getBottom());
- mContentInitRect.set(mContentView.getLeft(), mContentView.getTop(), mContentView.getRight(), mContentView.getBottom());
这三个变量mTouchPoint、mHeadInitRect、mContentInitRect分别表示手指点击位置、topView的初始化位置和listview的初始化位置。另外还有两个变量:- mIsMoving = false;
- mEnableTouch = false;
这两个变量现在讲出来,如果只看这篇文章可能有点难理解,有关他们的详细说明已经在《PullScrollView详解(三)——PullScrollView实现》
详细说明,大家可以先看看这篇博客。这里我简单的说一下他们的意义,mIsMoving是用来标识当前View是不是正在向下移动,用来在ACTION_UP时,回弹View.如果不标识会怎样,那就会在ACTION_UP事件到来时,都会弹一下。比如当前你是在向上滚动,而不是在下拉,在你放开手指时,是不应该回弹的,这里给你回弹一下,你还能受得了,所以这个变量就是用来标识当手指放开时,要不要回弹的。mEnableTouch这个变量的解释就有点难度了,它是用来标识是否禁止控件自身的移动的。这里估计也说不清楚,大家先知道就好,下面用到时会再讲。3、获取listview滚动高度
在讲解下拉之前,我们得先考虑一个问题,即如何判断什么时候该下拉了,在PullScrollView中我们可以通过getScrollY()==0来判断当前View是不是在顶部,如果在顶部而且又是在下拉,那么就可以下拉滚动了。
那在ListView中要怎么判断当前是不是到顶部了呢?大家可以尝试在listview中,getScrollY()始终等于0. 有关《延伸:为什么PullScrollView中getScrollY()有值而ListView中的getScrollY()却一直为零》的原因,可以参考在下一篇的延伸部分。
我这里就直接讲在listview中要怎么获取滚动高度的方法。先看看下面的这幅图:
在这幅图中,listview的item分别是两个高度不等的headview和各个item。
在图片下面被黄色框框起来的部分,代表屏幕。那黄色框以上的部分,就表示已经滚出去的部分,那么它的高度就是我们要计算的scrollHeight;
所以就这个图片当前状态而言
scrollHeight = 所有的header高度 + 已经滚动过去的所有Item的高度 + 当前可见item的滚动部分。
在正式开始之前先给大家讲ListView的两个函数:
-
- public int getFirstVisiblePosition()
-
- public View getChildAt(int index)
getFirstVisiblePosition()
这个函数用于获取当前可见item的索引,索引是从第一个headerview开始算的,第一个headerview索引是0,向下逐个加1.需要注意的是,只要当前item还能看见一丢丢,就算可见哦。比如,我上面的图中,利用getFirstVisiblePosition()得到的值会是3.即第四个item,虽然它向上滚动了一半,但它仍是可见的,所以getFirstVisiblePosition()获取到的是第四个item.
getChildAt(int index)
这个函数需要非常注意,它的index索引是从当前可见的位置开始的。当前第一个可见的Item的索引是0!!!!!不是从第一个headerview开始的!!!!就上面那个图而言,由于当前可见的item是第四个item.这时候调用getChildAt(0)获得是第四个item的View!!!!一定要非常注意。
但正是这个特性正好,我们可以使用getChildAt(0)来获得当前第一个可见item的视图,来得到它滚动的距离。
好了,介绍完计算方法和这两个函数,下面我们就开始看通过代码怎么计算滚动高度吧。
(1)、重写addHeaderView(View v)
因为我们会计算headview的高度,那我们需要得到所有headview的View。我们可以在重写ListView的addHeaderView(View v)方法,在用户利用addHeaderView(View v)添加headview时,我们顺便将它加入到我们的mHeadViews数组中。
- private ArrayList<View> mHeadViews = new ArrayList<View>();
-
- @Override
- public void addHeaderView(View v) {
- super.addHeaderView(v);
- if (v != null) {
- mHeadViews.add(v);
- }
- }
-
- @Override
- public void addHeaderView(View v, Object data, boolean isSelectable) {
- super.addHeaderView(v, data, isSelectable);
- if (v != null) {
- mHeadViews.add(v);
- }
- }
在获得滚动高度时,因为每个headerview的高度一般都不一样,所以我们要分两种情况:第一种,没有完全滚出所有headview。第二种:完全滚出所有Headview(2)没有完全滚出所有headview在没有完全滚出所有Headview时,滚动高度就是已经滚动过去的所有headview的高度和加上当前可见的headview滚动过去的高度。代码如下:下面会细讲
- public int getScrollHeight(ListView list, ArrayList<? extends View> headviews) {
- if (list == null || headviews == null){
- return -1;
- }
-
- int headCount = list.getHeaderViewsCount();
- int firstVisiblePos = list.getFirstVisiblePosition();
- int scrollHeight = 0;
- if (firstVisiblePos < headCount) {
-
- if (headviews.size() == 0) {
- new Exception("内部含有headerView,请在函数入口处设置headview list");
- return -1;
- }
- for (int i = 0; i <= firstVisiblePos; i++) {
- View view = headviews.get(i);
- if (view != null && i == firstVisiblePos) {
-
- scrollHeight += (-view.getTop());
- } else if (i != firstVisiblePos) {
- scrollHeight += view.getHeight();
- }
- }
- } else {
-
- …………
- }
- return scrollHeight;
- }
这段代码分为两部分:- int headCount = list.getHeaderViewsCount();
- int firstVisiblePos = list.getFirstVisiblePosition();
- int scrollHeight = 0;
- if (firstVisiblePos < headCount) {
-
- }else{
-
- }
在这段代码中,首先根据list.getHeaderViewsCount()获取到当前Headview的数量,其实使用headviews.size()也是一样的。然后利用list.getFirstVisiblePosition()得到当前可见Item的索引。然后判断当前可见的item是不是已经把headview全都给滚过去了。如果firstVisiblePos < headCount,即还没有完全滚过去,就开始if里的代码计算。这里要注意的是headCount是所有Headview的个数,而firstVisiblePos的值是从0开始的,所以要用小于号。当firstVisiblePos 等于 headCount的值的时候,其实已经过了Headview在第一个item项里了。下面是计算滚动高度的代码了。- for (int i = 0; i <= firstVisiblePos; i++) {
- View view = headviews.get(i);
- if (view != null && i == firstVisiblePos) {
-
- scrollHeight += (-view.getTop());
- } else if (i != firstVisiblePos) {
- scrollHeight += view.getHeight();
- }
- }
在上面的代码中,首先将所有已经滚过去的Headview的高度相加:- if (i != firstVisiblePos) {
- scrollHeight += view.getHeight();
- }
当到当前可见的Item时,直接利用当前view.getTop()方法获取当前的顶点位置。它的位置在屏幕原点的位置是(0,0),现在在不可见区域,所以它的top值的绝对值就是当前已经滚出去的高度。其实这里说屏幕原点是不准确的,它的位置坐标系以是所在父控件的左上角为坐标系原点的。因为父控件是充满整个屏幕的,当然它的(0,0)原点就在屏幕左上角。- if (view != null && i == firstVisiblePos) {
-
- scrollHeight += (-view.getTop());
- }
到这里就得到在没有滚出headview区域时的scrollHeight值了。(3)、完全滚出所有headview下面就来看看当滚出所有Headview时的代码计算方法:
- public int getScrollHeight(ListView list, ArrayList<? extends View> headviews) {
- …………
- if (firstVisiblePos < headCount) {
-
-
- } else {
-
- if (headviews != null) {
- for (View view : headviews) {
- scrollHeight += view.getHeight();
- }
- }
-
- View itemView = list.getChildAt(0);
- if (itemView != null) {
-
- scrollHeight += (firstVisiblePos - headCount) * itemView.getHeight();
- }
-
- scrollHeight += (-itemView.getTop());
- }
- return scrollHeight;
- }
这里分为三部分:首先,得到所有Headview的高度和:- if (headviews != null) {
- for (View view : headviews) {
- scrollHeight += view.getHeight();
- }
- }
然后得到所有滚过去的item的高度和:- View itemView = list.getChildAt(0);
- if (itemView != null) {
-
- scrollHeight += (firstVisiblePos - headCount) * itemView.getHeight();
- }
最后,再加上当前可见的item滚动过去的高度:- scrollHeight += (-itemView.getTop());
所以完整的获取滚动高度的代码如下:- public int getScrollHeight(ListView list, ArrayList<? extends View> headviews) {
- if (list == null || headviews == null){
- return -1;
- }
-
-
- int headCount = list.getHeaderViewsCount();
- int firstVisiblePos = list.getFirstVisiblePosition();
- int scrollHeight = 0;
- if (firstVisiblePos < headCount) {
-
- if (headviews.size() == 0) {
- new Exception("内部含有headerView,请在函数入口处设置headview list");
- return -1;
- }
- for (int i = 0; i <= firstVisiblePos; i++) {
- View view = headviews.get(i);
- if (view != null && i == firstVisiblePos) {
-
- scrollHeight += (-view.getTop());
- } else if (i != firstVisiblePos) {
- scrollHeight += view.getHeight();
- }
- }
- } else {
-
-
- if (headviews != null) {
- for (View view : headviews) {
- scrollHeight += view.getHeight();
- }
- }
-
- View itemView = list.getChildAt(0);
-
- if (itemView != null) {
-
- scrollHeight += (firstVisiblePos - headCount) * itemView.getHeight();
- }
-
- scrollHeight += (-itemView.getTop());
- }
- return scrollHeight;
- }
4、下拉
在费了这么大劲讲解了getScrollHeight()的方法后,下面说说下拉的代码。在onTouchEvent()中捕捉ACTION_MOVE事件,当满足条件时就使用layout(left,top,right,bottom)函数来移动当前布局。代码如下,后面会细讲。- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_MOVE: {
- int moveHeight = (int) event.getY() - mTouchPoint.y;
- int scrolledHeight = getScrollHeight(this, mHeadViews);;
-
- if (moveHeight > 0 && scrolledHeight == 0) {
- if (moveHeight > mContentMaxMoveHeight){
- moveHeight = mContentMaxMoveHeight;
- }
-
- float headerMoveHeight = moveHeight * 0.5f * SCROLL_RATIO;
- float contentMoveHeight = moveHeight * SCROLL_RATIO;
-
- mHeaderCurTop = (int) (mHeadInitRect.top + headerMoveHeight);
- mContentTop = (int) (mContentInitRect.top + contentMoveHeight);
-
- mTopView.layout(mHeadInitRect.left, mHeaderCurTop, mHeadInitRect.right, (int) (mHeadInitRect.bottom + headerMoveHeight));
- mContentView.layout(mContentInitRect.left, mContentTop, mContentInitRect.right, (int) (mContentInitRect.bottom + contentMoveHeight));
-
-
- mIsMoving = true;
- mEnableTouch = true;
- } else {
- mEnableTouch = false;
- }
- }
- break;
- …………
- return mEnableTouch || super.onTouchEvent(event);
- }
这里代码比较好理解,首先是判断当前是不是在顶部滑动- int moveHeight = (int) event.getY() - mTouchPoint.y;
- int scrolledHeight = getScrollHeight(this, mHeadViews);;
- if (moveHeight > 0 && scrolledHeight == 0) {
- }
其中moveHeight是手指的移动距离,只有当手指有移动,而且当前Listview在顶部,即滚动距离为0时,才开始向下拉动。因为我们设置了最大移动距离,所以当移动距离超过最大移动距离时,就将其始终赋值为最大移动距离。- if (moveHeight > mContentMaxMoveHeight){
- moveHeight = mContentMaxMoveHeight;
- }
然后根据手指的移动距离计算出topview和当前listview(也就是这里的contentview)的移动距离。因为我们要让topView(底部的小狗图片)移动的更慢些,所以在它的移动距离上额外乘以0.5.- float headerMoveHeight = moveHeight * 0.5f * SCROLL_RATIO;
- float contentMoveHeight = moveHeight * SCROLL_RATIO;
-
- mHeaderCurTop = (int) (mHeadInitRect.top + headerMoveHeight);
- mContentTop = (int) (mContentInitRect.top + contentMoveHeight);
在计算出移动距离后,就是利用layout()函数将TopView和当前的listview(也就是这里的contentview)移动到指定的位置。- mTopView.layout(mHeadInitRect.left, mHeaderCurTop, mHeadInitRect.right, (int) (mHeadInitRect.bottom + headerMoveHeight));
- mContentView.layout(mContentInitRect.left, mContentTop, mContentInitRect.right, (int) (mContentInitRect.bottom + contentMoveHeight));
有关mEnableTouch的变量的作用,我不想再讲一遍了,这里的篇幅已经太长了,在《 PullScrollView详解(三)——PullScrollView实现》
已经讲过了。大家可以去看看。其实建议大家先看这一篇,然后再回来看这篇,这样会理解的更透彻。5、回弹
在下拉之后,当ACTION_UP时,要回弹的初始化位置。代码如下:- case MotionEvent.ACTION_UP: {
-
- if (mIsMoving) {
- mTopView.layout(mHeadInitRect.left, mHeadInitRect.top, mHeadInitRect.right, mHeadInitRect.bottom);
- TranslateAnimation headAnim = new TranslateAnimation(0, 0, mHeaderCurTop - mHeadInitRect.top, 0);
- headAnim.setDuration(200);
- mTopView.startAnimation(headAnim);
- mContentView.layout(mContentInitRect.left, mContentInitRect.top, mContentInitRect.right, mContentInitRect.bottom);
-
- TranslateAnimation contentAnim = new TranslateAnimation(0, 0, mContentTop - mContentInitRect.top, 0);
- contentAnim.setDuration(200);
- mContentView.startAnimation(contentAnim);
- mIsMoving = false;
- }
- mEnableTouch = false;
- }
- break;
这部分,涉及到layout()的应用,及与Animation的结合的方法。看起来这只有几行代码,但真正理解出来还是比较有难度的,我在第二篇博客中详细讲解了,大家可以去看《PullScrollView详解(二)——Animation、Layout与下拉回弹》 大家看了之后,这里就不会有什么问题了。就不再细讲了。
好了,到这里所有代码都讲完了
**源码在文章底部给出**
三、延伸拓展
1、ViewHelper.setTranslationY()——完美的滚动方案
我们博客中所有用到滚动的地方都用的layout()函数来实现的,但当布局层级比较复杂的时候,layout()会失效。这里向大家一个能够完美实现滚动的类:ViewHelper;它是nineoldandroids.jar包里的类。
NineOldAndroids的官网:http://nineoldandroids.com
NineOldAndroids源码地址:https://github.com/JakeWharton/NineOldAndroids
这个类能实现有关动画的很多功能,而且出错率很小,我们项目中也一直在用。
这里我们用到下面的函数,意义是将指定View在指定Y轴上移动指定距离。
- public static void setTranslationY(View view, float translationY)
需要非常注意的是,这里的float translation的取值的意义。比如我们先执行ViewHelper.setTranslationY(view,200);然后再执行ViewHelper.setTranslationY(view,0);那它的位置在哪呢?比如我们原来的位置在A,然后执行setTranslationY(view,200)时View向下移动200像素到B,当执行.setTranslationY(view,0)时View会再回到A点!!!!这说明,setTranslationY()的坐标原点始终是不会变的!!!!其实从函数名也比较好理解,Translation表示滚动。setTranslationY(view,200)表示沿Y轴向下滚动到200像素的位置。注意是滚动到XXX位置,而不是向下滚动XXX像素,他的意义是scrollTo()而不是scrollBy(),setTranslationY(view,translationY)中的translationY代表的是目的Y坐标!!!!讲到这,大家应该都理解了,下面我们就将我们下拉和反弹部分替换成ViewHelper.setTranslationY(),代码如下:下面的代码中只是把layout()换成了setTranslationY(),其它都没变,这里就不再讲了。- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_MOVE: {
- int moveHeight = (int) event.getY() - mTouchPoint.y;
- int scrolledHeight = getScrollHeight(this, mHeadViews);
- if (moveHeight > 0 && scrolledHeight == 0) {
- if (moveHeight > mContentMaxMoveHeight){
- moveHeight = mContentMaxMoveHeight;
- }
-
- float headerMoveHeight = moveHeight * 0.5f * SCROLL_RATIO;
- float contentMoveHeight = moveHeight * SCROLL_RATIO;
- mHeaderCurTop = (int) (mHeadInitRect.top + headerMoveHeight);
- mContentTop = (int) (mContentInitRect.top + contentMoveHeight);
-
-
- ViewHelper.setTranslationY(mTopView, headerMoveHeight);
- ViewHelper.setTranslationY(mContentView, contentMoveHeight);
-
- mIsMoving = true;
- mEnableTouch = true;
- } else {
- mEnableTouch = false;
- }
- }
- break;
- case MotionEvent.ACTION_UP: {
-
- if (mIsMoving) {
- ViewHelper.setTranslationY(mTopView, 0);
- TranslateAnimation headAnim = new TranslateAnimation(0, 0, mHeaderCurTop - mHeadInitRect.top, 0);
- headAnim.setDuration(200);
- mTopView.startAnimation(headAnim);
-
- ViewHelper.setTranslationY(mContentView, 0);
- TranslateAnimation contentAnim = new TranslateAnimation(0, 0, mContentTop - mContentInitRect.top, 0);
- contentAnim.setDuration(200);
- mContentView.startAnimation(contentAnim);
- mIsMoving = false;
- }
- mEnableTouch = false;
- }
- break;
- case MotionEvent.ACTION_CANCEL: {
- mEnableTouch = false;
- }
- break;
- }
-
-
-
-
-
-
- return mEnableTouch || super.onTouchEvent(event);
- }
源码内容:
如果本文有帮到你,记得加关注哦
源码下载地址:http://download.csdn.net/detail/harvic880925/9062013
请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/48087649 谢谢 0 0