Android Open Source - Intro-to-Android Presentation Activity






From Project

Back to project page Intro-to-Android.

License

The source code is released under:

Apache License

If you think the Android project Intro-to-Android listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2011 Michael Imamura/*  ww  w.  j  a  va  2 s  .c  o m*/
 * 
 * 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 org.lugatgt.zoogie.present.ui;

import android.animation.ObjectAnimator;
import android.app.ActionBar;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Handler;
import android.os.StrictMode;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.DecelerateInterpolator;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;

import org.lugatgt.zoogie.present.Presentation;
import org.lugatgt.zoogie.present.R;
import org.lugatgt.zoogie.present.Slide;
import org.lugatgt.zoogie.present.SlideTransition;


/**
 * Main presentation activity.
 * <p>
 * The activity can be in one of two states, based on the {@code tocVisible}
 * flag:
 * <ol>
 * <li>Normal ("presentation") mode: This is the initial mode.</li>
 * <li>Table of Contents ("TOC") mode: The slide view is minimized to a
 *     preview in the corner, allowing the user to jump quickly to any
 *     slide in the presentation.</li>
 * </ol>
 * 
 * @author Michael Imamura
 */
public abstract class PresentationActivity extends Activity implements Presentation.OnIndexChangedListener {
    
    private static final String TAG = PresentationActivity.class.getSimpleName();
    
    /** Time (in ms) until the action bar automatically fades out. */
    private static final int ACTIONBAR_AUTOHIDE_TIMEOUT = 3000;
    
    private static final String CONTENT_FRAG_TAG = "contentSlide";
    
    private static final String TOC_VISIBLE_KEY = "tocVisible";
    private static final String NAV_TOOLBAR_VISIBLE_KEY = "navToolbarVisible";
    
    private Presentation presentation;
    private boolean tocVisible = false;
    private boolean navToolbarVisible = true;
    
    /** Cache of the slide titles (we assume that they won't change). */
    private CharSequence[] slideTitles;
    
    private View rootView;
    
    private TextView actionbarSlideTitleLbl;
    
    private ViewGroup slideFrame;
    private ViewGroup navToolbarFrame;
    private ViewGroup tocFrame;
    
    private ImageButton prevBtn;
    private ImageButton nextBtn;
    
    private ListView tocList;
    
    private Handler handler;
    private Runnable fadeoutRunnable;
    
    // CONSTRUCTORS ////////////////////////////////////////////////////////////
    
    public PresentationActivity(Presentation presentation) {
        this.presentation = presentation;
        presentation.setOnIndexChangedListener(this);
    }
    
    // FIELD ACCESS ////////////////////////////////////////////////////////////
    
    protected Presentation getPresentation() {
        return presentation;
    }
    
    // LIFECYCLE ///////////////////////////////////////////////////////////////
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // Check if we are in a debug build.
        boolean debugPackage = false;
        try {
            debugPackage = (getPackageManager().getPackageInfo(getPackageName(), 0).applicationInfo.flags &
                ApplicationInfo.FLAG_DEBUGGABLE) != 0;
        } catch (NameNotFoundException ex) {
            // Shouldn't happen (in theory).
            Log.w(TAG, "Failed to retrieve our own debug status", ex);
        }
        
        if (debugPackage) {
            Log.i(TAG, "Initializing strict mode for debug build");
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build());
        }
        
        slideTitles = presentation.getSlideTitles(this);
        // Since the first slide's title is the name of the presentation,
        // we replace it for listing purposes with just "Title".
        slideTitles[0] = getString(R.string.title_slide_title);
        
        requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
        setContentView(R.layout.toc);
        
        rootView = findViewById(R.id.presenterRoot);
        
        ActionBar bar = getActionBar();
        bar.setDisplayHomeAsUpEnabled(false);
        bar.setDisplayUseLogoEnabled(false);
        bar.setDisplayShowCustomEnabled(true);
        bar.setCustomView(R.layout.actionbar);
        bar.addOnMenuVisibilityListener(new ActionBar.OnMenuVisibilityListener() {
            @Override
            public void onMenuVisibilityChanged(boolean isVisible) {
                // Keep the action bar visible while the menu is shown.
                resetActionBarTimeout(isVisible);
            }
        });
        
        View actionbarView = bar.getCustomView();
        actionbarSlideTitleLbl = (TextView)actionbarView.findViewById(R.id.actionbar_slideTitle);
        
        slideFrame = (ViewGroup)findViewById(R.id.presentationFrame);
        navToolbarFrame = (ViewGroup)findViewById(R.id.mainToolbarFrame);
        tocFrame = (ViewGroup)findViewById(R.id.tocFrame);
        
        prevBtn = (ImageButton)findViewById(R.id.prevBtn);
        prevBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presentation.prev();
            }
        });
        
        nextBtn = (ImageButton)findViewById(R.id.nextBtn);
        nextBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presentation.next();
            }
        });
        
        // Tapping on the slide preview in TOC mode switches back to
        // presentation mode.
        findViewById(R.id.slidePreview).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                toggleTableOfContents();
            }
        });
        
        // Tapping on the title frag makes the action bar visible and resets
        // the fade-out timer.
        getFragmentManager().findFragmentById(R.id.titleFragment).getView().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                resetActionBarTimeout(false);
            }
        });
        
        if (savedInstanceState == null) {
            // Starting from scratch.
            onAfterIndexChanged(null, 0, true);
        } else {
            // The title and slide fragments will restore their own state;
            // we only need to restore the presentation internal state.
            presentation.onRestoreInstanceState(savedInstanceState);
            tocVisible = savedInstanceState.getBoolean(TOC_VISIBLE_KEY);
            navToolbarVisible = savedInstanceState.getBoolean(NAV_TOOLBAR_VISIBLE_KEY, true);
            
            Slide curSlide = presentation.getCurrentSlide();
            int curSlideIdx = presentation.getCurrentSlideIndex();
            updateNavigation(curSlide, curSlideIdx);
            updateToolbarState(curSlide, curSlideIdx);
            
            setTocViewState(tocVisible ? 0.0f : 1.0f);
            if (tocVisible) {
                initToc();
            }
        }
        
        // Set up the callback for auto-hiding the action bar after a delay.
        handler = new Handler();
        fadeoutRunnable = new Runnable() {
            @Override
            public void run() {
                rootView.setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
                getActionBar().hide();
            }
        };
        if (!tocVisible) {
            resetActionBarTimeout(false);
        }
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.presentation, menu);
        return true;
    }
    
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        menu.findItem(R.id.menu_nav_toolbar).setTitle(
            navToolbarVisible ? R.string.menu_hide_nav_toolbar : R.string.menu_show_nav_toolbar);
        return true;
    }
    
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        
        presentation.onSaveInstanceState(outState);
        outState.putBoolean(TOC_VISIBLE_KEY, tocVisible);
        outState.putBoolean(NAV_TOOLBAR_VISIBLE_KEY, navToolbarVisible);
    }
    
    // EVENTS //////////////////////////////////////////////////////////////////
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        
        // These define the global navigation keys.
        // Since this is only called if a view on a slide doesn't handle the
        // keypress first, slides should avoid defining keyboard shortcuts
        // that interfere with the following keys.
        // (Of course, for some views it's unavoidable, e.g. EditText). 
        
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_RIGHT:
            case KeyEvent.KEYCODE_SPACE:
                presentation.next();
                return true;
                
            case KeyEvent.KEYCODE_DPAD_LEFT:
                presentation.prev();
                return true;
        }
        
        return super.onKeyDown(keyCode, event);
    }
    
    @Override
    public void onBackPressed() {
        // Going back from TOC mode is normal mode.
        if (tocVisible) {
            toggleTableOfContents();
        } else {
            super.onBackPressed();
        }
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        
        if (id == R.id.menu_title) {
            presentation.jumpTo(0);
            if (tocVisible) {
                toggleTableOfContents();
            }
            return true;
        } else if (id == R.id.menu_nav_toolbar) {
            navToolbarVisible = !navToolbarVisible;
            updateToolbarState(presentation.getCurrentSlide(), presentation.getCurrentSlideIndex());
            return true;
        } else if (id == R.id.menu_toc) {
            toggleTableOfContents();
            return true;
        } else {
            return super.onOptionsItemSelected(item);
        }
    }
    
    // CONTENT FRAGMENT ////////////////////////////////////////////////////////
    
    /**
     * Prepare a transaction for transitioning to a new slide.
     * @param prevSlide The previous slide (may be null if no previous slide).
     * @param slide The new slide (may not be null).
     * @param animated true if the transition should be animated.
     * @return The initialized transaction (never null).
     *         It is up to the caller to amend and commit it.
     */
    protected FragmentTransaction createFragmentTransaction(Slide prevSlide, Slide slide, boolean animated) {
        FragmentManager fragMgr = getFragmentManager();
        
        // Create the slide fragment.
        SlideFragment slideFrag;
        try {
            slideFrag = slide.createFragment();
            if (slideFrag == null) {
                throw new RuntimeException("Slide fragment is null: " + slide);
            }
        } catch (Exception ex) {
            Log.e(TAG, "Unable to instantiate slide fragment: " + slide, ex);
            //TODO: Use a standard placeholder slide fragment.
            throw new RuntimeException(ex);
        }
        
        // Add or replace the slide fragment, depending if
        // there's a content fragment or not.
        FragmentTransaction ft = fragMgr.beginTransaction();
        
        if (animated) {
            // The animations must be specified before the action (replace/add)
            // is specified, otherwise no animation will play.
            if (prevSlide != null) {
                SlideTransition transition = prevSlide.getTransition();
                if (transition != null) {
                    ft.setCustomAnimations(
                        transition.getInAnimationRes(),
                        transition.getOutAnimationRes());
                }
            }
        }
        
        // If there is an existing visible slide fragment, then detach it (mark
        // it as no longer the active slide) and replace it and mark the new
        // fragment as active.  Otherwise, just add the new fragment and mark it
        // as active.
        SlideFragment oldFragment = (SlideFragment)fragMgr.findFragmentByTag(CONTENT_FRAG_TAG);
        if (oldFragment != null) {
            oldFragment.onDetachFromPresentation();
            ft.replace(R.id.slideContainer, slideFrag, CONTENT_FRAG_TAG);
        } else {
            ft.add(R.id.slideContainer, slideFrag, CONTENT_FRAG_TAG);
        }
        slideFrag.onAttachToPresentation(presentation);
        
        return ft;
    }
    
    // TABLE OF CONTENTS ///////////////////////////////////////////////////////
    
    /**
     * Toggle the visible state of the table of contents.
     */
    protected void toggleTableOfContents() {
        ObjectAnimator anim = null;
        if (tocVisible) {
            // Leaving TOC mode.
            anim = ObjectAnimator.ofFloat(this, "tocViewState", 0.0f, 1.0f);
            //TODO: Destroy the TOC views when animation is finished.
        } else {
            // Entering TOC mode.
            anim = ObjectAnimator.ofFloat(this, "tocViewState", 1.0f, 0.0f);
            initToc();
        }
        anim.setDuration(getResources().getInteger(R.integer.tocTransitionDuration));
        anim.setInterpolator(new DecelerateInterpolator());
        anim.start();
        
        tocVisible = !tocVisible;
        
        // Keep the action bar visible while TOC mode is active.
        resetActionBarTimeout(tocVisible);
        
        updateNavigation(presentation.getCurrentSlide(), presentation.getCurrentSlideIndex());
    }
    
    /**
     * Set how far expanded the slide fragment container is.
     * @param amount 1.0f for fully-expanded (table of contents not visible) to
     *               0.0f for fully-shrunk (table of contents visible).
     */
    public void setTocViewState(float amount) {
        slideFrame.setX((1.0f - amount) * getResources().getDimension(R.dimen.toc_slide_shrink_translate_x));
        slideFrame.setY((1.0f - amount) * getResources().getDimension(R.dimen.toc_slide_shrink_translate_y));
        slideFrame.setScaleX((1.0f/3.0f) + (amount * (2.0f / 3.0f)));
        slideFrame.setScaleY((1.0f/3.0f) + (amount * (2.0f / 3.0f)));
        
        // Hide the prev/next toolbar since it doesn't work in TOC mode.
        navToolbarFrame.setAlpha(amount);
        
        tocFrame.setVisibility((amount < 0.99f) ? View.VISIBLE : View.GONE);
        tocFrame.setScaleX((2.0f * amount) + 1.0f);
        tocFrame.setScaleY((2.0f * amount) + 1.0f);
        tocFrame.setAlpha(1.0f - amount);
    }
    
    /**
     * Set up the table of contents views.
     */
    protected void initToc() {
        // Lazily initialize the table of contents list.
        if (tocList == null) {
            tocList = (ListView)findViewById(R.id.tocList);
            tocList.setAdapter(new ArrayAdapter<CharSequence>(this, R.layout.toc_list_item, slideTitles));
            tocList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    getPresentation().jumpTo(position);
                }
            });
        }
        
        int pos = presentation.getCurrentSlideIndex();
        tocList.setSelection(pos);
        tocList.setItemChecked(pos, true);
    }
    
    // ACTION BAR //////////////////////////////////////////////////////////////
    
    /**
     * Show the action bar, optionally auto-hiding it after a delay.
     * @param keepVisible true to keep the action bar visible,
     *                    false to auto-hide it after a fixed interval.
     */
    protected void resetActionBarTimeout(boolean keepVisible) {
        rootView.setSystemUiVisibility(View.STATUS_BAR_VISIBLE);
        getActionBar().show();
        
        // Remove any pending auto-hide event, and if auto-hide is requested,
        // then set it again with the full timeout time.
        handler.removeCallbacks(fadeoutRunnable);
        if (!keepVisible) {
            handler.postDelayed(fadeoutRunnable, ACTIONBAR_AUTOHIDE_TIMEOUT);
        }
    }
    
    // VIEW STATE //////////////////////////////////////////////////////////////
    
    /**
     * Convenience function to update all UI elements on slide transition.
     * @param slide The current slide (may not be null).
     * @param idx The index of the current slide.
     * @param animate true to animate the transition, if possible.
     */
    private void updateUi(Slide slide, int idx, boolean animate) {
        updateTitle(slide, idx, animate);
        updateNavigation(slide, idx);
        updateToolbarState(slide, idx);
    }
    
    /**
     * Update the navigation controls in the action bar.
     * @param slide The current slide (may not be null).
     * @param idx The index of the current slide.
     */
    protected void updateNavigation(Slide slide, int idx) {
        if (tocVisible) {
            actionbarSlideTitleLbl.setText(R.string.menu_toc);
        } else {
            actionbarSlideTitleLbl.setText(slideTitles[idx]);
        }
    }
    
    /**
     * Update the title fragment.
     * @param slide The current slide (may not be null).
     * @param idx The index of the current slide.
     * @param animate true to animate the transition, if possible.
     */
    protected void updateTitle(Slide slide, int idx, boolean animate) {
        FragmentManager fragMgr = getFragmentManager();
        TitleFragment titleFrag = (TitleFragment)fragMgr.findFragmentById(R.id.titleFragment);
        titleFrag.setSlide(slide, idx, animate);
    }
    
    /**
     * Update the state of the toolbar buttons.
     * @param slide The current slide (may not be null).
     * @param idx The index of the current slide.
     */
    protected void updateToolbarState(Slide slide, int idx) {
        navToolbarFrame.setVisibility(navToolbarVisible ? View.VISIBLE : View.GONE);
        
        int curSlideIndex = presentation.getCurrentSlideIndex();
        prevBtn.setEnabled(curSlideIndex > 0);
        nextBtn.setEnabled(curSlideIndex < presentation.getSlideCount() - 1);
    }
    
    // Presentation.OnIndexChangedListener /////////////////////////////////////
    
    public void onAfterIndexChanged(Slide oldSlide, int oldIndex, boolean immediate) {
        Slide slide = presentation.getCurrentSlide();
        createFragmentTransaction(oldSlide, slide, !immediate).commit();
        updateUi(slide, presentation.getCurrentSlideIndex(), !immediate);
    }
    
}




Java Source Code List

org.lugatgt.zoogie.introtoandroid.AboutActivity.java
org.lugatgt.zoogie.introtoandroid.MainActivity.java
org.lugatgt.zoogie.introtoandroid.MainPresentation.java
org.lugatgt.zoogie.introtoandroid.slide.AndroidSlide.java
org.lugatgt.zoogie.introtoandroid.slide.ApiHistorySlide.java
org.lugatgt.zoogie.introtoandroid.slide.BasicTextSlide.java
org.lugatgt.zoogie.introtoandroid.slide.ForkThisSlide.java
org.lugatgt.zoogie.introtoandroid.slide.InternalsSlide.java
org.lugatgt.zoogie.introtoandroid.slide.PartsSlide.java
org.lugatgt.zoogie.introtoandroid.slide.TitleSlide.java
org.lugatgt.zoogie.introtoandroid.ui.PartLayer.java
org.lugatgt.zoogie.introtoandroid.ui.PartsView.java
org.lugatgt.zoogie.present.Presentation.java
org.lugatgt.zoogie.present.SlideTransition.java
org.lugatgt.zoogie.present.Slide.java
org.lugatgt.zoogie.present.Transitions.java
org.lugatgt.zoogie.present.ui.AboutActivity.java
org.lugatgt.zoogie.present.ui.PresentationActivity.java
org.lugatgt.zoogie.present.ui.SlideFragment.java
org.lugatgt.zoogie.present.ui.SlideListSpinnerAdapter.java
org.lugatgt.zoogie.present.ui.TitleFragment.java