Wednesday, September 9, 2015

MyNotes: Rethinking Toolbars

 

So one the big changes I made was to convert the toolbars for editing a note from fragments to components.   I really wasn’t happy with the interactions when they were fragments.  There was a weird delegation model where it felt like too much of the behavior was exposed.   They also required listeners to switch between modes and setting up listeners between fragments sucks due to the lifecycle.  If you haven’t tried it yet, you will have a chicken and the egg problem. 

When you first add the fragments you can guarantee that the Listenee Fragment exists before the Listener Fragment.  But then a restore happens and all bets are off. 

You normally end up with some fun retry logic because you have a 50/50 chance that the Listenee Fragment actually created and attached before the Listener Fragment.  T

his can be solved by using the Activity as a mediator, but then it feels like playing telephone game.   Fragment A tells the Activity something which then passes it on to Fragment B and then vice versa.  That doesn’t fully eliminate the problem either since the Fragment A may not be aware of the Activity yet either due to the lifecycle.

I really thought hard about doing a child fragment model, but that doesn’t fully fix the problem either and the child fragment manager isn’t supported before API 17 and I still want to target API 15 as my min.

Hello Components…

The more I looked at it, a component model worked better for the toolbar. 

  • It is pure UI interaction.  All of the reactions to the UI interaction happens elsewhere.
  • None of the toolbar stuff needs any awareness of the lifecycle
  • It can be easily self-contained
  • The whole UI interaction is screaming for a ViewAnimator.

My first step was to convert the NoteEditMainToolbarFragment and NoteEditColorPickerFragment into the NoteEditOptionsBar and NoteEditColorPalette respectively.  This was really just copying and pasting code.  I did add ImageSwitchers, but I will get to that in another post.

I then added a new component, NoteEditToolbar that manages contains and manages the whole toolbar for the note editing.   I used a ViewFlipper to manage switching between the toolbar states (normal and color picking).

ViewSwitchers are one of the most fun layouts to play with in Android.  Basically, You give it multiple child views and it only shows one at a time.  You can also define animations when moving between the displayed children.

Here is the xml for the NoteEditToolbar:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <ViewFlipper
   3:     xmlns:android="http://schemas.android.com/apk/res/android"
   4:     android:id="@+id/component_note_edit_toolbar_flipper"
   5:     android:layout_width="match_parent"
   6:     android:layout_height="match_parent"
   7:     android:inAnimation="@anim/slide_down"
   8:     android:outAnimation="@anim/slide_up">
   9:  
  10:     <com.fsk.mynotes.presentation.components.NoteEditOptionsBar
  11:         android:id="@+id/component_note_edit_toolbar_options"
  12:         android:layout_width="match_parent"
  13:         android:layout_height="match_parent"/>
  14:  
  15:     <com.fsk.mynotes.presentation.components.NoteEditColorPalette
  16:         android:id="@+id/component_note_edit_toolbar_palette"
  17:         android:layout_width="match_parent"
  18:         android:layout_height="match_parent"/>
  19: </ViewFlipper>

It really simple.  The ViewFlipper can take multiple children, but will only display one at a time.  You have full control over child is displayed and when.  It also lets you define the animations to use when switching between children.  In this case I want the old child to slide up and the new child to slide down.


The displayed child programmatically by calling setDisplayedChild(int whichChild).  The parameter value is the position of the child in the layout.  So the first child added is 0, the next is 1, etc.



   1: /**
   2:  * UI compound view that provides the user with the options needed to edit the note.  These include
   3:  * cancelling/deleting, saving, and changing the color.
   4:  */
   5: public class NoteEditToolbar extends FrameLayout {
   6:  
   7:     /**
   8:      * The modes for the toolbar. This directly corresponds to the types of children loaded into the
   9:      * {@link ViewFlipper}.
  10:      */
  11:     enum Mode {
  12:         /**
  13:          * The main options for editing the text.  This will display the save/delete icons.
  14:          */
  15:         OPTIONS_BAR,
  16:  
  17:         /**
  18:          * The color palette to allow the user to change colors.
  19:          */
  20:         PALETTE_BAR
  21:     }
  22:  
  23:  
  24:     /**
  25:      * UI component to flip between the toolbar modes.
  26:      */
  27:     @InjectView(R.id.component_note_edit_toolbar_flipper)
  28:     ViewFlipper mViewFlipper;
  29:  
  30:  
  31:     /**
  32:      * UI component to allow the user to change the note color.
  33:      */
  34:     @InjectView(R.id.component_note_edit_toolbar_palette)
  35:     NoteEditColorPalette mNoteEditColorPalette;
  36:  
  37:  
  38:     /**
  39:      * UI component to allow the user to save/delete the note.
  40:      */
  41:     @InjectView(R.id.component_note_edit_toolbar_options)
  42:     NoteEditOptionsBar mNoteEditOptionsBar;
  43:  
  44:  
  45:     /**
  46:      * The listener to the user being done selecting note colors. This will change the mode to
  47:      * {@link Mode#OPTIONS_BAR}
  48:      */
  49:     final NoteEditColorPalette.OnDoneClickListener mOnColorPaletteDoneClickListener =
  50:             new NoteEditColorPalette.OnDoneClickListener() {
  51:  
  52:  
  53:                 @Override
  54:                 public void onDoneClicked() {
  55:                     mViewFlipper.setDisplayedChild(Mode.OPTIONS_BAR.ordinal());
  56:                 }
  57:             };
  58:  
  59:  
  60:     /**
  61:      * The listener to the user requesting to change the note color. This will change the mode to
  62:      * {@link Mode#PALETTE_BAR}
  63:      */
  64:     final NoteEditOptionsBar.OnPaletteClickListener mOnColorPaletteClickListener =
  65:             new NoteEditOptionsBar.OnPaletteClickListener() {
  66:                 @Override
  67:                 public void onPaletteClicked() {
  68:                     mViewFlipper.setDisplayedChild(Mode.PALETTE_BAR.ordinal());
  69:                 }
  70:             };
  71:  
  72:  
  73:     /**
  74:      * Simple constructor to use when creating a view from code.
  75:      *
  76:      * @param context
  77:      *         The Context to associate with the view
  78:      */
  79:     public NoteEditToolbar(final Context context) {
  80:         this(context, null);
  81:     }
  82:  
  83:  
  84:     /**
  85:      * Constructor that is called when inflating a view from XML.
  86:      *
  87:      * @param context
  88:      *         The Context to associate with the view.
  89:      * @param attrs
  90:      *         the Attributes to customize the view.
  91:      */
  92:     public NoteEditToolbar(final Context context, final AttributeSet attrs) {
  93:         this(context, attrs, 0);
  94:     }
  95:  
  96:  
  97:     /**
  98:      * Constructor. Perform inflation from XML and apply a class-specific base style
  99:      *
 100:      * @param context
 101:      *         The Context to associate with the view.
 102:      * @param attrs
 103:      *         the Attributes to customize the view.
 104:      * @param defStyleAttr
 105:      *         The class-specific base style.
 106:      */
 107:     public NoteEditToolbar(final Context context, final AttributeSet attrs,
 108:                            final int defStyleAttr) {
 109:         super(context, attrs, defStyleAttr);
 110:         initialize(context);
 111:     }
 112:  
 113:  
 114:     /**
 115:      * Constructor. Perform inflation from XML and apply a class-specific base style
 116:      *
 117:      * @param context
 118:      *         The Context to associate with the view.
 119:      * @param attrs
 120:      *         the Attributes to customize the view.
 121:      * @param defStyleAttr
 122:      *         The class-specific base style.
 123:      * @param defStyleRes
 124:      *         I have no idea right now.
 125:      */
 126:     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 127:     public NoteEditToolbar(final Context context, final AttributeSet attrs, final int defStyleAttr,
 128:                            final int defStyleRes) {
 129:         super(context, attrs, defStyleAttr, defStyleRes);
 130:         initialize(context);
 131:     }
 132:  
 133:  
 134:     /**
 135:      * Inflate the UI and set the component views.
 136:      *
 137:      * @param context
 138:      *         the context to use for the inflation.
 139:      */
 140:     private void initialize(@NonNull Context context) {
 141:         View rootView = LayoutInflater.from(context)
 142:                                       .inflate(R.layout.component_note_edit_toolbar, this, true);
 143:         ButterKnife.inject(this, rootView);
 144:         mNoteEditOptionsBar.setOnPaletteClickListener(mOnColorPaletteClickListener);
 145:         mNoteEditColorPalette.setOnDoneClickListener(mOnColorPaletteDoneClickListener);
 146:     }
 147:  
 148:  
 149:     /**
 150:      * Update the UI based on the note data.
 151:      *
 152:      * @param note
 153:      *         the note containing the data to use for the UI.
 154:      */
 155:     public void updateNote(@NonNull Note note) {
 156:         mNoteEditColorPalette.updateSelectedColor(note.getColor());
 157:         mNoteEditOptionsBar.updateUiForNote(note);
 158:     }
 159:  
 160:  
 161:     /**
 162:      * Set the listener to receive notifications of a color selection
 163:      *
 164:      * @param onColorSelectedListener
 165:      *         the listener to set.  This will replace the current listener.
 166:      */
 167:     public void setOnColorSelectedListener(
 168:             final NoteEditColorPalette.OnColorSelectedListener onColorSelectedListener) {
 169:         mNoteEditColorPalette.setOnColorSelectedListener(onColorSelectedListener);
 170:     }
 171:  
 172:  
 173:     /**
 174:      * Set the listener to receive notifications of a save or delete request.
 175:      *
 176:      * @param onPersistenceClickListener
 177:      *         the listener to set.  This will replace the current listener.
 178:      */
 179:     public void setOnPersistenceClickListener(
 180:             final NoteEditOptionsBar.OnPersistenceClickListener onPersistenceClickListener) {
 181:         mNoteEditOptionsBar.setOnPersistenceClickListener(onPersistenceClickListener);
 182:     }
 183:  
 184: }

There really isn’t much as else to class except some simple logic to update the underlying components with the note data and set up listeners.


Commits


These changes can be found on github in commit https://github.com/fsk-software/mynotes/commit/0558ef8dfec2ecebe3199cd4b8ecc5266f9c22cf.

No comments :

Post a Comment