zoukankan      html  css  js  c++  java
  • 开源Android-PullToRefresh下拉刷新源代码分析

      PullToRefresh 这个库用的是很至多。github  今天主要分析一下源代码实现.

    我们通过ListView的下拉刷新进行分析。其他的类似。

    整个下拉刷新  父View是LinearLayout, 在LinearLayout加入了Header View ,Footer View,和ListView

    PullToRefreshBase 是父类 扩展了 LinearLayout水平布局   假设我们使用ListView 须要观看子类  PullToRefreshAdapterViewBase -> PullToRefreshListView  


     初始化代码在PullToRefreshBase init方法中

    重点代码:

    		// Refreshable View
    		// By passing the attrs, we can add ListView/GridView params via XML
    		mRefreshableView = createRefreshableView(context, attrs);//通过子类传入的View,ListView或者ScrollView等
    		addRefreshableView(context, mRefreshableView);//加入view到布局中
    
    		// We need to create now layouts now  创建Header和Footer视图,默认是INVISIBLE。要加入到父窗体
    		mHeaderLayout = createLoadingLayout(context, Mode.PULL_FROM_START, a);
    		mFooterLayout = createLoadingLayout(context, Mode.PULL_FROM_END, a);
    
    
    		handleStyledAttributes(a);//加入loadingView效果。这里是把View加入到ListView HeaderView里面去
    		updateUIForMode(); //把布局加入到父View中

    	protected void updateUIForMode() {
    		final LinearLayout.LayoutParams lp = getLoadingLayoutLayoutParams();
    		// Remove Header, and then add Header Loading View again if needed
    		if (this == mHeaderLayout.getParent()) {
    			removeView(mHeaderLayout);
    		}
    		if (mMode.showHeaderLoadingLayout()) {
    			addViewInternal(mHeaderLayout, 0, lp);//增加View到LinearLayout
    		}
    
    		// Remove Footer, and then add Footer Loading View again if needed
    		if (this == mFooterLayout.getParent()) {
    			removeView(mFooterLayout);
    		}
    		if (mMode.showFooterLoadingLayout()) {
    			addViewInternal(mFooterLayout, lp);//增加View到LinearLayout
    		}
    
    		// Hide Loading Views
    		refreshLoadingViewsSize();//把headerView隐藏起来,其有用的是padding的方式 设置为负值 就到屏幕顶部的外面了 
    
    		// If we're not using Mode.BOTH, set mCurrentMode to mMode, otherwise
    		// set it to pull down
    		mCurrentMode = (mMode != Mode.BOTH) ?

    mMode : Mode.PULL_FROM_START; }


    //这里有2个LoadingView,一个是增加到LinearLayout中去了,另一个是增加到ListView本身的Header里面

    看看handleStyledAttributes方法 定位到子类复写的地方

    FrameLayout frame = new FrameLayout(getContext());
    mHeaderLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_START, a);
    mHeaderLoadingView.setVisibility(View.GONE);
    frame.addView(mHeaderLoadingView, lp);
    mRefreshableView.addHeaderView(frame, null, false);//加入LoadingView到ListView Header上

            //headerView一共同拥有2个LoadingView,一个是被增加到LinearLayout一个是被增加到ListView的HeaderView
    addViewInternal方法就是增加到LinearLayout父类中 


    看看LoadingLayout 有2种  FlipLoadingLayout 和  RotateLoadingLayout 一般我们用旋转的载入动画
    左边一个旋转图片,右边是文字和时间提示
        
    第一个LoadingLayout主要显示 :下拉刷新,放开以刷新
    第二个LoadingLayout显示松手后的文字:正在加载...

    结构是这样



    当UI初始化好,以下看看onTouch 下拉捕获事件


    	public final boolean onTouchEvent(MotionEvent event) {
    		if (!isPullToRefreshEnabled()) {
    			return false;
    		}
    		// If we're refreshing, and the flag is set. Eat the event
    		if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
    			return true;
    		}
    		if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
    			return false;
    		}
    		switch (event.getAction()) {
    			case MotionEvent.ACTION_MOVE: {
    				if (mIsBeingDragged) {
    					mLastMotionY = event.getY();
    					mLastMotionX = event.getX();
    					pullEvent();//開始下拉,移动 
    					return true;
    				}
    				break;
    			}
    
    			case MotionEvent.ACTION_DOWN: {
    				if (isReadyForPull()) {//按下 開始下拉
    					mLastMotionY = mInitialMotionY = event.getY();
    					mLastMotionX = mInitialMotionX = event.getX();
    					return true;
    				}
    				break;
    			}
    
    			case MotionEvent.ACTION_CANCEL:
    			case MotionEvent.ACTION_UP: { //停止下拉的时候
    				if (mIsBeingDragged) {
    					mIsBeingDragged = false;
    					if (mState == State.RELEASE_TO_REFRESH
    							&& (null != mOnRefreshListener || null != mOnRefreshListener2)) {
    						setState(State.REFRESHING, true);//放下手指開始回调,运行我们的回调任务
    						return true;
    					}
    
    					// If we're already refreshing, just scroll back to the top
    					if (isRefreshing()) {
    						smoothScrollTo(0);
    						return true;
    					}
    
    					// If we haven't returned by here, then we're not in a state
    					// to pull, so just reset
    					setState(State.RESET); //恢复到原来的UI状态
    
    					return true;
    				}
    				break;
    			}
    		}
    
    		return false;
    	}


    看看pullEvent方法
    	private void pullEvent() {
    		final int newScrollValue;
    		final int itemDimension;
    		final float initialMotionValue, lastMotionValue;
    
    		switch (getPullToRefreshScrollDirection()) {
    			case HORIZONTAL:
    				initialMotionValue = mInitialMotionX;
    				lastMotionValue = mLastMotionX;
    				break;
    			case VERTICAL:
    			default:
    				initialMotionValue = mInitialMotionY;
    				lastMotionValue = mLastMotionY;
    				break;
    		}
    		//计算下拉移动了多少
    		switch (mCurrentMode) {
    			case PULL_FROM_END://上拉
    				newScrollValue = Math.round(Math.max(initialMotionValue - lastMotionValue, 0) / FRICTION);
    				itemDimension = getFooterSize();
    				break;
    			case PULL_FROM_START://下拉
    			default:
    				newScrollValue = Math.round(Math.min(initialMotionValue - lastMotionValue, 0) / FRICTION);
    				itemDimension = getHeaderSize();
    				break;
    		}
    
    		//显示HeaderView 得到移动的值。能够让LoadingView显示出来
    		setHeaderScroll(newScrollValue);
    
    		if (newScrollValue != 0 && !isRefreshing()) {
    			float scale = Math.abs(newScrollValue) / (float) itemDimension;
    			switch (mCurrentMode) {
    				case PULL_FROM_END:
    					mFooterLayout.onPull(scale);
    					break;
    				case PULL_FROM_START:
    				default:
    				 mHeaderLayout.onPull(scale);//旋转左边的载入图片,显示文字和图片 这个地方终于会运行LoadingLayout中的 onPullImpl方法
    					break;
    			}
    			//更新状态 包含2中 释放按下触摸,还有就是 没释放手的触摸
    			if (mState != State.PULL_TO_REFRESH && itemDimension >= Math.abs(newScrollValue)) {
    				setState(State.PULL_TO_REFRESH);
    			} else if (mState == State.PULL_TO_REFRESH && itemDimension < Math.abs(newScrollValue)) {
    				setState(State.RELEASE_TO_REFRESH);//下拉松手 能够松手了
    			}
    		}
    	}



    再看看setHeaderScroll方法代码
    	protected final void setHeaderScroll(int value) {
    		if (DEBUG) {
    			Log.d(LOG_TAG, "setHeaderScroll: " + value);
    		}
    		
    		if (DEBUG) {
    			Log.d(LOG_TAG, "setHeaderScroll:" + value );
    		}
    
    		// Clamp value to with pull scroll range
    		final int maximumPullScroll = getMaximumPullScroll();
    		value = Math.min(maximumPullScroll, Math.max(-maximumPullScroll, value));
    
    		if (mLayoutVisibilityChangesEnabled) {
    			if (value < 0) { //有位移才显示
    				mHeaderLayout.setVisibility(View.VISIBLE);
    			} else if (value > 0) {  <span style="font-family: Arial, Helvetica, sans-serif;">//有位移才显示</span>
    				mFooterLayout.setVisibility(View.VISIBLE);
    			} else { 
    				mHeaderLayout.setVisibility(View.INVISIBLE);
    				mFooterLayout.setVisibility(View.INVISIBLE);
    			}
    		}
    
    		if (USE_HW_LAYERS) {
    			/**
    			 * Use a Hardware Layer on the Refreshable View if we've scrolled at
    			 * all. We don't use them on the Header/Footer Views as they change
    			 * often, which would negate any HW layer performance boost.
    			 */
    			ViewCompat.setLayerType(mRefreshableViewWrapper, value != 0 ? View.LAYER_TYPE_HARDWARE
    					: View.LAYER_TYPE_NONE);
    		}
    
    		//回到最原始的scrollTo 最经常使用的 移动布局
    		switch (getPullToRefreshScrollDirection()) {
    			case VERTICAL:
    				scrollTo(0, value);
    				break;
    			case HORIZONTAL:
    				scrollTo(value, 0);
    				break;
    		}
    	}

    setState(State.REFRESHING, true);//拉倒最顶部 松手,会运行onRefreshing方法。回调我们实现的任务接口 也就是OnRefreshListener


    protected void onRefreshing(final boolean doScroll) {
    		if (mMode.showHeaderLoadingLayout()) {
    			mHeaderLayout.refreshing();
    		}
    		if (mMode.showFooterLoadingLayout()) {
    			mFooterLayout.refreshing();
    		}
    
    		if (doScroll) {
    			if (mShowViewWhileRefreshing) {
    
    				// Call Refresh Listener when the Scroll has finished
    				OnSmoothScrollFinishedListener listener = new OnSmoothScrollFinishedListener() {
    					@Override
    					public void onSmoothScrollFinished() {
    						callRefreshListener();//回调接口运行
    					}
    				};
    
    				switch (mCurrentMode) {
    					case MANUAL_REFRESH_ONLY:
    					case PULL_FROM_END:
    						smoothScrollTo(getFooterSize(), listener);
    						break;
    					default:
    					case PULL_FROM_START:
    						smoothScrollTo(-getHeaderSize(), listener);
    						break;
    				}
    			} else {
    				smoothScrollTo(0);//回到原来的位置
    			}
    		} else {
    			// We're not scrolling, so just call Refresh Listener now
    			callRefreshListener();//回调接口运行
    		}
    	}



    	private void callRefreshListener() {
    		if (null != mOnRefreshListener) {
    			mOnRefreshListener.onRefresh(this);//回调
    		} else if (null != mOnRefreshListener2) { //这个是上拉,下拉都能够的情况,使用 onRefreshListener2
    			if (mCurrentMode == Mode.PULL_FROM_START) {
    				mOnRefreshListener2.onPullDownToRefresh(this);
    			} else if (mCurrentMode == Mode.PULL_FROM_END) {
    				mOnRefreshListener2.onPullUpToRefresh(this);
    			}
    		}
    	}

    总结:状态包含下拉刷新。松手刷新,正在刷新,Loading隐藏。移动UI还是用的scrollTo最主要的代码. 动画部分能够看LoadingLayout的2个子类

    基本的就这些。还有非常多细节没有分析。若有问题请指出谢谢。



  • 相关阅读:
    servlet(二)
    servlet(一)
    static、final、static final的区别
    java基础知识(一)
    IntelliJ IDEA中Debug的使用技巧
    IDEA从SVN中导入多模块项目
    Java实现MD5加密
    冒泡排序
    ulimit: core file size: cannot modify limit: Operation not permitted
    Linux 禁用摄像头
  • 原文地址:https://www.cnblogs.com/zsychanpin/p/7020175.html
Copyright © 2011-2022 走看看