/** * Starts the underlying Animator for a set of properties. We use a single animator that simply * runs from 0 to 1, and then use that fractional value to set each property value accordingly. */ private void startAnimation() { if (mRTBackend != null && mRTBackend.startAnimation(this)) { return; } mView.setHasTransientState(true); ValueAnimator animator = ValueAnimator.ofFloat(1.0f); ArrayList<NameValuesHolder> nameValueList = (ArrayList<NameValuesHolder>) mPendingAnimations.clone(); mPendingAnimations.clear(); int propertyMask = 0; int propertyCount = nameValueList.size(); for (int i = 0; i < propertyCount; ++i) { NameValuesHolder nameValuesHolder = nameValueList.get(i); propertyMask |= nameValuesHolder.mNameConstant; } mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList)); if (mPendingSetupAction != null) { mAnimatorSetupMap.put(animator, mPendingSetupAction); mPendingSetupAction = null; } if (mPendingCleanupAction != null) { mAnimatorCleanupMap.put(animator, mPendingCleanupAction); mPendingCleanupAction = null; } if (mPendingOnStartAction != null) { mAnimatorOnStartMap.put(animator, mPendingOnStartAction); mPendingOnStartAction = null; } if (mPendingOnEndAction != null) { mAnimatorOnEndMap.put(animator, mPendingOnEndAction); mPendingOnEndAction = null; } animator.addUpdateListener(mAnimatorEventListener); animator.addListener(mAnimatorEventListener); if (mStartDelaySet) { animator.setStartDelay(mStartDelay); } if (mDurationSet) { animator.setDuration(mDuration); } if (mInterpolatorSet) { animator.setInterpolator(mInterpolator); } animator.start(); }
/** * This method collapses the view that was clicked and animates all the views around it to close * around the collapsing view. There are several steps required to do this which are outlined * below. * * <p>1. Update the layout parameters of the view clicked so as to minimize its height to the * original collapsed (default) state. 2. After invoking a layout, the listview will shift all the * cells so as to display them most efficiently. Therefore, during the first predraw pass, the * listview must be offset by some amount such that given the custom bound change upon collapse, * all the cells that need to be on the screen after the layout are rendered by the listview. 3. * On the second predraw pass, all the items are first returned to their original location (before * the first layout). 4. The collapsing view's bounds are animated to what the final values should * be. 5. The bounds above the collapsing view are animated downwards while the bounds below the * collapsing view are animated upwards. 6. The extra text is faded out as its contents become * visible throughout the animation process. */ private void prepareCollapseView(final CardView view, final View expandingLayout) { final Card card = (Card) getItemAtPosition(getPositionForView(view)); /* Store the original top and bottom bounds of all the cells.*/ final int oldTop = view.getTop(); final int oldBottom = view.getBottom(); final HashMap<View, int[]> oldCoordinates = new HashMap<View, int[]>(); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View v = getChildAt(i); if (Build.VERSION.SDK_INT >= 16) { v.setHasTransientState(true); } oldCoordinates.put(v, new int[] {v.getTop(), v.getBottom()}); } /* Update the layout so the extra content becomes invisible.*/ view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, view.getCollapsedHeight())); /* Add an onPreDraw listener. */ final ViewTreeObserver observer = getViewTreeObserver(); observer.addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { if (!mShouldRemoveObserver) { /*Same as for expandingView, the parameters for setSelectionFromTop must * be determined such that the necessary cells of the ListView are rendered * and added to it.*/ mShouldRemoveObserver = true; int newTop = view.getTop(); int newBottom = view.getBottom(); int newHeight = newBottom - newTop; int oldHeight = oldBottom - oldTop; int deltaHeight = oldHeight - newHeight; mTranslate = getTopAndBottomTranslations(oldTop, oldBottom, deltaHeight, false); int currentTop = view.getTop(); int futureTop = oldTop + mTranslate[0]; int firstChildStartTop = getChildAt(0).getTop(); int firstVisiblePosition = getFirstVisiblePosition(); int deltaTop = currentTop - futureTop; int i; int childCount = getChildCount(); for (i = 0; i < childCount; i++) { View v = getChildAt(i); int height = v.getBottom() - Math.max(0, v.getTop()); if (deltaTop - height > 0) { firstVisiblePosition++; deltaTop -= height; } else { break; } } if (i > 0) { firstChildStartTop = 0; } setSelection(firstVisiblePosition); // setSelectionFromTop(firstVisiblePosition, firstChildStartTop - deltaTop); requestLayout(); return false; } mShouldRemoveObserver = false; observer.removeOnPreDrawListener(this); int yTranslateTop = mTranslate[0]; int yTranslateBottom = mTranslate[1]; int index = indexOfChild(view); int numOfColumns = getNumColumns(); int rowOfSelectedItem = (int) index / numOfColumns; int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View v = getChildAt(i); int[] old = oldCoordinates.get(v); if (old != null) { /* If the cell was present in the ListView before the collapse and * after the collapse then the bounds are reset to their old values.*/ v.setTop(old[0]); v.setBottom(old[1]); if (Build.VERSION.SDK_INT >= 16) { v.setHasTransientState(false); } } else { /* If the cell is present in the ListView after the collapse but * not before the collapse then the bounds are calculated using * the bottom and top translation of the collapsing cell.*/ int rowOfv = (int) i / numOfColumns; int delta = (i > index && rowOfv > rowOfSelectedItem) ? yTranslateBottom : -yTranslateTop; v.setTop(v.getTop() + delta); v.setBottom(v.getBottom() + delta); } } /* Animates all the cells present on the screen after the collapse. */ ArrayList<Animator> animations = new ArrayList<Animator>(); for (int i = 0; i < childCount; i++) { View v = getChildAt(i); if (v != view) { float diff = i > index ? -yTranslateBottom : yTranslateTop; animations.add(getAnimation(v, diff, diff)); } } /* ValueAnimator animator = ValueAnimator.ofInt( yTranslateTop,-yTranslateBottom); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { int value = (Integer) valueAnimator.getAnimatedValue(); ViewGroup.LayoutParams layoutParams = expandingLayout.getLayoutParams(); layoutParams.height = value; expandingLayout.setLayoutParams(layoutParams); } }); animations.add(animator);*/ /* Adds animation for collapsing the cell that was clicked. */ animations.add(getAnimation(view, yTranslateTop, -yTranslateBottom)); /* Adds an animation for fading out the extra content. */ animations.add(ObjectAnimator.ofFloat(expandingLayout, ALPHA, 1, 0)); /* Disabled the ListView for the duration of the animation.*/ setEnabled(false); setClickable(false); /* Play all the animations created above together at the same time. */ AnimatorSet s = new AnimatorSet(); s.playTogether(animations); s.addListener( new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { expandingLayout.setVisibility(View.GONE); view.setLayoutParams( new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); view.setExpanded(false); setEnabled(true); setClickable(true); /* Note that alpha must be set back to 1 in case this view is reused * by a cell that was expanded, but not yet collapsed, so its state * should persist in an expanded state with the extra content visible.*/ expandingLayout.setAlpha(1); if (card.getOnCollapseAnimatorEndListener() != null) card.getOnCollapseAnimatorEndListener().onCollapseEnd(card); } }); s.start(); return true; } }); }
private void prepareExpandView(final CardView view, final View expandingLayout) { final Card card = (Card) getItemAtPosition(getPositionForView(view)); /* Store the original top and bottom bounds of all the cells.*/ final int oldTop = view.getTop(); final int oldBottom = view.getBottom(); final HashMap<View, int[]> oldCoordinates = new HashMap<View, int[]>(); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View v = getChildAt(i); if (Build.VERSION.SDK_INT >= 16) { v.setHasTransientState(true); } oldCoordinates.put(v, new int[] {v.getTop(), v.getBottom()}); } /* Update the layout so the extra content becomes visible.*/ if (expandingLayout != null) expandingLayout.setVisibility(View.VISIBLE); /* Add an onPreDraw Listener to the listview. onPreDraw will get invoked after onLayout * and onMeasure have run but before anything has been drawn. This * means that the final post layout properties for all the items have already been * determined, but still have not been rendered onto the screen.*/ final ViewTreeObserver observer = getViewTreeObserver(); observer.addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { /* Determine if this is the first or second pass.*/ if (!mShouldRemoveObserver) { mShouldRemoveObserver = true; /* Calculate what the parameters should be for setSelectionFromTop. * The ListView must be offset in a way, such that after the animation * takes place, all the cells that remain visible are rendered completely * by the ListView.*/ int newTop = view.getTop(); int newBottom = view.getBottom(); int newHeight = newBottom - newTop; int oldHeight = oldBottom - oldTop; int delta = newHeight - oldHeight; mTranslate = getTopAndBottomTranslations(oldTop, oldBottom, delta, true); int currentTop = view.getTop(); int futureTop = oldTop - mTranslate[0]; int firstChildStartTop = getChildAt(0).getTop(); int firstVisiblePosition = getFirstVisiblePosition(); int deltaTop = currentTop - futureTop; int i; int childCount = getChildCount(); for (i = 0; i < childCount; i++) { View v = getChildAt(i); int height = v.getBottom() - Math.max(0, v.getTop()); if (deltaTop - height > 0) { firstVisiblePosition++; deltaTop -= height; } else { break; } } if (i > 0) { firstChildStartTop = 0; } setSelection(firstVisiblePosition); // setSelectionFromTop(firstVisiblePosition, firstChildStartTop - deltaTop); /* Request another layout to update the layout parameters of the cells.*/ requestLayout(); /* Return false such that the ListView does not redraw its contents on * this layout but only updates all the parameters associated with its * children.*/ return false; } /* Remove the predraw listener so this method does not keep getting called. */ mShouldRemoveObserver = false; observer.removeOnPreDrawListener(this); int yTranslateTop = mTranslate[0]; int yTranslateBottom = mTranslate[1]; ArrayList<Animator> animations = new ArrayList<Animator>(); int index = indexOfChild(view); int numOfColumns = getNumColumns(); int rowOfSelectedItem = (int) index / numOfColumns; /* Loop through all the views that were on the screen before the cell was * expanded. Some cells will still be children of the ListView while * others will not. The cells that remain children of the ListView * simply have their bounds animated appropriately. The cells that are no * longer children of the ListView also have their bounds animated, but * must also be added to a list of views which will be drawn in dispatchDraw.*/ for (View v : oldCoordinates.keySet()) { int[] old = oldCoordinates.get(v); v.setTop(old[0]); v.setBottom(old[1]); if (v.getParent() == null) { mViewsToDraw.add(v); int delta = old[0] < oldTop ? -yTranslateTop : yTranslateBottom; animations.add(getAnimation(v, delta, delta)); } else { int i = indexOfChild(v); if (v != view) { int rowOfv = (int) i / numOfColumns; int delta = (i > index && rowOfv > rowOfSelectedItem) ? yTranslateBottom : -yTranslateTop; animations.add(getAnimation(v, delta, delta)); } v.setHasTransientState(false); } } /* Adds animation for expanding the cell that was clicked. */ animations.add(getAnimation(view, -yTranslateTop, yTranslateBottom)); /* Adds an animation for fading in the extra content. */ animations.add(ObjectAnimator.ofFloat(expandingLayout, ALPHA, 0, 1)); /* Disabled the ListView for the duration of the animation.*/ setEnabled(false); setClickable(false); /* Play all the animations created above together at the same time. */ AnimatorSet s = new AnimatorSet(); s.playTogether(animations); s.addListener( new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setExpanded(true); // card.setExpanded(true); setEnabled(true); setClickable(true); if (mViewsToDraw.size() > 0) { for (View v : mViewsToDraw) { if (Build.VERSION.SDK_INT >= 16) { v.setHasTransientState(false); } } } mViewsToDraw.clear(); if (card.getOnExpandAnimatorEndListener() != null) card.getOnExpandAnimatorEndListener().onExpandEnd(card); } }); s.start(); return true; } }); }
public static void setHasTransientState(View view, boolean hasTransientState) { view.setHasTransientState(hasTransientState); }