我正在尝试修改this示例(code),以便在将项目添加到listview时,listview会在添加新项目时同时滚动。默认行为是首先为新项目创建空间,然后添加新项目。
我得到了大部分正确但问题是listview不会滚动到最后添加的项目。它只是在当前显示的视图中动画。
这是完整代码的github repo
这是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 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));
}
};
}
适配器代码
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;
}
}
活动
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行
<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"/>