Custom header with parallax effect in ListView

We will see how to set a custom header to our ListView and apply a parallax effect to the header image.

You can download the example code and the apk.

Parallax effect

Here is the custom header I use:

Custom header

You can check the layout files:

The main layout is a ListView, and we define the element layout in its own xml.

Each element of the ListView will represent a Model. For the example I use a basic model that has a name and a description.

public class Model {
    private String name;
    private String description;

    public Model(String name, String description) {
        this.name = name;
        this.description = description;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

We also have to define an adapter for that model.

public class ModelAdapter extends SlothArrayAdapter<Model> {

    @InjectView(R.id.name)          TextView name;
    @InjectView(R.id.description)   TextView description;

    public ModelAdapter(Context mContext, int layoutResourceId, List<Model> data) {
        super(mContext, layoutResourceId, data);
    }

    @Override
    protected void onCreateViewForPosition(View viewCreated, int position, Model data) {
        name.setText(data.getName());
        description.setText(data.getDescription());
    }
}

I’m using a custom array adapter from slothframework. That way I can use view injection and two custom methods onCreateViewForPosition and onCreatingLastView.
The view injection it’s not done unnecessarily, I’ve tried to implement some kind of ViewHolder pattern[performance tips for android’s ListViews].

Of course you can have your own adapter.

Now we will analyze the code of the MainActivity. The first thing we do is initialize the adapter and set it to the view.

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

    initAdapter();
    ...
}

private void initAdapter() {
    // instantiate the adapter and attach it to the listview
    adapter = new ModelAdapter(this, R.layout.element_list_view, modelList);
    listView.setAdapter(adapter);
}

We inflate and set the custom header:

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    inflateHeader();
    ...
}
...
private void inflateHeader() {
    // inflate custom header and attach it to the list
    LayoutInflater inflater = getLayoutInflater();
    ViewGroup header = (ViewGroup)inflater.inflate(R.layout.custom_header, listView, false);
    listView.addHeaderView(header, null, false);

    // we take the background image and button reference from the header
    backgroundImage = (ImageView) header.findViewById(R.id.customHeaderBackground);
    postButton  = (Button) header.findViewById(R.id.postsButton);
}

Parallax effect

There are other ways to get the parallax effect, but here we will see a very simple one. By now this parallax effect is for api level 11 and above (Android 3.0).

Our ListView must have its custom ScrollListener, so we add it in the onCreate method of our MainActivity. We only add it to devices with api level 11 and above for compatibility.

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    addScrollListenerForSDKsAbove11();
    ...
}

private void addScrollListenerForSDKsAbove11() {
    if (Integer.valueOf(Build.VERSION.SDK_INT)>11) {
        listView.setOnScrollListener(this);
    }
}

We must override the onScroll method and set the top of the image to its middle value:

// override the OnScrollListener methods: onScrollStateChanged & onScroll
@Override
public void onScrollStateChanged(AbsListView absListView, int i) {}

@Override
public void onScroll(AbsListView absListView, int i, int i2, int i3) {
    parallaxImage(backgroundImage);
}

private void parallaxImage(View view) {
    Rect rect = new Rect();
    view.getLocalVisibleRect(rect);
    if (lastTopValueAssigned != rect.top){
        lastTopValueAssigned = rect.top;
        view.setY((float) (rect.top/2.0));
    }
}

The effect is done in the parallaxImage method, when we call view.setY. Here we are displacing the view down half the size that the view goes up. For each 2 pixels that the view goes up the screen we displace the view 1 pixel down.

To avoid calling view.setY() unnecessarily we had defined a field to check if the value has changed (lastTopValueAssigned)

Here are the code references again:

Background image

Advertisements