问题
I am trying to modify this example (code) so that when an item is added to listview, listview scrolls at the same time when new item is added. The default behaviour is first listview make space for new item and then new item is added.
I am getting most of the things right but the problem is listview doesn't scrolls to the last added item. It just animate within the currently displayed views.
This is the github repo for full code
Here is the code of listview and adapter
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 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(newObj);
adapter.addStableIdForDataAtPosition(mData.size()-1);
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();
/** 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, 150, 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, 150, 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);
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);
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);
}
}
}
/** 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);
}
/** 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;
}
/**
* 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));
}
};
}
Adapter code
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;
}
}
Activity
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);
}
public void addRow(View view) {
mItemNum++;
ListItemObject obj = mValues[mItemNum % mValues.length];
final ListItemObject newObj = new ListItemObject(obj.getTitle(), obj.getImgResource(),
mCellHeight);
mListView.addRow(newObj);
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);
}
}
Listview row
<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"/>
来源:https://stackoverflow.com/questions/32054181/listview-animation-on-item-addition-not-working-properly