First commit for ListViewExpandingCells.

Change-Id: I8d64562f8c7c0e207aef0b36df10ee3744dd446a
(cherry picked from commit 8cfd63c979293145b068ffdf14a24b3ea87c7267)
diff --git a/samples/devbytes/animation/ListViewExpandingCells/AndroidManifest.xml b/samples/devbytes/animation/ListViewExpandingCells/AndroidManifest.xml
new file mode 100644
index 0000000..1633d01
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.example.android.expandingcells"
+          android:versionCode="1"
+          android:versionName="1.0">
+    <uses-sdk android:minSdkVersion="16"
+              android:targetSdkVersion="17"/>
+    <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
+        <activity android:name=".ExpandingCells"
+                  android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/border.9.png b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/border.9.png
new file mode 100644
index 0000000..f76e008
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/border.9.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/chameleon.jpg b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/chameleon.jpg
new file mode 100644
index 0000000..686cc88
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/chameleon.jpg
Binary files differ
diff --git a/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/flower.jpg b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/flower.jpg
new file mode 100644
index 0000000..8842483
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/flower.jpg
Binary files differ
diff --git a/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/rock.jpg b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/rock.jpg
new file mode 100644
index 0000000..8ea0e85
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-hdpi/rock.jpg
Binary files differ
diff --git a/samples/devbytes/animation/ListViewExpandingCells/res/drawable-ldpi/ic_launcher.png b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewExpandingCells/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewExpandingCells/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewExpandingCells/res/layout/activity_main.xml b/samples/devbytes/animation/ListViewExpandingCells/res/layout/activity_main.xml
new file mode 100644
index 0000000..d904a58
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/res/layout/activity_main.xml
@@ -0,0 +1,21 @@
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.example.android.expandingcells.ExpandingListView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/main_list_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity" />
diff --git a/samples/devbytes/animation/ListViewExpandingCells/res/layout/list_view_item.xml b/samples/devbytes/animation/ListViewExpandingCells/res/layout/list_view_item.xml
new file mode 100644
index 0000000..c2c2209
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/res/layout/list_view_item.xml
@@ -0,0 +1,68 @@
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/border"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/item_linear_layout"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/image_view"
+            android:layout_height="match_parent"
+            android:layout_width="0dp"
+            android:gravity="center_vertical"
+            android:layout_weight="1"
+            android:scaleType="center"/>
+
+        <TextView
+            android:id="@+id/title_view"
+            android:layout_height="fill_parent"
+            android:layout_width="0dp"
+            android:gravity="center"
+            android:layout_weight="2"
+            android:textStyle="bold"
+            android:textSize="22sp"
+            android:textColor="#ffffff"/>
+
+    </LinearLayout>
+
+    <com.example.android.expandingcells.ExpandingLayout
+            android:id="@+id/expanding_layout"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:visibility="gone">
+
+        <TextView
+            android:id="@+id/text_view"
+            android:layout_height="match_parent"
+            android:layout_width="match_parent"
+            android:textStyle="bold"
+            android:textSize="22sp"
+            android:textColor="#ffffff"
+            android:gravity="center_horizontal"
+            android:paddingLeft="20dp"
+            android:paddingRight="20dp"
+            android:paddingBottom="20dp"/>
+
+    </com.example.android.expandingcells.ExpandingLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ListViewExpandingCells/res/values/strings.xml b/samples/devbytes/animation/ListViewExpandingCells/res/values/strings.xml
new file mode 100644
index 0000000..1fcee40
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/res/values/strings.xml
@@ -0,0 +1,23 @@
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <string name="app_name">ExpandingCells</string>
+    <string name="short_lorem_ipsum">Lorem ipsum dolor sit amet, consectetur adipiscing elit</string>
+    <string name="medium_lorem_ipsum">"Pellentesque dictum sit amet sapien in faucibus. Curabitur fermentum, nulla quis placerat imperdiet, est nisi placerat arcu, non ornare erat justo at enim. Nam vitae porttitor sem. Quisque non quam nisi. Proin quis urna id elit ultrices cursus non tempus dolor"</string>
+    <string name="long_lorem_ipsum">"Mauris dapibus convallis massa, vitae ultrices est ultricies ut. Nam porttitor et metus ac bibendum. Nam at justo vitae felis lacinia ultrices laoreet ut arcu. Nam ac purus et turpis convallis mollis. Integer lorem eros, hendrerit imperdiet interdum vitae, sagittis eget ipsum. Donec dignissim tortor at felis fringilla, sed dignissim diam vulputate. Nam sit amet facilisis massa. Suspendisse posuere quam quis augue dapibus venenatis."</string>
+
+</resources>
diff --git a/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/CustomArrayAdapter.java b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/CustomArrayAdapter.java
new file mode 100644
index 0000000..68919a2
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/CustomArrayAdapter.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.expandingcells;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * This is a custom array adapter used to populate the listview whose items will
+ * expand to display extra content in addition to the default display.
+ */
+public class CustomArrayAdapter extends ArrayAdapter<ExpandableListItem> {
+
+    private List<ExpandableListItem> mData;
+    private int mLayoutViewResourceId;
+
+    public CustomArrayAdapter(Context context, int layoutViewResourceId,
+                              List<ExpandableListItem> data) {
+        super(context, layoutViewResourceId, data);
+        mData = data;
+        mLayoutViewResourceId = layoutViewResourceId;
+    }
+
+    /**
+     * Populates the item in the listview cell with the appropriate data. This method
+     * sets the thumbnail image, the title and the extra text. This method also updates
+     * the layout parameters of the item's view so that the image and title are centered
+     * in the bounds of the collapsed view, and such that the extra text is not displayed
+     * in the collapsed state of the cell.
+     */
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+
+        final ExpandableListItem object = mData.get(position);
+
+        if(convertView == null) {
+            LayoutInflater inflater = ((Activity) getContext()).getLayoutInflater();
+            convertView = inflater.inflate(mLayoutViewResourceId, parent, false);
+        }
+
+        LinearLayout linearLayout = (LinearLayout)(convertView.findViewById(
+                R.id.item_linear_layout));
+        LinearLayout.LayoutParams linearLayoutParams = new LinearLayout.LayoutParams
+                (AbsListView.LayoutParams.MATCH_PARENT, object.getCollapsedHeight());
+        linearLayout.setLayoutParams(linearLayoutParams);
+
+        ImageView imgView = (ImageView)convertView.findViewById(R.id.image_view);
+        TextView titleView = (TextView)convertView.findViewById(R.id.title_view);
+        TextView textView = (TextView)convertView.findViewById(R.id.text_view);
+
+        titleView.setText(object.getTitle());
+        imgView.setImageBitmap(getCroppedBitmap(BitmapFactory.decodeResource(getContext()
+                .getResources(), object.getImgResource(), null)));
+        textView.setText(object.getText());
+
+        convertView.setLayoutParams(new ListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
+                AbsListView.LayoutParams.WRAP_CONTENT));
+
+        ExpandingLayout expandingLayout = (ExpandingLayout)convertView.findViewById(R.id
+                .expanding_layout);
+        expandingLayout.setExpandedHeight(object.getExpandedHeight());
+        expandingLayout.setSizeChangedListener(object);
+
+        if (!object.isExpanded()) {
+            expandingLayout.setVisibility(View.GONE);
+        } else {
+            expandingLayout.setVisibility(View.VISIBLE);
+        }
+
+        return convertView;
+    }
+
+    /**
+     * Crops a circle out of the thumbnail photo.
+     */
+    public Bitmap getCroppedBitmap(Bitmap bitmap) {
+        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
+                Config.ARGB_8888);
+
+        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+
+        Canvas canvas = new Canvas(output);
+
+        final Paint paint = new Paint();
+        paint.setAntiAlias(true);
+
+        int halfWidth = bitmap.getWidth()/2;
+        int halfHeight = bitmap.getHeight()/2;
+
+        canvas.drawCircle(halfWidth, halfHeight, Math.max(halfWidth, halfHeight), paint);
+
+        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+
+        canvas.drawBitmap(bitmap, rect, rect, paint);
+
+        return output;
+    }
+
+
+}
\ No newline at end of file
diff --git a/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandableListItem.java b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandableListItem.java
new file mode 100644
index 0000000..1eb4fc0
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandableListItem.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.expandingcells;
+
+/**
+ * This custom object is used to populate the list adapter. It contains a reference
+ * to an image, title, and the extra text to be displayed. Furthermore, it keeps track
+ * of the current state (collapsed/expanded) of the corresponding item in the list,
+ * as well as store the height of the cell in its collapsed state.
+ */
+public class ExpandableListItem implements OnSizeChangedListener {
+
+    private String mTitle;
+    private String mText;
+    private boolean mIsExpanded;
+    private int mImgResource;
+    private int mCollapsedHeight;
+    private int mExpandedHeight;
+
+    public ExpandableListItem(String title, int imgResource, int collapsedHeight, String text) {
+        mTitle = title;
+        mImgResource = imgResource;
+        mCollapsedHeight = collapsedHeight;
+        mIsExpanded = false;
+        mText = text;
+        mExpandedHeight = -1;
+    }
+
+    public boolean isExpanded() {
+        return mIsExpanded;
+    }
+
+    public void setExpanded(boolean isExpanded) {
+        mIsExpanded = isExpanded;
+    }
+
+    public String getTitle() {
+        return mTitle;
+    }
+
+    public int getImgResource() {
+        return mImgResource;
+    }
+
+    public int getCollapsedHeight() {
+        return mCollapsedHeight;
+    }
+
+    public void setCollapsedHeight(int collapsedHeight) {
+        mCollapsedHeight = collapsedHeight;
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    public void setText(String text) {
+        mText = text;
+    }
+
+    public int getExpandedHeight() {
+        return mExpandedHeight;
+    }
+
+    public void setExpandedHeight(int expandedHeight) {
+        mExpandedHeight = expandedHeight;
+    }
+
+    @Override
+    public void onSizeChanged(int newHeight) {
+        setExpandedHeight(newHeight);
+    }
+}
diff --git a/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandingCells.java b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandingCells.java
new file mode 100644
index 0000000..6d6d4f1
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandingCells.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.expandingcells;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This activity creates a listview whose items can be clicked to expand and show
+ * additional content.
+ *
+ * In this specific demo, each item in a listview displays an image and a corresponding
+ * title. These two items are centered in the default (collapsed) state of the listview's
+ * item. When the item is clicked, it expands to display text of some varying length.
+ * The item persists in this expanded state (even if the user scrolls away and then scrolls
+ * back to the same location) until it is clicked again, at which point the cell collapses
+ * back to its default state.
+ */
+public class ExpandingCells extends Activity {
+
+    private final int CELL_DEFAULT_HEIGHT = 200;
+    private final int NUM_OF_CELLS = 30;
+
+    private ExpandingListView mListView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        ExpandableListItem[] values = new ExpandableListItem[] {
+                new ExpandableListItem("Chameleon", R.drawable.chameleon, CELL_DEFAULT_HEIGHT,
+                        getResources().getString(R.string.short_lorem_ipsum)),
+                new ExpandableListItem("Rock", R.drawable.rock, CELL_DEFAULT_HEIGHT,
+                        getResources().getString(R.string.medium_lorem_ipsum)),
+                new ExpandableListItem("Flower", R.drawable.flower, CELL_DEFAULT_HEIGHT,
+                        getResources().getString(R.string.long_lorem_ipsum)),
+        };
+
+        List<ExpandableListItem> mData = new ArrayList<ExpandableListItem>();
+
+        for (int i = 0; i < NUM_OF_CELLS; i++) {
+            ExpandableListItem obj = values[i % values.length];
+            mData.add(new ExpandableListItem(obj.getTitle(), obj.getImgResource(),
+                    obj.getCollapsedHeight(), obj.getText()));
+        }
+
+        CustomArrayAdapter adapter = new CustomArrayAdapter(this, R.layout.list_view_item, mData);
+
+        mListView = (ExpandingListView)findViewById(R.id.main_list_view);
+        mListView.setAdapter(adapter);
+        mListView.setDivider(null);
+    }
+}
\ No newline at end of file
diff --git a/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandingLayout.java b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandingLayout.java
new file mode 100644
index 0000000..5add667
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandingLayout.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.expandingcells;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.RelativeLayout;
+
+/**
+ * This layout is used to contain the extra information that will be displayed
+ * when a certain cell is expanded. The custom relative layout is created in
+ * order to achieve a fading affect of this layout's contents as it is being
+ * expanded or collapsed as opposed to just fading the content in(out) after(before)
+ * the cell expands(collapses).
+ *
+ * During expansion, layout takes place so the full contents of this layout can
+ * be displayed. When the size changes to display the full contents of the layout,
+ * its height is stored. When the view is collapsing, this layout's height becomes 0
+ * since it is no longer in the visible part of the cell.By overriding onMeasure, and
+ * setting the height back to its max height, it is still visible during the collapse
+ * animation, and so, a fade out effect can be achieved.
+ */
+public class ExpandingLayout extends RelativeLayout {
+
+
+    private OnSizeChangedListener mSizeChangedListener;
+    private int mExpandedHeight = -1;
+
+    public ExpandingLayout(Context context) {
+        super(context);
+    }
+
+    public ExpandingLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ExpandingLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
+        if (mExpandedHeight > 0) {
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(mExpandedHeight, MeasureSpec.AT_MOST);
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
+        mExpandedHeight = h;
+        //Notifies the list data object corresponding to this layout that its size has changed.
+        mSizeChangedListener.onSizeChanged(h);
+    }
+
+    public int getExpandedHeight() {
+        return mExpandedHeight;
+    }
+
+    public void setExpandedHeight(int expandedHeight) {
+        mExpandedHeight = expandedHeight;
+    }
+
+    public void setSizeChangedListener(OnSizeChangedListener listener) {
+        mSizeChangedListener = listener;
+    }
+}
diff --git a/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandingListView.java b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandingListView.java
new file mode 100644
index 0000000..0ba5d5f
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/ExpandingListView.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.expandingcells;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * A custom listview which supports the preview of extra content corresponding to each cell
+ * by clicking on the cell to hide and show the extra content.
+ */
+public class ExpandingListView extends ListView {
+
+    private boolean mShouldRemoveObserver = false;
+
+    private List<View> mViewsToDraw = new ArrayList<View>();
+
+    private int[] mTranslate;
+
+    public ExpandingListView(Context context) {
+        super(context);
+        init();
+    }
+
+    public ExpandingListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public ExpandingListView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    private void init() {
+        setOnItemClickListener(mItemClickListener);
+    }
+
+    /**
+     * Listens for item clicks and expands or collapses the selected view depending on
+     * its current state.
+     */
+    private AdapterView.OnItemClickListener mItemClickListener = new AdapterView
+            .OnItemClickListener() {
+        @Override
+        public void onItemClick (AdapterView<?> parent, View view, int position, long id) {
+            ExpandableListItem viewObject = (ExpandableListItem)getItemAtPosition(getPositionForView
+                    (view));
+            if (!viewObject.isExpanded()) {
+                expandView(view);
+            } else {
+                collapseView(view);
+            }
+        }
+    };
+
+    /**
+     * Calculates the top and bottom bound changes of the selected item. These values are
+     * also used to move the bounds of the items around the one that is actually being
+     * expanded or collapsed.
+     *
+     * This method can be modified to achieve different user experiences depending
+     * on how you want the cells to expand or collapse. In this specific demo, the cells
+     * always try to expand downwards (leaving top bound untouched), and similarly,
+     * collapse upwards (leaving top bound untouched). If the change in bounds
+     * results in the complete disappearance of a cell, its lower bound is moved is
+     * moved to the top of the screen so as not to hide any additional content that
+     * the user has not interacted with yet. Furthermore, if the collapsed cell is
+     * partially off screen when it is first clicked, it is translated such that its
+     * full contents are visible. Lastly, this behaviour varies slightly near the bottom
+     * of the listview in order to account for the fact that the bottom bounds of the actual
+     * listview cannot be modified.
+     */
+    private int[] getTopAndBottomTranslations(int top, int bottom, int yDelta,
+                                              boolean isExpanding) {
+        int yTranslateTop = 0;
+        int yTranslateBottom = yDelta;
+
+        int height = bottom - top;
+
+        if (isExpanding) {
+            boolean isOverTop = top < 0;
+            boolean isBelowBottom = (top + height + yDelta) > getHeight();
+            if (isOverTop) {
+                yTranslateTop = top;
+                yTranslateBottom = yDelta - yTranslateTop;
+            } else if (isBelowBottom){
+                int deltaBelow = top + height + yDelta - getHeight();
+                yTranslateTop = top - deltaBelow < 0 ? top : deltaBelow;
+                yTranslateBottom = yDelta - yTranslateTop;
+            }
+        } else {
+            int offset = computeVerticalScrollOffset();
+            int range = computeVerticalScrollRange();
+            int extent = computeVerticalScrollExtent();
+            int leftoverExtent = range-offset - extent;
+
+            boolean isCollapsingBelowBottom = (yTranslateBottom > leftoverExtent);
+            boolean isCellCompletelyDisappearing = bottom - yTranslateBottom < 0;
+
+            if (isCollapsingBelowBottom) {
+                yTranslateTop = yTranslateBottom - leftoverExtent;
+                yTranslateBottom = yDelta - yTranslateTop;
+            } else if (isCellCompletelyDisappearing) {
+                yTranslateBottom = bottom;
+                yTranslateTop = yDelta - yTranslateBottom;
+            }
+        }
+
+        return new int[] {yTranslateTop, yTranslateBottom};
+    }
+
+    /**
+     * This method expands the view that was clicked and animates all the views
+     * around it to make room for the expanding view. There are several steps required
+     * to do this which are outlined below.
+     *
+     * 1. Store the current top and bottom bounds of each visible item in the listview.
+     * 2. Update the layout parameters of the selected view. In the context of this
+     *    method, the view should be originally collapsed and set to some custom height.
+     *    The layout parameters are updated so as to wrap the content of the additional
+     *    text that is to be displayed.
+     *
+     * After invoking a layout to take place, the listview will order all the items
+     * such that there is space for each view. This layout will be independent of what
+     * the bounds of the items were prior to the layout so two pre-draw passes will
+     * be made. This is necessary because after the layout takes place, some views that
+     * were visible before the layout may now be off bounds but a reference to these
+     * views is required so the animation completes as intended.
+     *
+     * 3. The first predraw pass will set the bounds of all the visible items to
+     *    their original location before the layout took place and then force another
+     *    layout. Since the bounds of the cells cannot be set directly, the method
+     *    setSelectionFromTop can be used to achieve a very similar effect.
+     * 4. The expanding view's bounds are animated to what the final values should be
+     *    from the original bounds.
+     * 5. The bounds above the expanding view are animated upwards while the bounds
+     *    below the expanding view are animated downwards.
+     * 6. The extra text is faded in as its contents become visible throughout the
+     *    animation process.
+     *
+     * It is important to note that the listview is disabled during the animation
+     * because the scrolling behaviour is unpredictable if the bounds of the items
+     * within the listview are not constant during the scroll.
+     */
+
+    private void expandView(final View view) {
+        final ExpandableListItem viewObject = (ExpandableListItem)getItemAtPosition(getPositionForView
+                (view));
+
+        /* Store the original top and bottom bounds of all the cells.*/
+        final int oldTop = view.getTop();
+        final int oldBottom = view.getBottom();
+
+        final HashMap<View, int[]> oldCoordinates = new HashMap<View, int[]>();
+
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View v = getChildAt(i);
+            v.setHasTransientState(true);
+            oldCoordinates.put(v, new int[] {v.getTop(), v.getBottom()});
+        }
+
+        /* Update the layout so the extra content becomes visible.*/
+        final View expandingLayout = view.findViewById(R.id.expanding_layout);
+        expandingLayout.setVisibility(View.VISIBLE);
+
+        /* Add an onPreDraw Listener to the listview. onPreDraw will get invoked after onLayout
+        * and onMeasure have run but before anything has been drawn. This
+        * means that the final post layout properties for all the items have already been
+        * determined, but still have not been rendered onto the screen.*/
+        final ViewTreeObserver observer = getViewTreeObserver();
+        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+
+            @Override
+            public boolean onPreDraw() {
+                /* Determine if this is the first or second pass.*/
+                if (!mShouldRemoveObserver) {
+                    mShouldRemoveObserver = true;
+
+                    /* Calculate what the parameters should be for setSelectionFromTop.
+                    * The ListView must be offset in a way, such that after the animation
+                    * takes place, all the cells that remain visible are rendered completely
+                    * by the ListView.*/
+                    int newTop = view.getTop();
+                    int newBottom = view.getBottom();
+
+                    int newHeight = newBottom - newTop;
+                    int oldHeight = oldBottom - oldTop;
+                    int delta = newHeight - oldHeight;
+
+                    mTranslate = getTopAndBottomTranslations(oldTop, oldBottom, delta, true);
+
+                    int currentTop = view.getTop();
+                    int futureTop = oldTop - mTranslate[0];
+
+                    int firstChildStartTop = getChildAt(0).getTop();
+                    int firstVisiblePosition = getFirstVisiblePosition();
+                    int deltaTop = currentTop - futureTop;
+
+                    int i;
+                    int childCount = getChildCount();
+                    for (i = 0; i < childCount; i++) {
+                        View v = getChildAt(i);
+                        int height = v.getBottom() - Math.max(0, v.getTop());
+                        if (deltaTop - height > 0) {
+                            firstVisiblePosition++;
+                            deltaTop -= height;
+                        } else {
+                            break;
+                        }
+                    }
+
+                    if (i > 0) {
+                        firstChildStartTop = 0;
+                    }
+
+                    setSelectionFromTop(firstVisiblePosition, firstChildStartTop - deltaTop);
+
+                    /* Request another layout to update the layout parameters of the cells.*/
+                    requestLayout();
+
+                    /* Return false such that the ListView does not redraw its contents on
+                     * this layout but only updates all the parameters associated with its
+                     * children.*/
+                    return false;
+                }
+
+                /* Remove the predraw listener so this method does not keep getting called. */
+                mShouldRemoveObserver = false;
+                observer.removeOnPreDrawListener(this);
+
+                int yTranslateTop = mTranslate[0];
+                int yTranslateBottom = mTranslate[1];
+
+                ArrayList <Animator> animations = new ArrayList<Animator>();
+
+                int index = indexOfChild(view);
+
+                /* Loop through all the views that were on the screen before the cell was
+                *  expanded. Some cells will still be children of the ListView while
+                *  others will not. The cells that remain children of the ListView
+                *  simply have their bounds animated appropriately. The cells that are no
+                *  longer children of the ListView also have their bounds animated, but
+                *  must also be added to a list of views which will be drawn in dispatchDraw.*/
+                for (View v: oldCoordinates.keySet()) {
+                    int[] old = oldCoordinates.get(v);
+                    v.setTop(old[0]);
+                    v.setBottom(old[1]);
+                    if (v.getParent() == null) {
+                        mViewsToDraw.add(v);
+                        int delta = old[0] < oldTop ? -yTranslateTop : yTranslateBottom;
+                        animations.add(getAnimation(v, delta, delta));
+                    } else {
+                        int i = indexOfChild(v);
+                        if (v != view) {
+                            int delta = i > index ? yTranslateBottom : -yTranslateTop;
+                            animations.add(getAnimation(v, delta, delta));
+                        }
+                        v.setHasTransientState(false);
+                    }
+                }
+
+                /* Adds animation for expanding the cell that was clicked. */
+                animations.add(getAnimation(view, -yTranslateTop, yTranslateBottom));
+
+                /* Adds an animation for fading in the extra content. */
+                animations.add(ObjectAnimator.ofFloat(view.findViewById(R.id.expanding_layout),
+                        View.ALPHA, 0, 1));
+
+                /* Disabled the ListView for the duration of the animation.*/
+                setEnabled(false);
+                setClickable(false);
+
+                /* Play all the animations created above together at the same time. */
+                AnimatorSet s = new AnimatorSet();
+                s.playTogether(animations);
+                s.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        viewObject.setExpanded(true);
+                        setEnabled(true);
+                        setClickable(true);
+                        if (mViewsToDraw.size() > 0) {
+                            for (View v : mViewsToDraw) {
+                                v.setHasTransientState(false);
+                            }
+                        }
+                        mViewsToDraw.clear();
+                    }
+                });
+                s.start();
+                return true;
+            }
+        });
+    }
+
+    /**
+     * By overriding dispatchDraw, we can draw the cells that disappear during the
+     * expansion process. When the cell expands, some items below or above the expanding
+     * cell may be moved off screen and are thus no longer children of the ListView's
+     * layout. By storing a reference to these views prior to the layout, and
+     * guaranteeing that these cells do not get recycled, the cells can be drawn
+     * directly onto the canvas during the animation process. After the animation
+     * completes, the references to the extra views can then be discarded.
+     */
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        if (mViewsToDraw.size() == 0) {
+            return;
+        }
+
+        for (View v: mViewsToDraw) {
+            canvas.translate(0, v.getTop());
+            v.draw(canvas);
+            canvas.translate(0, -v.getTop());
+        }
+    }
+
+    /**
+     * This method collapses the view that was clicked and animates all the views
+     * around it to close around the collapsing view. There are several steps required
+     * to do this which are outlined below.
+     *
+     * 1. Update the layout parameters of the view clicked so as to minimize its height
+     *    to the original collapsed (default) state.
+     * 2. After invoking a layout, the listview will shift all the cells so as to display
+     *    them most efficiently. Therefore, during the first predraw pass, the listview
+     *    must be offset by some amount such that given the custom bound change upon
+     *    collapse, all the cells that need to be on the screen after the layout
+     *    are rendered by the listview.
+     * 3. On the second predraw pass, all the items are first returned to their original
+     *    location (before the first layout).
+     * 4. The collapsing view's bounds are animated to what the final values should be.
+     * 5. The bounds above the collapsing view are animated downwards while the bounds
+     *    below the collapsing view are animated upwards.
+     * 6. The extra text is faded out as its contents become visible throughout the
+     *    animation process.
+     */
+
+     private void collapseView(final View view) {
+        final ExpandableListItem viewObject = (ExpandableListItem)getItemAtPosition
+                (getPositionForView(view));
+
+        /* Store the original top and bottom bounds of all the cells.*/
+        final int oldTop = view.getTop();
+        final int oldBottom = view.getBottom();
+
+        final HashMap<View, int[]> oldCoordinates = new HashMap<View, int[]>();
+
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View v = getChildAt(i);
+            v.setHasTransientState(true);
+            oldCoordinates.put(v, new int [] {v.getTop(), v.getBottom()});
+        }
+
+        /* Update the layout so the extra content becomes invisible.*/
+        view.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
+                 viewObject.getCollapsedHeight()));
+
+         /* Add an onPreDraw listener. */
+        final ViewTreeObserver observer = getViewTreeObserver();
+        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+
+            @Override
+            public boolean onPreDraw() {
+
+                if (!mShouldRemoveObserver) {
+                    /*Same as for expandingView, the parameters for setSelectionFromTop must
+                    * be determined such that the necessary cells of the ListView are rendered
+                    * and added to it.*/
+                    mShouldRemoveObserver = true;
+
+                    int newTop = view.getTop();
+                    int newBottom = view.getBottom();
+
+                    int newHeight = newBottom - newTop;
+                    int oldHeight = oldBottom - oldTop;
+                    int deltaHeight = oldHeight - newHeight;
+
+                    mTranslate = getTopAndBottomTranslations(oldTop, oldBottom, deltaHeight, false);
+
+                    int currentTop = view.getTop();
+                    int futureTop = oldTop + mTranslate[0];
+
+                    int firstChildStartTop = getChildAt(0).getTop();
+                    int firstVisiblePosition = getFirstVisiblePosition();
+                    int deltaTop = currentTop - futureTop;
+
+                    int i;
+                    int childCount = getChildCount();
+                    for (i = 0; i < childCount; i++) {
+                        View v = getChildAt(i);
+                        int height = v.getBottom() - Math.max(0, v.getTop());
+                        if (deltaTop - height > 0) {
+                            firstVisiblePosition++;
+                            deltaTop -= height;
+                        } else {
+                            break;
+                        }
+                    }
+
+                    if (i > 0) {
+                        firstChildStartTop = 0;
+                    }
+
+                    setSelectionFromTop(firstVisiblePosition, firstChildStartTop - deltaTop);
+
+                    requestLayout();
+
+                    return false;
+                }
+
+                mShouldRemoveObserver = false;
+                observer.removeOnPreDrawListener(this);
+
+                int yTranslateTop = mTranslate[0];
+                int yTranslateBottom = mTranslate[1];
+
+                int index = indexOfChild(view);
+                int childCount = getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                    View v = getChildAt(i);
+                    int [] old = oldCoordinates.get(v);
+                    if (old != null) {
+                        /* If the cell was present in the ListView before the collapse and
+                        * after the collapse then the bounds are reset to their old values.*/
+                        v.setTop(old[0]);
+                        v.setBottom(old[1]);
+                        v.setHasTransientState(false);
+                    } else {
+                        /* If the cell is present in the ListView after the collapse but
+                         * not before the collapse then the bounds are calculated using
+                         * the bottom and top translation of the collapsing cell.*/
+                        int delta = i > index ? yTranslateBottom : -yTranslateTop;
+                        v.setTop(v.getTop() + delta);
+                        v.setBottom(v.getBottom() + delta);
+                    }
+                }
+
+                final View expandingLayout = view.findViewById (R.id.expanding_layout);
+
+                /* Animates all the cells present on the screen after the collapse. */
+                ArrayList <Animator> animations = new ArrayList<Animator>();
+                for (int i = 0; i < childCount; i++) {
+                    View v = getChildAt(i);
+                    if (v != view) {
+                        float diff = i > index ? -yTranslateBottom : yTranslateTop;
+                        animations.add(getAnimation(v, diff, diff));
+                    }
+                }
+
+
+                /* Adds animation for collapsing the cell that was clicked. */
+                animations.add(getAnimation(view, yTranslateTop, -yTranslateBottom));
+
+                /* Adds an animation for fading out the extra content. */
+                animations.add(ObjectAnimator.ofFloat(expandingLayout, View.ALPHA, 1, 0));
+
+                /* Disabled the ListView for the duration of the animation.*/
+                setEnabled(false);
+                setClickable(false);
+
+                /* Play all the animations created above together at the same time. */
+                AnimatorSet s = new AnimatorSet();
+                s.playTogether(animations);
+                s.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        expandingLayout.setVisibility(View.GONE);
+                        view.setLayoutParams(new AbsListView.LayoutParams(AbsListView
+                                .LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT));
+                        viewObject.setExpanded(false);
+                        setEnabled(true);
+                        setClickable(true);
+                        /* Note that alpha must be set back to 1 in case this view is reused
+                        * by a cell that was expanded, but not yet collapsed, so its state
+                        * should persist in an expanded state with the extra content visible.*/
+                        expandingLayout.setAlpha(1);
+                    }
+                });
+                s.start();
+
+                return true;
+            }
+        });
+    }
+
+    /**
+     * This method takes some view and the values by which its top and bottom bounds
+     * should be changed by. Given these params, an animation which will animate
+     * these bound changes is created and returned.
+     */
+    private Animator getAnimation(final View view, float translateTop, float translateBottom) {
+
+        int top = view.getTop();
+        int bottom = view.getBottom();
+
+        int endTop = (int)(top + translateTop);
+        int endBottom = (int)(bottom + translateBottom);
+
+        PropertyValuesHolder translationTop = PropertyValuesHolder.ofInt("top", top, endTop);
+        PropertyValuesHolder translationBottom = PropertyValuesHolder.ofInt("bottom", bottom,
+                endBottom);
+
+        return ObjectAnimator.ofPropertyValuesHolder(view, translationTop, translationBottom);
+    }
+}
diff --git a/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/OnSizeChangedListener.java b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/OnSizeChangedListener.java
new file mode 100644
index 0000000..ec51950
--- /dev/null
+++ b/samples/devbytes/animation/ListViewExpandingCells/src/com/example/android/expandingcells/OnSizeChangedListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.expandingcells;
+
+/**
+ * A listener used to update the list data object when the corresponding expanding
+ * layout experiences a size change.
+ */
+public interface OnSizeChangedListener {
+    public void onSizeChanged(int newHeight);
+}