Prism / ViewPager

Prism Fundamentals – Part 2

IMPORTANT: Updates to Prism are on indefinite hold – more details can be found in the README in the within the Prism source. I have decided to continue publishing this short series because it documents how to use Prism in its current form and so still may be useful.

I am extremely excited to announce the release of Prism – an all-new dynamic theming library for Android. This is an initial release to get the basic functionality out there, but it is already pretty powerful. However there are some exciting additions in the pipeline which will make it more powerful still. In this series of articles we’ll cover the various aspects of prism to enable you to make use of it and even extend it yourself to meet the requirements of your project.

Prism LogoPreviously we looked at wiring up some UI components to a Prism instance so that calling setColour(int colour) on the Prism instance resulted in colour changes for all of the components that we’d wired up. So while using a combination of Setters and Filters wired together in a Prism instance simplified things and removed a lot of boilerplate code, it didn’t really do anything that we couldn’t do fairly easily. However when we add a Trigger in to the equation things become much more interesting!

First we need to add prism-viewpager as a dependency to our project:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.0 rc3"

    defaultConfig {
        applicationId "com.stylingandroid.prism.sample.viewpager"
        minSdkVersion 7
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.android.support:design:22.2.0'
    compile 'com.stylingandroid.prism:prism:1.0.1'
    compile 'com.stylingandroid.prism:prism-viewpager:1.0.1'
}

A Trigger sits in front of the Prism instance and is essentially what triggers the colour changes. We’ll begin by looking at ViewPagerTrigger which will trigger colour changes in response to user interactions with a ViewPager. In order for this to happen the Adapter for the ViewPager will need to provide colour information for each position and so much implement the ColourProvider interface (or ColorProvider, if you prefer, and don’t mind the tiny performance hit that incurs):

public interface ColourProvider {
    @ColorInt int getColour(int position);
    int getCount();
}
public interface ColorProvider {
    @ColorInt int getColor(int position);
    int getCount();
}

For anyone who has ever used PagerTitleStrip or the new design library TabLayout will be familiar with the concept of providing a title for each pager position, and this interface does precisely the same thing only it provides an RGB colour value instead of a title String. You don’t need to worry about implementing getCount() if you’re extending an Adapter as this is already built in to the Adapter. All that’s required is to implement this in to your Adapter:

public class RainbowPagerAdapter extends FragmentPagerAdapter implements ColourProvider {
    private static final Rainbow[] COLOURS = {
            Rainbow.Red, Rainbow.Orange, Rainbow.Yellow, Rainbow.Green,
            Rainbow.Blue, Rainbow.Indigo, Rainbow.Violet
    };

    private final Context context;

    public RainbowPagerAdapter(Context context, FragmentManager fragmentManager) {
        super(fragmentManager);
        this.context = context;
    }

    @Override
    public Fragment getItem(int position) {
        Rainbow colour = COLOURS[position];
        return ColourFragment.newInstance(context, getPageTitle(position), colour.getColour());
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        FragmentManager manager = ((Fragment) object).getFragmentManager();
        FragmentTransaction trans = manager.beginTransaction();
        trans.remove((Fragment) object);
        trans.commit();
        super.destroyItem(container, position, object);
    }

    @Override
    public int getCount() {
        return COLOURS.length;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return COLOURS[position].name();
    }

    @Override
    public int getColour(int position) {
        return COLOURS[position].getColour();
    }

    private enum Rainbow {
        Red(Color.rgb(0xFF, 0x00, 0x00)),
        Orange(Color.rgb(0xFF, 0x7F, 0x00)),
        Yellow(Color.rgb(0xCF, 0xCF, 0x00)),
        Green(Color.rgb(0x00, 0xAF, 0x00)),
        Blue(Color.rgb(0x00, 0x00, 0xFF)),
        Indigo(Color.rgb(0x4B, 0x00, 0x82)),
        Violet(Color.rgb(0x7F, 0x00, 0xFF));

        private final int colour;

        Rainbow(int colour) {
            this.colour = colour;
        }

        public int getColour() {
            return colour;
        }
    }
}

So now that we have an Adapter which implements ColourProvider we can hook this up to the Prism ViewPagerTrigger:

public class MainActivity extends AppCompatActivity {
    private static final float TINT_FACTOR_50_PERCENT = 0.5f;
    private DrawerLayout drawerLayout;
    private View navHeader;
    private AppBarLayout appBar;
    private Toolbar toolbar;
    private TabLayout tabLayout;
    private ViewPager viewPager;
    private FloatingActionButton fab;

    private Prism prism = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        navHeader = findViewById(R.id.nav_header);
        appBar = (AppBarLayout) findViewById(R.id.app_bar);
        toolbar = (Toolbar) findViewById(R.id.toolbar);
        tabLayout = (TabLayout) findViewById(R.id.tab_layout);
        viewPager = (ViewPager) findViewById(R.id.viewpager);
        fab = (FloatingActionButton) findViewById(R.id.fab);

        setupToolbar();
        setupViewPager();
    }

    @Override
    protected void onDestroy() {
        if (prism != null) {
            prism.destroy();
        }
        super.onDestroy();
    }

    private void setupToolbar() {
        setSupportActionBar(toolbar);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setTitle(R.string.app_title);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                drawerLayout.openDrawer(GravityCompat.START);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    private void setupViewPager() {
        RainbowPagerAdapter adapter = new RainbowPagerAdapter(this, getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        Filter tint = new TintFilter(TINT_FACTOR_50_PERCENT);
        Trigger trigger = ViewPagerTrigger.newInstance(viewPager, adapter);
        prism = Prism.Builder.newInstance()
                .add(trigger)
                .background(appBar)
                .background(getWindow())
                .background(navHeader)
                .background(fab, tint)
                .colour(viewPager, tint)
                .build();
        tabLayout.setupWithViewPager(viewPager);
        viewPager.setCurrentItem(0);
    }
}

In setupViewPager() we first create an instance of our RainbowPagerAdapter and set this on the ViewPager. Next we create the a TintFilter which we’ll use to provide a lighter background for the FAB, and then create a Trigger instance based on the ViewPager and the Adapter.

Now we construct our Prism instance in exactly the same way as we did before. The only differences are that we’re hooking up a couple more components, and we add the Trigger that we just created. You may notice that we’re setting the colour of the ViewPager itself. This will actually change the ViewPager over-scroll glow colour. (This is actually quite tricky because it is done differently on different OS levels, but Prism takes care of all of this internally).

Then we wire the TabLayout up to the ViewPager (this is required by TabLayout and not Prism), and finally set the current selection of the ViewPager to the first item.

That is it. The UI will now change colour in response to the user scrolling between tabs:

The more observant will notice that the colour changes do not jump – the transitions are actually animated smoothly as the ViewPager animates between individual pages. The colour transitions will even follow the user’s dragging:

There is some more subtle stuff going on. If the user were to tap on tabs a long distance apart, then normal behaviour would be to transition between each colour in between the start and end tab. However this creates a kind of strobing effect so ViewPagerTrigger actually performs a smooth transition between the start and end colours (note how the colour transitions between yellow and violet directly without going through green, blue, and indigo):

Finally let’s have a look at the ViewPager over-scroll that we mentioned earlier:

So, with an understanding of what this does, take a look back at the code which was required to achieve this: Some additions to the Adapter to provide colour information, and 10 lines code to wire up Prism (plus another 3 lines of code to clean up).

But that’s not all – we can also trigger Prism from Palette and we’ll cover that in the next article in this series.

The full source for this sample is available as one of the sample apps within the Prism source.

© 2015, Mark Allison. All rights reserved.

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

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.