A lot of apps have a landing screen with images and logos where you can slide through various screens. There are various third party libraries in the open source world that makes this easy, but none of them are highly customizable. In this post, I’ll go over how to build one of these slideshows from scratch.
Just so that we’re on the same page, an example library that lets you easily build an Intro Slider is https://github.com/apl-devs/AppIntro. In one of the apps I’m working on, I had to build out something similar, but with two static buttons in the bottom of the page that the user can interact with. Needless to say, none of the libraries I could find was able to accommodate this. So, I had to build my own.
What we will be building
The repository for the code is here
https://github.com/chrisjeon/IntroSliderDemo
And here’s the video of the intro slider that we’ll be building
1 – Define the slider position dots and various colors
Open your colors.xml
and define two colors for our slider dots, one for an inactive state and one for an active state. Also, go ahead and define a color for the white color and colors for the three background images that we’ll have. These colors will come into play later.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- Bunch of other color definitions up here --> ................... <color name="white">#FFFFFF</color> <!-- Slider dot colors --> <color name="dot_slider_inactive">#1E1D20</color> <color name="dot_slider_active">#713878</color> <!-- Slider Background Images --> <color name="slider_1_background">#88C4F8</color> <color name="slider_2_background">#E897F8</color> <color name="slider_3_background">#F8736F</color> </resources> |
You can change those color definitions to however you want them.
2 – Remove the Toolbar and then hide the notification bar in MainActivity
We want to remove the Toolbar that comes with the standard MainActivity and then hide the notification bar to give more prominence to our intro slider.
For removing the Toolbar, we need to add two style definitions to our AppTheme. So open up styles.xml
and then add these two lines into your main app theme.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <!-- Add these two lines --> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> </resources> |
Adding those two lines with the windowActionBar
and windowNoTitle
will set an application wide theme where the Toolbar (or ActionBar for those of you who want to proper naming) will be hidden by default.
Now, we want to hide the notification bar. This has to be done in code and isn’t available in all versions of Android. Open up your MainActivity file (or whatever activity you are displaying your intro sliders) and add this code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Making notification bar transparent if (Build.VERSION.SDK_INT >= 21) { getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } setContentView(R.layout.activity_main); } } |
Make sure you add those two lines before you call setContentView
. We want it to take effect before we set the view of the activity. What this will do is hide the notification bar if the feature is available on the device running the app (SDK version of the device is higher than 21).
3 – Creating the slides
We want to create the 3 slides that the user will be able to swipe through (you can create more if the app you’re building requires more slides). Under your res/layouts
folder, create three layout files named landing_slide_1.xml
, landing_slide_2.xml
, and landing_slide_3.xml
respectively.
landing_slide_1.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/slider_1_background"> <TextView android:id="@+id/title_text_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/slide_1_title" android:layout_marginTop="128dp" android:textSize="32sp" android:gravity="center" android:textColor="@color/white"/> <TextView android:id="@+id/description_text_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/slide_1_description" android:gravity="center" android:layout_marginTop="8dp" android:textSize="16sp" android:layout_below="@id/title_text_view" android:textColor="@color/white"/> </RelativeLayout> |
landing_slide_2.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/slider_2_background"> <TextView android:id="@+id/title_text_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/slide_2_title" android:layout_marginTop="128dp" android:textSize="32sp" android:gravity="center" android:textColor="@color/white"/> <TextView android:id="@+id/description_text_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/slide_2_description" android:gravity="center" android:layout_marginTop="8dp" android:textSize="16sp" android:layout_below="@id/title_text_view" android:textColor="@color/white"/> </RelativeLayout> |
landing_slide_3.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/slider_3_background"> <TextView android:id="@+id/title_text_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/slide_3_title" android:layout_marginTop="128dp" android:textSize="32sp" android:gravity="center" android:textColor="@color/white"/> <TextView android:id="@+id/description_text_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/slide_3_description" android:gravity="center" android:layout_marginTop="8dp" android:textSize="16sp" android:layout_below="@id/title_text_view" android:textColor="@color/white"/> </RelativeLayout> |
All of the slides are practically the same with the exception of the texts and the background colors that we’re displaying. This is to distinguish the slides from each other so that you will be able to see that different slides are displaying as you are swiping through the ViewPager
that we’ll be building. You can also replace the texts with ImageViews
and other components as you see fit.
4 – Build the MainActivity XML layout
We want the above landing slide layout files to display in our activity_main layout file. We also want them to be displayed inside a ViewPager
. Finally, we want to have two static buttons at the bottom that stay in place as the user swipes through. Open up your activity_main.xml
file and copy/paste this in.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v4.view.ViewPager android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="match_parent" /> <LinearLayout android:id="@+id/button_linear_layout" android:orientation="horizontal" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:weightSum="1.0" android:layout_marginBottom="16dp"> <Button android:id="@+id/register_button" android:layout_weight="0.5" android:layout_height="wrap_content" android:layout_width="match_parent" android:layout_marginLeft="16dp" android:layout_marginRight="4.5dp" android:text="Register"/> <Button android:id="@+id/sign_in_button" android:layout_weight="0.5" android:layout_height="wrap_content" android:layout_width="match_parent" android:layout_marginRight="16dp" android:layout_marginLeft="4.5dp" android:text="Sign In" /> </LinearLayout> <LinearLayout android:id="@+id/layout_dots" android:layout_width="match_parent" android:layout_height="30dp" android:layout_marginBottom="22dp" android:layout_above="@id/button_linear_layout" android:gravity="center" android:orientation="horizontal" /> </RelativeLayout> |
We three main components in our activity_main.xml
file: ViewPager
, two Buttons
, and the LinearLayout
that will be holding the dots.
We’re finally finished with our layout files. We’re now ready to hook everything up in our MainActivity.
5 – Wire up the Intro Slider in MainActivity
Open up your MainActivity and add additional code to reflect what’s below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
public class MainActivity extends AppCompatActivity { private ViewPager mViewPager; private SliderViewPagerAdapter mViewPagerAdapter; private LinearLayout mDotsLayout; private TextView[] mDots; private int[] mLayouts; private Button mRegisterButton; private Button mSignInButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Making notification bar transparent if (Build.VERSION.SDK_INT >= 21) { getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } setContentView(R.layout.activity_main); mViewPager = (ViewPager) findViewById(R.id.view_pager); mDotsLayout = (LinearLayout) findViewById(R.id.layout_dots); mRegisterButton = (Button) findViewById(R.id.register_button); mSignInButton = (Button) findViewById(R.id.sign_in_button); mRegisterButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getApplicationContext(), "Register button clicked", Toast.LENGTH_SHORT).show(); } }); mSignInButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getApplicationContext(), "Sign in button clicked", Toast.LENGTH_SHORT).show(); } }); mLayouts = new int[] { R.layout.landing_slide_1, R.layout.landing_slide_2, R.layout.landing_slide_3 }; addBottomDots(0); changeStatusBarColor(); mViewPagerAdapter = new SliderViewPagerAdapter(); mViewPager.setAdapter(mViewPagerAdapter); mViewPager.addOnPageChangeListener(viewPagerPageChangeListener); } private void addBottomDots(int currentPage) { mDots = new TextView[mLayouts.length]; mDotsLayout.removeAllViews(); for (int i = 0; i < mDots.length; i++) { mDots[i] = new TextView(this); mDots[i].setText(Html.fromHtml("&#8226;")); mDots[i].setTextSize(35); mDots[i].setTextColor(getResources().getColor(R.color.dot_slider_inactive)); mDotsLayout.addView(mDots[i]); } if (mDots.length > 0) mDots[currentPage].setTextColor(getResources().getColor(R.color.dot_slider_active)); } /** * Making notification bar transparent */ private void changeStatusBarColor() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(Color.TRANSPARENT); } } ViewPager.OnPageChangeListener viewPagerPageChangeListener = new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { addBottomDots(position); } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageScrollStateChanged(int arg0) { } }; public class SliderViewPagerAdapter extends PagerAdapter { private LayoutInflater layoutInflater; public SliderViewPagerAdapter() { } @Override public Object instantiateItem(ViewGroup container, int position) { layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = layoutInflater.inflate(mLayouts[position], container, false); container.addView(view); return view; } @Override public int getCount() { return mLayouts.length; } @Override public boolean isViewFromObject(View view, Object obj) { return view == obj; } @Override public void destroyItem(ViewGroup container, int position, Object object) { View view = (View) object; container.removeView(view); } } } |
I’ll go over the important parts of this code step by step.
First important parts are the mLayouts
array containing various layout ids and the addBottomDots
method. The mLayouts
integer array holds all of the layout files that you’ll be displaying in your intro app slider. In our case, we have three: landing_slide_1
, landing_slide_2
, and landing_slide_3
. We then have our addBottomDots
method that will programmatically add the slider dots to our layout file. These dots will help indicate to the user exactly which slide he/she is in.
1 2 3 4 5 6 |
mLayouts = new int[] { R.layout.landing_slide_1, R.layout.landing_slide_2, R.layout.landing_slide_3 }; addBottomDots(0); |
When we are defining these in our onCreate
method, we want to pass in “0” into our addBottomDots
method so that when the user first opens the app, the first dot will be highlighted. In our addBottomDots
method, you can see at the bottom of the method that we retrieve the current dot with the currentPage integer passed in to set the text color of that dot to R.color.dot_slider_active
that we defined in our color definitions at the beginning of this tutorial.
1 2 |
if (mDots.length > 0) mDots[currentPage].setTextColor(getResources().getColor(R.color.dot_slider_active)); |
The next (and last) important parts of our MainActivity is the ViewPagerAdapter and the ViewPager ChangeListener. Let’s go over the ViewPager ChangeListener first.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
ViewPager.OnPageChangeListener viewPagerPageChangeListener = new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { addBottomDots(position); } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageScrollStateChanged(int arg0) { } }; |
The sole reason for us to even have this OnPageChangeListener
is to call the addBottomDots
method with our current position so that the correct dot is highlighted as the user swipes through the various slides. As you can see, we call our addBottomDots
method in the overridden onPageSelected
method, passing in the current position of the ViewPager
page. This will dynamically activate the correct dot and inactivate the rest of the dots with appropriate colors as the user swipes through the various landing screens.
Second piece of code that we need to write is our ViewPagerAdapter. We define it as SliderViewPagerAdapter
in our sample app and it extends itself from the PagerAdapter
class. This part of the code is pretty simple. The adapter determines which view to inflate using the mLayouts
array we defined above (the one that contains the various layout ids).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class SliderViewPagerAdapter extends PagerAdapter { private LayoutInflater layoutInflater; public SliderViewPagerAdapter() { } @Override public Object instantiateItem(ViewGroup container, int position) { layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = layoutInflater.inflate(mLayouts[position], container, false); container.addView(view); return view; } @Override public int getCount() { return mLayouts.length; } @Override public boolean isViewFromObject(View view, Object obj) { return view == obj; } @Override public void destroyItem(ViewGroup container, int position, Object object) { View view = (View) object; container.removeView(view); } } |
After all this, we finish setting up our MainActivity
by setting an instance of our SliderViewPagerAdapter
to our ViewPager
and then setting our viewPagerPageChangeListener
as our ViewPager’s OnPageChangeListener.
1 2 3 |
mViewPagerAdapter = new SliderViewPagerAdapter(); mViewPager.setAdapter(mViewPagerAdapter); mViewPager.addOnPageChangeListener(viewPagerPageChangeListener); |
And that’s it! Enjoy your new slider intro 🙂