Подтвердить что ты не робот

Как анимировать ImageView из центра, чтобы заполнить экран и наоборот (стиль facebook)?

Фон

Приложение Facebook имеет приятную анимацию перехода между маленьким изображением на столбе и расширенный режим, который пользователь может также увеличить.

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

Это можно увидеть, используя следующий эскиз, который я сделал:

enter image description here

Вопрос

Как они это сделали? у них действительно есть 2 просмотра, оживляющие, чтобы раскрыть контент?

Как они делали это настолько жидким, как если бы это было одно представление?

единственный учебник, который я видел (ссылка здесь) изображения, которое увеличено до полноэкранного не отображается хорошо, когда миниатюра настроена на обрезку по центру.

Не только это, но и работает даже на низком API Android.

Кто-нибудь знает о библиотеке, которая обладает подобной способностью?


EDIT: я нашел способ и отправил ответ, но он основан на изменении layoutParams, и я думаю, что это не эффективным и рекомендуемым.

Я пробовал использовать обычные анимации и другие анимационные трюки, но пока что единственное, что сработало для меня.

Если кто-то знает, что делать, чтобы заставить его работать лучше, напишите его.

4b9b3361

Ответ 1

Хорошо, я нашел возможный способ сделать это. Я сделал layoutParams как переменные, которые продолжают меняться с помощью ObjectAnimator в девятой библиотеке Android.. я думаю, что это не лучший способ добиться этого, поскольку он вызывает много onDraw и onLayout, но если контейнер имеет только несколько просмотров и не меняет его размер, возможно, это нормально.

предполагается, что изображениеView, которое я animate, в конечном итоге примет нужный размер, и что (в настоящее время) и миниатюра, и анимированное изображениеView имеют один и тот же контейнер (но его легко изменить.

как я протестировал, также можно добавить функции масштабирования, расширив класс TouchImageView. вы просто устанавливаете тип шкалы в начале для обрезки по центру, и когда анимация заканчивается, вы возвращаете ее обратно в матрицу, и если хотите, вы можете установить layoutParams для заполнения всего контейнера (и установить маржу на 0,0).

Мне также интересно, почему AnimatorSet не работает для меня, поэтому я покажу здесь что-то, что работает, надеясь, что кто-то скажет мне, что я должен делать.

здесь код:

MainActivity.java

public class MainActivity extends Activity {
    private static final int IMAGE_RES_ID = R.drawable.test_image_res_id;
    private static final int ANIM_DURATION = 5000;
    private final Handler mHandler = new Handler();
    private ImageView mThumbnailImageView;
    private CustomImageView mFullImageView;
    private Point mFitSizeBitmap;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mFullImageView = (CustomImageView) findViewById(R.id.fullImageView);
        mThumbnailImageView = (ImageView) findViewById(R.id.thumbnailImageView);
        mHandler.postDelayed(new Runnable() {

            @Override
            public void run() {
                prepareAndStartAnimation();
            }

        }, 2000);
    }

    private void prepareAndStartAnimation() {
        final int thumbX = mThumbnailImageView.getLeft(), thumbY = mThumbnailImageView.getTop();
        final int thumbWidth = mThumbnailImageView.getWidth(), thumbHeight = mThumbnailImageView.getHeight();
        final View container = (View) mFullImageView.getParent();
        final int containerWidth = container.getWidth(), containerHeight = container.getHeight();
        final Options bitmapOptions = getBitmapOptions(getResources(), IMAGE_RES_ID);
        mFitSizeBitmap = getFitSize(bitmapOptions.outWidth, bitmapOptions.outHeight, containerWidth, containerHeight);

        mThumbnailImageView.setVisibility(View.GONE);
        mFullImageView.setVisibility(View.VISIBLE);
        mFullImageView.setContentWidth(thumbWidth);
        mFullImageView.setContentHeight(thumbHeight);
        mFullImageView.setContentX(thumbX);
        mFullImageView.setContentY(thumbY);
        runEnterAnimation(containerWidth, containerHeight);
    }

    private Point getFitSize(final int width, final int height, final int containerWidth, final int containerHeight) {
        int resultHeight, resultWidth;
        resultHeight = height * containerWidth / width;
        if (resultHeight <= containerHeight) {
            resultWidth = containerWidth;
        } else {
            resultWidth = width * containerHeight / height;
            resultHeight = containerHeight;
        }
        return new Point(resultWidth, resultHeight);
    }

    public void runEnterAnimation(final int containerWidth, final int containerHeight) {
        final ObjectAnimator widthAnim = ObjectAnimator.ofInt(mFullImageView, "contentWidth", mFitSizeBitmap.x)
                .setDuration(ANIM_DURATION);
        final ObjectAnimator heightAnim = ObjectAnimator.ofInt(mFullImageView, "contentHeight", mFitSizeBitmap.y)
                .setDuration(ANIM_DURATION);
        final ObjectAnimator xAnim = ObjectAnimator.ofInt(mFullImageView, "contentX",
                (containerWidth - mFitSizeBitmap.x) / 2).setDuration(ANIM_DURATION);
        final ObjectAnimator yAnim = ObjectAnimator.ofInt(mFullImageView, "contentY",
                (containerHeight - mFitSizeBitmap.y) / 2).setDuration(ANIM_DURATION);
        widthAnim.start();
        heightAnim.start();
        xAnim.start();
        yAnim.start();
        // TODO check why using AnimatorSet doesn't work here:
        // final com.nineoldandroids.animation.AnimatorSet set = new AnimatorSet();
        // set.playTogether(widthAnim, heightAnim, xAnim, yAnim);
    }

    public static BitmapFactory.Options getBitmapOptions(final Resources res, final int resId) {
        final BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
        bitmapOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, bitmapOptions);
        return bitmapOptions;
    }

}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <com.example.facebookstylepictureanimationtest.CustomImageView
        android:id="@+id/fullImageView"
        android:layout_width="0px"
        android:layout_height="0px"
        android:background="#33ff0000"
        android:scaleType="centerCrop"
        android:src="@drawable/test_image_res_id"
        android:visibility="invisible" />

    <ImageView
        android:id="@+id/thumbnailImageView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:scaleType="centerCrop"
        android:src="@drawable/test_image_res_id" />

</RelativeLayout>

CustomImageView.java

public class CustomImageView extends ImageView {
    public CustomImageView(final Context context) {
        super(context);
    }

    public CustomImageView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomImageView(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setContentHeight(final int contentHeight) {
        final LayoutParams layoutParams = getLayoutParams();
        layoutParams.height = contentHeight;
        setLayoutParams(layoutParams);
    }

    public void setContentWidth(final int contentWidth) {
        final LayoutParams layoutParams = getLayoutParams();
        layoutParams.width = contentWidth;
        setLayoutParams(layoutParams);
    }

    public int getContentHeight() {
        return getLayoutParams().height;
    }

    public int getContentWidth() {
        return getLayoutParams().width;
    }

    public int getContentX() {
        return ((MarginLayoutParams) getLayoutParams()).leftMargin;
    }

    public void setContentX(final int contentX) {
        final MarginLayoutParams layoutParams = (MarginLayoutParams) getLayoutParams();
        layoutParams.leftMargin = contentX;
        setLayoutParams(layoutParams);
    }

    public int getContentY() {
        return ((MarginLayoutParams) getLayoutParams()).topMargin;
    }

    public void setContentY(final int contentY) {
        final MarginLayoutParams layoutParams = (MarginLayoutParams) getLayoutParams();
        layoutParams.topMargin = contentY;
        setLayoutParams(layoutParams);
    }

}

Ответ 2

Другое решение, если вы просто хотите сделать анимацию изображения от маленького до большого, вы можете попробовать ActivityOptions.makeThumbnailScaleUpAnimation или makeScaleUpAnimation и посмотреть, подходят ли они вам.

http://developer.android.com/reference/android/app/ActivityOptions.html#makeThumbnailScaleUpAnimation (android.view.View, android.graphics. Bitmap, int, int)

Ответ 3

Вы можете достичь этого через Transition Api, и это resut gif:

Demo

необходимый код ниже:

private void zoomIn() {
    ViewGroup.LayoutParams layoutParams = mImage.getLayoutParams();
    int width = layoutParams.width;
    int height = layoutParams.height;
    layoutParams.width = (int) (width * 2);
    layoutParams.height = height * 2;
    mImage.setLayoutParams(layoutParams);
    mImage.setScaleType(ImageView.ScaleType.FIT_CENTER);

    TransitionSet transitionSet = new TransitionSet();
    Transition bound = new ChangeBounds();
    transitionSet.addTransition(bound);
    Transition changeImageTransform = new ChangeImageTransform();
    transitionSet.addTransition(changeImageTransform);
    transitionSet.setDuration(1000);
    TransitionManager.beginDelayedTransition(mRootView, transitionSet);
}

Просмотр демонстрации на github

sdk version >= 21

Ответ 4

Я нашел способ получить аналогичный аффект в быстром прототипе. Возможно, это не подходит для использования в производстве (я все еще изучаю), но это быстро и просто.

  • Используйте переход затухания на ваш переход активности/фрагмента (который начинается с ImageView в точно такой же позиции). Версия фрагмента:

    final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();    
    fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);    
    ...etc    
    

    Версия действия:

    Intent intent = new Intent(context, MyDetailActivity.class);
    startActivity(intent);
    getActivity().overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    

    Это дает плавный переход без мерцания.

  • Отрегулируйте макеты динамически в onStart() нового фрагмента (вам нужно сохранить поля участника в соответствующих частях пользовательского интерфейса в onCreateView и добавить несколько флагов, чтобы этот код вызывался только один раз).

    @Override    
    public void onStart() {    
        super.onStart();
    
        // Remove the padding on any layouts that the image view is inside
        mMainLayout.setPadding(0, 0, 0, 0);
    
        // Get the screen size using a utility method, e.g. 
        //  http://stackoverflow.com/a/12082061/112705
        // then work out your desired height, e.g. using the image aspect ratio.
        int desiredHeight = (int) (screenWidth * imgAspectRatio);
    
        // Resize the image to fill the whole screen width, removing 
        // any layout margins that it might have (you may need to remove 
        // padding too)
        LinearLayout.LayoutParams layoutParams =
                new LinearLayout.LayoutParams(screenWidth, desiredHeight);
        layoutParams.setMargins(0, 0, 0, 0);
    
        mImageView.setLayoutParams(layoutParams);
    }    
    

Ответ 5

Я думаю, что самый простой способ - оживить высоту ImageView (обычное изображение, не обязательно настраиваемое представление), сохраняя значение scaleType в центре, до полной высоты, которое вы можете знать заранее, если вы установите высоту изображения на wrap_content в вашем макете, а затем используйте ViewTreeObserver, чтобы узнать, когда макет закончился, чтобы вы могли получить высоту ImageView, а затем установить новую "рушительную" высоту. Я не тестировал его, но так я и сделал бы это.

Вы также можете посмотреть этот пост, они делают что-то подобное http://nerds.airbnb.com/host-experience-android/

Ответ 6

Я не уверен, почему все говорят о структуре. Использование кода других людей может быть большим время от времени; но похоже, что то, за чем вы находитесь, - это точный контроль над внешним видом. Получив доступ к графическому контексту, вы можете получить это. Задача довольно проста в любой среде с графическим контекстом. В android вы можете получить его, переопределив метод onDraw и используя Canvas Object. В нем есть все необходимое для рисования изображения в разных масштабах, положениях и вырезках. Вы даже можете использовать матрицу, если вы знакомы с этим типом вещей.

Шаги

  • Убедитесь, что у вас есть точное управление позиционированием, масштабированием и клипом. Это означает отключение любых макетов или авто-выравнивание, которые могут быть настроены внутри контейнера объектов.

  • Покажите, что ваш параметр t будет для линейной интерполяции и как вы хотите, чтобы он относился к времени. Как быстро или медленно, и будет ли какое-то ослабление. t должен зависеть от времени.

  • После того, как миниатюры будут кешированы, загрузите полномасштабное изображение в фоновом режиме. Но пока не показывайте его.

  • Когда срабатывает триггер анимации, покажите большое изображение и покажите анимацию с помощью параметра t, используя интерполяцию между состояниями начальных свойств и состояниями конечных свойств. Сделайте это для всех трех свойств, положения, масштаба и клипа. Поэтому для всех свойств выполните следующие действия:

    Sinterpolated = Sinitial * (t-1)    +   Sfinal * t;
    // where
    //    t is between 0.0 and 1.0
    //    and S is the states value
    //    for every part of scale, position, and clip
    //
    //    Sinitial is what you are going from
    //    Sfinal   is what you are going to
    //    
    //    t should change from 0.0->1.0 in
    //    over time anywhere from 12/sec or 60/sec.
    

Если все ваши свойства управляются одним и тем же параметром, анимация будет плавной. В качестве дополнительного бонуса, вот подсказка о сроках. Пока вы можете сохранить свой параметр t между 0 и 1, ослабление входа или выхода можно взломать одной строкой кода:

// After your t is all setup
t = t * t;        // for easing in
// or
t = Math.sqrt(t); // for easing out