Affiliate links on Android Authority may earn us a commission. Learn more.
How to build a custom launcher in Android Studio - Part Two
Welcome to part two of this custom launcher tutorial! If you haven’t already read part one of this series, read it and come back. To an even greater extent than part one, this is a somewhat advanced project. If you aren’t familiar with classes, the Android SDK, and java, I recommend you also do some more background reading first.
Still with me?
Good. If you’ve followed along with part one, you should now have a launcher that loads when you boot your phone. It should also have a working app drawer. For the moment, that app drawer is a little slow and there’s only a single page displaying a single app though. In other words, we have work to do!
First, it’s time to load the icons into the drawer in a separate thread. This will avoid busying the main UI thread, which means the list will load in the background, ready to use.
To do this, we will be using something called ASyncTask.
Speeding up the apps drawer
Here’s the plan.
Make your apps list public and create a method in our radaptor.java class to add new items to that list:
public RAdapter(Context c) {
appsList = new ArrayList<>();
}
We don’t need to create our list in the constructor anymore, so we’ll just declare it.
Instead, add the following subclass to AppsDrawer.java to perform the same thing with AsyncTask. This will perform the same action in a separate thread, so the app can still deal with user interactions while working through it. The code should look familiar:
public class myThread extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... Params) {
PackageManager pm = getPackageManager();
appsList = new ArrayList<>();
Intent i = new Intent(Intent.ACTION_MAIN, null);
i.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> allApps = pm.queryIntentActivities(i, 0);
for(ResolveInfo ri:allApps) {
AppInfo app = new AppInfo();
app.label = ri.loadLabel(pm);
app.packageName = ri.activityInfo.packageName;
app.icon = ri.activityInfo.loadIcon(pm);
radapter.addApp(app);
}
return "Success";
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
updateStuff();
}
}
Of course you also need to delete the duplicate code from the adapter class. We can then simply trigger our ASync class in the onCreate() method of the AppsDawer.java file:
new myThread().execute();
Try running your launcher and the apps drawer should now spring to life pretty seamlessly. The eagle-eyed among you will also have noticed I created another new method:
public void updateStuff() {
radapter.notifyItemInserted(radapter.getItemCount()-1);
}
Notice the method radaptor.notifiyItemInserted(). This allows the dynamic adding of items to the list in our recyclers. It will be useful in the future for you serious launcher designers, because it can listen for newly installed or deleted apps and update the view accordingly.
This all looks a lot better but there’s something wrong still. At the moment, we are calling onCreate() and making a new app drawer every time the activity is created. To avoid this happening, we want to add a line to our manifest in the <application> tag for AppsDrawer:
android:launchMode="singleTask"
To be extra safe, we can also override the onBackPressed() method in our AppsDrawer.java file.
Using fragments
The app drawer has gotten quicker, but it would be even better if it was created when the app launches, rather than when the user first clicks the app drawer button. That way, it would be ready before it was clicked. We could bend over backwards to do this, but the best solution is to place our app drawer into a fragment — shelve that for a moment, we’ll come back to it.
Fragments are incredibly powerful for building dynamic UIs and they're perfect for our launcher!
Fragments also provide the best way to create a nice series of homescreens to swipe through when choosing our apps!
We’ll be creating fragments and then swiping through them with ViewPager.
Basically a fragment is an activity-lite. It has its own life-cycle and can contain lots of views but more than one fragments can be visible onscreen at once (unlike an activity). Fragments can also behave like objects, in that multiple instances of the same fragment can exist at once. This again lends itself well to a homepage, because users could add and remove homepages as necessary to house lots of different apps and widgets. Fragments are incredibly powerful for building dynamic UIs and they’re perfect for our launcher!
To create a fragment, go to File > New > Fragment. You’ll then have the option to create a new fragment, which we will call Homescreen. Untick the factory methods and callbacks boxes and click finish. This should generate a new XML file, fragment_homescreen.xml, and a new Java file, Homescreen.java, just like an activity.
For now, add another image view and place it in the center of the screen using gravity. Give it the ID “icon” and give the fragment itself the ID “home.”
To get this to run inside our fragment, we unfortunately can’t just drag and drop the onClick() code from before. Instead, examine the code below to see how the whole thing should work:
public class Homescreen extends Fragment implements View.OnClickListener{
public Homescreen() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_homescreen, container, false);
ImageView Icon = v.findViewById(R.id.icon);
Icon.setImageDrawable(MainActivity.getActivityIcon(this.getContext(), "com.android.chrome", "com.google.android.apps.chrome.Main"));
Icon.setOnClickListener(this);
return v;
}
@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.icon:
Intent launchIntent = MainActivity.baseContext.getPackageManager().getLaunchIntentForPackage("com.android.chrome");
startActivity(launchIntent);
break;
}
}
}
It’s a little more fiddly, but you should be able to reverse engineer this to work as you require it to. Just override the various onClicks.
Notice that I was able to use the getActivityIcon from MainActivity because I made the method static. Static methods from other classes are usable without creating multiple instances of that class. You see, there is method to my madness (and my methods too)!
Add the fragment to your activity_main.xml and arrange it nicely above the app drawer button. You’ll now be able to see the Chrome icon button just as before. It’s a lot of code to achieve the exact same outcome, but that’s programming for you!
Of course, the real reason we went to all of this effort was because it would allow us to do more exciting things going forward. Now we can create multiple fragments using the exact same Java code and the exact same XML.
That we could run two instances of the same screen and change the icons that display based on the ID we give to each one in the XML!
It gets better, too.
ViewPager
Using fragments also means that we can use ViewPager to scroll through our homescreens, as would be normal in any launcher app. ViewPager also gives us the option to animate the screens as we transition between them.
Using fragments also means that we can use ViewPager to scroll through our homescreens as you expect to be able to in any launcher app.
You can find the official documentation for using ViewPager here. It’s not too tricky, thankfully.
First, we need to drag and drop our ViewPager into the activity_main.xml, just as in any other view. Just stick it where the fragment is currently.
Now we need to create another class. This one will be called “HomescreenAdapter” and will extend FragmentStatePageAdapter. This adapter will place our fragments inside the ViewPager.
It looks like this:
private class HomescreenAdapter extends FragmentStatePagerAdapter {
public HomescreenAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return new Homescreen();
}
@Override
public int getCount() {
return NUM_PAGES;
}
}
}
We need a global variable like static final int NUM_PAGES to define however many pages you want. You might not want it to be a “final” in future though, as most apps allow their users to add extra homepages.
Set up the adapter in your MainActivity’s onCreate() method:
mPager = (ViewPager) findViewById(R.id.homescreenPager);
mPagerAdapter = new HomescreenAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
Load that up and you should now have a swipe-able portion of the screen, with each one showing our Chrome icon. The app drawer button should also stay right where it is at the bottom of the screen.
In the future, you may need to adapt this to show different icons on each page. You would do that by passing the position int from getItem() as a bundle and using a switch statement to load different icons or layouts.
With that, you now have a series of screens through which you can swipe, as well as a beautifully snappy app drawer! This is starting to look and feel a lot like a real launcher. At the bottom of that official documentation, you can even add a range of fancy animations just like the best launchers out there!
Showing widgets
Launchers don’t just show icons though: they show widgets too.
The first thing you’ll need to do to get that to work is add this permission to your manifest:
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
Ignore the warning about permission only being granted to system apps. These days you also need to grant your app permission at runtime using a dialog.
You’re going to use an AppWidgetHost class in order to manage and display widgets, which will have its own ID. This ID is important and needs to remain constant so the widgets know they are communicating with your app.
Each widget will likewise be given its own ID when it’s bound to your host, which will happen each time the app launcher is loaded. AppWidgetHostView will be a container displaying the host and widget. You’ll use the options bundle to pass information to and from widgets, like the size at which they should be displayed and what information from within the app they’ll be showing, among other things.
This is an incredibly involved process, especially once you start doing things like saving which widgets the user wants to use and the settings they have chosen. You’ll need to use multiple XML files and classes just to get the basics working. This is too involved to go through step-by-step in this post.
You can find more information on how to host widgets here but this is somewhat brief. You can also find working code for a full launcher here. The code used in the tutorial comes from this, so if you read through that and lift the snippets from the project, you can reverse engineer it to the point where it’ll run.
Reverse engineering and hunting for clues is very often the reality of programming on Android, especially when you're trying to do something that is rare and not required for the vast majority of applications.
I recommend you start out by testing this in a separate activity within your project (or even a separate project entirely) and move it to your homepage fragments only once you’ve got everything working nicely. Reverse engineering and hunting for clues is very often the reality of programming on Android, especially when you’re trying to do something rare, or unnecessary for most applications.
You’ll also need to check the section at the bottom of the documentation in order to upgrade this process for Android 4.0 and above.
There is lots more to do!
Like I said, building a launcher is a big undertaking. If you’ve managed to work your way through the headache of adding widgets, there’s still plenty of other things worth adding:
- Icon packs
- Handling screen rotations (if you choose to do that!)
- Letting users drag and drop their icons around the screen
- Customizations
- Folders
Plus whatever will make your app unique!
That’s no small undertaking, but it can be a particularly fun and rewarding job to take on and the results will be something that you (and any users) will use every single day.
Good luck, share your thoughts on the process in the comments below and let me know if you’d like to see the addition of widgets (or anything else for that matter) handled in a separate post!