2016-05-12 247 views
5

我按照SwipeRefreshLayout.class編寫了RecyclerViewRefresh。當我拉視圖直到它不移動然後釋放時,視圖重置爲原始視圖。問題是該視圖應該觸發定時器,然後該定時器重置視圖。我找不到原因。LinearLayout滾動到頂部

請告訴我爲什麼offsetTopAndBottom()可以使視圖自動回到原來的位置。謝謝。

我使用setY()來解決這個問題。但我也想知道爲什麼。而我讀了offsetTopAndBottom()的源碼,也找不到任何線索。

RecyclerViewRefresh代碼:

public class RecyclerViewRefresh extends LinearLayout { 
private static final String LOG_TAG=RecyclerViewRefresh.class.getSimpleName(); 
private static final int INVALID_POINTER=-1; 
//Default offset in dips from the top of the view to where the progress 
//spinner should stop 
private static final int DEFAULT_CIRCLE_TARGET=64; 
private static final float DRAG_RATE=.5f; 

private View headerView,footerView,thisView; 
private View mTarget; //the target of the gesture 
private ImageView arrowIv; 
private TextView refreshTv; 
private ProgressBar progressBar; 
private OnPullToRefresh refreshListener=null; 
private OnDragToLoad loadListener=null; 
float startY=0; 

private int headerHeight=0; 
private boolean mReturningToStart; 
private boolean mRefreshing=false; 
private boolean mNestedScrollInProgress; 
private int mCurrentTargetOffsetTop; 
protected int mOriginalOffsetTop; 
private boolean mIsBeingDragged; 
private int mActivePointerId=INVALID_POINTER; 
private float mInitailDownY; 
private int mTouchSlop; 
private float mTotalDragDistance=-1; 
private float mInitialMotionY; 
private float mSpinnerFinalOffset; 
private boolean updateHeader=true; 
private Handler handler=new Handler(); 
private Timer timer; 

public RecyclerViewRefresh(Context context) { 
    super(context); 
    initView(context); 
} 

public RecyclerViewRefresh(Context context, AttributeSet attrs) { 
    super(context, attrs); 
    initView(context); 
} 

public RecyclerViewRefresh(Context context, AttributeSet attrs, int defStyleAttr) { 
    super(context, attrs, defStyleAttr); 
    initView(context); 
} 
private void initView(Context context) 
{ 
    thisView=this; 
    mTouchSlop= ViewConfiguration.get(context).getScaledTouchSlop(); 
    headerView=LayoutInflater.from(context).inflate(R.layout.header_layout,null); 
    footerView=LayoutInflater.from(context).inflate(R.layout.header_layout,null); 
    measureView(headerView); 
    arrowIv=(ImageView)headerView.findViewById(R.id.arrow); 
    refreshTv=(TextView)headerView.findViewById(R.id.tip); 
    progressBar=(ProgressBar)headerView.findViewById(R.id.progress); 
    headerHeight=headerView.getMeasuredHeight(); 
    LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 
      headerView.getMeasuredHeight()); 
    this.addView(headerView,lp); 
    setTopHeader(headerHeight); 

    final DisplayMetrics metrics=getResources().getDisplayMetrics(); 
    mSpinnerFinalOffset=DEFAULT_CIRCLE_TARGET*metrics.density; 
    mTotalDragDistance=mSpinnerFinalOffset; 
} 
/** 
* 通知父佈局,佔用的寬,高; 
* 
* @param view 
*/ 
private void measureView(View view) { 
    ViewGroup.LayoutParams p = view.getLayoutParams(); 
    if (p == null) { 
     p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 
       ViewGroup.LayoutParams.WRAP_CONTENT); 
    } 
    int width = ViewGroup.getChildMeasureSpec(0, 0, p.width); 
    int height; 
    int tempHeight = p.height; 
    if (tempHeight > 0) { 
     height = MeasureSpec.makeMeasureSpec(tempHeight, 
       MeasureSpec.EXACTLY); 
    } else { 
     height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 
    } 
    view.measure(width, height); 
} 
private void setTopHeader(int height) 
{ 
    if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB) 
    { 
     this.setY(-height); 
    }else{ 
     LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,height); 
     lp.topMargin=-height; 
     this.setLayoutParams(lp); 
    } 
    headerView.invalidate(); 
} 

/** 
* Set the listener to be notified when a refresh is triggered via the 
* pull gesture. 
* @param listener 
*/ 
public void setOnPullToRefresh(OnPullToRefresh listener) 
{ 
    this.refreshListener=listener; 
} 

/** 
* Set the listener to be notified when a load is triggered via the 
* drag gesture 
* @param listener 
*/ 
public void setOnDragToLoad(OnDragToLoad listener) 
{ 
    this.loadListener=listener; 
} 


private void ensureTarget(){ 
    if(mTarget==null){ 
     for(int i=0;i<getChildCount();i++) 
     { 
      View child=getChildAt(i); 
      if(child instanceof RecyclerView) 
      { 
       mTarget=child; 
       break; 
      } 
     } 
    } 
} 

/** 
* @return Whether it is possible for the child view of this layout to 
* scroll up.Override this if the child view is a custom view. 
*/ 
public boolean canChildScrollUp(){ 
    if(mTarget==null) 
    { 
     ensureTarget(); 
    } 
    if(Build.VERSION.SDK_INT<14) 
    { 
     if(mTarget instanceof AbsListView) 
     { 
      final AbsListView absListView=(AbsListView)mTarget; 
      return absListView.getChildCount()>0 
        &&(absListView.getFirstVisiblePosition()>0 
        ||absListView.getChildAt(0).getTop()<absListView.getPaddingTop()); 
     }else{ 
      return ViewCompat.canScrollVertically(mTarget,-1)|| mTarget.getScrollY()>0; 
     } 
    }else{ 
     return ViewCompat.canScrollVertically(mTarget,-1); 
    } 
} 

@Override 
public boolean onInterceptTouchEvent(MotionEvent ev) { 
    ensureTarget(); 
    final int action=MotionEventCompat.getActionMasked(ev); 

    if(mReturningToStart && action == MotionEvent.ACTION_DOWN){ 
     mReturningToStart = false; 
    } 

    if(!isEnabled() || mReturningToStart || canChildScrollUp() 
      ||mRefreshing || mNestedScrollInProgress){ 
     return false; 
    } 

    switch (action){ 
     case MotionEvent.ACTION_DOWN: 
      setTargetOffsetTopAndBottom(mOriginalOffsetTop-headerView.getTop(),true); 
      mActivePointerId=MotionEventCompat.getPointerId(ev,0); 
      mIsBeingDragged=false; 
      final float initialDownY=getMotionEventY(ev,mActivePointerId); 
      if(initialDownY==-1){ 
       return false; 
      } 
      mInitailDownY=initialDownY; 
      updateHeader=true; 
      break; 
     case MotionEvent.ACTION_MOVE: 
      if(mActivePointerId==INVALID_POINTER){ 
       Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id."); 
       return false; 
      } 
      final float y=getMotionEventY(ev,mActivePointerId); 
      if(y==-1){ 
       return false; 
      } 
      final float yDiff=y-mInitailDownY; 
      if(yDiff>mTouchSlop && !mIsBeingDragged){ 
       mInitialMotionY=mInitailDownY+mTouchSlop; 
       mIsBeingDragged=true; 
      } 
      break; 
     case MotionEventCompat.ACTION_POINTER_UP: 
      onSecondaryPointerUp(ev); 
      break; 
     case MotionEvent.ACTION_UP: 
     case MotionEvent.ACTION_CANCEL: 
      mIsBeingDragged=false; 
      mActivePointerId=INVALID_POINTER; 
      break; 
    } 
    return mIsBeingDragged; 
} 

private float getMotionEventY(MotionEvent ev,int activePointerId){ 
    final int index=MotionEventCompat.findPointerIndex(ev,activePointerId); 
    if(index<0){ 
     return -1; 
    } 
    return MotionEventCompat.getY(ev,index); 
} 
private void setTargetOffsetTopAndBottom(int offset,boolean requiresUpdate){ 
    if(this.getTop()<headerHeight+5) 
    { 
     this.offsetTopAndBottom(offset); 
     mCurrentTargetOffsetTop=this.getTop(); 
     if(requiresUpdate && Build.VERSION.SDK_INT<11){ 
      invalidate(); 
     } 

     if(this.getTop()>headerHeight) 
     { 
      if(updateHeader){ 
       updateHeader=false; 
       refreshTv.setText(getResources().getText(R.string.releasetorefresh)); 
       RotateAnimation animation=new RotateAnimation(0,180, 
         Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f); 
       animation.setDuration(800); 
       animation.setFillAfter(true); 
       arrowIv.startAnimation(animation); 
      } 
     } 
    } 

} 

private void onSecondaryPointerUp(MotionEvent ev){ 
    final int pointerIndex=MotionEventCompat.getActionIndex(ev); 
    final int pointerId=MotionEventCompat.getPointerId(ev,pointerIndex); 
    if(pointerId==mActivePointerId){ 
     //This was our active pointer going up. Choose a new 
     //active pointer and adjust accordingly. 
     final int newPointerIndex=pointerIndex==0?1:0; 
     mActivePointerId=MotionEventCompat.getPointerId(ev,newPointerIndex); 
    } 
} 
@Override 
public boolean onTouchEvent(MotionEvent event) 
{ 
    final int action=MotionEventCompat.getActionMasked(event); 
    int pointerIndex=-1; 

    if(mReturningToStart&&action==MotionEvent.ACTION_DOWN){ 
     mReturningToStart=false; 
    } 
    if(!isEnabled() || mReturningToStart 
      || canChildScrollUp() || mNestedScrollInProgress){ 
     //Fail fast if we're not in a state where a swipe is possible 
     return false; 
    } 
    switch(action){ 
     case MotionEvent.ACTION_DOWN: 
      mActivePointerId=MotionEventCompat.getPointerId(event,0); 
      mIsBeingDragged=false; 
      break; 
     case MotionEvent.ACTION_MOVE:{ 
      pointerIndex=MotionEventCompat.findPointerIndex(event,mActivePointerId); 
      if(pointerIndex<0){ 
       Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id."); 
       return false; 
      } 
      final float y=MotionEventCompat.getY(event,pointerIndex); 
      final float overscrollTop=(y-mInitialMotionY)*DRAG_RATE; 
      if(mIsBeingDragged){ 
       if(overscrollTop>0){ 
        moveSpinner(overscrollTop); 
       }else{ 
        return false; 
       } 
      } 
      break; 
     } 
     case MotionEventCompat.ACTION_POINTER_DOWN:{ 
      pointerIndex=MotionEventCompat.getActionIndex(event); 
      if(pointerIndex<0){ 
       Log.e(LOG_TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index."); 
       return false; 
      } 
      mActivePointerId=MotionEventCompat.getPointerId(event,pointerIndex); 
      break; 
     } 
     case MotionEvent.ACTION_POINTER_UP: 
      onSecondaryPointerUp(event); 
      break; 
     case MotionEvent.ACTION_UP:{ 
      pointerIndex=MotionEventCompat.findPointerIndex(event,mActivePointerId); 
      if(pointerIndex<0){ 
       Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id."); 
       return false; 
      } 
      final float y=MotionEventCompat.getY(event,pointerIndex); 
      mIsBeingDragged=false; 
      finishSpinner(); 
      mActivePointerId=INVALID_POINTER; 
      return false; 
     } 
     case MotionEvent.ACTION_CANCEL: 
      return false; 
    } 
    return true; 
} 

private void moveSpinner(float overscrollTop){ 
    float originalDragPercent=overscrollTop/mTotalDragDistance; 
    float dragPercent=Math.min(1f,Math.abs(originalDragPercent)); 
    float adjustedPercent=(float)Math.max(dragPercent-.4,0)*5/3; 
    float extraOS=Math.abs(overscrollTop)-mTotalDragDistance; 
    float slingshotDist=mSpinnerFinalOffset; 
    float tensionSlingshotPercent=Math.max(0,Math.min(extraOS,slingshotDist*2)/slingshotDist); 
    float tensionPercent=(float)((tensionSlingshotPercent/4)-Math.pow(
      (tensionSlingshotPercent/4),2))*2f; 
    float extraMove=(slingshotDist)*tensionPercent*2; 

    int targetY=mOriginalOffsetTop+(int)((slingshotDist*dragPercent)+extraMove); 
    setTargetOffsetTopAndBottom(targetY-mCurrentTargetOffsetTop,true); 
} 
private void finishSpinner(){ 
    if(this.getTop()>headerHeight){ 
     setRefreshing(true,true); 
    }else{ 
     //cancel refresh 
     mRefreshing=false; 
     animateOffsetToStartPosition(); 
    } 
} 
private void setRefreshing(boolean refreshing,final boolean notify) 
{ 
    if(mRefreshing!=refreshing){ 
     ensureTarget(); 
     mRefreshing=refreshing; 
     if(mRefreshing){ 
      refreshListener.onRefresh(); 
      arrowIv.setVisibility(View.GONE); 
      progressBar.setVisibility(View.VISIBLE); 
     }else{ 
      arrowIv.setVisibility(View.VISIBLE); 
      progressBar.setVisibility(View.GONE); 
      animateOffsetToStartPosition(); 
     } 
    } 
} 
public void setRefreshing(boolean refreshing){ 
    if(!refreshing){ 
     setRefreshing(refreshing,false); 
    } 
} 
private void animateOffsetToStartPosition(){ 
    refreshTv.setText(getResources().getText(R.string.pulltorefresh)); 
    arrowIv.clearAnimation(); 
    Log.d(LOG_TAG,"getTop="+this.getTop()+" timer="+((timer==null)?"null":"notnumm")); 
    if(timer==null&&this.getTop()>0) 
    { 
     timer=new Timer(); 
     timer.schedule(new TimerTask() { 
      @Override 
      public void run() { 
       handler.post(new Runnable() { 
        @Override 
        public void run() { 
         if(thisView.getTop()>0) 
         { 
          thisView.offsetTopAndBottom(-1); 
          mCurrentTargetOffsetTop = headerView.getTop(); 
          if (Build.VERSION.SDK_INT < 11) { 
           invalidate(); 
          } 
         }else{ 
          Log.d(LOG_TAG,"cancel"); 
          timer.cancel(); 
          timer=null; 
         } 
        } 
       }); 
      } 
     },10,10); 
    } 
} 

/** 
* Classes that wish to be notified when the pull gesture correctly 
* triggers a refresh should implement this interface. 
*/ 
public interface OnPullToRefresh{ 
    public void onRefresh(); 
} 

/** 
* Classes that wish to be notified when the drag gesture correctly 
* triggers a load should implement this interface. 
*/ 
public interface OnDragToLoad{ 
    public void onLoad(); 
}} 
+0

我已經使用setY()來解決這個問題。但我也想知道爲什麼。而我讀了offsetTopAndBottom()的源碼,也找不到任何線索。 –

回答

0

offsetTopAndBottom(偏移量)將增加TOP與mBottom查看由抵消。

private void animateOffsetToStartPosition(){ 
    refreshTv.setText(getResources().getText(R.string.pulltorefresh)); 
    arrowIv.clearAnimation(); 
    Log.d(LOG_TAG,"getTop="+this.getTop()+" timer="+((timer==null)?"null":"notnumm")); 
    if(timer==null&&this.getTop()>0) 
    { 
     timer=new Timer(); 
     timer.schedule(new TimerTask() { 
      @Override 
      public void run() { 
       handler.post(new Runnable() { 
        @Override 
        public void run() { 
         if(thisView.getTop()>0) 
         { 
          //this line says that if top of thisView is not 0,add mtop and mBottom of thisView by -1 
          //this timer will change the mTop to 0.thisView will be back to original place if mTop == 0. 
          thisView.offsetTopAndBottom(-1); 
          mCurrentTargetOffsetTop = headerView.getTop(); 
          if (Build.VERSION.SDK_INT < 11) { 
           invalidate(); 
          } 
         }else{ 
          Log.d(LOG_TAG,"cancel"); 
          timer.cancel(); 
          timer=null; 
         } 
        } 
       }); 
      } 
     },10,10); 
    } 
} 
+0

我知道,有時會自動回到頂端。 –

+0

你的意思是說,如果你經常拉它,有時候會立即回到頂端? –

0

SETY()調用setTranslationY(),這使得兩個調用invalidateViewProperty(布爾invalidateParent,布爾forceRedraw)。 在setTranslationY()中,當它調用invalidateViewProperty時,它將forceRedraw作爲true傳遞,重繪視圖並將其恢復爲原始狀態。