Spinning Icons to Visually Queue Load States Using True Type Font Pack Font Awesome

Posted by on in Tips, Tricks and Techniques

Keeping the end user informed about what your application is up to can be the difference between retention and abandonment. Using spinning icons to visually queue the end user that the app is in a load state will ensure that your end user knows that the application is working on something. The last thing your app should do in this situation is freeze the UI until the processes has completed. 

This blog post will demonstrate the use of System.Threading to keep the UI responsive while simulating a load state.

The information in this blog post is valid for Appmethod, RAD Studio, and C++ Builder.

Project Files

Download the project files. C++, and compatable with Android, iOS, OSX, and Windows.

 

Deploying True Type Fonts

I recently published a blog post and video which outlines the steps needed to build a multi-device Android and iOS application that uses a true type font, like Font Awesome, for the purpose of iconography. Using a font for icons has advantages over using raster image files in that they are light weight and vector based so they scale up nicely to high resolutions.

For complete insturctions on how to deploy an Android or iOS application with custom loaded True Type Fonts, check out my blog post entitled True Type Font Iconography for Android and iOS Apps.

I also recently ran a Skill Sprint covering this topic entitled Developer Skill Sprint - Customize the Look and Feel of UI Elements with the Bitmap Style Designer and True Type Fonts.

Setup

Here is a screenshot of the Project Structure.

Structure

To create the spinning icon, a TFloatAnimation is attached to the TLabel and named as SpinAnim. The Loop and Enabled properties for the SpinAnim are set to True. The StartValue is set to 0 and the StopValue is set to 360. This will endlessly spin the Label around 360 degrees.

A TCircle contains the Label and has it's own set of TFloatAnimation components for moving the circle (and the label it contains) down from the top of the screen and then back up off the screen.

A TButton (Button1) is used to initate a simulated load event.

A TComboBox (ComboBox1) allows the selection of different Font Awesome spinner icons.

 

Code

Initalization

A map is used to connect the TComboBox selection to the set of Font Awesome icon variables loaded in from FontAwesomeCodes.h (included in project file download).

 

__fastcall TForm2::TForm2(TComponent* Owner)
	: TForm(Owner)
{
	//- map font awesome combo box items to the values in FontAwesomeCodes.h
	FontAwesomeIcons["Circle Notch"] 	= fa_circle_o_notch;
	FontAwesomeIcons["Cog"] 			= fa_cog;
	FontAwesomeIcons["Gear"] 			= fa_gear;
	FontAwesomeIcons["Refresh"] 		= fa_refresh;
	FontAwesomeIcons["Spinner"] 		= fa_spinner;

	//- Hide the spinner until we need it
	Circle1->Visible 			= false;

	//- set animation values relative to circle height
	MoveDownAnim->StartValue	= -1 * Circle1->Height;
	MoveDownAnim->StopValue		= Circle1->Height / 2;

	MoveUpAnim->StartValue		= Circle1->Height / 2;
	MoveUpAnim->StopValue   	= -1 * Circle1->Height;

	//- set inital spinner icon
	Label1->Text = FontAwesomeIcons[ ComboBox1->Selected->Text ];
}

ComboBox Change

 

void __fastcall TForm2::ComboBox1Change(TObject *Sender)
{
	//- because this application uses threads, the combo box will remain
	//		active during the load state. It is possible to change the loading
	//		icon while the simulated load is running.
	Label1->Text = FontAwesomeIcons[ ComboBox1->Selected->Text ];
}

Button Click

When the button is clicked, a new TTask is started. This action will simulate a loading sequence that will take 5 seconds. Using a TTask in this context will prevent the UI from locking up. In reality, this could be a RESTful API call, loading a media asset, or anything else that would potentially lock the UI.

 

void __fastcall TForm2::Button1Click(TObject *Sender)
{
	//- reset animations
	MoveDownAnim->Enabled		= false;
	MoveUpAnim->Enabled			= false;

	//- initiate UI state within the main thread and not in the task itself
	Button1->Enabled 			= false;
	Circle1->Visible  			= true;

	//- reveal spinning icon container
	MoveDownAnim->Enabled 		= true;

	//- fire off a new taask to simulate a load event
	//		here we pass in the animation to run at the end of
	//		the simulated loading task along with UI elements we want to
	//		disable.
	TTask::Run( _di_TProc(new TCppTask(5000, MoveUpAnim, Button1)) );
}

 Move Up Animation Finish

When the MoveUpAnim has completed the Circle should be hidden. This helps keeps the loading icon off the screen until it is actually needed.

 

void __fastcall TForm2::MoveUpAnimFinish(TObject *Sender)
{
	//- hide the circle and spinner icon after the animation is finished
	Circle1->Visible = false;
}

Threading a Simulated Load State

For more information on how to use the System.Threading library please check out the following resources:

 

class TCppSync : public TCppInterfacedObject<TThreadProcedure> {
  int sleepTime;//lValue;
  TFloatAnimation *FloatAnimation;
  TButton *Button;
public:
  TCppSync(int l, TFloatAnimation *fa, TButton *b) : sleepTime(l), FloatAnimation(fa), Button(b)
  {}
  void __fastcall Invoke() {
	//- fire off the animation passed in to this task
	FloatAnimation->Enabled 	= true;

	//- unlock the UI, simulated load state is finished.
	Button->Enabled 			= true;
  }
};

class TCppTask : public TCppInterfacedObject<TProc> {
  int sleepTime;//lValue;
  TFloatAnimation *FloatAnimation;
  TButton *Button;
public:
  TCppTask(int l, TFloatAnimation *fa, TButton *b) : sleepTime(l), FloatAnimation(fa), Button(b)
  {}
  void __fastcall Invoke() {

	//- simulate a load event that takes some amount of seconds
	Sleep(sleepTime);

	//- the task has finished, pass control back to the main thread with synchronize
	TThread::Synchronize(0, _di_TThreadProcedure(new TCppSync(sleepTime, FloatAnimation, Button)));
  }
};

 

 

 

 



About
Gold User, Rank: 8, Points: 399
Brian Alexakis is a Product Marketing Manager at Embarcadero Technologies. He is focused on leveraging the connected world of technology to build new experiences for the Internet of Things.

Comments

  • Manuel M5925
    Manuel M5925 Tuesday, 26 May 2015

    Hi Brian, I'm learning to do with xe7 FireMonkey applications and want to make a pre-writing program for children. I need to add a source called "acali_u6". I followed the steps you indicate in your blog including font FontAwesome but to compile the program or the Android emulator or my mobile source icon is displayed. I do not know what happens. I think it may be the FMX.GLYPHS.ANDROID.PAS file that does not recognize the new font as a new source without being of the font family using default android.

    Can you help me?
    thanks for your support.

  • bernard R4127
    bernard R4127 Thursday, 19 February 2015

    Hi Brian,

    I can't wait for your new post and I am a bit surprised to be the first one to raise this question.

    Actually, I am not loading data from a database but processing files from a local or remote source. Splitting the job into chunks is not really a good option in this case for 2 reasons: 1) the number of files varies between two requests and can go from a handful to a full hard drive so a test would be required upfront before splitting, and, 2) processing varies depending on the size of a file (like computing a SHA256 hash).

    Another option would be for my app to pre-compute the processing time before doing it but I am not there yet and that may not be fully accurate when files are remote. So, at this stage, a good live "busy" indicator would be good enough while files are processed.

    Thanks for your support,

    bernard

  • Brian Alexakis
    Brian Alexakis Tuesday, 17 February 2015

    Hi Bernard,

    I have had some internal discussion on your question and really to answer it thoroughly would require a follow up blog post. I'm going to gather more information and do just that in the near future. The blog post will feature a simulation of larger data sets being loaded and updating the UI as it gets loaded.

    In the meantime I can offer you the following:

    Generally speaking, there are two ways to approach updating the UI as data is loaded (as opposed to waiting until all the data is loaded).

    First, you can paginate data loading into smaller chunks by limiting the database querying. Once each 'chunk' of data is finished you can then update the UI, fire a new thread, and move on to the next page of data load. The limitation here is that data load stops between each paginated set.

    The better approach is to keep the thread alive, continuously loading data, and then invoking synchronize to pass that data over. There are important issues to consider with this approach and I will illustrate that with the follow up blog post.

    Hope that was helpful!

    -brian

  • bernard R4127
    bernard R4127 Friday, 13 February 2015

    Hi Brian,

    Thanks for the reply and unfortunately that is what I suspected. Is there a solution to run have two (or more) processes running in parallel (or looking like they are from a user stand point) where process 1 is a "busy indicator" such as TAniIndicator and process 2 is printing lines or updating an UI control in "real time"?

    Regards,

    bernard

  • Brian Alexakis
    Brian Alexakis Wednesday, 11 February 2015

    Hi Bernard!

    The problem you are encountering has to do with the fact that you are trying to update the UI from within TCppTask::Invoke. The UI cannot be updated within the thread but rather should be deferred to the Synchronize invocation.

    So try passing the TMemo to the TCppSync and then add new Lines within TCppSync::Invoke.

    To recap, data load should occur within the TCppTask and then that data can be propagated to the UI within TCppSync.

    Let me know if this advice did the trick or if you have any other questions!

  • bernard R4127
    bernard R4127 Wednesday, 11 February 2015

    Hi, I added a TMemo on the form and modified

    class TCppTask : public TCppInterfacedObject {
    int sleepTime;
    TFloatAnimation *FloatAnimation;
    TButton *Button;
    TMemo *Memo;
    public:
    TCppTask(int l, TFloatAnimation *fa, TButton *b, TMemo *m) : sleepTime(l), FloatAnimation(fa), Button(b), Memo(m)
    {}
    void __fastcall Invoke() {

    //- simulate a load event that takes some amount of seconds
    //Sleep(sleepTime);
    for(int i = 0; i Lines->Add("Some text");
    //- the task has finished, pass control back to the main thread with synchronize
    TThread::Synchronize(0, _di_TThreadProcedure(new TCppSync(sleepTime, FloatAnimation, Button, Memo)));
    }
    };

    This crashes after a few lines on the TMemo with an "Access violation in fmx210.bpl. Writing at address 00000024"

    Any idea of what I am doing wrong?
    bernard

  • Please login first in order for you to submit comments
  • Page :
  • 1

Check out more tips and tricks in this development video: