Full Code of danylovolokh/ImageTransition for AI

master 69e28f7e0917 cached
39 files
78.1 KB
19.5k tokens
106 symbols
1 requests
Download .txt
Repository: danylovolokh/ImageTransition
Branch: master
Commit: 69e28f7e0917
Files: 39
Total size: 78.1 KB

Directory structure:
gitextract_v8hwl3px/

├── .gitignore
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── volokh/
│       │               └── danylo/
│       │                   └── imagetransition/
│       │                       └── ApplicationTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── volokh/
│       │   │           └── danylo/
│       │   │               └── imagetransition/
│       │   │                   ├── ImageFilesCreateLoader.java
│       │   │                   ├── activities/
│       │   │                   │   ├── ImageDetailsActivity.java
│       │   │                   │   └── ImagesListActivity.java
│       │   │                   ├── activities_v21/
│       │   │                   │   ├── ImageDetailsActivity_v21.java
│       │   │                   │   └── ImagesListActivity_v21.java
│       │   │                   ├── adapter/
│       │   │                   │   ├── Image.java
│       │   │                   │   ├── ImagesAdapter.java
│       │   │                   │   └── ImagesViewHolder.java
│       │   │                   ├── animations/
│       │   │                   │   ├── EnterScreenAnimations.java
│       │   │                   │   ├── ExitScreenAnimations.java
│       │   │                   │   ├── MatrixEvaluator.java
│       │   │                   │   ├── MatrixUtils.java
│       │   │                   │   ├── ScreenAnimation.java
│       │   │                   │   └── SimpleAnimationListener.java
│       │   │                   └── event_bus/
│       │   │                       ├── ChangeImageThumbnailVisibility.java
│       │   │                       └── EventBusCreator.java
│       │   └── res/
│       │       ├── layout/
│       │       │   ├── image_details_activity_layout.xml
│       │       │   ├── image_item.xml
│       │       │   └── images_list.xml
│       │       ├── transition/
│       │       │   ├── change_image_transition.xml
│       │       │   └── enter_exit.xml
│       │       ├── values/
│       │       │   ├── colors.xml
│       │       │   └── styles.xml
│       │       └── values-v21/
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── volokh/
│                       └── danylo/
│                           └── imagetransition/
│                               └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures


================================================
FILE: README.md
================================================
# ImageTransition
This is a pet project that shows how to implement shared element image transition on pre-Lollipop devices on Android.

# Pre-Lolipop Demo
If you look closer there is almost no difference between the native Android Lollipop shared element aniamation

Animation and canceling animation in the middle of it.

![support_animation](https://cloud.githubusercontent.com/assets/2686355/13902301/783b604c-ee4c-11e5-8428-bab7a67f6fff.gif)     ![cancel support animation in the middle](https://cloud.githubusercontent.com/assets/2686355/13902305/80beb502-ee4c-11e5-8dde-fcfade1eb93c.gif)

# Lollipop native animation
![lollipop animation](https://cloud.githubusercontent.com/assets/2686355/13902304/7d066c52-ee4c-11e5-9329-a455c0baa440.gif)

# Details of implementation

[![Medium](https://img.shields.io/badge/Meduim-Implementing%20ImageView%20transition%20between%20activities%20for%20pre--Lollipop%20devices.-blue.svg)](https://medium.com/@v.danylo/implementing-imageview-transition-between-activities-for-pre-lollipop-devices-8b24bc387a2a)



# License

Copyright 2016 Danylo Volokh

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.



================================================
FILE: app/.gitignore
================================================
/build


================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.volokh.danylo.imagetransition"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:design:23.1.1'
    compile 'com.squareup.picasso:picasso:2.5.2'
    compile 'com.squareup:otto:1.3.8'

}


================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in D:\Projects\android_sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: app/src/androidTest/java/com/volokh/danylo/imagetransition/ApplicationTest.java
================================================
package com.volokh.danylo.imagetransition;

import android.app.Application;
import android.test.ApplicationTestCase;

/**
 * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
 */
public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }
}

================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.volokh.danylo.imagetransition">

    <application

        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:theme="@style/AppTheme">
        <activity
            android:name=".activities_v21.ImagesListActivity_v21"
            android:theme="@style/ImagesListActivityStyle_v21">

        </activity>

        <activity
            android:name=".activities_v21.ImageDetailsActivity_v21"
            android:theme="@style/ImagesDetailsActivityStyle_v21">
        </activity>

        <activity
            android:name=".activities.ImagesListActivity"
            android:theme="@style/ImagesListActivityStyle">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name=".activities.ImageDetailsActivity"
            android:theme="@style/ImagesDetailsActivityStyle">
        </activity>
    </application>

</manifest>


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/ImageFilesCreateLoader.java
================================================
package com.volokh.danylo.imagetransition;

import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import android.util.Log;
import android.util.SparseArray;

import com.volokh.danylo.imagetransition.adapter.Image;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created by danylo.volokh on 3/12/16.
 */
public class ImageFilesCreateLoader implements LoaderManager.LoaderCallbacks<List<File>> {

    private static final String TAG = ImageFilesCreateLoader.class.getSimpleName();

    private static final String AVATAR = "avatar.png";
    private static final String BEST_APP_OF_THE_YEAR = "best_app_of_the_year.png";
    private static final String HB_DANYLO = "hb_danylo.png";
    private static final String LENA_DANYLO_VIKTOR = "lena_danylo_victor.png";
    private static final String VENECIA_LENA_DANYLO_OLYA = "venecia_lena_danylo_olya.png";
    private static final String DANYLO = "danylo.jng";

    private static final SparseArray<String> mImagesResourcesList = new SparseArray<>();

    static {
        mImagesResourcesList.put(R.raw.avatar, AVATAR);
        mImagesResourcesList.put(R.raw.best_app_of_the_year, BEST_APP_OF_THE_YEAR);
        mImagesResourcesList.put(R.raw.hb_danylo, HB_DANYLO);
        mImagesResourcesList.put(R.raw.lena_danylo_victor, LENA_DANYLO_VIKTOR);
        mImagesResourcesList.put(R.raw.venecia_lena_danylo_olya, VENECIA_LENA_DANYLO_OLYA);
        mImagesResourcesList.put(R.raw.danylo, DANYLO);
    }

    private final Context mContext;
    private LoadFinishedCallback mLoadFinishedCallback;

    public interface LoadFinishedCallback{
        void onLoadFinished(List<Image> imagesList);
    }

    public ImageFilesCreateLoader(Context context, LoadFinishedCallback loadFinishedCallback) {
        mContext = context;
        mLoadFinishedCallback = loadFinishedCallback;
    }

    @Override
    public Loader<List<File>> onCreateLoader(int id, Bundle args) {
        return new AsyncTaskLoader<List<File>>(mContext) {
            @Override
            public List<File> loadInBackground() {
                Log.v(TAG, "loadInBackground");
                List<File> resultList = new ArrayList<>();

                for (int resourceIndex = 0; resourceIndex < mImagesResourcesList.size(); resourceIndex++) {

                    writeResourceToFile(resultList, resourceIndex);
                }
                Log.v(TAG, "loadInBackground, resultList " + resultList);

                return resultList;
            }
        };
    }

    private void writeResourceToFile(List<File> resultList, int resourceIndex) {
        File fileDir = mContext.getCacheDir();

        List<String> existingFiles = Arrays.asList(fileDir.list());

        String fileName = mImagesResourcesList.valueAt(resourceIndex);
        File file = new File(fileDir + File.separator + fileName);
        if (existingFiles.contains(fileName)) {
            resultList.add(file);
        } else {
            saveIntoFile(file, mImagesResourcesList.keyAt(resourceIndex));
            resultList.add(file);
        }
    }

    private void saveIntoFile(File file, Integer resource) {
        try {
            InputStream inputStream = mContext.getResources().openRawResource(resource);
            FileOutputStream fileOutputStream = new FileOutputStream(file);

            byte buf[] = new byte[1024];
            int len;
            while ((len = inputStream.read(buf)) > 0) {
                fileOutputStream.write(buf, 0, len);
            }

            fileOutputStream.close();
            inputStream.close();
        } catch (IOException e1) {
        }
    }

    @Override
    public void onLoadFinished(Loader<List<File>> loader, List<File> data) {
        Log.v(TAG, "onLoadFinished, data " + data);

        fillImageList(data);
    }

    private void fillImageList(List<File> data) {
        List<Image> imagesList = new ArrayList<>();

        int imageId = 0;
        int times = 7;
        while(--times > 0){
            for (int i = 0; i < data.size(); i++, imageId++) {
                File file = data.get(i);

                Log.v(TAG, "fillImageList, imageId " + imageId);

                imagesList.add(new Image(imageId, file));

            }
        }
        mLoadFinishedCallback.onLoadFinished(imagesList);

    }

    @Override
    public void onLoaderReset(Loader<List<File>> loader) {

    }
}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/activities/ImageDetailsActivity.java
================================================
package com.volokh.danylo.imagetransition.activities;

import android.animation.AnimatorSet;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageView;

import com.squareup.otto.Bus;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import com.volokh.danylo.imagetransition.animations.EnterScreenAnimations;
import com.volokh.danylo.imagetransition.animations.ExitScreenAnimations;
import com.volokh.danylo.imagetransition.event_bus.ChangeImageThumbnailVisibility;
import com.volokh.danylo.imagetransition.event_bus.EventBusCreator;
import com.volokh.danylo.imagetransition.R;

import java.io.File;

/**
 * Created by danylo.volokh on 2/21/2016.
 */
public class ImageDetailsActivity extends Activity {

    private static final String IMAGE_FILE_KEY = "IMAGE_FILE_KEY";
    private static final String KEY_THUMBNAIL_INIT_TOP_POSITION = "KEY_THUMBNAIL_INIT_TOP_POSITION";
    private static final String KEY_THUMBNAIL_INIT_LEFT_POSITION = "KEY_THUMBNAIL_INIT_LEFT_POSITION";
    private static final String KEY_THUMBNAIL_INIT_WIDTH = "KEY_THUMBNAIL_INIT_WIDTH";
    private static final String KEY_THUMBNAIL_INIT_HEIGHT = "KEY_THUMBNAIL_INIT_HEIGHT";
    private static final String KEY_SCALE_TYPE = "KEY_SCALE_TYPE";

    private static final String TAG = ImageDetailsActivity.class.getSimpleName();

    private static final long IMAGE_TRANSLATION_DURATION = 3000;

    private ImageView mEnlargedImage;
    private ImageView mTransitionImage;

    private Picasso mImageDownloader;

    private final Bus mBus = EventBusCreator.defaultEventBus();

    private AnimatorSet mExitingAnimation;

    private EnterScreenAnimations mEnterScreenAnimations;
    private ExitScreenAnimations mExitScreenAnimations;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        overridePendingTransition(0, 0);

        setContentView(R.layout.image_details_activity_layout);
        mEnlargedImage = (ImageView) findViewById(R.id.enlarged_image);

        mImageDownloader = Picasso.with(this);

        File imageFile = (File) getIntent().getSerializableExtra(IMAGE_FILE_KEY);

        final View mainContainer = findViewById(R.id.main_container);

        if(savedInstanceState == null){
            // We entered activity for the first time.
            // Initialize Image view that will be transitioned
            initializeTransitionView();
        } else {
            // Activity is retrieved. Main container is invisible. Make it visible
            mainContainer.setAlpha(1.0f);
        }

        mEnterScreenAnimations = new EnterScreenAnimations(mTransitionImage, mEnlargedImage, mainContainer);
        mExitScreenAnimations = new ExitScreenAnimations(mTransitionImage, mEnlargedImage, mainContainer);

        initializeEnlargedImageAndRunAnimation(savedInstanceState, imageFile);
    }

    /**
     * This method waits for the main "big" image is loaded.
     * And then if activity is started for the first time - it runs "entering animation"
     *
     * Activity is entered fro the first time if saveInstanceState is null
     *
     */

    private void initializeEnlargedImageAndRunAnimation(final Bundle savedInstanceState, File imageFile) {
        Log.v(TAG, "initializeEnlargedImageAndRunAnimation");

        mImageDownloader.load(imageFile).into(mEnlargedImage, new Callback() {

            /**
             * Image is loaded when this method is called
             */
            @Override
            public void onSuccess() {
                Log.v(TAG, "onSuccess, mEnlargedImage");

                // In this callback we already have image set into ImageView and we can use it's Matrix for animation
                // But we have to wait for final measurements. We use OnPreDrawListener to be sure everything is measured

                if (savedInstanceState == null) {
                    // if savedInstanceState is null activity is started for the first time.
                    // run the animation
                    runEnteringAnimation();
                } else {
                    // activity was retrieved from recent apps. No animation needed, just load the image
                }
            }

            @Override
            public void onError() {
                // CAUTION: on error is not handled. If OutOfMemory emerged during image loading we have to handle it here
                Log.v(TAG, "onError, mEnlargedImage");
            }
        });
    }

    /**
     * This method does very tricky part:
     *
     * It sets up {@link android.view.ViewTreeObserver.OnPreDrawListener}
     * When onPreDraw() method is called the layout is already measured.
     * It means that we can use locations of images on the screen at tis point.
     *
     * 1. When first frame is rendered we start animation.
     * 2. We just let second frame to render
     * 3. Make a view on the previous screen invisible and remove onPreDrawListener
     *
     * Why do we do that:
     * The Android rendering system is double-buffered.
     * Similar technique is used in the SDK. See here : {@link android.app.EnterTransitionCoordinator#startSharedElementTransition}
     *
     * You can read more about it here : https://source.android.com/devices/graphics/architecture.html
     *
     */
    private void runEnteringAnimation() {
        Log.v(TAG, "runEnteringAnimation, addOnPreDrawListener");

        mEnlargedImage.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {

            int mFrames = 0;

            @Override
            public boolean onPreDraw() {
                // When this method is called we already have everything laid out and measured so we can start our animation
                Log.v(TAG, "onPreDraw, mFrames " + mFrames);

                switch (mFrames++) {
                    case 0:
                        /**
                         * 1. start animation on first frame
                         */
                        final int[] finalLocationOnTheScreen = new int[2];
                        mEnlargedImage.getLocationOnScreen(finalLocationOnTheScreen);

                        mEnterScreenAnimations.playEnteringAnimation(
                                finalLocationOnTheScreen[0], // left
                                finalLocationOnTheScreen[1], // top
                                mEnlargedImage.getWidth(),
                                mEnlargedImage.getHeight());

                        return true;
                    case 1:
                        /**
                         * 2. Do nothing. We just draw this frame
                         */

                        return true;
                }
                /**
                 * 3.
                 * Make view on previous screen invisible on after this drawing frame
                 * Here we ensure that animated view will be visible when we make the viw behind invisible
                 */
                Log.v(TAG, "run, onAnimationStart");
                mBus.post(new ChangeImageThumbnailVisibility(false));

                mEnlargedImage.getViewTreeObserver().removeOnPreDrawListener(this);

                Log.v(TAG, "onPreDraw, << mFrames " + mFrames);

                return true;
            }
        });
    }


    private void initializeTransitionView() {
        Log.v(TAG, "initializeTransitionView");

        FrameLayout androidContent = (FrameLayout) getWindow().getDecorView().findViewById(android.R.id.content);
        mTransitionImage = new ImageView(this);
        androidContent.addView(mTransitionImage);

        Bundle bundle = getIntent().getExtras();

        int thumbnailTop = bundle.getInt(KEY_THUMBNAIL_INIT_TOP_POSITION)
                - getStatusBarHeight();
        int thumbnailLeft = bundle.getInt(KEY_THUMBNAIL_INIT_LEFT_POSITION);
        int thumbnailWidth = bundle.getInt(KEY_THUMBNAIL_INIT_WIDTH);

        int thumbnailHeight = bundle.getInt(KEY_THUMBNAIL_INIT_HEIGHT);

        ImageView.ScaleType scaleType = (ImageView.ScaleType) bundle.getSerializable(KEY_SCALE_TYPE);

        Log.v(TAG, "initInitialThumbnail, thumbnailTop [" + thumbnailTop + "]");
        Log.v(TAG, "initInitialThumbnail, thumbnailLeft [" + thumbnailLeft + "]");
        Log.v(TAG, "initInitialThumbnail, thumbnailWidth [" + thumbnailWidth + "]");
        Log.v(TAG, "initInitialThumbnail, thumbnailHeight [" + thumbnailHeight + "]");
        Log.v(TAG, "initInitialThumbnail, scaleType " + scaleType);

        // We set initial margins to the view so that it was situated at exact same spot that view from the previous screen were.
        FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mTransitionImage.getLayoutParams();
        layoutParams.height = thumbnailHeight;
        layoutParams.width = thumbnailWidth;
        layoutParams.setMargins(thumbnailLeft, thumbnailTop, 0, 0);

        File imageFile = (File) getIntent().getSerializableExtra(IMAGE_FILE_KEY);
        mTransitionImage.setScaleType(scaleType);

        mImageDownloader.load(imageFile).noFade().into(mTransitionImage);
    }

    private int getStatusBarHeight() {
        int result = 0;
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // prevent leaking activity if image was not loaded yet
        Picasso.with(this).cancelRequest(mEnlargedImage);
    }

    @Override
    public void onBackPressed() {
//        We don't call super to leave this activity on the screen when back is pressed
//        super.onBackPressed();

        Log.v(TAG, "onBackPressed");

        mEnterScreenAnimations.cancelRunningAnimations();

        Log.v(TAG, "onBackPressed, mExitingAnimation " + mExitingAnimation);

        Bundle initialBundle = getIntent().getExtras();
        int toTop = initialBundle.getInt(KEY_THUMBNAIL_INIT_TOP_POSITION);
        int toLeft = initialBundle.getInt(KEY_THUMBNAIL_INIT_LEFT_POSITION);
        int toWidth = initialBundle.getInt(KEY_THUMBNAIL_INIT_WIDTH);
        int toHeight = initialBundle.getInt(KEY_THUMBNAIL_INIT_HEIGHT);

        mExitScreenAnimations.playExitAnimations(
                toTop,
                toLeft,
                toWidth,
                toHeight,
                mEnterScreenAnimations.getInitialThumbnailMatrixValues());
    }

    public static Intent getStartIntent(Activity activity, File imageFile, int left, int top, int width, int height, ImageView.ScaleType scaleType) {
        Log.v(TAG, "getStartIntent, imageFile " + imageFile);

        Intent startIntent = new Intent(activity, ImageDetailsActivity.class);
        startIntent.putExtra(IMAGE_FILE_KEY, imageFile);

        startIntent.putExtra(KEY_THUMBNAIL_INIT_TOP_POSITION, top);
        startIntent.putExtra(KEY_THUMBNAIL_INIT_LEFT_POSITION, left);
        startIntent.putExtra(KEY_THUMBNAIL_INIT_WIDTH, width);
        startIntent.putExtra(KEY_THUMBNAIL_INIT_HEIGHT, height);
        startIntent.putExtra(KEY_SCALE_TYPE, scaleType);

        return startIntent;
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.v(TAG, "onResume");

    }
}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/activities/ImagesListActivity.java
================================================
package com.volokh.danylo.imagetransition.activities;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
import com.squareup.picasso.Picasso;
import com.volokh.danylo.imagetransition.event_bus.EventBusCreator;
import com.volokh.danylo.imagetransition.ImageFilesCreateLoader;
import com.volokh.danylo.imagetransition.adapter.ImagesAdapter;
import com.volokh.danylo.imagetransition.activities_v21.ImagesListActivity_v21;
import com.volokh.danylo.imagetransition.R;
import com.volokh.danylo.imagetransition.event_bus.ChangeImageThumbnailVisibility;
import com.volokh.danylo.imagetransition.adapter.Image;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ImagesListActivity extends Activity implements ImagesAdapter.ImagesAdapterCallback {

    private static final String TAG = ImagesListActivity.class.getSimpleName();

    private static final String IMAGE_DETAILS_IMAGE_MODEL = "IMAGE_DETAILS_IMAGE_MODEL";

    private final List<Image> mImagesList = new ArrayList<>();

    private static final int SPAN_COUNT = 2;

    private Picasso mImageDownloader;

    private RecyclerView mRecyclerView;

    private GridLayoutManager mLayoutManager;

    private ImagesAdapter mAdapter;

    private Image mImageDetailsImageModel;

    private final Bus mBus = EventBusCreator.defaultEventBus();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.images_list);

        initializeImagesDownloader();

        initializeAdapter();

        initializeImages();

        initializeRecyclerView();

        initializeTitle();

        initializeSavedImageModel(savedInstanceState);

        initializeSwitchButton();
    }

    private void initializeImagesDownloader() {
        mImageDownloader = Picasso.with(this);
    }

    private void initializeAdapter() {
        mAdapter = new ImagesAdapter(this, mImagesList, mImageDownloader, SPAN_COUNT);
    }

    private void initializeImages() {
        // load images
        getLoaderManager().initLoader(0, null, new ImageFilesCreateLoader(this, new ImageFilesCreateLoader.LoadFinishedCallback() {
            @Override
            public void onLoadFinished(List<Image> imagesList) {
                mImagesList.addAll(imagesList);
                mAdapter.notifyDataSetChanged();
            }
        })).forceLoad();
    }

    private void initializeSwitchButton() {
        Button switchButton = (Button) findViewById(R.id.switch_to);

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            switchButton.setVisibility(View.VISIBLE);
            switchButton.setText("Switch to Lollipop List Activity");
            switchButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {

                    ImagesListActivity.this.finish();

                    Intent startActivity_v21_intent = new Intent(ImagesListActivity.this, ImagesListActivity_v21.class);
                    startActivity(startActivity_v21_intent);

                }
            });
        }
    }

    private void initializeSavedImageModel(Bundle savedInstanceState) {
        Log.v(TAG, "initializeSavedImageModel, savedInstanceState " + savedInstanceState);

        if(savedInstanceState != null){
            mImageDetailsImageModel = savedInstanceState.getParcelable(IMAGE_DETAILS_IMAGE_MODEL);
        }

        if(mImageDetailsImageModel != null) {
            updateModel(mImageDetailsImageModel);
        }
    }

    private void initializeTitle() {
        TextView title = (TextView) findViewById(R.id.title);
        title.setText("List Activity Ice Cream Sandwich");
    }

    private void initializeRecyclerView() {
        mRecyclerView = (RecyclerView) findViewById(R.id.accounts_recycler_view);
        mLayoutManager = new GridLayoutManager(this, SPAN_COUNT);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setAdapter(mAdapter);
        // we don't need animation when items are hidden or shown
        mRecyclerView.setItemAnimator(null);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.v(TAG, "onStart");
        mBus.register(this);

    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.v(TAG, "onStop");
        mBus.unregister(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.v(TAG, "onResume");

        if(mRecyclerView.getChildCount() > 1){
            Log.v(TAG, "onResume, " + mRecyclerView.getChildAt(4).getVisibility());
        }
    }

    /**
     * This method is called when event is sent from {@link ImageDetailsActivity}
     * via event bus.
     *
     * When it's called it means that transition animation started and we have to hide the image on this activity in order to look
     * like image from here is transitioned to the new screen
     *
     */
    @Subscribe
    public void hideImageThumbnail(ChangeImageThumbnailVisibility message){
        Log.v(TAG, ">> hideImageThumbnail");

        mImageDetailsImageModel.setVisibility(message.isVisible());

        updateModel(mImageDetailsImageModel);

        Log.v(TAG, "<< hideImageThumbnail");
    }

    /**
     * This method basically changes visibility of concrete item
     */
    private void updateModel(Image imageToUpdate) {
        Log.v(TAG, "updateModel, imageToUpdate " + imageToUpdate);
        for (Image image : mImagesList) {

            if(image.equals(imageToUpdate)){
                Log.v(TAG, "updateModel, found imageToUpdate " + imageToUpdate);
                image.setVisibility(imageToUpdate.isVisible());
                break;
            }
        }
        int index = mImagesList.indexOf(imageToUpdate);
        Log.v(TAG, "updateModel, index " + index);

        mAdapter.notifyItemChanged(index);

        /**
         * For some reason recycler view is not always redrawn when adapter updated.
         * onBindViewHolder is called but image doesn't disappear from screen
         * That's why we have to do this invalidation
         */
        Rect dirty = new Rect();
        View viewAtPosition = mLayoutManager.findViewByPosition(index);
        viewAtPosition.getDrawingRect(dirty);
        mRecyclerView.invalidate(dirty);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putParcelable(IMAGE_DETAILS_IMAGE_MODEL, mImageDetailsImageModel);
    }

    @Override
    public void enterImageDetails(String sharedImageTransitionName, File imageFile, final ImageView image, Image imageModel) {
        Log.v(TAG, "enterImageDetails, imageFile " + imageFile);
        Log.v(TAG, "enterImageDetails, image.getScaleType() " + image.getScaleType());

        /**
         * We store this model for two purposes:
         * 1. When image on the next screen will be transitioned we have to hide the view
         * representation of this model in order it to look like this exact view is transitioned.
         * Transitioned view will leave empty space behind it
         *
         * 2. Activity might be destroyed at some point and we have to store this information to restore view
         * visibility when activity is recreated.
         */
        mImageDetailsImageModel = imageModel;

        int[] screenLocation = new int[2];
        image.getLocationInWindow(screenLocation);

        Intent startIntent = ImageDetailsActivity.getStartIntent(this, imageFile,
                screenLocation[0],
                screenLocation[1],
                image.getWidth(),
                image.getHeight(),
                image.getScaleType());

        startActivity(startIntent);
    }

}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/activities_v21/ImageDetailsActivity_v21.java
================================================
package com.volokh.danylo.imagetransition.activities_v21;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.view.View;
import android.widget.ImageView;

import com.squareup.picasso.Picasso;
import com.volokh.danylo.imagetransition.R;

import java.io.File;

/**
 * Created by danylo.volokh on 2/21/2016.
 */
public class ImageDetailsActivity_v21 extends Activity{

    public static final String SHARED_ELEMENT_IMAGE_KEY = "SHARED_ELEMENT_IMAGE_KEY";
    public static final String IMAGE_FILE_KEY = "IMAGE_FILE_KEY";

    private ImageView mImage;

    private Picasso mImageDownloader;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_details_activity_layout);

        mImage = (ImageView) findViewById(R.id.enlarged_image);
        mImage.setVisibility(View.VISIBLE);

        String imageTransitionName = getIntent().getStringExtra(SHARED_ELEMENT_IMAGE_KEY);
        ViewCompat.setTransitionName(mImage, imageTransitionName);

        View mainContainer = findViewById(R.id.main_container);
        mainContainer.setAlpha(1.f);
        mImageDownloader = Picasso.with(this);

        File imageFile = (File) getIntent().getSerializableExtra(IMAGE_FILE_KEY);

        mImageDownloader.load(imageFile).into(mImage);

    }

    public static Intent getStartIntent(Activity activity, String sharedImageTransitionName, File imageFile) {
        Intent startIntent = new Intent(activity, ImageDetailsActivity_v21.class);
        startIntent.putExtra(SHARED_ELEMENT_IMAGE_KEY, sharedImageTransitionName);
        startIntent.putExtra(IMAGE_FILE_KEY, imageFile);
        return startIntent;
    }
}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/activities_v21/ImagesListActivity_v21.java
================================================
package com.volokh.danylo.imagetransition.activities_v21;

import android.app.Activity;
import android.app.ActivityOptions;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.transition.ChangeImageTransform;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.picasso.Picasso;
import com.volokh.danylo.imagetransition.ImageFilesCreateLoader;
import com.volokh.danylo.imagetransition.adapter.ImagesAdapter;
import com.volokh.danylo.imagetransition.R;
import com.volokh.danylo.imagetransition.activities.ImagesListActivity;
import com.volokh.danylo.imagetransition.adapter.Image;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ImagesListActivity_v21 extends Activity implements ImagesAdapter.ImagesAdapterCallback {

    private static final String TAG = ImagesListActivity_v21.class.getSimpleName();

    private final List<Image> mImagesList = new ArrayList<>();

    private static final int SPAN_COUNT = 2;

    private Picasso mImageDownloader;
    private RecyclerView mRecyclerView;
    private ImagesAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.images_list);

        mImageDownloader = Picasso.with(this);

        mAdapter = new ImagesAdapter(this, mImagesList, mImageDownloader, SPAN_COUNT);

        getLoaderManager().initLoader(0, null, new ImageFilesCreateLoader(this, new ImageFilesCreateLoader.LoadFinishedCallback() {
            @Override
            public void onLoadFinished(List<Image> imagesList) {
                mImagesList.addAll(imagesList);
                mAdapter.notifyDataSetChanged();
            }
        })).forceLoad();

        mRecyclerView = (RecyclerView) findViewById(R.id.accounts_recycler_view);

        mRecyclerView.setLayoutManager(new GridLayoutManager(this, SPAN_COUNT));
        mRecyclerView.setAdapter(mAdapter);

        TextView title = (TextView) findViewById(R.id.title);
        title.setText("List Activity Lollipop");

        Button switchButton = (Button) findViewById(R.id.switch_to);

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            switchButton.setVisibility(View.VISIBLE);
            switchButton.setText("Switch to Ice Cream Sandwich List Activity");
            switchButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {

                    ImagesListActivity_v21.this.finish();

                    Intent startActivityIntent = new Intent(ImagesListActivity_v21.this, ImagesListActivity.class);
                    startActivity(startActivityIntent);

                }
            });
        }
    }




    @Override
    public void enterImageDetails(String sharedImageTransitionName, File imageFile, ImageView image, Image imageModel) {
        ActivityOptions activityOptions =
                ActivityOptions.makeSceneTransitionAnimation(this, image, sharedImageTransitionName);

        getWindow().setSharedElementEnterTransition(new ChangeImageTransform(this, null));

        Intent startIntent = ImageDetailsActivity_v21.getStartIntent(this, sharedImageTransitionName, imageFile);
        startActivity(startIntent, activityOptions.toBundle());
    }
}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/adapter/Image.java
================================================
package com.volokh.danylo.imagetransition.adapter;

import android.os.Parcel;
import android.os.Parcelable;

import java.io.File;

/**
 * Created by danylo.volokh on 2/21/2016.
 *
 * We use Parcelable in order it to store this model in bundle.
 *
 * NOTE: it is better to use some cache for models.(Maybe ORM)
 * Parcelable is more verbose then Serializable, but we use it because #PerfMatters
 */
public class Image implements Parcelable{

    public final int imageId;
    public final File imageFile;

    /**
     * By default the image is visible but for the purpose of the animation it will be changed to invisible at some time.
     */
    private boolean mImageIsVisible = true;

    public Image(int imageId, File imageFile) {
        this.imageId = imageId;
        this.imageFile = imageFile;
    }

    protected Image(Parcel in) {
        imageId = in.readInt();
        imageFile = (File) in.readSerializable();
        mImageIsVisible = in.readByte() != 0;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Image image = (Image) o;

        return imageId == image.imageId;
    }

    @Override
    public int hashCode() {
        return imageId;
    }

    /**
     * This is generated by studio
     */
    @Override
    public String toString() {
        return "Image{" +
                "imageId=" + imageId +
                ", imageFile=" + imageFile +
                ", mImageIsVisible=" + mImageIsVisible +
                '}';
    }

    public static final Creator<Image> CREATOR = new Creator<Image>() {
        @Override
        public Image createFromParcel(Parcel in) {
            return new Image(in);
        }

        @Override
        public Image[] newArray(int size) {
            return new Image[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(imageId);
        dest.writeSerializable(imageFile);
        dest.writeByte((byte) (mImageIsVisible ? 1 : 0));
    }

    public boolean isVisible() {
        return mImageIsVisible;
    }

    public void setVisibility(boolean isVisible){
        mImageIsVisible = isVisible;
    }
}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/adapter/ImagesAdapter.java
================================================
package com.volokh.danylo.imagetransition.adapter;

import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.squareup.picasso.Picasso;
import com.volokh.danylo.imagetransition.R;

import java.io.File;
import java.util.List;

/**
 * Created by danylo.volokh on 2/19/2016.
 *
 * This adapter class is for the list of images. There is 6 types of item types.
 * Each on for different {@link android.widget.ImageView.ScaleType}
 */
public class ImagesAdapter extends RecyclerView.Adapter<ImagesViewHolder> {

    private static final String TAG = ImagesAdapter.class.getSimpleName();

    private final ImagesAdapterCallback mImagesAdapterCallback;
    private final List<Image> mImagesList;
    private final Picasso mImageDownloader;
    private final int mSpanCount;

    public interface ImagesAdapterCallback{
        void enterImageDetails(String sharedImageTransitionName, File imageFile, ImageView image, Image imageModel);
    }

    public ImagesAdapter(ImagesAdapterCallback imagesAdapterCallback, List<Image> imagesList, Picasso imageDownloader, int spanCount) {

        mImagesAdapterCallback = imagesAdapterCallback;
        mImagesList = imagesList;
        mImageDownloader = imageDownloader;
        mSpanCount = spanCount;
    }

    public static String createSharedImageTransitionName(int position){
        return "SharedImage" + position;
    }

    @Override
    public ImagesViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
        View item = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_item, parent, false);
        RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) item.getLayoutParams();
        layoutParams.height = item.getResources().getDisplayMetrics().widthPixels / mSpanCount;

        ImagesViewHolder viewHolder = new ImagesViewHolder(item);

        /**
         * Depends on View type we set the according {@link android.widget.ImageView.ScaleType
         */

        if(viewType == 0) {
            viewHolder.image.setScaleType(ImageView.ScaleType.CENTER);
        }

        if(viewType == 1) {
            viewHolder.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
        }

        if(viewType == 2) {
            viewHolder.image.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        }

        if(viewType == 3) {
            viewHolder.image.setScaleType(ImageView.ScaleType.FIT_CENTER);
        }

        if(viewType == 4) {
            viewHolder.image.setScaleType(ImageView.ScaleType.FIT_XY);
        }

        if(viewType == 5) {
            viewHolder.image.setScaleType(ImageView.ScaleType.MATRIX);
        }

        return viewHolder;
    }

    @Override
    public void onBindViewHolder(final ImagesViewHolder holder, final int position) {
        final Image image = mImagesList.get(position);
        Log.v(TAG, "onBindViewHolder holder.imageFile " + image.imageFile);

        Log.v(TAG, "onBindViewHolder holder.image " + holder.image);
        Log.v(TAG, "onBindViewHolder holder.matrix " + holder.image.getMatrix());
        Log.v(TAG, "onBindViewHolder holder.scaleType " + holder.image.getScaleType());

        // set transition name for sdk 21 shared element transition
        final String sharedImageTransitionName = createSharedImageTransitionName(position);
        ViewCompat.setTransitionName(holder.image, sharedImageTransitionName);

        Log.v(TAG, "onBindViewHolder isVisible " + image.isVisible());
        Log.v(TAG, "onBindViewHolder isVisible " + holder.image.getVisibility());

        if(image.isVisible()){
            mImageDownloader.load(image.imageFile).into(holder.image);
            holder.image.setVisibility(View.VISIBLE);
        } else {
            /** in purpose of animation image might become invisible */
            holder.image.setVisibility(View.INVISIBLE);
        }
        Log.v(TAG, "onBindViewHolder isVisible " + holder.image.getVisibility());


        holder.image.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.v(TAG, "onClick image " + holder.image );
                Log.v(TAG, "onClick matrix " + holder.image.getMatrix() );

                mImagesAdapterCallback.enterImageDetails(sharedImageTransitionName, image.imageFile, holder.image, image);

            }
        });
        Log.v(TAG, "onBindViewHolder position " + position);
    }

    @Override
    public int getItemCount() {
        return mImagesList.size();
    }

    /**
     * We create 6 item types for each {@link android.widget.ImageView.ScaleType} there is,
     * besides:
     *      {@link android.widget.ImageView.ScaleType#FIT_START}
     *      {@link android.widget.ImageView.ScaleType#FIT_END}
     */
    @Override
    public int getItemViewType(int position) {

        if(position % 6 == 0) {
            return 0;
        }

        if(position % 5 == 0) {
            return 1;
        }

        if(position % 4 == 0) {
            return 2;
        }

        if(position % 3 == 0) {
            return 3;
        }

        if(position % 2 == 0) {
            return 4;
        }

        return 5;
    }
}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/adapter/ImagesViewHolder.java
================================================
package com.volokh.danylo.imagetransition.adapter;

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;

import com.volokh.danylo.imagetransition.R;

public class ImagesViewHolder extends RecyclerView.ViewHolder{

    public final ImageView image;

    public ImagesViewHolder(View itemView) {
        super(itemView);
        image = (ImageView) itemView.findViewById(R.id.image);
    }
}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/EnterScreenAnimations.java
================================================
package com.volokh.danylo.imagetransition.animations;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.graphics.Matrix;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;

import java.util.Arrays;

/**
 * Created by danylo.volokh on 3/16/16.
 */
public class EnterScreenAnimations extends ScreenAnimation{

    private static final String TAG = EnterScreenAnimations.class.getSimpleName();

    /**
     * Image with is initially situated on the place from where transition starts
     */
    private final ImageView mImageTo;

    /**
     * This is image that represents of how should transitioned image look like at the end of transition
     */
    private final ImageView mAnimatedImage;

    private View mMainContainer;

    private AnimatorSet mEnteringAnimation;

    /**
     * This array will contain the data about image matrix of original image.
     * We will use that matrix to animate image back to original state
     */
    private float[] mInitThumbnailMatrixValues = new float[9];

    /**
     * These values represent the final position of a Image that is translated
     */
    private int mToTop;
    private int mToLeft;
    private int mToWidth;
    private int mToHeight;

    public EnterScreenAnimations(ImageView animatedImage, ImageView imageTo, View mainContainer) {
        super(animatedImage.getContext());
        mAnimatedImage = animatedImage;
        mImageTo = imageTo;
        mMainContainer = mainContainer;
    }

    /**
     * This method combines several animations when screen is opened.
     * 1. Animation of ImageView position and image matrix.
     * 2. Animation of main container elements - they are fading in after image is animated to position
     */
    public void playEnteringAnimation(int left, int top, int width, int height) {
        Log.v(TAG, ">> playEnteringAnimation");

        mToLeft = left;
        mToTop = top;
        mToWidth = width;
        mToHeight =  height;

        AnimatorSet imageAnimatorSet = createEnteringImageAnimation();

        Animator mainContainerFadeAnimator = createEnteringFadeAnimator();

        mEnteringAnimation = new AnimatorSet();
        mEnteringAnimation.setDuration(IMAGE_TRANSLATION_DURATION);
        mEnteringAnimation.setInterpolator(new AccelerateInterpolator());
        mEnteringAnimation.addListener(new SimpleAnimationListener() {

            @Override
            public void onAnimationCancel(Animator animation) {
                Log.v(TAG, "onAnimationCancel, mEnteringAnimation " + mEnteringAnimation);
                mEnteringAnimation = null;
            }

            @Override
            public void onAnimationEnd(Animator animation) {

                Log.v(TAG, "onAnimationEnd, mEnteringAnimation " + mEnteringAnimation);
                if (mEnteringAnimation != null) {
                    mEnteringAnimation = null;

                    mImageTo.setVisibility(View.VISIBLE);
                    mAnimatedImage.setVisibility(View.INVISIBLE);
                } else {
                    // Animation was cancelled. Do nothing
                }

            }
        });

        mEnteringAnimation.playTogether(
                imageAnimatorSet,
                mainContainerFadeAnimator
        );

        mEnteringAnimation.start();
        Log.v(TAG, "<< playEnteringAnimation");
    }

    /**
     * Animator returned form this method animates fade in of all other elements on the screen besides ImageView
     */
    private ObjectAnimator createEnteringFadeAnimator() {
        ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat(mMainContainer, "alpha", 0.0f, 1.0f);
        return fadeInAnimator;
    }
    /**
     * This method creates an animator set of 2 animations:
     * 1. ImageView position animation when screen is opened
     * 2. ImageView image matrix animation when screen is opened
     */
    @NonNull
    private AnimatorSet createEnteringImageAnimation() {
        Log.v(TAG, ">> createEnteringImageAnimation");

        ObjectAnimator positionAnimator = createEnteringImagePositionAnimator();
        ObjectAnimator matrixAnimator = createEnteringImageMatrixAnimator();

        AnimatorSet enteringImageAnimation = new AnimatorSet();
        enteringImageAnimation.playTogether(positionAnimator, matrixAnimator);

        Log.v(TAG, "<< createEnteringImageAnimation");
        return enteringImageAnimation;
    }

    /**
     * This method creates an animator that changes ImageView position on the screen.
     * It will look like view is translated from its position on previous screen to its new position on this screen
     */
    @NonNull
    private ObjectAnimator createEnteringImagePositionAnimator() {

        Log.v(TAG, "createEnteringImagePositionAnimator");

        PropertyValuesHolder propertyLeft = PropertyValuesHolder.ofInt("left", mAnimatedImage.getLeft(), mToLeft);
        PropertyValuesHolder propertyTop = PropertyValuesHolder.ofInt("top", mAnimatedImage.getTop(),
                mToTop - getStatusBarHeight());

        PropertyValuesHolder propertyRight = PropertyValuesHolder.ofInt("right", mAnimatedImage.getRight(), mToLeft + mToWidth);
        PropertyValuesHolder propertyBottom = PropertyValuesHolder.ofInt("bottom", mAnimatedImage.getBottom(), mToTop + mToHeight - getStatusBarHeight());

        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mAnimatedImage, propertyLeft, propertyTop, propertyRight, propertyBottom);
        animator.addListener(new SimpleAnimationListener() {
            @Override
            public void onAnimationEnd(Animator animation) {
                // set new parameters of animated ImageView. This will prevent blinking of view when set visibility to visible in Exit animation
                FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mAnimatedImage.getLayoutParams();
                layoutParams.height = mImageTo.getHeight();
                layoutParams.width = mImageTo.getWidth();
                layoutParams.setMargins(mToLeft, mToTop - getStatusBarHeight(), 0, 0);
            }
        });
        return animator;
    }

    /**
     * This method creates Animator that will animate matrix of ImageView.
     * It is needed in order to show the effect when scaling of one view is smoothly changed to the scale of the second view.
     * <p>
     * For example: first view can have scaleType: centerCrop, and the other one fitCenter.
     * The image inside ImageView will smoothly change from one to another
     */
    private ObjectAnimator createEnteringImageMatrixAnimator() {

        Matrix initMatrix = MatrixUtils.getImageMatrix(mAnimatedImage);
        // store the data about original matrix into array.
        // this array will be used later for exit animation
        initMatrix.getValues(mInitThumbnailMatrixValues);

        final Matrix endMatrix = MatrixUtils.getImageMatrix(mImageTo);
        Log.v(TAG, "createEnteringImageMatrixAnimator, mInitThumbnailMatrixValues " + Arrays.toString(mInitThumbnailMatrixValues));

        Log.v(TAG, "createEnteringImageMatrixAnimator, initMatrix " + initMatrix);
        Log.v(TAG, "createEnteringImageMatrixAnimator,  endMatrix " + endMatrix);

        mAnimatedImage.setScaleType(ImageView.ScaleType.MATRIX);

        return ObjectAnimator.ofObject(mAnimatedImage, MatrixEvaluator.ANIMATED_TRANSFORM_PROPERTY,
                new MatrixEvaluator(), initMatrix, endMatrix);
    }

    public float[] getInitialThumbnailMatrixValues() {
        return mInitThumbnailMatrixValues;
    }

    public void cancelRunningAnimations() {
        Log.v(TAG, "cancelRunningAnimations, mEnteringAnimation " + mEnteringAnimation);

        if (mEnteringAnimation != null) {
            // cancel existing animation
            mEnteringAnimation.cancel();
        }
    }
}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/ExitScreenAnimations.java
================================================
package com.volokh.danylo.imagetransition.animations;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.app.Activity;
import android.graphics.Matrix;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.widget.ImageView;

import com.squareup.otto.Bus;
import com.volokh.danylo.imagetransition.event_bus.ChangeImageThumbnailVisibility;
import com.volokh.danylo.imagetransition.event_bus.EventBusCreator;

/**
 * Created by danylo.volokh on 3/16/16.
 */
public class ExitScreenAnimations extends ScreenAnimation{

    private static final String TAG = ExitScreenAnimations.class.getSimpleName();

    private final Bus mBus = EventBusCreator.defaultEventBus();

    private final ImageView mAnimatedImage;
    private final ImageView mImageTo;
    private final View mMainContainer;

    /**
     * These values represent the final position of a Image that is translated
     */
    private int mToTop;
    private int mToLeft;
    private int mToWidth;
    private int mToHeight;

    private AnimatorSet mExitingAnimation;

    private float[] mToThumbnailMatrixValues;

    public ExitScreenAnimations(ImageView animatedImage, ImageView imageTo, View mainContainer) {
        super(animatedImage.getContext());
        mAnimatedImage = animatedImage;
        mImageTo = imageTo;
        mMainContainer = mainContainer;
    }

    public void playExitAnimations(int toTop, int toLeft, int toWidth, int toHeight, float[] toThumbnailMatrixValues) {
        mToTop = toTop;
        mToLeft = toLeft;
        mToWidth = toWidth;
        mToHeight = toHeight;

        mToThumbnailMatrixValues = toThumbnailMatrixValues;

        Log.v(TAG, "playExitAnimations, mExitingAnimation " + mExitingAnimation);
        if (mExitingAnimation == null) {
            playExitingAnimation();
        }
    }

    private void playExitingAnimation() {
        Log.v(TAG, "playExitingAnimation");

        mAnimatedImage.setVisibility(View.VISIBLE);
        mImageTo.setVisibility(View.INVISIBLE);

        AnimatorSet imageAnimatorSet = createExitingImageAnimation();

        Animator mainContainerFadeAnimator = createExitingFadeAnimator();

        mExitingAnimation = new AnimatorSet();
        mExitingAnimation.setDuration(IMAGE_TRANSLATION_DURATION);
        mExitingAnimation.setInterpolator(new AccelerateInterpolator());
        mExitingAnimation.addListener(new SimpleAnimationListener() {

            @Override
            public void onAnimationEnd(Animator animation) {

                mBus.post(new ChangeImageThumbnailVisibility(true));

                Log.v(TAG, "onAnimationEnd, mExitingAnimation " + mExitingAnimation);
                mExitingAnimation = null;

                // finish the activity when animation is finished
                Activity activity = (Activity) mAnimatedImage.getContext();
                activity.finish();
                activity.overridePendingTransition(0, 0);
            }
        });

        mExitingAnimation.playTogether(
                imageAnimatorSet,
                mainContainerFadeAnimator
        );

        mExitingAnimation.start();
    }

    /**
     * This method creates an animator set of 2 animations:
     * 1. ImageView position animation when screen is closed
     * 2. ImageView image matrix animation when screen is closed
     */
    private AnimatorSet createExitingImageAnimation() {
        Log.v(TAG, ">> createExitingImageAnimation");

        ObjectAnimator positionAnimator = createExitingImagePositionAnimator();
        ObjectAnimator matrixAnimator = createExitingImageMatrixAnimator();

        AnimatorSet exitingImageAnimation = new AnimatorSet();
        exitingImageAnimation.playTogether(positionAnimator, matrixAnimator);

        Log.v(TAG, "<< createExitingImageAnimation");
        return exitingImageAnimation;
    }

    /**
     * This method creates an animator that changes ImageView position on the screen.
     * It will look like view is translated from its position on this screen to its position on previous screen
     */
    @NonNull
    private ObjectAnimator createExitingImagePositionAnimator() {

        // get initial location on the screen and start animation from there
        int[] locationOnScreen = new int[2];
        mAnimatedImage.getLocationOnScreen(locationOnScreen);

        PropertyValuesHolder propertyLeft = PropertyValuesHolder.ofInt("left",
                locationOnScreen[0],
                mToLeft);

        PropertyValuesHolder propertyTop = PropertyValuesHolder.ofInt("top",
                locationOnScreen[1] - getStatusBarHeight(),
                mToTop - getStatusBarHeight());

        PropertyValuesHolder propertyRight = PropertyValuesHolder.ofInt("right",
                locationOnScreen[0] + mAnimatedImage.getWidth(),
                mToLeft + mToWidth);


        PropertyValuesHolder propertyBottom = PropertyValuesHolder.ofInt("bottom",
                mAnimatedImage.getBottom(),
                mToTop + mToHeight - getStatusBarHeight());

        return ObjectAnimator.ofPropertyValuesHolder(mAnimatedImage, propertyLeft, propertyTop, propertyRight, propertyBottom);
    }


    /**
     * This method creates animator that animates Matrix of ImageView.
     * It is needed in order to show the effect when scaling of one view is smoothly changed to the scale of the second view.
     * <p>
     * For example: first view can have scaleType: centerCrop, and the other one fitCenter.
     * The image inside ImageView will smoothly change from one to another
     */
    private ObjectAnimator createExitingImageMatrixAnimator() {

        Matrix initialMatrix = MatrixUtils.getImageMatrix(mAnimatedImage);

        Matrix endMatrix = new Matrix();
        endMatrix.setValues(mToThumbnailMatrixValues);

        Log.v(TAG, "createExitingImageMatrixAnimator, initialMatrix " + initialMatrix);
        Log.v(TAG, "createExitingImageMatrixAnimator,     endMatrix " + endMatrix);

        mAnimatedImage.setScaleType(ImageView.ScaleType.MATRIX);

        return ObjectAnimator.ofObject(mAnimatedImage, MatrixEvaluator.ANIMATED_TRANSFORM_PROPERTY,
                new MatrixEvaluator(), initialMatrix, endMatrix);
    }

    private ObjectAnimator createExitingFadeAnimator() {
        ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat(mMainContainer, "alpha", 1.0f, 0.0f);
        return fadeInAnimator;
    }

}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/MatrixEvaluator.java
================================================
package com.volokh.danylo.imagetransition.animations;

import android.animation.TypeEvaluator;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.util.Property;
import android.widget.ImageView;

/**
 * This class is passed to ObjectAnimator in order to animate changes in ImageView image matrix
 */
public class MatrixEvaluator implements TypeEvaluator<Matrix> {

    private static final String TAG = MatrixEvaluator.class.getSimpleName();

    public static TypeEvaluator<Matrix> NULL_MATRIX_EVALUATOR = new TypeEvaluator<Matrix>() {
        @Override
        public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
            return null;
        }
    };

    /**
     * This property is passed to ObjectAnimator when we are animating image matrix of ImageView
     */
    public static final Property<ImageView, Matrix> ANIMATED_TRANSFORM_PROPERTY = new Property<ImageView, Matrix>(Matrix.class,
            "animatedTransform") {

        /**
         * This is copy-paste form ImageView#animateTransform - method is invisible in sdk
         */
        @Override
        public void set(ImageView imageView, Matrix matrix) {
            Drawable drawable = imageView.getDrawable();
            if (drawable == null) {
                return;
            }
            if (matrix == null) {
                drawable.setBounds(0, 0, imageView.getWidth(), imageView.getHeight());
            } else {

                drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
                Matrix drawMatrix = imageView.getImageMatrix();
                if (drawMatrix == null) {
                    drawMatrix = new Matrix();
                    imageView.setImageMatrix(drawMatrix);
                }
                imageView.setImageMatrix(matrix);
            }
            imageView.invalidate();
        }

        @Override
        public Matrix get(ImageView object) {
            return null;
        }
    };

    float[] mTempStartValues = new float[9];

    float[] mTempEndValues = new float[9];

    Matrix mTempMatrix = new Matrix();

    @Override
    public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
        startValue.getValues(mTempStartValues);
        endValue.getValues(mTempEndValues);
        for (int i = 0; i < 9; i++) {
            float diff = mTempEndValues[i] - mTempStartValues[i];
            mTempEndValues[i] = mTempStartValues[i] + (fraction * diff);
        }
        mTempMatrix.setValues(mTempEndValues);

        return mTempMatrix;
    }
}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/MatrixUtils.java
================================================
package com.volokh.danylo.imagetransition.animations;

import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.widget.ImageView;

/**
 * Created by danylo.volokh on 3/14/16.
 */
public class MatrixUtils {

    private static final String TAG = MatrixUtils.class.getSimpleName();

    public static Matrix getImageMatrix(ImageView imageView) {
        Log.v(TAG, "getImageMatrix, imageView " + imageView);

        int left = imageView.getLeft();
        int top = imageView.getTop();
        int right = imageView.getRight();
        int bottom = imageView.getBottom();

        Rect bounds = new Rect(left, top, right, bottom);

        Drawable drawable = imageView.getDrawable();

        Matrix matrix;
        ImageView.ScaleType scaleType = imageView.getScaleType();
        Log.v(TAG, "getImageMatrix, scaleType " + scaleType);

        if (scaleType == ImageView.ScaleType.FIT_XY) {
            matrix = imageView.getImageMatrix();
            if (!matrix.isIdentity()) {
                matrix = new Matrix(matrix);
            } else {
                int drawableWidth = drawable.getIntrinsicWidth();
                int drawableHeight = drawable.getIntrinsicHeight();
                if (drawableWidth > 0 && drawableHeight > 0) {
                    float scaleX = ((float) bounds.width()) / drawableWidth;
                    float scaleY = ((float) bounds.height()) / drawableHeight;
                    matrix = new Matrix();
                    matrix.setScale(scaleX, scaleY);
                } else {
                    matrix = null;
                }
            }
        } else {
            matrix = new Matrix(imageView.getImageMatrix());
        }

        return matrix;
    }

}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/ScreenAnimation.java
================================================
package com.volokh.danylo.imagetransition.animations;

import android.content.Context;

/**
 * Created by danylo.volokh on 3/19/16.
 */
public abstract class ScreenAnimation {

    protected static final long IMAGE_TRANSLATION_DURATION = 1000;

    private final Context mContext;

    protected ScreenAnimation(Context context) {
        mContext = context;
    }

    protected int getStatusBarHeight() {
        int result = 0;
        int resourceId = mContext.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = mContext.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/SimpleAnimationListener.java
================================================
package com.volokh.danylo.imagetransition.animations;

import android.animation.Animator;

/**
 * Created by danylo.volokh on 3/9/16.
 */
public class SimpleAnimationListener implements Animator.AnimatorListener {

    /**
     * For overriding
     */
    @Override
    public void onAnimationStart(Animator animation) {
    }

    /**
     * For overriding
     */
    @Override
    public void onAnimationEnd(Animator animation) {
    }

    /**
     * For overriding
     */
    @Override
    public void onAnimationCancel(Animator animation) {
    }

    /**
     * For overriding
     */
    @Override
    public void onAnimationRepeat(Animator animation) {
    }
}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/event_bus/ChangeImageThumbnailVisibility.java
================================================
package com.volokh.danylo.imagetransition.event_bus;

/**
 * Created by danylo.volokh on 3/15/16.
 *
 * This message is sent from {@link com.volokh.danylo.imagetransition.activities.ImageDetailsActivity}
 * to {@link com.volokh.danylo.imagetransition.activities.ImagesListActivity}
 *
 * When image transition is about to start. This message should invoke hiding of original image
 * Which transition we are imitating.
 *
 */
public class ChangeImageThumbnailVisibility {

    private final boolean visible;

    public ChangeImageThumbnailVisibility(boolean visible) {
        this.visible = visible;
    }

    public boolean isVisible() {
        return visible;
    }
}


================================================
FILE: app/src/main/java/com/volokh/danylo/imagetransition/event_bus/EventBusCreator.java
================================================
package com.volokh.danylo.imagetransition.event_bus;

import com.squareup.otto.Bus;

/**
 * Created by danylo.volokh on 3/14/16.
 * Double checked singleton (basically we need it only in UI thread) for Otto event bus.
 */
public class EventBusCreator {

    private static Bus bus;

    public static Bus defaultEventBus() {

        if (bus == null) {
            synchronized (EventBusCreator.class) {
                if (bus == null) {
                    bus = new Bus();
                }
            }
        }
        return bus;
    }
}


================================================
FILE: app/src/main/res/layout/image_details_activity_layout.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:alpha="0"
    android:orientation="vertical"
    android:background="@color/green">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="@color/white2"
        android:gravity="center"
        android:text="Image Details Activity" />

    <ImageView
        android:id="@+id/enlarged_image"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:scaleType="centerCrop"
        android:visibility="invisible"/>

    <TextView
        android:id="@+id/image_description"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="@color/orange"
        android:gravity="top|left"
        android:padding="20dp"
        android:text="I see a beautiful city and a brilliant people rising from this abyss. I see the lives for which I lay down my life, peaceful, useful, prosperous and happy. I see that I hold a sanctuary in their hearts, and in the hearts of their descendants, generations hence. It is a far, far better thing that I do, than I have ever done; it is a far, far better rest that I go to than I have ever known." />

</LinearLayout>

================================================
FILE: app/src/main/res/layout/image_item.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/image"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

================================================
FILE: app/src/main/res/layout/images_list.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:fitsSystemWindows="true"
    tools:context=".activities.ImagesListActivity"
    android:id="@+id/root_layout"
    android:orientation="vertical">

    <TextView
        android:id="@+id/title"
        android:textSize="18sp"
        android:background="?attr/android:windowBackground"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:gravity="center"/>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/accounts_recycler_view"
        android:layout_width="match_parent"
        android:layout_weight="1"
        android:layout_height="0dp">
    </android.support.v7.widget.RecyclerView>

    <Button
        android:id="@+id/switch_to"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_gravity="bottom"
        android:layout_height="48dp" />

</LinearLayout>


================================================
FILE: app/src/main/res/transition/change_image_transition.xml
================================================
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"

    android:duration="1000">

    <changeClipBounds/>
    <changeBounds/>
    <changeImageTransform/>
</transitionSet>

================================================
FILE: app/src/main/res/transition/enter_exit.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <fade
        android:duration="1000"/>

</transitionSet>

================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>

    <color name="green">@color/material_deep_teal_500</color>
    <color name="dark_blue">@color/material_deep_teal_200</color>
    <color name="orange">#F7D896</color>
    <color name="white2">#DDDDDD</color>

</resources>


================================================
FILE: app/src/main/res/values/styles.xml
================================================
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />


    <style name="ImagesDetailsActivityStyle" parent="AppTheme">

        <item name="colorPrimaryDark">@color/white2</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>

    </style>

    <style name="ImagesListActivityStyle" parent="AppTheme">

        <item name="colorPrimaryDark">@color/green</item>
        <item name="android:windowBackground">@color/green</item>

    </style>
</resources>


================================================
FILE: app/src/main/res/values-v21/styles.xml
================================================
<resources>>

    <style name="AppTheme.NoActionBar_v21" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">false</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/transparent</item>

    </style>

    <style name="ImagesDetailsActivityStyle_v21" parent="AppTheme.NoActionBar_v21">

        <item name="android:windowBackground">@color/green</item>

        <item name="android:windowActivityTransitions">true</item>
        <item name="android:windowContentTransitions">true</item>

        <item name="android:windowAllowEnterTransitionOverlap">true</item>
        <item name="android:windowAllowReturnTransitionOverlap">true</item>

        <item name="android:windowExitTransition">@transition/enter_exit</item>
        <item name="android:windowEnterTransition">@transition/enter_exit</item>

        <item name="android:windowSharedElementEnterTransition">@transition/change_image_transition</item>
        <item name="android:windowSharedElementExitTransition">@transition/change_image_transition</item>

    </style>

    <style name="ImagesListActivityStyle_v21" parent="AppTheme.NoActionBar_v21">

        <item name="android:windowBackground">@color/dark_blue</item>

        <item name="android:windowActivityTransitions">true</item>
        <item name="android:windowContentTransitions">true</item>

        <item name="android:windowExitTransition">@transition/enter_exit</item>
        <item name="android:windowEnterTransition">@transition/enter_exit</item>

        <item name="android:windowSharedElementEnterTransition">@transition/change_image_transition</item>
        <item name="android:windowSharedElementExitTransition">@transition/change_image_transition</item>

    </style>
</resources>


================================================
FILE: app/src/test/java/com/volokh/danylo/imagetransition/ExampleUnitTest.java
================================================
package com.volokh.danylo.imagetransition;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * To work on unit tests, switch the Test Artifact in the Build Variants view.
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Wed Oct 21 11:34:03 PDT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip


================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

================================================
FILE: gradlew
================================================
#!/usr/bin/env bash

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
    echo "$*"
}

die ( ) {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
esac

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
    JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"


================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windowz variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: settings.gradle
================================================
include ':app'
Download .txt
gitextract_v8hwl3px/

├── .gitignore
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── volokh/
│       │               └── danylo/
│       │                   └── imagetransition/
│       │                       └── ApplicationTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── volokh/
│       │   │           └── danylo/
│       │   │               └── imagetransition/
│       │   │                   ├── ImageFilesCreateLoader.java
│       │   │                   ├── activities/
│       │   │                   │   ├── ImageDetailsActivity.java
│       │   │                   │   └── ImagesListActivity.java
│       │   │                   ├── activities_v21/
│       │   │                   │   ├── ImageDetailsActivity_v21.java
│       │   │                   │   └── ImagesListActivity_v21.java
│       │   │                   ├── adapter/
│       │   │                   │   ├── Image.java
│       │   │                   │   ├── ImagesAdapter.java
│       │   │                   │   └── ImagesViewHolder.java
│       │   │                   ├── animations/
│       │   │                   │   ├── EnterScreenAnimations.java
│       │   │                   │   ├── ExitScreenAnimations.java
│       │   │                   │   ├── MatrixEvaluator.java
│       │   │                   │   ├── MatrixUtils.java
│       │   │                   │   ├── ScreenAnimation.java
│       │   │                   │   └── SimpleAnimationListener.java
│       │   │                   └── event_bus/
│       │   │                       ├── ChangeImageThumbnailVisibility.java
│       │   │                       └── EventBusCreator.java
│       │   └── res/
│       │       ├── layout/
│       │       │   ├── image_details_activity_layout.xml
│       │       │   ├── image_item.xml
│       │       │   └── images_list.xml
│       │       ├── transition/
│       │       │   ├── change_image_transition.xml
│       │       │   └── enter_exit.xml
│       │       ├── values/
│       │       │   ├── colors.xml
│       │       │   └── styles.xml
│       │       └── values-v21/
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── volokh/
│                       └── danylo/
│                           └── imagetransition/
│                               └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
Download .txt
SYMBOL INDEX (106 symbols across 18 files)

FILE: app/src/androidTest/java/com/volokh/danylo/imagetransition/ApplicationTest.java
  class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
    method ApplicationTest (line 10) | public ApplicationTest() {

FILE: app/src/main/java/com/volokh/danylo/imagetransition/ImageFilesCreateLoader.java
  class ImageFilesCreateLoader (line 24) | public class ImageFilesCreateLoader implements LoaderManager.LoaderCallb...
    type LoadFinishedCallback (line 49) | public interface LoadFinishedCallback{
      method onLoadFinished (line 50) | void onLoadFinished(List<Image> imagesList);
    method ImageFilesCreateLoader (line 53) | public ImageFilesCreateLoader(Context context, LoadFinishedCallback lo...
    method onCreateLoader (line 58) | @Override
    method writeResourceToFile (line 77) | private void writeResourceToFile(List<File> resultList, int resourceIn...
    method saveIntoFile (line 92) | private void saveIntoFile(File file, Integer resource) {
    method onLoadFinished (line 109) | @Override
    method fillImageList (line 116) | private void fillImageList(List<File> data) {
    method onLoaderReset (line 135) | @Override

FILE: app/src/main/java/com/volokh/danylo/imagetransition/activities/ImageDetailsActivity.java
  class ImageDetailsActivity (line 27) | public class ImageDetailsActivity extends Activity {
    method onCreate (line 52) | @Override
    method initializeEnlargedImageAndRunAnimation (line 89) | private void initializeEnlargedImageAndRunAnimation(final Bundle saved...
    method runEnteringAnimation (line 139) | private void runEnteringAnimation() {
    method initializeTransitionView (line 191) | private void initializeTransitionView() {
    method getStatusBarHeight (line 227) | private int getStatusBarHeight() {
    method onDestroy (line 236) | @Override
    method onBackPressed (line 243) | @Override
    method getStartIntent (line 268) | public static Intent getStartIntent(Activity activity, File imageFile,...
    method onResume (line 283) | @Override

FILE: app/src/main/java/com/volokh/danylo/imagetransition/activities/ImagesListActivity.java
  class ImagesListActivity (line 31) | public class ImagesListActivity extends Activity implements ImagesAdapte...
    method onCreate (line 53) | @Override
    method initializeImagesDownloader (line 74) | private void initializeImagesDownloader() {
    method initializeAdapter (line 78) | private void initializeAdapter() {
    method initializeImages (line 82) | private void initializeImages() {
    method initializeSwitchButton (line 93) | private void initializeSwitchButton() {
    method initializeSavedImageModel (line 113) | private void initializeSavedImageModel(Bundle savedInstanceState) {
    method initializeTitle (line 125) | private void initializeTitle() {
    method initializeRecyclerView (line 130) | private void initializeRecyclerView() {
    method onStart (line 139) | @Override
    method onStop (line 147) | @Override
    method onResume (line 154) | @Override
    method hideImageThumbnail (line 172) | @Subscribe
    method updateModel (line 186) | private void updateModel(Image imageToUpdate) {
    method onSaveInstanceState (line 212) | @Override
    method enterImageDetails (line 218) | @Override

FILE: app/src/main/java/com/volokh/danylo/imagetransition/activities_v21/ImageDetailsActivity_v21.java
  class ImageDetailsActivity_v21 (line 18) | public class ImageDetailsActivity_v21 extends Activity{
    method onCreate (line 28) | @Override
    method getStartIntent (line 49) | public static Intent getStartIntent(Activity activity, String sharedIm...

FILE: app/src/main/java/com/volokh/danylo/imagetransition/activities_v21/ImagesListActivity_v21.java
  class ImagesListActivity_v21 (line 27) | public class ImagesListActivity_v21 extends Activity implements ImagesAd...
    method onCreate (line 39) | @Override
    method enterImageDetails (line 86) | @Override

FILE: app/src/main/java/com/volokh/danylo/imagetransition/adapter/Image.java
  class Image (line 16) | public class Image implements Parcelable{
    method Image (line 26) | public Image(int imageId, File imageFile) {
    method Image (line 31) | protected Image(Parcel in) {
    method equals (line 37) | @Override
    method hashCode (line 47) | @Override
    method toString (line 55) | @Override
    method createFromParcel (line 65) | @Override
    method newArray (line 70) | @Override
    method describeContents (line 76) | @Override
    method writeToParcel (line 81) | @Override
    method isVisible (line 88) | public boolean isVisible() {
    method setVisibility (line 92) | public void setVisibility(boolean isVisible){

FILE: app/src/main/java/com/volokh/danylo/imagetransition/adapter/ImagesAdapter.java
  class ImagesAdapter (line 23) | public class ImagesAdapter extends RecyclerView.Adapter<ImagesViewHolder> {
    type ImagesAdapterCallback (line 32) | public interface ImagesAdapterCallback{
      method enterImageDetails (line 33) | void enterImageDetails(String sharedImageTransitionName, File imageF...
    method ImagesAdapter (line 36) | public ImagesAdapter(ImagesAdapterCallback imagesAdapterCallback, List...
    method createSharedImageTransitionName (line 44) | public static String createSharedImageTransitionName(int position){
    method onCreateViewHolder (line 48) | @Override
    method onBindViewHolder (line 87) | @Override
    method getItemCount (line 126) | @Override
    method getItemViewType (line 137) | @Override

FILE: app/src/main/java/com/volokh/danylo/imagetransition/adapter/ImagesViewHolder.java
  class ImagesViewHolder (line 9) | public class ImagesViewHolder extends RecyclerView.ViewHolder{
    method ImagesViewHolder (line 13) | public ImagesViewHolder(View itemView) {

FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/EnterScreenAnimations.java
  class EnterScreenAnimations (line 20) | public class EnterScreenAnimations extends ScreenAnimation{
    method EnterScreenAnimations (line 52) | public EnterScreenAnimations(ImageView animatedImage, ImageView imageT...
    method playEnteringAnimation (line 64) | public void playEnteringAnimation(int left, int top, int width, int he...
    method createEnteringFadeAnimator (line 115) | private ObjectAnimator createEnteringFadeAnimator() {
    method createEnteringImageAnimation (line 124) | @NonNull
    method createEnteringImagePositionAnimator (line 142) | @NonNull
    method createEnteringImageMatrixAnimator (line 175) | private ObjectAnimator createEnteringImageMatrixAnimator() {
    method getInitialThumbnailMatrixValues (line 194) | public float[] getInitialThumbnailMatrixValues() {
    method cancelRunningAnimations (line 198) | public void cancelRunningAnimations() {

FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/ExitScreenAnimations.java
  class ExitScreenAnimations (line 22) | public class ExitScreenAnimations extends ScreenAnimation{
    method ExitScreenAnimations (line 44) | public ExitScreenAnimations(ImageView animatedImage, ImageView imageTo...
    method playExitAnimations (line 51) | public void playExitAnimations(int toTop, int toLeft, int toWidth, int...
    method playExitingAnimation (line 65) | private void playExitingAnimation() {
    method createExitingImageAnimation (line 108) | private AnimatorSet createExitingImageAnimation() {
    method createExitingImagePositionAnimator (line 125) | @NonNull
    method createExitingImageMatrixAnimator (line 160) | private ObjectAnimator createExitingImageMatrixAnimator() {
    method createExitingFadeAnimator (line 176) | private ObjectAnimator createExitingFadeAnimator() {

FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/MatrixEvaluator.java
  class MatrixEvaluator (line 12) | public class MatrixEvaluator implements TypeEvaluator<Matrix> {
    method evaluate (line 17) | @Override
    method set (line 32) | @Override
    method get (line 53) | @Override
    method evaluate (line 65) | @Override

FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/MatrixUtils.java
  class MatrixUtils (line 12) | public class MatrixUtils {
    method getImageMatrix (line 16) | public static Matrix getImageMatrix(ImageView imageView) {

FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/ScreenAnimation.java
  class ScreenAnimation (line 8) | public abstract class ScreenAnimation {
    method ScreenAnimation (line 14) | protected ScreenAnimation(Context context) {
    method getStatusBarHeight (line 18) | protected int getStatusBarHeight() {

FILE: app/src/main/java/com/volokh/danylo/imagetransition/animations/SimpleAnimationListener.java
  class SimpleAnimationListener (line 8) | public class SimpleAnimationListener implements Animator.AnimatorListener {
    method onAnimationStart (line 13) | @Override
    method onAnimationEnd (line 20) | @Override
    method onAnimationCancel (line 27) | @Override
    method onAnimationRepeat (line 34) | @Override

FILE: app/src/main/java/com/volokh/danylo/imagetransition/event_bus/ChangeImageThumbnailVisibility.java
  class ChangeImageThumbnailVisibility (line 13) | public class ChangeImageThumbnailVisibility {
    method ChangeImageThumbnailVisibility (line 17) | public ChangeImageThumbnailVisibility(boolean visible) {
    method isVisible (line 21) | public boolean isVisible() {

FILE: app/src/main/java/com/volokh/danylo/imagetransition/event_bus/EventBusCreator.java
  class EventBusCreator (line 9) | public class EventBusCreator {
    method defaultEventBus (line 13) | public static Bus defaultEventBus() {

FILE: app/src/test/java/com/volokh/danylo/imagetransition/ExampleUnitTest.java
  class ExampleUnitTest (line 10) | public class ExampleUnitTest {
    method addition_isCorrect (line 11) | @Test
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (86K chars).
[
  {
    "path": ".gitignore",
    "chars": 97,
    "preview": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n"
  },
  {
    "path": "README.md",
    "chars": 1619,
    "preview": "# ImageTransition\nThis is a pet project that shows how to implement shared element image transition on pre-Lollipop devi"
  },
  {
    "path": "app/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "chars": 770,
    "preview": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion \"23.0.2\"\n\n    defaultC"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 654,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in D:"
  },
  {
    "path": "app/src/androidTest/java/com/volokh/danylo/imagetransition/ApplicationTest.java",
    "chars": 364,
    "preview": "package com.volokh.danylo.imagetransition;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/*"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 1193,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package="
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/ImageFilesCreateLoader.java",
    "chars": 4612,
    "preview": "package com.volokh.danylo.imagetransition;\n\nimport android.app.LoaderManager;\nimport android.content.AsyncTaskLoader;\nim"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/activities/ImageDetailsActivity.java",
    "chars": 11589,
    "preview": "package com.volokh.danylo.imagetransition.activities;\n\nimport android.animation.AnimatorSet;\nimport android.app.Activity"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/activities/ImagesListActivity.java",
    "chars": 8231,
    "preview": "package com.volokh.danylo.imagetransition.activities;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimpor"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/activities_v21/ImageDetailsActivity_v21.java",
    "chars": 1796,
    "preview": "package com.volokh.danylo.imagetransition.activities_v21;\n\nimport android.app.Activity;\nimport android.content.Intent;\ni"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/activities_v21/ImagesListActivity_v21.java",
    "chars": 3539,
    "preview": "package com.volokh.danylo.imagetransition.activities_v21;\n\nimport android.app.Activity;\nimport android.app.ActivityOptio"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/adapter/Image.java",
    "chars": 2335,
    "preview": "package com.volokh.danylo.imagetransition.adapter;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport java"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/adapter/ImagesAdapter.java",
    "chars": 5365,
    "preview": "package com.volokh.danylo.imagetransition.adapter;\n\nimport android.support.v4.view.ViewCompat;\nimport android.support.v7"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/adapter/ImagesViewHolder.java",
    "chars": 444,
    "preview": "package com.volokh.danylo.imagetransition.adapter;\n\nimport android.support.v7.widget.RecyclerView;\nimport android.view.V"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/animations/EnterScreenAnimations.java",
    "chars": 8104,
    "preview": "package com.volokh.danylo.imagetransition.animations;\n\nimport android.animation.Animator;\nimport android.animation.Anima"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/animations/ExitScreenAnimations.java",
    "chars": 6604,
    "preview": "package com.volokh.danylo.imagetransition.animations;\n\nimport android.animation.Animator;\nimport android.animation.Anima"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/animations/MatrixEvaluator.java",
    "chars": 2605,
    "preview": "package com.volokh.danylo.imagetransition.animations;\n\nimport android.animation.TypeEvaluator;\nimport android.graphics.M"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/animations/MatrixUtils.java",
    "chars": 1797,
    "preview": "package com.volokh.danylo.imagetransition.animations;\n\nimport android.graphics.Matrix;\nimport android.graphics.Rect;\nimp"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/animations/ScreenAnimation.java",
    "chars": 688,
    "preview": "package com.volokh.danylo.imagetransition.animations;\n\nimport android.content.Context;\n\n/**\n * Created by danylo.volokh "
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/animations/SimpleAnimationListener.java",
    "chars": 672,
    "preview": "package com.volokh.danylo.imagetransition.animations;\n\nimport android.animation.Animator;\n\n/**\n * Created by danylo.volo"
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/event_bus/ChangeImageThumbnailVisibility.java",
    "chars": 674,
    "preview": "package com.volokh.danylo.imagetransition.event_bus;\n\n/**\n * Created by danylo.volokh on 3/15/16.\n *\n * This message is "
  },
  {
    "path": "app/src/main/java/com/volokh/danylo/imagetransition/event_bus/EventBusCreator.java",
    "chars": 546,
    "preview": "package com.volokh.danylo.imagetransition.event_bus;\n\nimport com.squareup.otto.Bus;\n\n/**\n * Created by danylo.volokh on "
  },
  {
    "path": "app/src/main/res/layout/image_details_activity_layout.xml",
    "chars": 1514,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/main/res/layout/image_item.xml",
    "chars": 220,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android"
  },
  {
    "path": "app/src/main/res/layout/images_list.xml",
    "chars": 1136,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "app/src/main/res/transition/change_image_transition.xml",
    "chars": 193,
    "preview": "<transitionSet xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\n    android:duration=\"1000\">\n\n    <changeClip"
  },
  {
    "path": "app/src/main/res/transition/enter_exit.xml",
    "chars": 175,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<transitionSet xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <f"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 420,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"color"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 969,
    "preview": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar"
  },
  {
    "path": "app/src/main/res/values-v21/styles.xml",
    "chars": 1884,
    "preview": "<resources>>\n\n    <style name=\"AppTheme.NoActionBar_v21\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <item nam"
  },
  {
    "path": "app/src/test/java/com/volokh/danylo/imagetransition/ExampleUnitTest.java",
    "chars": 326,
    "preview": "package com.volokh.danylo.imagetransition;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * To work on"
  },
  {
    "path": "build.gradle",
    "chars": 498,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    r"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 230,
    "preview": "#Wed Oct 21 11:34:03 PDT 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradle.properties",
    "chars": 855,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
  },
  {
    "path": "gradlew",
    "chars": 4971,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2314,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
  },
  {
    "path": "settings.gradle",
    "chars": 15,
    "preview": "include ':app'\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the danylovolokh/ImageTransition GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (78.1 KB), approximately 19.5k tokens, and a symbol index with 106 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!