22 December 2014

Android Lollipop Shared Elements


In Lollipop the new material design has given us a new feature called shared elements or shared transitions. This allows us an easy method of animating common elements between screens. Sure it doesn't add any features, but it looks nice. Anything that gives the user a tiny bit of a happy design feeling is worth some effort.

The kodaline example demonstrates this:
http://android-developers.blogspot.co.uk/2014/10/implementing-material-design-in-your.html

It wasn't immediately obvious to me how this works after following Android developer's code examples there. So I created a simple example. I wasn't really interested in the full page animation with all the other pieces animating into place. I just wanted a really quick and easy win to take advantage of material. My example is a main activity with two small images on. Then a details activity with a single large activity. You click on an image, it gets bigger.

So there are four elements to this simple transition.


  1. values-v21/styles.xml
    Create a values-v21 directory in your res folder and add in a styles.xml file. This will override your existing styles file for Lollipop, you need the windowContentTransitions element. Here's my whole file:

    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:android="http://schemas.android.com/apk/res/android">
    
        <style name="AppTheme" parent="android:Theme.Material">
            <item name="android:windowContentTransitions">true</item>
        </style>
    </resources>
    


  2. Activity xml
    Add a transitionName element to the xml of the item you wish to share. For my example I'm just using an image, so I've added the transitionName  to the image in activity_main, and of course the image in the details activity.

    Activity main:
        <ImageView
            android:id="@+id/activity_main_monkey"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:layout_gravity="center_horizontal"
            android:tag="@string/monkey"
            android:layout_marginTop="30dp"
            android:transitionName="@string/MyTransitionName"
            android:src="@drawable/monkey" />
    

    Activity Details:
        <ImageView
            android:id="@+id/activity_details_image"
            android:layout_width="wrap_content"
            android:layout_height="400dp"
            android:layout_gravity="top|center_horizontal"
            android:layout_marginTop="10dp"
            android:transitionName="@string/MyTransitionName"
            android:src="@drawable/monkey" />
    


  3. Launch detail activity from main
    Now comes the bit where we launch the second activity, from the first. Normally this is just a simple startActivity but the magic of shared transitions works by adding some options here. The important bit is this:
    options = ActivityOptions.makeSceneTransitionAnimation(this, v, getString(R.string.MyTransitionName));
    

    As you can see we use the same transition name we put in the xml. I've also added in a few if statements so we can run this on pre lollipop and it won't crash.

        @SuppressLint("NewApi")
        private void startDetailsActivity(View v){
            Intent intent = new Intent(this, DetailsActivity.class);
            ActivityOptions options = null;
            // create the transition animation - the images in the layouts of both activities are defined with android:transitionName="MyTransition"
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.L) {
                options = ActivityOptions.makeSceneTransitionAnimation(this, v, getString(R.string.MyTransitionName));
            }
            intent.putExtra(DetailsActivity.TAG_IMAGE_NAME, (String) v.getTag());
            // start the new activity
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && options != null) {
                this.startActivity(intent, options.toBundle());
            }else{
                startActivity(intent);
            }
        }
    
That's it. Hope it helps.

Here's the code: https://github.com/jimbo1299/shared-transitions



05 December 2014

Lollipop update for Nexus 7 (2012) - Update


I previously blogged about problems with Lollipop on my Nexus 7. After quite a few emails with Google Play support I have been promised that an update to fix this issue began roll out globally on 3rd December. So fingers crossed the many upset users out there will shortly have the problem solved. Well done Google (assuming it works).

20 November 2014

Android Studio Keyboard shortcuts


I've previously blogged about my frustrations moving from Eclipse to Android Studio. Largely this extends from a re-work of the keyboard mappings. I've used Eclipse for many years and have become very reliant on my keyboard short-cuts to speed up my work.

Recently I discovered the ability to make Android Studio run using Eclipse shortcuts. This literally changed my whole working day and has made me so happy!

Simply click File -> Settings then either scroll down to keymap or search for keymap.

In the dropdown just change the keyboard shortcuts to Eclipse:


Brilliant :D

Lollipop with Nexus 7 (2012)

Just a quick one to anyone who owns the 2012 version of the Nexus 7. DON'T upgrade to Lollipop.
I got the Over the Air (OTA) update last week and put it off for a few days as I don't like to rush. Eventually I got fed up with the reminders and went ahead.

Well what a disaster, its made my Nexus 7 almost unusable. The Keyboard doesn't open for sometimes up to a minute, very few of the apps run at all and its generally slow and un-responsive. 

This is a crying shame as I think Lollipop is a great release, I like the direction Android has taken. Its just a real shame Google didn't test this before pushing it out to everyone.

05 November 2014

Android ripple effect in Lollipop

Just a quick how-to today.

So Google have made a big thing of the new ripple effect in Android Lollipop.
http://android-developers.blogspot.co.uk/2014/10/implementing-material-design-in-your.html

 To my eyes however it wasn't immediately clear how to implement it, particularly if you wanted your app to work in older versions of Android, which almost everyone will.

So the first point to note is android buttons automatically implement the ripple effect if you've got the material theme running. However I mostly used TextViews as buttons with custom drawables for their background and onClick state.

Second I'm not talking about getting ripple working in old versions of Android, just a graceful rollback to a pressed state.

So first create a drawable and a drawable-v21 folder under res, in each add a button_selector.xml

res/drawable/button_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Pressed -->
    <item android:state_pressed="true" android:drawable="@color/colorPrimaryDark" />
    <!-- Selected -->
    <item android:state_selected="true" android:drawable="@color/colorPrimaryDark" />
    <!-- Focus -->
    <item android:state_focused="true" android:drawable="@color/colorPrimaryDark" />
    <!-- Default -->
    <item android:drawable="@color/colorPrimary"/>
</selector>

res/drawable-v21/button_selector.xml
<ripple
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="@color/accent">
    <!-- Pressed -->
    <item android:state_pressed="true" android:drawable="@color/colorPrimaryDark" />
    <!-- Selected -->
    <item android:state_selected="true" android:drawable="@color/colorPrimaryDark" />
    <!-- Focus -->
    <item android:state_focused="true" android:drawable="@color/colorPrimaryDark" />
    <!-- Default -->
    <item android:drawable="@color/colorPrimary"/>
</ripple>

now in your activity_main or wherever you need to add a button with a background

<TextView
    android:id="@+id/activity_main_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:background="@drawable/button_selector"
    android:padding="20dp"
    android:text="Button"/>

You'll notice the ripple xml basically incorporates the same drawables as the selector does, adding on the ripple effect.

04 November 2014

Android CursorAdapter


So the other day some stack overflow numpty yelled at me that I should use a cursor adapter instead of an array adapter. Well, just a point of habbit I thought. However I'd not extensively used a cursor adapter before, so I thought I'd give it a shot before I dismissed his suggestion.

Anyway I decided to come up with a simple example as there aren't that many examples of straight cursorAdapters out there and simpleCursorAdapters just don't cut it. The basic idea of a cursor adapter is to directly link to database adapter, rather than say an array adapter, which displays an array of any type of data.

So first grab you data, obviously this can be whatever you want. I copied a Google contacts example and simplified it.

This is inside a Fragment:

    @SuppressLint("InlinedApi") 
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        
        @SuppressLint("InlinedApi")
        final String SORT_ORDER = Utils.hasHoneycomb() ? Contacts.SORT_KEY_PRIMARY : Contacts.DISPLAY_NAME;
        
        @SuppressLint("InlinedApi")
        final String[] PROJECTION = {
                Contacts._ID,
                Contacts.LOOKUP_KEY,
                Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME,
                Utils.hasHoneycomb() ? Contacts.PHOTO_THUMBNAIL_URI : Contacts._ID
        };
        
        final String SELECTION =
                (Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME) +
                "<>''" + " AND " + Contacts.IN_VISIBLE_GROUP + "=1";
        
        if(id == QUERY_ID){
            return new CursorLoader(getActivity(),
                    Contacts.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    null,
                    SORT_ORDER);
        }
        
        return null;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        if (loader.getId() == QUERY_ID) {
            mAdapter.swapCursor(data);
            mAdapter.notifyDataSetChanged();
        }
    }


Then we need our adapter:

public class AdapterNewContacts extends CursorAdapter{
    
    private LayoutInflater mInflater;
    
    public AdapterNewContacts(Context context) {
        super(context, null, 0);

        mInflater = LayoutInflater.from(context);
    }
    
    @SuppressLint("InlinedApi") 
    public void bindView(View view, Context context, Cursor cursor) {
        
        final ViewHolder holder = (ViewHolder) view.getTag();
        
        holder.textName.setText(
            cursor.getString(cursor.getColumnIndex((Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME)))
        );
    }

    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        View convertView = mInflater.inflate(R.layout.contacts_list_item, parent, false);
        
        final ViewHolder holder = new ViewHolder();
        holder.textName = (TextView) convertView.findViewById(R.id.contacts_list_item_name);
        
        convertView.setTag(holder);
        return convertView;
    }
    
    private class ViewHolder {
        TextView textName;
    }
}



That's it! Notice the difference between this and our normal ArrayAdapter, there's no getView() and we actually have one method for creating the view and one for re-aquiring the view. This is different from a getView where we have to create and get views in one method.

So pretty simple. I still don't agree with whoever suggested this though, it's too restrictive. In an ArrayAdapter I can create my own dataset and update it however I want. With this example I'm limited to only the one cursor.

Oh well, it was an interesting experiment.



20 September 2014

Android listview with differing rows


One of the Android questions I regularly see on stack overflow is how to have a listview with different rows. Different images, different highlights or whatever the UI calls for. I've answered the question more than once myself but as it comes up so often, I thought I'd write a article on it. Plus it lets me highlight a few extra points around adapters and caching that I often see missed.

So for this tutorial I'm going to create a super simple example of a few listview rows with differing color backgrounds. Hopefully from this super simple example you can see how you might add an imageview or various text elements, or whatever your heart my fancy.

First lets say each element in our listview is going to be an international rugby player. Now these big chaps all come from different countries so we'll identify them as such. First we need a rugby player object:


package example.com.multiitemlistview;

public class rugbyPlayer {

    public static final int COUNTRY_ENGLAND = 1;
    public static final int COUNTRY_NZ = 2;
    public static final int COUNTRY_AUS = 3;
    public static final int COUNTRY_SA = 4;

    private String playerName;
    private int countryId;

    public rugbyPlayer() {}

    public rugbyPlayer(String playerName, int countryId) {
        this.playerName = playerName;
        this.countryId = countryId;
    }

    public String getPlayerName(){
        return this.playerName;
    }

    public void setPlayerName(String playerName){
        this.playerName = playerName;
    }

    public int getCountryId(){
        return this.countryId;
    }

    public void setCountryId(int countryId){
        this.countryId = countryId;
    }
}

Note that I've added some public magic numbers to help easily identify where the rugbyPlayer is from.

Now our activity_main.xml:

<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">

    <ListView
        android:id="@+id/activity_main_listview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RelativeLayout>

and ActivityMain.java, nothing here should be rocket science, but take a minute to note that we create an ArrayList of players which we then pass in as the third argument to the adapter.

package example.com.multiitemlistview;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

import java.util.ArrayList;


public class ActivityMain extends Activity {

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

        rugbyPlayer player;

        //Create list of rugby players
        ArrayList<rugbyPlayer> players = new ArrayList<rugbyPlayer>();
        player = new rugbyPlayer("Jonny Wilkinson", rugbyPlayer.COUNTRY_ENGLAND);
        players.add(player);
        player = new rugbyPlayer("Richie McCaw", rugbyPlayer.COUNTRY_NZ);
        players.add(player);
        player = new rugbyPlayer("Martin Johnson", rugbyPlayer.COUNTRY_ENGLAND);
        players.add(player);
        player = new rugbyPlayer("Brian Habana", rugbyPlayer.COUNTRY_SA);
        players.add(player);

        //Create Adapter
        AdapterPlayers adapter = new AdapterPlayers(this, R.layout.item_player, players);

        //Set Listview adapter
        ((ListView) findViewById(R.id.activity_main_listview)).setAdapter(adapter);
    }

}



So far so good, now the important part is the Adapter which, as they say, is where the magic happens.

package example.com.multiitemlistview;


import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

import java.util.ArrayList;


public class AdapterPlayers extends ArrayAdapter {

    ArrayList<rugbyPlayer> mPlayers;
    LayoutInflater mInflater;
    Context mContext;

    public AdapterPlayers(Context context, int resource, ArrayList<rugbyPlayer> items) {
        super(context, resource);
        mInflater = LayoutInflater.from(context);
        mContext = context;
        mPlayers = items;
    }

    @Override
    public int getCount() {
        return mPlayers.size();
    }

    @Override
    public Object getItem(int position) {
        return mPlayers.get(position);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder viewHolder = new ViewHolder();
        rugbyPlayer currentPlayer = mPlayers.get(position);

        if(convertView == null){
            //If convertView is null we must re-infalte view
            convertView = mInflater.inflate(R.layout.item_player, parent, false);
            viewHolder.playerName = (TextView) convertView.findViewById(R.id.item_players_name);
        }else{
            //Else object has been cached
            viewHolder = (ViewHolder) convertView.getTag();
        }

        //Now we just set the name
        viewHolder.playerName.setText(currentPlayer.getPlayerName());

        //Now we set the background color
        switch(currentPlayer.getCountryId()){
            case rugbyPlayer.COUNTRY_ENGLAND:
                viewHolder.playerName.setBackgroundColor(mContext.getResources().getColor(R.color.color_white));
                viewHolder.playerName.setTextColor(mContext.getResources().getColor(R.color.color_black));
                break;
            case rugbyPlayer.COUNTRY_NZ:
                viewHolder.playerName.setBackgroundColor(mContext.getResources().getColor(R.color.color_black));
                viewHolder.playerName.setTextColor(mContext.getResources().getColor(R.color.color_white));
                break;
            case rugbyPlayer.COUNTRY_SA:
                viewHolder.playerName.setBackgroundColor(mContext.getResources().getColor(R.color.color_sa));
                viewHolder.playerName.setTextColor(mContext.getResources().getColor(R.color.color_black));
                break;
            case rugbyPlayer.COUNTRY_AUS:
                viewHolder.playerName.setBackgroundColor(mContext.getResources().getColor(R.color.color_aus));
                viewHolder.playerName.setTextColor(mContext.getResources().getColor(R.color.color_black));
                break;
        }

        convertView.setTag(viewHolder);
        return convertView;

    }

    static class ViewHolder {
        public TextView playerName;
    }

}

The interesting thing with regard to caching starts at the bottom. ViewHolder is a class we use to keep the whole listitem contained within. This represents every view in the layout file. It could be one imageview, it could be twenty. In my case it's one simple textview. So when we come to getView() we first get the current item from the adapter's arraylist (the adapter is maintaining the data, not the activity).
So first, if the convertView is null, it means the listview has never used this layout before, so lets inflate it. Then apply the elements in the layout are set to the viewHolder.
If convertview is not null, we can just read in the viewHolder which we are storing in the tag.

Now comes the bit where we can customize the list element for its data. Look how simple it is to set the background color based on the data. We just switch on the countryId.

One very important thing to remember in all of this is that the listview will cache views itself and re-use them. So if you set something, you must always set the opposite in the alternate case. For example if I just set the COUNTRY_NZ case to have white text, when that view is re-used it will STILL have white text, so we could have white text and white background! So just remember always re-set anything you change for the else condition :)

That's it. Hope it helps.