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


84

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

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


Я использовал ту же технику, что и Энди здесь, на моей 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"для этого макета.


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();
    }
}

Можно ли использовать это, чтобы сначала свернуть существующий LinearLayout, а затем снова развернуть тот же LinearLayout? Когда я пытаюсь это сделать, он просто сворачивается и больше не расширяется (вероятно, потому, что высота представления теперь равна 0 или что-то в этом роде).
AHaahr

Я обнаружил, что он работает более надежно, когда линейный макет содержит более одного представления. Если он содержит только одно представление, он не всегда будет расширяться.
Изучите OpenGL ES
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.