net.evendanan.pushingpixels.FragmentChauffeurActivity.java Source code

Java tutorial

Introduction

Here is the source code for net.evendanan.pushingpixels.FragmentChauffeurActivity.java

Source

/*
 * Copyright (c) 2013 Menny Even-Danan
 *
 * 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.
 */

package net.evendanan.pushingpixels;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.ActionBarActivity;
import android.view.View;

import com.anysoftkeyboard.utils.Log;
import com.menny.android.anysoftkeyboard.R;

public abstract class FragmentChauffeurActivity extends ActionBarActivity {

    public static enum FragmentUiContext {
        RootFragment, DeeperExperience, ExpandedItem, IncomingAlert
    }

    private static final String TAG = "chauffeur";

    private static final String ROOT_FRAGMENT_TAG = "FragmentChauffeurActivity_ROOT_FRAGMENT_TAG";

    private static final String KEY_FRAGMENT_CLASS_TO_ADD = "KEY_FRAGMENT_CLASS_TO_ADD";
    private static final String KEY_FRAGMENT_ARGS_TO_ADD = "KEY_FRAGMENT_ARGS_TO_ADD";

    public static void addIntentArgsForAddingFragmentToUi(@NonNull Intent intent,
            @NonNull Class<? extends Fragment> fragmentClass, @Nullable Bundle fragmentArgs) {
        intent.putExtra(KEY_FRAGMENT_CLASS_TO_ADD, fragmentClass);
        if (fragmentArgs != null)
            intent.putExtra(KEY_FRAGMENT_ARGS_TO_ADD, fragmentArgs);
    }

    private boolean mIsActivityShown = false;

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mIsActivityShown = true;
        if (savedInstanceState == null) {
            //setting up the root of the UI.
            setRootFragment(createRootFragmentInstance());
            //now, checking if there is a request to add a fragment on-top of this one.
            Bundle activityArgs = getIntent().getExtras();
            if (activityArgs != null && activityArgs.containsKey(KEY_FRAGMENT_CLASS_TO_ADD)) {
                Class<? extends Fragment> fragmentClass = (Class<? extends Fragment>) activityArgs
                        .get(KEY_FRAGMENT_CLASS_TO_ADD);
                //not sure that this is a best-practice, but I still need to remove this from the activity's args
                activityArgs.remove(KEY_FRAGMENT_CLASS_TO_ADD);
                try {
                    Fragment fragment = fragmentClass.newInstance();
                    if (activityArgs.containsKey(KEY_FRAGMENT_ARGS_TO_ADD)) {
                        fragment.setArguments(activityArgs.getBundle(KEY_FRAGMENT_ARGS_TO_ADD));
                        activityArgs.remove(KEY_FRAGMENT_CLASS_TO_ADD);
                    }
                    addFragmentToUi(fragment, FragmentUiContext.RootFragment);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    protected abstract int getFragmentRootUiElementId();

    protected abstract Fragment createRootFragmentInstance();

    public void returnToRootFragment() {
        if (!mIsActivityShown)
            return;

        getSupportFragmentManager().popBackStackImmediate(ROOT_FRAGMENT_TAG, 0 /*don't pop the root*/);
    }

    public void setRootFragment(Fragment fragment) {
        getSupportFragmentManager().popBackStack(ROOT_FRAGMENT_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.setCustomAnimations(R.anim.ui_context_root_add_in, R.anim.ui_context_root_add_out,
                R.anim.ui_context_root_pop_in, R.anim.ui_context_root_pop_out);
        transaction.replace(getFragmentRootUiElementId(), fragment);
        //bookmarking, so I can return easily.
        transaction.addToBackStack(ROOT_FRAGMENT_TAG);
        transaction.commit();
    }

    public void addFragmentToUi(@NonNull Fragment fragment, FragmentUiContext experience) {
        addFragmentToUi(fragment, experience, null);
    }

    /**
     * Adds the given fragment into the UI using the specified UI-context animation.
     *
     * @param fragment      any generic Fragment. For the ExpandedItem animation it is best to use a PassengerFragment
     * @param experience
     * @param originateView a hint view which will be used to fine-tune the ExpandedItem animation
     */
    public void addFragmentToUi(@NonNull Fragment fragment, FragmentUiContext experience,
            @Nullable View originateView) {
        if (!mIsActivityShown)
            return;

        if (experience == FragmentUiContext.RootFragment) {
            //in this case, I need to pop all the other fragments till the root.
            returnToRootFragment();
        }
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        //note: the animation should be declared before the fragment replace call, so the transaction will know to which fragment change it should be associated with.
        switch (experience) {
        case RootFragment:
            transaction.setCustomAnimations(R.anim.ui_context_root_add_in, R.anim.ui_context_root_add_out,
                    R.anim.ui_context_root_pop_in, R.anim.ui_context_root_pop_out);
            break;
        case DeeperExperience:
            transaction.setCustomAnimations(R.anim.ui_context_deeper_add_in, R.anim.ui_context_deeper_add_out,
                    R.anim.ui_context_deeper_pop_in, R.anim.ui_context_deeper_pop_out);
            break;
        case ExpandedItem:
            //although this managing Activity can handle any generic Fragment, in this case we'll need some help from the fragment.
            //it is required to fine tune the pivot of the scale animation.
            //so, I'll need the specialized fragment PassengerFragment
            if (fragment instanceof Passengerable && originateView != null) {
                View fragmentParent = findViewById(getFragmentRootUiElementId());

                // Idea taken from:
                // http://developer.android.com/training/animation/zoom.html
                final float scaleX = ((float) originateView.getWidth()) / ((float) fragmentParent.getWidth());
                final float scaleY = ((float) originateView.getHeight()) / ((float) fragmentParent.getHeight());
                // some preparations
                // the Y pivot is tricky, it should be the middle of the button, but in
                // the fragmentParent coordinates
                int[] originateLocation = new int[2];
                originateView.getLocationInWindow(originateLocation);
                int[] parentLocation = new int[2];
                fragmentParent.getLocationInWindow(parentLocation);
                final int pivotY = originateLocation[1] - parentLocation[1] + (originateView.getHeight() / 2);
                final int pivotX = originateLocation[0] - parentLocation[0] + (originateView.getWidth() / 2);

                Passengerable passengerFragment = (Passengerable) fragment;
                passengerFragment.setItemExpandExtraData(pivotX, pivotY, scaleX, scaleY);
                transaction.setCustomAnimations(R.anim.ui_context_expand_add_in, R.anim.ui_context_expand_add_out,
                        R.anim.ui_context_expand_pop_in, R.anim.ui_context_expand_pop_out);
            } else {
                //using the default scale animation, no pivot changes can be done on a generic fragment.
                transaction.setCustomAnimations(R.anim.ui_context_expand_add_in_default,
                        R.anim.ui_context_expand_add_out, R.anim.ui_context_expand_pop_in,
                        R.anim.ui_context_expand_pop_out_default);
            }
            break;
        case IncomingAlert:
            transaction.setCustomAnimations(R.anim.ui_context_dialog_add_in, R.anim.ui_context_dialog_add_out,
                    R.anim.ui_context_dialog_pop_in, R.anim.ui_context_dialog_pop_out);
            break;
        default:
            Log.wtf(TAG, "I don't know what is this UI experience type: " + experience);
            break;
        }
        //these two calls will make sure the back-button will switch to previous fragment
        transaction.replace(getFragmentRootUiElementId(), fragment);
        transaction.addToBackStack(null);
        transaction.commit();
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
            //the UI is empty. I can safely finish the activity
            finish();
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        mIsActivityShown = true;
    }

    @Override
    protected void onStop() {
        super.onStop();
        mIsActivityShown = false;
    }

    public final boolean isChaufferActivityVisible() {
        return mIsActivityShown;
    }
}