Layouts

Presenter – Part 3

Previously in this series we’ve looked at the app that I used for my presentation at AndroidConf Brasil 2011. In this part we’ll have a look at the custom controls used to manage the individual slides

Let’s dive straight in with a look at SlideFragment:

[java] public class SlideFragment extends Fragment
{
private int resource;

public SlideFragment( int resource )
{
this.resource = resource;
}

@Override
public View onCreateView( LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState )
{
View v = inflater.inflate( resource, container, false );
if( container instanceof DisplayLayout )
{
((DisplayLayout)container).setCurrentSlide(
(SlideLayout )v.findViewById( R.id.slide ) );
}
return v;
}
}
[/java]

This is really quite straightforward. The constructor takes a single argument – a resource ID of the layout that we want to use in this Fragment.

In our onCreateView method, we inflate the view. Note: it is assumed that the resource ID is valid. In production code it would be sensible to perform some checks. We then check if the parent is a DisplayLayout (see Part 2 of this series for details) and call setSlideLayout to set its currentSlide. The reason that we have to do this is because the slide will not exist until after the Fragment view has been created. This is managed by FragmentTransaction, so we cannot intercept this from within the DisplayLayout class, so we do it here, instead.

Next we have an interface named Phaseable:

[java] public interface Phaseable
{
public boolean setPhase( final int phase );
public int getLastPhase();
}
[/java]

This is implemented by any control which can be attached to specific slide phases. There was a discussion about slide phases at the end of the last article. The two methods are used to determine the highest phase that the visibility of the control changes, and to trigger a phase transition when required. We’ll look at the implementation of this in due course.

The main workhorse of the slide phase engine is the SlideLayout class. Each slide in our presentation must be held within a SlideLayout, which is an augmented LinearLayout.

Let’s have a look at the fields and constructors:

[java] public class SlideLayout extends LinearLayout
{
private final static String TAG = StandaloneDisplayActivity.TAG;

private List phaseables = new ArrayList();
private int phase = 0;

private int inAnimation = 0;
private int outAnimation = 0;

private String tweet = null;
private String notes = null;

public SlideLayout( Context context, AttributeSet attrs )
{
this( context, attrs, 0 );
}

public SlideLayout( Context context, AttributeSet attrs, int defStyle )
{
super( context, attrs, defStyle );
TypedArray ta = context.obtainStyledAttributes( attrs,
R.styleable.SlideLayout );
inAnimation = ta.getResourceId(
R.styleable.SlideLayout_nextInAnimation, 0 );
outAnimation = ta.getResourceId(
R.styleable.SlideLayout_nextOutAnimation, 0 );
int tweetRes = ta.getResourceId(
R.styleable.SlideLayout_tweet, 0 );
if( tweetRes > 0 )
{
tweet = context.getResources().getString( tweetRes );
}
else
{
tweet = ta.getString( R.styleable.SlideLayout_tweet );
}
int notesRes = ta.getResourceId(
R.styleable.SlideLayout_notes, 0 );
if( notesRes > 0 )
{
notes = context.getResources().getString( notesRes );
}
else
{
notes = ta.getString( R.styleable.SlideLayout_notes );
}
ta.recycle();
phase = 0;
}
.
.
.
}
[/java]

The fields are as follows:

  • String TAG
    A tag used for logging.
  • List phaseables
    A list of the Phaseable Views in the current layout.
  • int phase
    An representing the current phase.
  • int inAnimation
    A animation ID representing an animation to apply to an incoming slide.
  • int outAnimation
    A animation ID representing an animation to apply to an outgoing slide.
  • String tweet
    This is for the full version of the app which supports live tweeting as the slide loads.
  • String notes
    This is for the full version of the app which supports the display of slide notes in the controller app.

These should all be fairly self-explanatory, particularly if you’ve read the previous article which explained the DisplayLayout class. The tweet & notes fields are not relevant for the lite version of the app, so I’ll ignore them for the purposes of this description.

The only thing worth noting is that the animations defined here will, if set, override the defaults in DisplayLayout. There are a couple of getters for these animations should not require any explanation..

The constructors initialise some of these fields, and use the attributes defined in res/values/attrs.xml:

[xml]





[/xml]

Now let.s explore how the phase engine actually works. When the slide is first loaded, we search through the children to find any which are Phaseable and add them to our phaseables list:

[java] @Override
protected void onFinishInflate()
{
super.onFinishInflate();
phaseables.clear();
getPhases( this, phaseables );
phase = 0;
}

private static void getPhases( ViewGroup parent, List phaseables)
{
for( int i = 0; i < parent.getChildCount(); i++ ) { View child = parent.getChildAt( i ); int phase = 0; if( child instanceof Phaseable ) { Phaseable phaseable = (Phaseable)child; if( phaseable.getLastPhase() > 0 )
{
phaseables.add( (Phaseable )child );
}
}
Log.d( TAG, “Phase: ” + phase + “; ” + child.toString() );
if( child instanceof ViewGroup )
{
getPhases( (ViewGroup)child, phaseables );
}
}
}
[/java]

The getPhases method is an recursive function which is going to search the layout hierarchy for any View objects which implement Phaseable and add them to the phaseables list provided that the View is attached to a phase higher than 0.

The phase is advanced by calling the nextPhase method:

[java] public void nextPhase()
{
phase++;
List complete = new ArrayList();
for( Phaseable phaseable : phaseables )
{
if( phaseable.setPhase( phase ) )
{
complete.add( phaseable );
}
}
phaseables.removeAll( complete );
}
[/java]

Here we increment the phase, then iterate through the remaining Phaseables and call setPhase on each. If setPhase returns true it indicates that the Phaseable has reached the final phase at which it will transition. We add that Phaseable to a list of Phaseables which can be deleted and, before the method returns, we remove the completed Phaseables from the phaseables list.

This technique of removing Phaseables from our phaseables list makes the implementation of hasMorePhases extremel simple:

[java] public boolean hasMorePhases()
{
return !phaseables.isEmpty();
}
[/java]

So, we now have both our Display and Slide layouts in place but what about those conrols which implement Phaseable? I’ve actually only implemented one which is a customised TextView. In the next part of this series we’ll have a look at that.

The source code for PresenterLite can be found here.

© 2011, Mark Allison. All rights reserved.

Copyright © 2011 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

2 Comments

  1. Hi,
    I love your blog and I read every post you make.
    I’m trying to import your last serial: PresenterLite from GIT, but when I import it, it doesn’t compile. I’ve imported many GIT project before, but for some reason there is a problem with this specific project.
    1) Can you help me with that?
    2) If not I’ll be happy just to be able to download the APK (from the Market or from other resource – email maybe?) in order to see and feel the designs you’re talking about.

    Thanks a lot!

    Eitan

    1. Hi Eitan,

      Thanks for nice comments.

      Without knowing what error you’re getting, it’s difficult to understand what your problem might be. The obvious thing would be that the code is only tested and working for Honeycomb at present.

      If you’re still unable to get it to build please email me with any error messages to mark AT stylingandroid DOT com.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.