Как анимировать View.setVisibility (GONE)

84

Я хочу сделать, Animationкогда для Viewнего будет установлена ​​видимость GONE. Вместо того, чтобы просто исчезнуть, он Viewдолжен «рухнуть». Я пробовал это с помощью, ScaleAnimationно затем Viewпроисходит коллапс, но макет изменяет размер только после (или до) Animationостановок (или запусков).

Как я могу сделать Animationтак, чтобы во время анимации нижние Views оставались прямо под содержимым, вместо того, чтобы иметь пустое пространство?

MrSnowflake
источник
Я использовал ту же технику, что и Энди здесь, на моей ExpandAnimation: udinic.wordpress.com/2011/09/03/expanding-listview-items Я не использовал масштабную анимацию, я только что создал новую анимацию. класс для этого.
Udinic 03
Это было очень полезно, пока я пытался это сделать. Спасибо
atraudes
Отличный Удинич .. действительно решил мою проблему .. :) спасибо
Юсуф Куреши
Отлично, мне нужно приспособиться к моей проблеме, но в конце концов это работает. Для меня это решение было лучше, чем другой ответ.
Derzu

Ответы:

51

Кажется, не существует простого способа сделать это через API, потому что анимация просто изменяет матрицу рендеринга представления, а не фактический размер. Но мы можем установить отрицательное поле, чтобы заставить LinearLayout думать, что представление становится меньше.

Поэтому я бы рекомендовал создать свой собственный класс анимации на основе ScaleAnimation и переопределить метод «applyTransformation», чтобы установить новые поля и обновить макет. Как это...

public class Q2634073 extends Activity implements OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.q2634073);
        findViewById(R.id.item1).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        view.startAnimation(new MyScaler(1.0f, 1.0f, 1.0f, 0.0f, 500, view, true));
    }

    public class MyScaler extends ScaleAnimation {

        private View mView;

        private LayoutParams mLayoutParams;

        private int mMarginBottomFromY, mMarginBottomToY;

        private boolean mVanishAfter = false;

        public MyScaler(float fromX, float toX, float fromY, float toY, int duration, View view,
                boolean vanishAfter) {
            super(fromX, toX, fromY, toY);
            setDuration(duration);
            mView = view;
            mVanishAfter = vanishAfter;
            mLayoutParams = (LayoutParams) view.getLayoutParams();
            int height = mView.getHeight();
            mMarginBottomFromY = (int) (height * fromY) + mLayoutParams.bottomMargin - height;
            mMarginBottomToY = (int) (0 - ((height * toY) + mLayoutParams.bottomMargin)) - height;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            if (interpolatedTime < 1.0f) {
                int newMarginBottom = mMarginBottomFromY
                        + (int) ((mMarginBottomToY - mMarginBottomFromY) * interpolatedTime);
                mLayoutParams.setMargins(mLayoutParams.leftMargin, mLayoutParams.topMargin,
                    mLayoutParams.rightMargin, newMarginBottom);
                mView.getParent().requestLayout();
            } else if (mVanishAfter) {
                mView.setVisibility(View.GONE);
            }
        }

    }

}

Применяется обычное предостережение: поскольку мы переопределяем защищенный метод (applyTransformation), это не гарантирует работу в будущих версиях Android.

Энди
источник
3
Какого черта я об этом не подумал ?! Спасибо. Также я не понимаю: «Применяется обычное предостережение: поскольку мы переопределяем защищенный метод (applyTransformation), это не гарантирует работу в будущих версиях Android». - Почему защищенные функции различаются в разных версиях API? Они не скрыты и реализованы защищенными, чтобы вы могли их переопределить (в противном случае они были бы ограничены пакетом).
MrSnowflake
Вы, наверное, правы насчет защищенного метода. Я склонен излишне осторожно обращаться к ним через API.
Энди
1
Это отлично сработало для меня, за исключением того, что для того, чтобы заставить работать «сворачивание» (от Y = 0,0f, до Y = 1,0f), мне пришлось удалить 0 - в marginBottomToYрасчетах.
dmon
2
Я бы предложил использовать общий тип MarginLayoutParamsвместо того, чтобы приводить его к определенному LayoutParamтипу.
Пол Ламмертсма
3
Предположим, мне нужно переключить анимацию, тогда как это сделать в обратном порядке. Пожалуйста посоветуй.
Умеш
99

Поместите вид в макет, если это не так, и установите его android:animateLayoutChanges="true"для этого макета.

Vinay W
источник
1
Минимальный требуемый API - 11 или выше! Невозможно использовать этот метод для более ранней версии.
Нагарадж Алагусударам
2
это самый недооцененный атрибут макета ... спасибо!
Андрес Сантьяго
7

Я использовал ту же технику, что и Энди. Я написал для этого свой собственный класс Animation, который анимирует значение поля, вызывая исчезновение / появление эффекта элемента. Выглядит это так:

public class ExpandAnimation extends Animation {

// Initializations...

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    super.applyTransformation(interpolatedTime, t);

    if (interpolatedTime < 1.0f) {

        // Calculating the new bottom margin, and setting it
        mViewLayoutParams.bottomMargin = mMarginStart
                + (int) ((mMarginEnd - mMarginStart) * interpolatedTime);

        // Invalidating the layout, making us seeing the changes we made
        mAnimatedView.requestLayout();
    }
}
}

У меня есть полный пример, который работает в моем сообщении в блоге http://udinic.wordpress.com/2011/09/03/expanding-listview-items/

Удинич
источник
2

Здесь я использовал ту же технику, что и Энди, и усовершенствовал ее, чтобы ее можно было использовать для расширения и свертывания без сбоев, также используя метод, описанный здесь: https://stackoverflow.com/a/11426510/1317564

import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.ScaleAnimation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;

class LinearLayoutVerticalScaleAnimation extends ScaleAnimation {
    private final LinearLayout view;
    private final LinearLayout.LayoutParams layoutParams;

    private final float beginY;
    private final float endY;
    private final int originalBottomMargin;

    private int expandedHeight;
    private boolean marginsInitialized = false;
    private int marginBottomBegin;
    private int marginBottomEnd;

    private ViewTreeObserver.OnPreDrawListener preDrawListener;

    LinearLayoutVerticalScaleAnimation(float beginY, float endY,
            LinearLayout linearLayout) {
        super(1f, 1f, beginY, endY);

        this.view = linearLayout;
        this.layoutParams = (LinearLayout.LayoutParams) linearLayout.getLayoutParams();

        this.beginY = beginY;
        this.endY = endY;
        this.originalBottomMargin = layoutParams.bottomMargin;

        if (view.getHeight() != 0) {
            expandedHeight = view.getHeight();
            initializeMargins();
        }
    }

    private void initializeMargins() {
        final int beginHeight = (int) (expandedHeight * beginY);
        final int endHeight = (int) (expandedHeight * endY);

        marginBottomBegin = beginHeight + originalBottomMargin - expandedHeight;
        marginBottomEnd = endHeight + originalBottomMargin - expandedHeight;
        marginsInitialized = true;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);     

        if (!marginsInitialized && preDrawListener == null) {                       
            // To avoid glitches, don't draw until we've initialized everything.
            preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {                    
                    if (view.getHeight() != 0) {
                        expandedHeight = view.getHeight();
                        initializeMargins();
                        adjustViewBounds(0f);
                        view.getViewTreeObserver().removeOnPreDrawListener(this);                               
                    }

                    return false;
                }
            };

            view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);                   
        }

        if (interpolatedTime < 1.0f && view.getVisibility() != View.VISIBLE) {          
            view.setVisibility(View.VISIBLE);           
        }

        if (marginsInitialized) {           
            if (interpolatedTime < 1.0f) {
                adjustViewBounds(interpolatedTime);
            } else if (endY <= 0f && view.getVisibility() != View.GONE) {               
                view.setVisibility(View.GONE);
            }
        }
    }

    private void adjustViewBounds(float interpolatedTime) {
        layoutParams.bottomMargin = 
                marginBottomBegin + (int) ((marginBottomEnd - marginBottomBegin) * interpolatedTime);       

        view.getParent().requestLayout();
    }
}
Изучите OpenGL ES
источник
Можно ли использовать это, чтобы сначала свернуть существующий LinearLayout, а затем снова развернуть тот же LinearLayout? Когда я пытаюсь это сделать, он просто сворачивается и больше не расширяется (вероятно, потому, что высота представления теперь равна 0 или что-то в этом роде).
AHaahr
Я обнаружил, что он работает более надежно, когда линейный макет содержит более одного представления. Если он содержит только одно представление, он не всегда будет расширяться.
Изучите OpenGL ES