网上主要都是手势拖拽动画,关于按键的动画比较少,因此做了一套。
先来一个效果图:
下面是源码:
public class HorizontalPageScorllView extends HorizontalScrollView implements OnKeyListener {
private boolean mAnimationEnd = true;
/**
* 刚开始拖拽的item对应的View
*/
private View mStartDragItemView = null;
/**
* item镜像的布局参数
*/
private WindowManager.LayoutParams mWindowLayoutParams;
private WindowManager mWindowManager;
/**
* ScrollView距离屏幕顶部的偏移量
*/
private int mOffset2Top;
/**
* ScrollView距离屏幕左边的偏移量
*/
private int mOffset2Left;
/**
* 用于移动的镜像,这里直接用一个ImageView
*/
private ImageView mDragImageView;
/**
* 移动的item对应的Bitmap
*/
private Bitmap mDragBitmap;
/**
* 是否可以拖拽,默认不可以
*/
private boolean isDrag = false;
/**
* 正在拖拽的position
*/
private int mDragPosition = -1;
/**
* 镜像X坐标
*/
private int moveX;
/**
* 镜像Y坐标
*/
private int moveY;
/**
* 镜像X轴需要移动的距离
*/
private int moveXdistance = 0;
/**
* 镜像Y轴需要移动的距离
*/
private int moveYdistance = 0;
/**
* 翻页距离
*/
private int scrollXDelta;
private static final int DEFAULT_EDGE_OFFSET = 60;
private int mEdgeOffset = DEFAULT_EDGE_OFFSET;
private LinearLayout mLinearLayout;
private ArrayList<View> childrens;
private final int ANIMATION_TIME = 800;
private final int MARGIN_RIGHT = 20;
public HorizontalPageScorllView(Context context) {
this(context, null);
}
public HorizontalPageScorllView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HorizontalPageScorllView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
setWillNotDraw(false);
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mWindowLayoutParams = new WindowManager.LayoutParams();
childrens = new ArrayList<View>();
}
@Override
public void draw(Canvas arg0) {
super.draw(arg0);
}
/**
* 设置边缘间隙值,默认为60px
*
* @param tOffSet
*/
public void setEdgeOffset(int tOffSet) {
mEdgeOffset = tOffSet;
}
public void setChildren(ArrayList<View> views) {
if (views == null || views.size() == 0)
return;
mLinearLayout = (LinearLayout) findViewById(R.id.linearlayout);
for (int i = 0; i < views.size(); i++) {
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(200, 200);
layoutParams.rightMargin = MARGIN_RIGHT;
mLinearLayout.addView(views.get(i), layoutParams);
childrens.add(views.get(i));
}
childrens.get(0).post(new Runnable() {
@Override
public void run() {
childrens.get(0).requestFocusFromTouch();
}
});
}
public void addChildren(View v, final int index) {
childrens.add(index, v);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(200, 200);
layoutParams.rightMargin = MARGIN_RIGHT;
mLinearLayout.addView(v, index, layoutParams);
final ViewTreeObserver observer = getViewTreeObserver();
observer.addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
animateMove(index, true);
return true;
}
});
}
public void removeChildren(final int index) {
childrens.remove(index);
final ViewTreeObserver observer = mLinearLayout.getViewTreeObserver();
observer.addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
animateMove(index, false);
return true;
}
});
mLinearLayout.removeViewAt(index);
}
/**
* Compute the amount to scroll in the X direction in order to get a
* rectangle completely on the screen (or, if taller than the screen, at
* least the first screen size chunk of it).
*
* @param rect
* The rect.
* @return The scroll delta.
*/
protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
/**
*
* 切页处理原理: 和recyclerview不同,scrollview里面所有的元素都是可见的,所以不需要对边界元素焦点控制等做特殊处理。
* scrollview只会对未完全显示在范围内的子元素做滚动处理,
* 所以只需要对滚动偏移量scrollXDelta的计算进行处理即可,并没有太复杂的计算处理。
*
*/
if (getChildCount() == 0)
return 0;
int width = getWidth();
int screenLeft = getScrollX();
int screenRight = screenLeft + width;
int fadingEdge = getHorizontalFadingEdgeLength();
if (rect.left > 0) {
screenLeft += fadingEdge;
}
if (rect.right < getChildAt(0).getWidth()) {
screenRight -= fadingEdge;
}
scrollXDelta = 0;
if (rect.right > screenRight && rect.left > screenLeft) {
scrollXDelta += (rect.left - screenLeft) - mEdgeOffset; // 修改
int right = getChildAt(0).getRight();
int distanceToRight = right - screenRight;
scrollXDelta = Math.min(scrollXDelta, distanceToRight);
} else if (rect.left < screenLeft && rect.right < screenRight) {
scrollXDelta -= (screenRight - rect.right) - mEdgeOffset; // 修改
scrollXDelta = Math.max(scrollXDelta, -getScrollX());
}
return scrollXDelta;
}
/**
* 创建拖动的镜像
*
* @param bitmap
* @param downX
* 按下的点相对父控件的X坐标
* @param downY
* 按下的点相对父控件的X坐标
*/
private void createDragImage(Bitmap bitmap, int downX, int downY) {
mWindowLayoutParams.format = PixelFormat.TRANSLUCENT; // 图片之外的其他地方透明
mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
mWindowLayoutParams.x = downX + mOffset2Left;
mWindowLayoutParams.y = downY + mOffset2Top;
mWindowLayoutParams.alpha = 0.55f; // 透明度
mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
mDragImageView = new ImageView(getContext());
mDragImageView.setImageBitmap(bitmap);
mWindowManager.addView(mDragImageView, mWindowLayoutParams);
}
private void removeDragImage() {
if (mDragImageView != null) {
mWindowManager.removeView(mDragImageView);
mDragImageView = null;
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mAnimationEnd != true)
return true;
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
if (isDrag) {
return true;
}
}
}
return super.dispatchKeyEvent(event);
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_ENTER:
if (!isDrag)
beginDrag(v);
else
stopDrag(v);
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_LEFT:
moveAction(keyCode);
break;
}
}
return false;
}
public void moveAction(int keycode) {
if (!isDrag)
return;
int newposition = 0;
if (isDrag && mStartDragItemView != null) {
switch (keycode) {
case KeyEvent.KEYCODE_DPAD_RIGHT:
newposition = mDragPosition + 1;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
newposition = mDragPosition - 1;
break;
}
}
if (newposition < 0 || newposition > childrens.size() - 1)
return;
moveXdistance = (int) mLinearLayout.getChildAt(newposition).getX()
- (int) mLinearLayout.getChildAt(mDragPosition).getX();
moveYdistance = 0;
moveX = (int) mLinearLayout.getChildAt(newposition).getX() - getScrollX();
moveY = (int) mLinearLayout.getChildAt(newposition).getY();
onSwapItem(newposition);
}
public void beginDrag(View v) {
// TODO 自动生成的方法存根
isDrag = true;
mDragPosition = mLinearLayout.indexOfChild(v);
mOffset2Top = this.getTop();
mOffset2Left = this.getLeft();
mStartDragItemView = v;
mStartDragItemView.setAlpha(0);// 隐藏该item
// 开启mDragItemView绘图缓存
mStartDragItemView.setDrawingCacheEnabled(true);
// 获取mDragItemView在缓存中的Bitmap对象
mDragBitmap = Bitmap.createBitmap(mStartDragItemView.getDrawingCache());
// 这一步很关键,释放绘图缓存,避免出现重复的镜像
mStartDragItemView.destroyDrawingCache();
// 根据我们按下的点显示item镜像
createDragImage(mDragBitmap, (int) mStartDragItemView.getX() - getScrollX(), (int) mStartDragItemView.getY());
}
public void stopDrag(View v) {
if (!isDrag)
return;
isDrag = false;
v.setAlpha(100);
removeDragImage();
}
private void onMoveImage(int moveX, int moveY) {
if (scrollXDelta == 0) {
ValueAnimator mAnimator = ValueAnimator.ofInt(moveX - scrollXDelta + mOffset2Left - moveXdistance,
moveX - scrollXDelta + mOffset2Left);
mAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 3.为目标对象的属性设置计算好的属性值
mWindowLayoutParams.x = Integer.parseInt(animation.getAnimatedValue().toString());
mWindowManager.updateViewLayout(mDragImageView, mWindowLayoutParams);
}
});
// 4.设置动画的持续时间、是否重复及重复次数等属性
mAnimator.setDuration(ANIMATION_TIME);
mAnimator.setRepeatCount(0);
// 5.为ValueAnimator设置目标对象并开始执行动画
mAnimator.setTarget(mDragImageView);
mAnimator.start();
} else {
mWindowLayoutParams.x = moveX + mOffset2Left;
mWindowLayoutParams.y = moveY + mOffset2Top;
mWindowManager.updateViewLayout(mDragImageView, mWindowLayoutParams); // 更新镜像的位置
}
}
/**
* 交换item,并且控制item之间的显示与隐藏效果
*
* @param moveX
* @param moveY
*/
private void onSwapItem(final int tempPosition) {
// 假如tempPosition 改变了并且tempPosition不等于-1,则进行交换
String t = ((TextView) childrens.get(mDragPosition)).getText().toString();
String t1 = ((TextView) childrens.get(tempPosition)).getText().toString();
((TextView) childrens.get(mDragPosition)).setText(t1);
((TextView) childrens.get(tempPosition)).setText(t);
childrens.get(mDragPosition).setAlpha(100);
childrens.get(tempPosition).setAlpha(0);
if (tempPosition != mDragPosition) {
final ViewTreeObserver observer = getViewTreeObserver();
observer.addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
animateReorder(mDragPosition, tempPosition);
mDragPosition = tempPosition;
return true;
}
});
}
}
/**
* item的交换动画效果
*
* @param oldPosition
* @param newPosition
*/
private void animateReorder(final int oldPosition, final int newPosition) {
final boolean isForward = newPosition > oldPosition;
List<Animator> resultList = new LinkedList<Animator>();
if (isForward) {
View view = mLinearLayout.getChildAt(oldPosition);
resultList.add(createTranslationAnimations(view, view.getWidth(), 0, 0, 0));
view = mLinearLayout.getChildAt(newPosition);
resultList.add(createTranslationAnimations(view, -view.getWidth(), 0, 0, 0));
} else {
View view = mLinearLayout.getChildAt(oldPosition);
resultList.add(createTranslationAnimations(view, -view.getWidth(), 0, 0, 0));
view = mLinearLayout.getChildAt(newPosition);
resultList.add(createTranslationAnimations(view, view.getWidth(), 0, 0, 0));
}
AnimatorSet resultSet = new AnimatorSet();
resultSet.playTogether(resultList);
if (scrollXDelta == 0)
resultSet.setDuration(ANIMATION_TIME);
else
resultSet.setDuration(200);
resultSet.setInterpolator(new AccelerateDecelerateInterpolator());
resultSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mAnimationEnd = false;
moveXdistance = moveXdistance - scrollXDelta;
onMoveImage(moveX - scrollXDelta, moveY);
}
@Override
public void onAnimationEnd(Animator animation) {
mAnimationEnd = true;
}
});
resultSet.start();
}
private void animateMove(int index, boolean isForward) {
List<Animator> resultList = new LinkedList<Animator>();
if (isForward) {
for (int i = index + 1; i < childrens.size(); i++) {
View view = childrens.get(i);
resultList.add(createTranslationAnimations(view, -view.getWidth(), 0, 0, 0));
}
} else {
for (int i = index; i < childrens.size(); i++) {
View view = childrens.get(i);
resultList.add(createTranslationAnimations(view, view.getWidth(), 0, 0, 0));
}
}
AnimatorSet resultSet = new AnimatorSet();
resultSet.playTogether(resultList);
if (scrollXDelta == 0)
resultSet.setDuration(ANIMATION_TIME);
else
resultSet.setDuration(200);
resultSet.setInterpolator(new AccelerateDecelerateInterpolator());
resultSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mAnimationEnd = false;
}
@Override
public void onAnimationEnd(Animator animation) {
mAnimationEnd = true;
}
});
resultSet.start();
}
/**
* 创建移动动画
*
* @param view
* @param startX
* @param endX
* @param startY
* @param endY
* @return
*/
private AnimatorSet createTranslationAnimations(View view, float startX, float endX, float startY, float endY) {
ObjectAnimator animX = ObjectAnimator.ofFloat(view, "translationX", startX, endX);
ObjectAnimator animY = ObjectAnimator.ofFloat(view, "translationY", startY, endY);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
return animSetXY;
}
}
主要思路是开始排序动画的时候,先把原来位置的隐藏,生成一个假的镜像。