2015-09-25 90 views
-1

我試圖在RecyclerView行中創建動畫,Helper類延伸ItemTouchHelper.Callback使用ItemTouchHelper.Callback實現「回彈」RecyclerView動畫

ItemTouchHelper默認的「滑動」行爲是,如果一行以足夠的速度滑動,它將從視圖中消失(即滑動到昏暗)。就我而言,我想創建一個動畫,如果發生這樣的事件,會導致該行直線跳回。

我已經能夠創建一個ValueAnimator,如果將它從屏幕上移開,就可以將行重新設置爲視圖。但問題是,我無法讓父類ItemTouchHelper類識別它存儲的x值已從行的寬度(因此它完全在屏幕外)變爲0(以便它重新顯示在屏幕上)。

這意味着,無論何時我在完成自定義動畫之後與該行進行交互,其行爲就好像該行仍處於屏幕外,即使它完全可見。這通常意味着按下它意味着行從X位置0跳轉到行寬度的X位置,因此再次從屏幕邊緣再次進入動畫。它會在完全重置並正常運行之前執行此操作幾次。

此視頻將幫助顯示問題。由於前兩次刷卡不在屏幕外,所以默認行爲有效。之後該行被刷過不過屏幕和彈回,點擊行顯示它從右側邊緣查驗回:

Youtube - Issue with ItemTouchHelper.Callback

這裏是我用來進行動畫代碼:

public class CustomItemTouchHelper extends ItemTouchHelper.Callback { 

    private RecoverAnimation ra; 
    private boolean animatorRunning; 

//... 

    @Override 
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { 
     final float per = (dX/viewHolder.itemView.getWidth()); 
     if (per > 0.8F && !animatorRunning && !isCurrentlyActive) { 
      ra = new RecoverAnimation(c, recyclerView, viewHolder, ItemTouchHelper.ANIMATION_TYPE_SWIPE_CANCEL, actionState, dX, dY, 0, 0); 
      ra.start(); 
     } else { 
      getDefaultUIUtil().onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState, isCurrentlyActive); 
     } 
    } 

//... 

注意,這個內部類,RecoverAnimation,從ItemTouchHelper源代碼拉:

private class RecoverAnimation implements AnimatorListenerCompat { 

    final float mStartDx; 

    final float mStartDy; 

    final float mTargetX; 

    final float mTargetY; 

    final Canvas mCanvas; 

    final RecyclerView mRecyclerView; 

    final RecyclerView.ViewHolder mViewHolder; 

    final int mActionState; 

    private final ValueAnimatorCompat mValueAnimator; 

    float mX; 

    float mY; 

    // if user starts touching a recovering view, we put it into interaction mode again, 
    // instantly. 
    boolean mOverridden = false; 

    private boolean mEnded = false; 

    private float mFraction; 

    public RecoverAnimation(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, 
          int actionState, float startDx, float startDy, float targetX, float targetY) { 

     mRecyclerView = recyclerView; 
     mCanvas = c; 

     mActionState = actionState; 
     mViewHolder = viewHolder; 
     mStartDx = startDx; 
     mStartDy = startDy; 
     mTargetX = targetX; 
     mTargetY = targetY; 
     mValueAnimator = AnimatorCompatHelper.emptyValueAnimator(); 
     mValueAnimator.addUpdateListener(
       new AnimatorUpdateListenerCompat() { 
        @Override 
        public void onAnimationUpdate(ValueAnimatorCompat animation) { 
         setFraction(animation.getAnimatedFraction()); 
         update(); 
        } 
       }); 
     mValueAnimator.setTarget(viewHolder.itemView); 
     mValueAnimator.addListener(this); 
     setFraction(0f); 
    } 

    public void setDuration(long duration) { 
     mValueAnimator.setDuration(duration); 
    } 

    public void start() { 
     animatorRunning = true; 
     mViewHolder.setIsRecyclable(false); 
     mValueAnimator.start(); 
    } 

    public void cancel() { 
     mValueAnimator.cancel(); 
    } 

    public void setFraction(float fraction) { 
     mFraction = fraction; 
    } 

    /** 
    * We run updates on onDraw method but use the fraction from animator callback. 
    * This way, we can sync translate x/y values w/ the animators to avoid one-off frames. 
    */ 
    public void update() { 
     if (mStartDx == mTargetX) { 
      mX = ViewCompat.getTranslationX(mViewHolder.itemView); 
     } else { 
      mX = mStartDx + mFraction * (mTargetX - mStartDx); 
     } 
     if (mStartDy == mTargetY) { 
      mY = ViewCompat.getTranslationY(mViewHolder.itemView); 
     } else { 
      mY = mStartDy + mFraction * (mTargetY - mStartDy); 
     } 
     getDefaultUIUtil().onDraw(mCanvas, mRecyclerView, mViewHolder.itemView, mX, mY, mActionState, false); 
    } 

    @Override 
    public void onAnimationStart(ValueAnimatorCompat animation) { 

    } 

    @Override 
    public void onAnimationEnd(ValueAnimatorCompat animation) { 
     animatorRunning = false; 
     mEnded = true; 
     getDefaultUIUtil().onDraw(mCanvas, mRecyclerView, mViewHolder.itemView, 0, 0, ItemTouchHelper.ACTION_STATE_IDLE, false); 
     getDefaultUIUtil().clearView(mViewHolder.itemView); 
    } 

    @Override 
    public void onAnimationCancel(ValueAnimatorCompat animation) { 
     setFraction(1f); //make sure we recover the view's state. 
    } 

    @Override 
    public void onAnimationRepeat(ValueAnimatorCompat animation) { 

    } 
} 

是療法我能做的任何事情來抵消這個問題,還是根本不可能(但)?

+0

你知道如何解決這個問題嗎? – IgorOliveira

+0

@IgorOliveira不!我最終認定這不值得冒險,並採用不同的解決方案。 – PPartisan

+0

按照你的代碼,我管理了一個很好的解決方案。不是最好的解決方案,但它的流暢和可靠(至少現在)。 – IgorOliveira

回答

4

以下解決方案將反彈回recyclerview項目,而無需實施您的自定義RecoverAnimation類。

public class CustomItemTouchHelper extends ItemTouchHelper.Callback { 

     private RecoverAnimation ra; 
     private boolean animatorRunning; 
     private boolean swipeBack = false; 

    //... 

     @Override 
     public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { 
      recyclerView.setOnTouchListener(new View.OnTouchListener() { 
      @Override 
      public boolean onTouch(View v, MotionEvent event) 
      { 
       if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) 
       { 
        swipeBack = true; 
       } 
       else 
       { 
        swipeBack = false; 
       } 
       return false; 
      } 
     }); 
     } 

     @Override 
     public int convertToAbsoluteDirection(int flags, int layoutDirection) { 
      return swipeBack ? 0 : super.convertToAbsoluteDirection(flags, layoutDirection); 
     } 

    //...