First commit for ListViewCellInsertion.
(cherry picked from commit c24035501d4dc20c042641710c45d6ef597ff5bd)
diff --git a/samples/devbytes/animation/ListViewCellInsertion/AndroidManifest.xml b/samples/devbytes/animation/ListViewCellInsertion/AndroidManifest.xml
new file mode 100644
index 0000000..230c2e7
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.insertingcells"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-sdk android:minSdkVersion="14"
+ android:targetSdkVersion="17"/>
+ <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
+ <activity android:name=".InsertingCells"
+ 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/ListViewCellInsertion/res/drawable-hdpi/border.9.png b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/border.9.png
new file mode 100644
index 0000000..f76e008
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/border.9.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/chameleon.jpg b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/chameleon.jpg
new file mode 100644
index 0000000..686cc88
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/chameleon.jpg
Binary files differ
diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/flower.jpg b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/flower.jpg
new file mode 100644
index 0000000..8842483
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/flower.jpg
Binary files differ
diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/rock.jpg b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/rock.jpg
new file mode 100644
index 0000000..8ea0e85
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/rock.jpg
Binary files differ
diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-ldpi/ic_launcher.png b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/layout/activity_main.xml b/samples/devbytes/animation/ListViewCellInsertion/res/layout/activity_main.xml
new file mode 100644
index 0000000..e515309
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/layout/activity_main.xml
@@ -0,0 +1,53 @@
+<!-- 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/relative_layout"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".MainActivity"
+ android:background="#111">
+
+ <LinearLayout
+ android:id="@+id/linear_layout"
+ android:layout_height="@dimen/cell_height"
+ android:layout_width="match_parent"
+ android:orientation="horizontal">
+
+ <com.example.android.insertingcells.RoundView
+ android:id="@+id/round_view"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <Button
+ android:id="@+id/add_row_button"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:text="@string/add_row"
+ android:onClick="addRow"
+ android:layout_weight="2"/>
+
+ </LinearLayout>
+
+ <com.example.android.insertingcells.InsertionListView
+ android:id="@+id/listview"
+ android:layout_below="@id/linear_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+</RelativeLayout>
diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/layout/list_view_item.xml b/samples/devbytes/animation/ListViewCellInsertion/res/layout/list_view_item.xml
new file mode 100644
index 0000000..e984b2e
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/layout/list_view_item.xml
@@ -0,0 +1,40 @@
+<!-- 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:id="@+id/item_linear_layout"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:orientation="horizontal"
+ android:background="@drawable/border">
+
+ <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/text_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>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/values/dimensions.xml b/samples/devbytes/animation/ListViewCellInsertion/res/values/dimensions.xml
new file mode 100644
index 0000000..a18add3
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/values/dimensions.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.
+-->
+
+<resources>
+
+ <dimen name="circle_radius">50dp</dimen>
+ <dimen name="cell_height">150dp</dimen>
+
+</resources>
diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/values/strings.xml b/samples/devbytes/animation/ListViewCellInsertion/res/values/strings.xml
new file mode 100644
index 0000000..cb57d69
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/res/values/strings.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.
+-->
+
+<resources>
+
+ <string name="app_name">InsertingCells</string>
+ <string name="add_row">Add Row</string>
+
+</resources>
diff --git a/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/CustomArrayAdapter.java b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/CustomArrayAdapter.java
new file mode 100644
index 0000000..870d279
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/CustomArrayAdapter.java
@@ -0,0 +1,138 @@
+/*
+ * 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.insertingcells;
+
+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.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This custom array adapter is used to populate the ListView in this application.
+ * This adapter also maintains a map of unique stable ids for each object in the data set.
+ * Since this adapter has to support the addition of a new cell to the 1ist index, it also
+ * provides a mechanism to add a stable ID for new data that was recently inserted.
+ */
+public class CustomArrayAdapter extends ArrayAdapter<ListItemObject> {
+
+ HashMap<ListItemObject, Integer> mIdMap = new HashMap<ListItemObject, Integer>();
+ List<ListItemObject> mData;
+ Context mContext;
+ int mLayoutViewResourceId;
+ int mCounter;
+
+ public CustomArrayAdapter(Context context, int layoutViewResourceId,
+ List <ListItemObject> data) {
+ super(context, layoutViewResourceId, data);
+ mData = data;
+ mContext = context;
+ mLayoutViewResourceId = layoutViewResourceId;
+ updateStableIds();
+ }
+
+ public long getItemId(int position) {
+ ListItemObject item = getItem(position);
+ if (mIdMap.containsKey(item)) {
+ return mIdMap.get(item);
+ }
+ return -1;
+ }
+
+ public void updateStableIds() {
+ mIdMap.clear();
+ mCounter = 0;
+ for (int i = 0; i < mData.size(); ++i) {
+ mIdMap.put(mData.get(i), mCounter++);
+ }
+ }
+
+ public void addStableIdForDataAtPosition(int position) {
+ mIdMap.put(mData.get(position), ++mCounter);
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+
+ ListItemObject obj = mData.get(position);
+
+ if(convertView == null) {
+ LayoutInflater inflater = ((Activity)mContext).getLayoutInflater();
+ convertView = inflater.inflate(mLayoutViewResourceId, parent, false);
+ }
+
+ convertView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams
+ .MATCH_PARENT, obj.getHeight()));
+
+ ImageView imgView = (ImageView)convertView.findViewById(R.id.image_view);
+ TextView textView = (TextView)convertView.findViewById(R.id.text_view);
+
+ Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),
+ obj.getImgResource(), null);
+
+ textView.setText(obj.getTitle());
+ imgView.setImageBitmap(CustomArrayAdapter.getCroppedBitmap(bitmap));
+
+ return convertView;
+ }
+
+ /**
+ * Returns a circular cropped version of the bitmap passed in.
+ */
+ public static 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/ListViewCellInsertion/src/com/example/android/insertingcells/InsertingCells.java b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/InsertingCells.java
new file mode 100644
index 0000000..a58dbfb
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/InsertingCells.java
@@ -0,0 +1,113 @@
+/*
+ * 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.insertingcells;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.RelativeLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This application creates a ListView to which new elements can be added from the
+ * top. When a new element is added, it is animated from above the bounds
+ * of the list to the top. When the list is scrolled all the way to the top and a new
+ * element is added, the row animation is accompanied by an image animation that pops
+ * out of the round view and pops into the correct position in the top cell.
+ */
+public class InsertingCells extends Activity implements OnRowAdditionAnimationListener {
+
+ private ListItemObject mValues[];
+
+ private InsertionListView mListView;
+
+ private Button mButton;
+
+ private Integer mItemNum = 0;
+
+ private RoundView mRoundView;
+
+ private int mCellHeight;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ mValues = new ListItemObject[] {
+ new ListItemObject("Chameleon", R.drawable.chameleon, 0),
+ new ListItemObject("Rock", R.drawable.rock, 0),
+ new ListItemObject("Flower", R.drawable.flower, 0),
+ };
+
+ mCellHeight = (int)(getResources().getDimension(R.dimen.cell_height));
+
+ List<ListItemObject> mData = new ArrayList<ListItemObject>();
+ CustomArrayAdapter mAdapter = new CustomArrayAdapter(this, R.layout.list_view_item, mData);
+ RelativeLayout mLayout = (RelativeLayout)findViewById(R.id.relative_layout);
+
+ mRoundView = (RoundView)findViewById(R.id.round_view);
+ mButton = (Button)findViewById(R.id.add_row_button);
+ mListView = (InsertionListView)findViewById(R.id.listview);
+
+ mListView.setAdapter(mAdapter);
+ mListView.setData(mData);
+ mListView.setLayout(mLayout);
+ mListView.setRowAdditionAnimationListener(this);
+ }
+
+ public void addRow(View view) {
+ mButton.setEnabled(false);
+
+ mItemNum++;
+ ListItemObject obj = mValues[mItemNum % mValues.length];
+ final ListItemObject newObj = new ListItemObject(obj.getTitle(), obj.getImgResource(),
+ mCellHeight);
+
+ boolean shouldAnimateInNewImage = mListView.shouldAnimateInNewImage();
+ if (!shouldAnimateInNewImage) {
+ mListView.addRow(newObj);
+ return;
+ }
+
+ mListView.setEnabled(false);
+ ObjectAnimator animator = mRoundView.getScalingAnimator();
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ mListView.addRow(newObj);
+ }
+ });
+ animator.start();
+ }
+
+ @Override
+ public void onRowAdditionAnimationStart() {
+ mButton.setEnabled(false);
+ }
+
+ @Override
+ public void onRowAdditionAnimationEnd() {
+ mButton.setEnabled(true);
+ }
+}
diff --git a/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/InsertionListView.java b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/InsertionListView.java
new file mode 100644
index 0000000..66866ba
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/InsertionListView.java
@@ -0,0 +1,376 @@
+/*
+ * 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.insertingcells;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.animation.OvershootInterpolator;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This ListView displays a set of ListItemObjects. By calling addRow with a new
+ * ListItemObject, it is added to the top of the ListView and the new row is animated
+ * in. If the ListView content is at the top (the scroll offset is 0), the animation of
+ * the new row is accompanied by an extra image animation that pops into place in its
+ * corresponding item in the ListView.
+ */
+public class InsertionListView extends ListView {
+
+ private static final int NEW_ROW_DURATION = 500;
+ private static final int OVERSHOOT_INTERPOLATOR_TENSION = 5;
+
+ private OvershootInterpolator sOvershootInterpolator;
+
+ private RelativeLayout mLayout;
+
+ private Context mContext;
+
+ private OnRowAdditionAnimationListener mRowAdditionAnimationListener;
+
+ private List<ListItemObject> mData;
+ private List<BitmapDrawable> mCellBitmapDrawables;
+
+ public InsertionListView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public InsertionListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public InsertionListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ public void init(Context context) {
+ setDivider(null);
+ mContext = context;
+ mCellBitmapDrawables = new ArrayList<BitmapDrawable>();
+ sOvershootInterpolator = new OvershootInterpolator(OVERSHOOT_INTERPOLATOR_TENSION);
+ }
+
+ /**
+ * Modifies the underlying data set and adapter through the addition of the new object
+ * to the first item of the ListView. The new cell is then animated into place from
+ * above the bounds of the ListView.
+ */
+ public void addRow(ListItemObject newObj) {
+
+ final CustomArrayAdapter adapter = (CustomArrayAdapter)getAdapter();
+
+ /**
+ * Stores the starting bounds and the corresponding bitmap drawables of every
+ * cell present in the ListView before the data set change takes place.
+ */
+ final HashMap<Long, Rect> listViewItemBounds = new HashMap<Long, Rect>();
+ final HashMap<Long, BitmapDrawable> listViewItemDrawables = new HashMap<Long,
+ BitmapDrawable>();
+
+ int firstVisiblePosition = getFirstVisiblePosition();
+ for (int i = 0; i < getChildCount(); ++i) {
+ View child = getChildAt(i);
+ int position = firstVisiblePosition + i;
+ long itemID = adapter.getItemId(position);
+ Rect startRect = new Rect(child.getLeft(), child.getTop(), child.getRight(),
+ child.getBottom());
+ listViewItemBounds.put(itemID, startRect);
+ listViewItemDrawables.put(itemID, getBitmapDrawableFromView(child));
+ }
+
+ /** Adds the new object to the data set, thereby modifying the adapter,
+ * as well as adding a stable Id for that specified object.*/
+ mData.add(0, newObj);
+ adapter.addStableIdForDataAtPosition(0);
+ adapter.notifyDataSetChanged();
+
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ public boolean onPreDraw() {
+ observer.removeOnPreDrawListener(this);
+
+ ArrayList<Animator> animations = new ArrayList<Animator>();
+
+ final View newCell = getChildAt(0);
+ final ImageView imgView = (ImageView)newCell.findViewById(R.id.image_view);
+ final ImageView copyImgView = new ImageView(mContext);
+
+ int firstVisiblePosition = getFirstVisiblePosition();
+ final boolean shouldAnimateInNewRow = shouldAnimateInNewRow();
+ final boolean shouldAnimateInImage = shouldAnimateInNewImage();
+
+ if (shouldAnimateInNewRow) {
+ /** Fades in the text of the first cell. */
+ TextView textView = (TextView)newCell.findViewById(R.id.text_view);
+ ObjectAnimator textAlphaAnimator = ObjectAnimator.ofFloat(textView,
+ View.ALPHA, 0.0f, 1.0f);
+ animations.add(textAlphaAnimator);
+
+ /** Animates in the extra hover view corresponding to the image
+ * in the top row of the ListView. */
+ if (shouldAnimateInImage) {
+
+ int width = imgView.getWidth();
+ int height = imgView.getHeight();
+
+ Point childLoc = getLocationOnScreen(newCell);
+ Point layoutLoc = getLocationOnScreen(mLayout);
+
+ ListItemObject obj = mData.get(0);
+ Bitmap bitmap = CustomArrayAdapter.getCroppedBitmap(BitmapFactory
+ .decodeResource(mContext.getResources(), obj.getImgResource(),
+ null));
+ copyImgView.setImageBitmap(bitmap);
+
+ imgView.setVisibility(View.INVISIBLE);
+
+ copyImgView.setScaleType(ImageView.ScaleType.CENTER);
+
+ ObjectAnimator imgViewTranslation = ObjectAnimator.ofFloat(copyImgView,
+ View.Y, childLoc.y - layoutLoc.y);
+
+ PropertyValuesHolder imgViewScaleY = PropertyValuesHolder.ofFloat
+ (View.SCALE_Y, 0, 1.0f);
+ PropertyValuesHolder imgViewScaleX = PropertyValuesHolder.ofFloat
+ (View.SCALE_X, 0, 1.0f);
+ ObjectAnimator imgViewScaleAnimator = ObjectAnimator
+ .ofPropertyValuesHolder(copyImgView, imgViewScaleX, imgViewScaleY);
+ imgViewScaleAnimator.setInterpolator(sOvershootInterpolator);
+ animations.add(imgViewTranslation);
+ animations.add(imgViewScaleAnimator);
+
+ RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams
+ (width, height);
+
+ mLayout.addView(copyImgView, params);
+ }
+ }
+
+ /** Loops through all the current visible cells in the ListView and animates
+ * all of them into their post layout positions from their original positions.*/
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ int position = firstVisiblePosition + i;
+ long itemId = adapter.getItemId(position);
+ Rect startRect = listViewItemBounds.get(itemId);
+ int top = child.getTop();
+ if (startRect != null) {
+ /** If the cell was visible before the data set change and
+ * after the data set change, then animate the cell between
+ * the two positions.*/
+ int startTop = startRect.top;
+ int delta = startTop - top;
+ ObjectAnimator animation = ObjectAnimator.ofFloat(child,
+ View.TRANSLATION_Y, delta, 0);
+ animations.add(animation);
+ } else {
+ /** If the cell was not visible (or present) before the data set
+ * change but is visible after the data set change, then use its
+ * height to determine the delta by which it should be animated.*/
+ int childHeight = child.getHeight() + getDividerHeight();
+ int startTop = top + (i > 0 ? childHeight : -childHeight);
+ int delta = startTop - top;
+ ObjectAnimator animation = ObjectAnimator.ofFloat(child,
+ View.TRANSLATION_Y, delta, 0);
+ animations.add(animation);
+ }
+ listViewItemBounds.remove(itemId);
+ listViewItemDrawables.remove(itemId);
+ }
+
+ /**
+ * Loops through all the cells that were visible before the data set
+ * changed but not after, and keeps track of their corresponding
+ * drawables. The bounds of each drawable are then animated from the
+ * original state to the new one (off the screen). By storing all
+ * the drawables that meet this criteria, they can be redrawn on top
+ * of the ListView via dispatchDraw as they are animating.
+ */
+ for (Long itemId: listViewItemBounds.keySet()) {
+ BitmapDrawable bitmapDrawable = listViewItemDrawables.get(itemId);
+ Rect startBounds = listViewItemBounds.get(itemId);
+ bitmapDrawable.setBounds(startBounds);
+
+ int childHeight = startBounds.bottom - startBounds.top + getDividerHeight();
+ Rect endBounds = new Rect(startBounds);
+ endBounds.offset(0, childHeight);
+
+ ObjectAnimator animation = ObjectAnimator.ofObject(bitmapDrawable,
+ "bounds", sBoundsEvaluator, startBounds, endBounds);
+ animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ private Rect mLastBound = null;
+ private Rect mCurrentBound = new Rect();
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ Rect bounds = (Rect)valueAnimator.getAnimatedValue();
+ mCurrentBound.set(bounds);
+ if (mLastBound != null) {
+ mCurrentBound.union(mLastBound);
+ }
+ mLastBound = bounds;
+ invalidate(mCurrentBound);
+ }
+ });
+
+ listViewItemBounds.remove(itemId);
+ listViewItemDrawables.remove(itemId);
+
+ mCellBitmapDrawables.add(bitmapDrawable);
+
+ animations.add(animation);
+ }
+
+ /** Animates all the cells from their old position to their new position
+ * at the same time.*/
+ setEnabled(false);
+ mRowAdditionAnimationListener.onRowAdditionAnimationStart();
+ AnimatorSet set = new AnimatorSet();
+ set.setDuration(NEW_ROW_DURATION);
+ set.playTogether(animations);
+ set.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCellBitmapDrawables.clear();
+ imgView.setVisibility(View.VISIBLE);
+ mLayout.removeView(copyImgView);
+ mRowAdditionAnimationListener.onRowAdditionAnimationEnd();
+ setEnabled(true);
+ invalidate();
+ }
+ });
+ set.start();
+
+ listViewItemBounds.clear();
+ listViewItemDrawables.clear();
+ return true;
+ }
+ });
+ }
+
+ /**
+ * By overriding dispatchDraw, the BitmapDrawables of all the cells that were on the
+ * screen before (but not after) the layout are drawn and animated off the screen.
+ */
+ @Override
+ protected void dispatchDraw (Canvas canvas) {
+ super.dispatchDraw(canvas);
+ if (mCellBitmapDrawables.size() > 0) {
+ for (BitmapDrawable bitmapDrawable: mCellBitmapDrawables) {
+ bitmapDrawable.draw(canvas);
+ }
+ }
+ }
+
+ public boolean shouldAnimateInNewRow() {
+ int firstVisiblePosition = getFirstVisiblePosition();
+ return (firstVisiblePosition == 0);
+ }
+
+ public boolean shouldAnimateInNewImage() {
+ if (getChildCount() == 0) {
+ return true;
+ }
+ boolean shouldAnimateInNewRow = shouldAnimateInNewRow();
+ View topCell = getChildAt(0);
+ return (shouldAnimateInNewRow && topCell.getTop() == 0);
+ }
+
+ /** Returns a bitmap drawable showing a screenshot of the view passed in. */
+ private BitmapDrawable getBitmapDrawableFromView(View v) {
+ Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas (bitmap);
+ v.draw(canvas);
+ return new BitmapDrawable(getResources(), bitmap);
+ }
+
+ /**
+ * Returns the absolute x,y coordinates of the view relative to the top left
+ * corner of the phone screen.
+ */
+ public Point getLocationOnScreen(View v) {
+ DisplayMetrics dm = new DisplayMetrics();
+ ((Activity)getContext()).getWindowManager().getDefaultDisplay().getMetrics(dm);
+
+ int[] location = new int[2];
+ v.getLocationOnScreen(location);
+
+ return new Point(location[0], location[1]);
+ }
+
+ /** Setter for the underlying data set controlling the adapter. */
+ public void setData(List<ListItemObject> data) {
+ mData = data;
+ }
+
+ /**
+ * Setter for the parent RelativeLayout of this ListView. A reference to this
+ * ViewGroup is required in order to add the custom animated overlaying bitmap
+ * when adding a new row.
+ */
+ public void setLayout(RelativeLayout layout) {
+ mLayout = layout;
+ }
+
+ public void setRowAdditionAnimationListener(OnRowAdditionAnimationListener
+ rowAdditionAnimationListener) {
+ mRowAdditionAnimationListener = rowAdditionAnimationListener;
+ }
+
+ /**
+ * This TypeEvaluator is used to animate the position of a BitmapDrawable
+ * by updating its bounds.
+ */
+ static final TypeEvaluator<Rect> sBoundsEvaluator = new TypeEvaluator<Rect>() {
+ public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
+ return new Rect(interpolate(startValue.left, endValue.left, fraction),
+ interpolate(startValue.top, endValue.top, fraction),
+ interpolate(startValue.right, endValue.right, fraction),
+ interpolate(startValue.bottom, endValue.bottom, fraction));
+ }
+
+ public int interpolate(int start, int end, float fraction) {
+ return (int)(start + fraction * (end - start));
+ }
+ };
+
+}
diff --git a/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/ListItemObject.java b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/ListItemObject.java
new file mode 100644
index 0000000..8ca56f5
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/ListItemObject.java
@@ -0,0 +1,48 @@
+/*
+ * 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.insertingcells;
+
+/**
+ * The data model for every cell in the ListView for this application. This model stores
+ * a title, an image resource and a default cell height for every item in the ListView.
+ */
+public class ListItemObject {
+
+ private String mTitle;
+ private int mImgResource;
+ private int mHeight;
+
+ public ListItemObject(String title, int imgResource, int height) {
+ super();
+ mTitle = title;
+ mImgResource = imgResource;
+ mHeight = height;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public int getImgResource() {
+ return mImgResource;
+ }
+
+ public int getHeight() {
+ return mHeight;
+ }
+
+}
diff --git a/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/OnRowAdditionAnimationListener.java b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/OnRowAdditionAnimationListener.java
new file mode 100644
index 0000000..f371f3e
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/OnRowAdditionAnimationListener.java
@@ -0,0 +1,28 @@
+/*
+ * 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.insertingcells;
+
+/**
+ * This listener is used to determine when the animation of a new row addition
+ * begins and ends. The primary use of this interface is to create a callback
+ * under which certain elements, such as the listview itself, can be disabled
+ * to prevent unpredictable behaviour during the actual cell animation.
+ */
+public interface OnRowAdditionAnimationListener {
+ public void onRowAdditionAnimationStart();
+ public void onRowAdditionAnimationEnd();
+}
diff --git a/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/RoundView.java b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/RoundView.java
new file mode 100644
index 0000000..320c8b9
--- /dev/null
+++ b/samples/devbytes/animation/ListViewCellInsertion/src/com/example/android/insertingcells/RoundView.java
@@ -0,0 +1,85 @@
+/*
+ * 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.insertingcells;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * This round view draws a circle from which the image pops out of and into
+ * the corresponding cell in the list.
+ */
+public class RoundView extends View {
+
+ private final int STROKE_WIDTH = 6;
+ private final int RADIUS = 20;
+ private final int ANIMATION_DURATION = 300;
+ private final float SCALE_FACTOR = 0.3f;
+
+ private Paint mPaint;
+
+ public RoundView(Context context) {
+ super(context);
+ init();
+ }
+
+ public RoundView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public RoundView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init() {
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setColor(Color.WHITE);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(STROKE_WIDTH);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawCircle(canvas.getWidth() / 2, canvas.getHeight() / 2,
+ RADIUS, mPaint);
+ }
+
+ public ObjectAnimator getScalingAnimator() {
+ PropertyValuesHolder imgViewScaleY = PropertyValuesHolder.ofFloat(View
+ .SCALE_Y, SCALE_FACTOR);
+ PropertyValuesHolder imgViewScaleX = PropertyValuesHolder.ofFloat(View
+ .SCALE_X, SCALE_FACTOR);
+
+ ObjectAnimator imgViewScaleAnimator = ObjectAnimator
+ .ofPropertyValuesHolder(this, imgViewScaleX, imgViewScaleY);
+ imgViewScaleAnimator.setRepeatCount(1);
+ imgViewScaleAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ imgViewScaleAnimator.setDuration(ANIMATION_DURATION);
+
+ return imgViewScaleAnimator;
+ }
+}