在DragView的拖拽过程即onDragOver中,会不断的进行判断距离当前DragView中心点mDragViewVisualCenter最近的Cell以及两者的distance。如果发现最近的Cell位置没有被占用,那么就会在Cell位置画一个拖拽的轮廓。
在CellLayout的成员变量中声明了以下几组变量:
private Rect[] mDragOutlines = new Rect[4];
private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
private InterruptibleInOutAnimator[] mDragOutlineAnims =
new InterruptibleInOutAnimator[mDragOutlines.length];
// Used as an index into the above 3 arrays; indicates which is the most current value.
private int mDragOutlineCurrent = 0;
1.mDragOutLines:用于存放最近四组拖拽轮廓占有的面积Rect。
2.mDragOutlineAlphas :用于存放最近四组拖拽控件的alpha值,值的范围0~128。如果alpha值>0,那么CellLayout就会在onDraw方法中把此轮廓画出来。
3.mDragOutlineAnims:用于存放最近四组拖拽的动画,此动画只是一个0至128(即mDragOutlineAlphas的alpha透明度值)的线性动画Animator。
4.mDragOutlineCurrent:用于表示当前拖拽过程中的轮廓所在的index,以此确定当前拖拽过程中轮廓的面积mDragOutLines[mDragOutlineCurrent]、透明度值mDragOutlineAlphas [mDragOutlineCurrent]、动画mDragOutlineAnims[mDragOutlineCurrent]。
这几组变量在接下来的过程中有啥用呢?
接下来我们从显示轮廓的主要方法入手,即CellLayout中的visualizeDropLocation方法:
/**
*
* @param v child 离DragView最近的cell中的应用图标控件
* @param dragOutline D被拖拽控件的bitmap
* @param originX DragView的中心点X
* @param originY DragView的中心点Y
* @param cellX 被拖拽控件的X位置
* @param cellY 被拖拽控件的Y位置
* @param spanX 被拖拽控件的X宽度
* @param spanY 被拖拽控件的Y宽度
* @param resize 是否resize
* @param dragOffset 拖拽的偏移量
* @param dragRegion 拖拽控件的范围
*/
void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
final int oldDragCellX = mDragCell[0];
final int oldDragCellY = mDragCell[1];
if (v != null && dragOffset == null) {
mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
} else {
mDragCenter.set(originX, originY);
}
if (dragOutline == null && v == null) {
return;
}
if (cellX != oldDragCellX || cellY != oldDragCellY) {
mDragCell[0] = cellX;
mDragCell[1] = cellY;
// Find the top left corner of the rect the object will occupy
final int[] topLeft = mTmpPoint;
//把cellX与cellY转换成坐标点
//横轴方向上的坐标x:getPaddingLeft() + cellX * (mCellWidth + mWidthGap)
//纵轴方向上的坐标y:getPaddingTop() + cellY * (mCellHeight + mHeightGap)
cellToPoint(cellX, cellY, topLeft);
int left = topLeft[0];
int top = topLeft[1];
if (v != null && dragOffset == null) {
//需要加上控件本身的margin值
MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
left += lp.leftMargin;
top += lp.topMargin;
// Offsets due to the size difference between the View and the dragOutline.
// There is a size difference to account for the outer blur, which may lie
// outside the bounds of the view.
top += (v.getHeight() - dragOutline.getHeight()) / 2;
// We center about the x axis
left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- dragOutline.getWidth()) / 2;
Log.i(TAG,"visualizeDropLocation->1");
} else {
if (dragOffset != null && dragRegion != null) {
// Center the drag region *horizontally* in the cell and apply a drag
// outline offset
//此处还需计算Cell内部图片的位置
left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- dragRegion.width()) / 2;
top += dragOffset.y;
Log.i(TAG,"visualizeDropLocation->2");
} else {
// Center the drag outline in the cell
left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- dragOutline.getWidth()) / 2;
top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
- dragOutline.getHeight()) / 2;
Log.i(TAG,"visualizeDropLocation->3");
}
}
final int oldIndex = mDragOutlineCurrent;
mDragOutlineAnims[oldIndex].animateOut();
mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Rect r = mDragOutlines[mDragOutlineCurrent];
r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
if (resize) {
cellToRect(cellX, cellY, spanX, spanY, r);
}
mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
mDragOutlineAnims[mDragOutlineCurrent].animateIn();
}
}
此方法做了以下几个操作:
1.计算拖拽中信mDragCenter。
2.计算拖拽轮廓的left和top值,以确定轮廓的位置。
3.让上一个轮廓animateOut逐渐消失。其内部主要通过让alpha值从128线性渐变为0,使上一个显示的轮廓逐渐透明直至消失。
4.更新mDragOutlineCurrent(自增求余),同时把轮廓的大小与位置保存到mDragOutlines[mDragOutlineCurrent]。
5.保存轮廓图并对当前轮廓做animateIn动画。其内部主要通过alpha值由0线性渐变为128,达到逐渐显示的目的。
为了更清晰的了解动画的过程,我们理应去看下动画时如何执行的。首先进入InterruptibleInOutAnimator中的animateIn和animateOut中发现,他们最终都是调用了以下方法:
private void animate(int direction) {
final long currentPlayTime = mAnimator.getCurrentPlayTime();
final float toValue = (direction == IN) ? mOriginalToValue : mOriginalFromValue;
final float startValue = mFirstRun ? mOriginalFromValue :
((Float) mAnimator.getAnimatedValue()).floatValue();
// Make sure it's stopped before we modify any values
cancel();
// TODO: We don't really need to do the animation if startValue == toValue, but
// somehow that doesn't seem to work, possibly a quirk of the animation framework
mDirection = direction;
// Ensure we don't calculate a non-sensical duration
long duration = mOriginalDuration - currentPlayTime;
mAnimator.setDuration(Math.max(0, Math.min(duration, mOriginalDuration)));
mAnimator.setFloatValues(startValue, toValue);
mAnimator.start();
mFirstRun = false;
}
通过是out还是in来判断动画的开始值与结束值。如果是animateIn,那么值就从0至128,如果是animateOut,那么值就从128至0。在方法最后调用了start开始动画的执行。那么动画执行过程的监听在哪呢?
它在CellLayout的构造方法中:
for (int i = 0; i < mDragOutlineAnims.length; i++) {
final InterruptibleInOutAnimator anim =
new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
anim.getAnimator().setInterpolator(mEaseOutInterpolator);
final int thisIndex = i;
anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
final Bitmap outline = (Bitmap)anim.getTag();
// If an animation is started and then stopped very quickly, we can still
// get spurious updates we've cleared the tag. Guard against this.
if (outline == null) {
@SuppressWarnings("all") // suppress dead code warning
final boolean debug = false;
if (debug) {
Object val = animation.getAnimatedValue();
Log.d(TAG, "anim " + thisIndex + " update: " + val +
", isStopped " + anim.isStopped());
}
// Try to prevent it from continuing to run
animation.cancel();
} else {
mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
CellLayout.this.invalidate(mDragOutlines[thisIndex]);
}
}
});
// The animation holds a reference to the drag outline bitmap as long is it's
// running. This way the bitmap can be GCed when the animations are complete.
anim.getAnimator().addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
anim.setTag(null);
}
}
});
mDragOutlineAnims[i] = anim;
}
上面就是构建了四组Animator。具体过程如下:
1.获取保存在动画Animator中的Tag即轮廓图。
2.获取当前的线性渐变alpha值(范围0~128)。
3.进行区域内重绘invalidate(Rect rect)。
4.在动画结束时轮廓图置为空。
既然是进行区域重绘,那么必然我们需要去CellLayout的onDraw方法瞧瞧它是怎么重绘区域内的轮廓的。如下:
final Paint paint = mDragOutlinePaint;
for (int i = 0; i < mDragOutlines.length; i++) {
final float alpha = mDragOutlineAlphas[i];
if (alpha > 0) {
final Rect r = mDragOutlines[i];
scaleRectAboutCenter(r, temp, getChildrenScale());
final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
paint.setAlpha((int)(alpha + .5f));
canvas.drawBitmap(b, null, temp, paint);
}
}
也就是从最上边的几组变量中取出alpha大于0的bitmap进行绘制。
小结:CellLayout记录了轮廓图的最近四组位置信息,并通过alpha值来控制这四组位置上的轮廓图的在CellLayout的显示。