diff options
| author | Daniel Olshansky <olshansky@google.com> | 2013-07-15 09:29:51 -0700 |
|---|---|---|
| committer | Daniel Olshansky <olshansky@google.com> | 2013-07-29 11:05:36 -0700 |
| commit | c24035501d4dc20c042641710c45d6ef597ff5bd (patch) | |
| tree | d3823ec71a73eb36f5c67b23a01ec197c961444d /samples/devbytes | |
| parent | dda1626d9e2f4f20f64a5e7f50b01a35c90ae62f (diff) | |
| download | android_development-c24035501d4dc20c042641710c45d6ef597ff5bd.tar.gz android_development-c24035501d4dc20c042641710c45d6ef597ff5bd.tar.bz2 android_development-c24035501d4dc20c042641710c45d6ef597ff5bd.zip | |
First commit for ListViewCellInsertion.
Diffstat (limited to 'samples/devbytes')
19 files changed, 941 insertions, 0 deletions
diff --git a/samples/devbytes/animation/ListViewCellInsertion/AndroidManifest.xml b/samples/devbytes/animation/ListViewCellInsertion/AndroidManifest.xml new file mode 100644 index 000000000..230c2e762 --- /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 Binary files differnew file mode 100644 index 000000000..f76e008e9 --- /dev/null +++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/border.9.png diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/chameleon.jpg b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/chameleon.jpg Binary files differnew file mode 100644 index 000000000..686cc8814 --- /dev/null +++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/chameleon.jpg diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/flower.jpg b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/flower.jpg Binary files differnew file mode 100644 index 000000000..8842483b2 --- /dev/null +++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/flower.jpg diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000..96a442e5b --- /dev/null +++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/ic_launcher.png diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/rock.jpg b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/rock.jpg Binary files differnew file mode 100644 index 000000000..8ea0e85d5 --- /dev/null +++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-hdpi/rock.jpg diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-ldpi/ic_launcher.png b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-ldpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000..99238729d --- /dev/null +++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-ldpi/ic_launcher.png diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000..359047dfa --- /dev/null +++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-mdpi/ic_launcher.png diff --git a/samples/devbytes/animation/ListViewCellInsertion/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000..71c6d760f --- /dev/null +++ b/samples/devbytes/animation/ListViewCellInsertion/res/drawable-xhdpi/ic_launcher.png 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 000000000..e515309f8 --- /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 000000000..e984b2e3e --- /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 000000000..a18add3c5 --- /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 000000000..cb57d6977 --- /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 000000000..870d2795f --- /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 000000000..a58dbfbbd --- /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 000000000..66866ba11 --- /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 000000000..8ca56f5dc --- /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 000000000..f371f3eeb --- /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 000000000..320c8b982 --- /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; + } +} |
