分析ActionBar-PullToRefresh的代码思路


 
 
本文旨在分析 chris banes实现的ActionBar-PullToRefresh的代码思路。目的其实是帮助我本人阅读代码。如果能够碰巧对其他人有帮助就更好了。

在PullToRefreshLayout中,作者重写了onInterceptTouchEvent和onTouchEvent方法来处理触摸事件。如果对这两个方法的作用不是很了解,可以先看一篇博客http://blog.csdn.net/android_tutor/article/details/7193090

 

我们可以先来看看Android官方对onInterceptTouchEvent的解释。首先,onInterceptTouchEvent是定义在ViewGroup中的方法。

Implement this method to intercept all touch screen motion events. This allows you to watch events as they are dispatched to your children, and take ownership of the current gesture at any point.

实现这个方法是为了截取所有的触屏手势事件。这个方法允许你看到所有分发到你的子控件的事件,并在任何点上拿到当前手势的所有权。

Using this function takes some care, as it has a fairly complicated interaction withView.onTouchEvent(MotionEvent), and using it requires implementing that method as well as this one in the correct way. Events will be received in the following order:

使用这个方法需要很小心,因为它和onTouchEvent方法有非常复杂的相互作用,必须同时正确的实现这两个方法。事件会以下面的顺序收到:

  1. You will receive the down event here.     你会在这儿收到DOWN事件。
  2. The down event will be handled either by a child of this view group, or given to your own onTouchEvent() method to handle; this means you should implement onTouchEvent() to return true, so you will continue to see the rest of the gesture (instead of looking for a parent view to handle it). Also, by returning true from onTouchEvent(), you will not receive any following events in onInterceptTouchEvent() and all touch processing must happen in onTouchEvent() like normal.        这个DOWN事件要么会被这个View Group的子控件处理,要么会由你自己的onTouchEvent处理。这意味着你需要实现onTouchEvent方法去返回true,这样你就可以继续看见剩下的手势(而不是寻找父控件去处理)。而且,通过在onTouchEvent中返回true,onInterceptTouchEvent方法不会再收到剩下的事件,所有的触摸进程都会正常地在onTouchEvent中发生。
  3. For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent().     只要你在这个方法中返回false,每个剩下的事件都会被送到目标控件的onTouchEvent方法中。
  4. If you return true from here, you will not receive any following events: the target view will receive the same event but with the actionMotionEvent.ACTION_CANCEL, and all further events will be delivered to your onTouchEvent() method and no longer appear here.       如果在这里返回true,你不会在这个方法中收到剩下的事件:目标控件会收到一样的事件和CANCEL事件,并且所有剩下的事件都会送到你的onTouchEvent方法中,不会再出现在这儿。
Parameters:
event The motion event being dispatched down the hierarchy.
Returns:
Return true to steal motion events from the children and have them dispatched to this ViewGroup through onTouchEvent(). The current target will receive an ACTION_CANCEL event, and no further messages will be delivered here.     返回true去从子控件中偷取手势事件,并让它们分发到这个ViewGroup的onTouchEvent方法。当前目标控件会收到一个ACTION_CANCEL事件,并且不会再有信息传到这儿。 

 现在开始看PullToRefreshLayout中重写的onInterceptTouchEvent方法。

    @Override
    public final boolean onInterceptTouchEvent(MotionEvent event) {
        if (DEBUG) {
            Log.d(LOG_TAG, "onInterceptTouchEvent. " + event.toString());
        }
        if (isEnabled() && mPullToRefreshAttacher != null && getChildCount() > 0) { //  如果attacher不为空,且子控件数大于0.就将这个事件交由attacher去处理
            return mPullToRefreshAttacher.onInterceptTouchEvent(event);
        }
        return false;  //  否则返回false,将事件交由子控件去处理
    }

   

现在看看PullToRefreshAttacher中的onInterceptTouchEvent方法。

    final boolean onInterceptTouchEvent(MotionEvent event) {
        if (DEBUG) {
            Log.d(LOG_TAG, "onInterceptTouchEvent: " + event.toString());
        }

        // If we're not enabled or currently refreshing don't handle any touch
        // events
        if (isRefreshing()) {
            return false;
        }

        final float x = event.getX(), y = event.getY();    //getX()是表示Widget相对于自身左上角的x坐标

        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE: {
                // We're not currently being dragged so check to see if the user has
                // scrolled enough
                if (!mIsBeingDragged && mInitialMotionY > 0f) {
                    final float yDiff = y - mInitialMotionY;
                    final float xDiff = x - mInitialMotionX;

                    if (yDiff > xDiff && yDiff > mTouchSlop) {// mTouchSlop : Distance in pixels a touch can wander before we think the user is scrolling
                        mIsBeingDragged = true;   //  下拉开始
                        onPullStarted(y);
                    } else if (yDiff < -mTouchSlop) {   //  反向移动
                        resetTouch();  
                    }
                }
                break;
            }

            case MotionEvent.ACTION_DOWN: {
                // If we're already refreshing, ignore
                if (canRefresh(true)) {
                    for (View view : mRefreshableViews.keySet()) {
                        if (isViewBeingDragged(view, event)) {    //  判断是哪个view被下拉了,可以看出具体的判断逻辑在isViewBeingDragged这个方法里,下面我们会具体分析
                            mInitialMotionX = x;   
                            mInitialMotionY = y;
                            mViewBeingDragged = view;
                        }
                    }
                }
                break;
            }

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                resetTouch();
                break;
            }
        }

        if (DEBUG) Log.d(LOG_TAG, "onInterceptTouchEvent. Returning " + mIsBeingDragged);

        return mIsBeingDragged;
    }

下面分析PullToRefreshAttacher里的isViewBeingDragged方法:

   final boolean isViewBeingDragged(View view, MotionEvent event) {
        if (view.isShown() && mRefreshableViews.containsKey(view)) {
            // First we need to set the rect to the view's screen co-ordinates
            view.getLocationOnScreen(mViewLocationResult);
            final int viewLeft = mViewLocationResult[0], viewTop = mViewLocationResult[1];
            mRect.set(viewLeft, viewTop, viewLeft + view.getWidth(), viewTop + view.getHeight());

            if (DEBUG) Log.d(LOG_TAG, "isViewBeingDragged. View Rect: " + mRect.toString());

            final int rawX = (int) event.getRawX(), rawY = (int) event.getRawY();   // getRawX()是表示相对于屏幕左上角的x坐标值
            if (mRect.contains(rawX, rawY)) {
                // The Touch Event is within the View's display Rect
                ViewDelegate delegate = mRefreshableViews.get(view);
                if (delegate != null) {
                    // Now call the delegate, converting the X/Y into the View's co-ordinate system
                    return delegate.isReadyForPull(view, rawX - mRect.left, rawY - mRect.top);    // rawX - mRect.left的效果就是getX()
                }
            }
        }
        return false;
    }

具体的逻辑又转移到了ViewDelegate的isReadyForPull方法,这是一个接口,需要自己去实现。作者已经给出了AbsListViewDelegate、ScrollYDelegate、WebViewDelegate三个实现。我们就不具体去研究子类的代码,只需要看看作者对isReadyForPull这个方法的解释:Allows you to provide support for View which do not have built-in support. In this method you should cast <code>view</code> to it's native class, and check if it is scrolled to the top.  检查这个view是不是滑动到了它的顶部。

到现在为止,我们已经可以分析出下拉开始的动作了,在下拉开始的时候 PullToRefreshAttacher的处理是  mIsBeingDragged = true;   onPullStarted(y);
设置了   mIsBeingDragged = true; 之后,按照代码的逻辑接下来的手势事件处理就交由 onTouchEvent 方法来处理了。下面我们看看 onTouchEvent 方法的代码:
      final boolean onTouchEvent (MotionEvent event) {
        if (DEBUG ) {
            Log. d( LOG_TAG, "onTouchEvent: " + event.toString());
        }

        // Record whether our handling is started from ACTION_DOWN
        if (event.getAction() == MotionEvent. ACTION_DOWN) {
            mHandlingTouchEventFromDown = true ;
        }

        // If we're being called from ACTION_DOWN then we must call through to
        // onInterceptTouchEvent until it sets mIsBeingDragged
        if (mHandlingTouchEventFromDown && ! mIsBeingDragged) {     // 从mIsBeingDragged = true;onPullStarted(y); 开始才在onTouchEvent 中处理手势事件, 在此之前都由onInterceptTouchEvent方法处理。
            onInterceptTouchEvent(event);
            return true ;
        }

        if (mViewBeingDragged == null) {
            return false ;
        }

        switch (event.getAction()) {
            case MotionEvent. ACTION_MOVE: {
                // If we're already refreshing ignore it
                if (isRefreshing()) {
                    return false ;
                }

                final float y = event.getY();

                if (mIsBeingDragged && y != mLastMotionY) {
                    final float yDx = y - mLastMotionY;

                    /**
                     * Check to see if the user is scrolling the right direction
                     * (down). We allow a small scroll up which is the check against
                     * negative touch slop.
                     */
                    if (yDx >= -mTouchSlop ) {
                        onPull( mViewBeingDragged , y);
                        // Only record the y motion if the user has scrolled down.
                        if (yDx > 0f) {
                            mLastMotionY = y;
                        }
                    } else {
                        onPullEnded();     //手势改为向上滑动会调用onPullEnded方法
                        resetTouch();
                    }
                }
                break ;
            }

            case MotionEvent. ACTION_CANCEL:
            case MotionEvent. ACTION_UP: {
                checkScrollForRefresh( mViewBeingDragged );
                if (mIsBeingDragged ) {
                    onPullEnded();     
                }
                resetTouch();
                break ;
            }
        }

        return true ;
    }

onInterceptTouchEvent方法中,由MOVE事件判断出下拉开始,调用 onPullStarted(y)方法;然后在 onTouchEvent 方法中,由MOVE或UP事件判断出下拉结束调用 onPullEnded()方法。至此,判断下拉手势的逻辑就分析完了。

下一篇文章会详细分析下拉的动画效果。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在ActionBar中使用自定义布局,你需要在Activity中使用以下代码: ```java ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); // 显示返回按钮 actionBar.setDisplayShowTitleEnabled(false); // 隐藏默认标题 actionBar.setDisplayShowCustomEnabled(true); // 显示自定义布局 actionBar.setCustomView(R.layout.actionbar_custom_layout); // 设置自定义布局 ``` 其中,`setCustomView()`方法用于设置自定义布局文件的资源ID。你可以根据需要修改布局文件的内容和资源ID。例如,以下是一个自定义布局文件actionbar_custom_layout.xml的示例: ```xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/actionbar_layout" android:layout_width="match_parent" android:layout_height="?android:attr/actionBarSize" android:background="@color/colorPrimary"> <ImageView android:id="@+id/actionbar_back" android:layout_width="wrap_content" android:layout_height="match_parent" android:padding="10dp" android:src="@drawable/ic_arrow_back_white_24dp" /> <TextView android:id="@+id/actionbar_title" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_centerInParent="true" android:text="My ActionBar" android:textColor="@color/white" android:textSize="20sp" /> <ImageView android:id="@+id/actionbar_menu" android:layout_width="wrap_content" android:layout_height="match_parent" android:padding="10dp" android:src="@drawable/ic_more_vert_white_24dp" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" /> </RelativeLayout> ``` 这个布局文件包含了一个RelativeLayout和三个子控件:一个ImageView用于显示返回按钮、一个TextView用于显示标题、一个ImageView用于显示菜单按钮。你可以根据需要修改布局文件的内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值