RotateCard(自定义旋转view)

时间:2024-10-09 21:36:39

RotateCard(自定义旋转view)

使用方法Demo

package com.example.displaydemo;

import java.util.ArrayList;

import com.example.displaydemo.RotateCard.OnItemClickListener;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast; public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); RotateCard card = (RotateCard) findViewById(R.id.card); ArrayList<View> views = new ArrayList<View>();
ImageView img; img = new ImageView(MainActivity.this);
img.setImageResource(R.drawable.p1);
views.add(img); img = new ImageView(MainActivity.this);
img.setImageResource(R.drawable.p2);
views.add(img); img = new ImageView(MainActivity.this);
img.setImageResource(R.drawable.p3);
views.add(img); img = new ImageView(MainActivity.this);
img.setImageResource(R.drawable.p4);
views.add(img); img = new ImageView(MainActivity.this);
img.setImageResource(R.drawable.p5);
views.add(img); img = new ImageView(MainActivity.this);
img.setImageResource(R.drawable.p6);
views.add(img); // 传入view列表和view的宽高
card.commitViews(views, 200, 200);
     // 添加点击事件监听
card.setOnItemClickListener(new OnItemClickListener() { @Override
public void onItemClickListener(View view, int index) {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this,
"The " + index + " is clicked!", Toast.LENGTH_SHORT)
.show();
} });
}
}

RotateCard.java

package com.example.displaydemo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout; /**
* RotateCard
*
* @author 小明湖畔
*
* @Enail 463785757@qq.com
*
* @time 2015-1-17 18:02
*/
public class RotateCard extends FrameLayout {
private static String INFO_TAG = "RotateCard";
private ArrayList<RotateCardViewHolder> viewHolderList;
private ArrayList<View> viewList;
private int ovalA, ovalB;// 椭圆轨迹的长半轴,短半轴
private int parentWidth, parentHeight;// RotateCard的宽高
private int viewWidth, viewHeight;// viewSet中的View的宽高
FrameLayout.LayoutParams viewpParams;// view的LayoutParams
private float angleOffset;// 手指每滑动一像素需要绕椭圆中心旋转的度数 private RotateCardOnTouchListener mTouchListener;// 供RotateCard和子View同时使用,处理滑动事件 // 增加绘制前的监听以获得RotateCard的宽高
private ViewTreeObserver.OnPreDrawListener onPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
parentWidth = RotateCard.this.getMeasuredWidth();
parentHeight = RotateCard.this.getMeasuredHeight();
angleOffset = (float) ((180.0 / parentWidth) * 1.5); // Log.i(INFO_TAG, "parentWidth:" + parentWidth);
// Log.i(INFO_TAG, "parentHeight:" + parentHeight);
// Log.i(INFO_TAG, "angleOffset:" + angleOffset); if (viewList != null && viewList.size() > 0) {
afterGetParamsAndViews();
} // 除去监听
RotateCard.this.getViewTreeObserver().removeOnPreDrawListener(
onPreDrawListener);
return true;
}
}; public RotateCard(Context context) {
super(context);
// TODO Auto-generated constructor stub
init();
} public RotateCard(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
init();
} public RotateCard(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
init();
} private void init() {
// 增加RotateCard绘制之前的监听
RotateCard.this.getViewTreeObserver().addOnPreDrawListener(
onPreDrawListener); // 增加RotateCard的滑动监听
mTouchListener = new RotateCardOnTouchListener();
this.setOnTouchListener(mTouchListener);
} /**
* 必须保证传入的View集合的每个View的大小一致,否则显示可能不准确
*
* @param viewSet
* View集合
*/
public void commitViews(ArrayList<View> viewList, int viewWidth,
int viewHeight) {
this.removeAllViews();
this.viewList = viewList;
this.viewWidth = viewWidth;
this.viewHeight = viewHeight;
viewpParams = new LayoutParams(viewWidth, viewHeight);
// Log.i(INFO_TAG, "viewWidth:" + viewWidth);
// Log.i(INFO_TAG, "viewHeight:" + viewHeight); if (parentWidth != 0 && parentHeight != 0) {
afterGetParamsAndViews();
}
} /**
* 在获得RotateCard后并且调用过commitViews函数后,此函数被调用
*/
private void afterGetParamsAndViews() { ovalA = (parentWidth - viewWidth) / 2;
ovalB = (int) ((parentHeight - viewHeight * 1.5) / 2); int viewNum = viewList.size();
float anglePerView = (float) (360.0 / viewNum); // Log.i(INFO_TAG, "ovalA:" + ovalA);
// Log.i(INFO_TAG, "ovalB:" + ovalB);
// Log.i(INFO_TAG, "viewNum:" + viewNum);
// Log.i(INFO_TAG, "anglePerView:" + anglePerView); if (viewHolderList == null) {
viewHolderList = new ArrayList<RotateCardViewHolder>();
} else {
viewHolderList.clear();
} float tmpAngle = 270.0f;// 第一个view在270度的位置,即最前的位置
for (int i = 0; i < viewNum; ++i) {
int index = i + 1;
View view = viewList.get(i);
// 添加子view的滑动事件监听
view.setOnTouchListener(mTouchListener);
// 添加子view的点击事件监听
view.setOnClickListener(new ItemOnClickListener(index));
// 为子view创建一个RotateCardViewHolder
RotateCardViewHolder holder = new RotateCardViewHolder(view,
tmpAngle, index);
// 将holder捆绑在子view上,方面从子view直接获取holder
view.setTag(holder);
viewHolderList.add(holder); // 增加anglePerView的角度,用于确定下一个view的位置
tmpAngle += anglePerView;
}
// 把子view添加到RotateCard中
loadViewBySequence();
} /**
* 把所有view旋转angleDiff的角度
*
* @param angleDiff
* 旋转的角度
*/
private void rotateViewsByAngle(float angleDiff) {
// 若有动画在进行,则不进行旋转
if (set != null && set.isRunning()) {
return;
}
// 旋转每一个view
for (RotateCardViewHolder holder : viewHolderList) {
holder.setAngle(holder.getAngle() + angleDiff);
}
// 重新载入view以确保他们正确的遮挡关系
loadViewBySequence();
} /**
* 通过对vew排序后重新把view添加到RotateCard中来决定他们的遮挡关系 ,越靠前的view越慢添加
*/
private void loadViewBySequence() {
Collections.sort(viewHolderList, new RotateCardViewHolderComparator());
this.removeAllViews();
for (RotateCardViewHolder holder : viewHolderList) {
this.addView(holder.getView(), viewpParams);
}
} /**
* 封装对view的一些操作
*/
public class RotateCardViewHolder {
private View mView;
private float mAngle;
private int index; public RotateCardViewHolder(View view, float angle, int index) {
this.mView = view;
this.mAngle = angle;
this.index = index;
setAngle(angle);
} /**
* 设置位置和大小
*/
public void resetPositionAndScale() {
// 设置大小
float angleDiff = getAngleDiffWith90();
float scale = (float) ((1.0 * angleDiff / 180) * 0.75 + 0.25);
mView.setScaleX(scale);
mView.setScaleY(scale); // 设置位置
float x = (float) (ovalA * Math.cos(mAngle / 180.0 * Math.PI)
+ parentWidth / 2 - viewWidth / 2);
float y = (float) (parentHeight / 2 - ovalB
* Math.sin(mAngle / 180.0 * Math.PI) - viewHeight / 2);
mView.setX(x);
mView.setY(y);
} /**
* 设置view的角度,同时改变他的显示的大小和位置
*
* @param angle
* view的角度
*/
public void setAngle(float angle) {
// 保证mAngle在0~360之间
mAngle = angle;
while (mAngle < 0 || mAngle > 360) {
if (mAngle < 0) {
mAngle = (mAngle + 360) % 360;
} else {
mAngle = mAngle % 360;
}
}
resetPositionAndScale();
} /**
* 获取view的角度
*
* @return view的角度
*/
public float getAngle() {
return mAngle;
} /**
* 获取view一开始的序号
*
* @return view的序号
*/
public int getIndex() {
return index;
} /**
* 获取view的实例
*
* @return view的实例
*/
public View getView() {
return mView;
} /**
* 获取该view当前位置跟90度位置相差的度数
*
* @return 跟90度相差的度数
*/
public float getAngleDiffWith90() {
float tmpAngle = mAngle > 270 ? mAngle - 360 : mAngle;
float angleDiff = Math.abs(tmpAngle - 90);
return angleDiff;
} /**
* 获取该view当前位置跟270度位置相差的度数
*
* @return 跟270度相差的度数
*/
public float getAngleDiffWith270() {
float tmpAngle = mAngle < 90 ? mAngle + 360 : mAngle;
float angleDiff = Math.abs(tmpAngle - 270);
return angleDiff;
} } /**
* 比较器,用来把view按照度数越接近270度越靠后的顺序排序
*
*/
public class RotateCardViewHolderComparator implements
Comparator<RotateCardViewHolder> { @Override
public int compare(RotateCardViewHolder a, RotateCardViewHolder b) {
// TODO Auto-generated method stub
return a.getAngleDiffWith270() > b.getAngleDiffWith270() ? -1 : 1; }
} private float lastX, nowX;// 上次手指的x坐标位置和当前手指的x坐标位置
private VelocityTracker vTracker;// 监控手指滑动的使用率
private AnimatorSet set;// 动画集
private boolean justCancelAnimation = false;// 是否刚刚通过点击屏幕使正在进行的动画取消,用于取消动画的同事发生的点击事件 @Override
public boolean dispatchTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
// 若动画正在进行,则取消动画
if (set != null && set.isRunning()) {
set.cancel();
set = null;
justCancelAnimation = true;
}
// 获取VelocityTracker的一个实例
vTracker = VelocityTracker.obtain();
// 记录lastX
lastX = event.getX();
}
break;
case MotionEvent.ACTION_MOVE: {
justCancelAnimation = false;
// 把动作送到vTracker
vTracker.addMovement(event);
// 记录nowX
nowX = event.getX();
}
break;
case MotionEvent.ACTION_UP: {
if (justCancelAnimation) {
justCancelAnimation = false;
return true;// 用来取消点击事件
}
// 设置计算单元,为1秒
vTracker.computeCurrentVelocity(1000);
// 获取在x轴方向1秒内划过的像素数
float xSpeed = vTracker.getXVelocity();
// 活动过快,则执行动画,让RotateCard再旋转一会才停下
if (Math.abs(xSpeed) > 800.0f) {
// 还要旋转的角度
float angleDiff = xSpeed / 10;
// 还要旋转的时间
int duration = (int) (Math.abs(angleDiff) / 180 * 1000); // 设置动画集
set = new AnimatorSet();
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f)
.setDuration(duration);
animation.addUpdateListener(new AnimatorUpdateListener() { @Override
public void onAnimationUpdate(ValueAnimator animation) {
// TODO Auto-generated method stub
loadViewBySequence();
} });
set.play(animation);
for (RotateCardViewHolder _holder : viewHolderList) {
ObjectAnimator oa = ObjectAnimator.ofFloat(_holder,
"angle", _holder.getAngle(),
_holder.getAngle() + angleDiff).setDuration(
duration);
oa.setInterpolator(new DecelerateInterpolator());
set.play(oa).with(animation);
}
// 开始动画集合
set.start();
}
}
break;
}
return super.dispatchTouchEvent(event);
} private class RotateCardOnTouchListener implements OnTouchListener { @Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: { }
break;
case MotionEvent.ACTION_MOVE: {
// 此次手指滑动需要旋转的度数
float angleDiff = (nowX - lastX) * angleOffset;
// 旋转angleDiff的角度
rotateViewsByAngle(angleDiff);
// 记录lastX
lastX = nowX;
}
break;
case MotionEvent.ACTION_UP: { }
break;
} if (v instanceof RotateCard) {
// 若是手指的位置是RotateCard,返回true让事件继续传递给RotateCard
return true;
} else {
// 若是手指的位置是子view,返回false让子view的点击事件可以响应
return false;
}
}
} public class ItemOnClickListener implements OnClickListener { private int index; public ItemOnClickListener(int index) {
this.index = index;
} /**
* * 把被点击的view旋转的最前面
*/
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
final View tView = v;
final RotateCardViewHolder holder = (RotateCardViewHolder) v
.getTag();
float angle = holder.getAngle();
// 需要旋转的度数
float angleDiff = angle >= 0.0f && angle <= 90.0f ? -90.0f - angle
: 270.0f - angle;
// 是否子执行view的点击事件(若被点击view在很靠前,则旋转后执行它的点击事件)
final boolean performClick = Math.abs(angleDiff) < 45.0f;
int duration = (int) (Math.abs(angleDiff) / 180 * 1000); // 初始化动画集
set = new AnimatorSet();
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f)
.setDuration(duration);
animation.addUpdateListener(new AnimatorUpdateListener() { @Override
public void onAnimationUpdate(ValueAnimator animation) {
// TODO Auto-generated method stub
loadViewBySequence();
} });
set.play(animation);
if (performClick) {
ObjectAnimator aa = ObjectAnimator.ofFloat(tView, "alpha",
1.0f, 0.5f, 1.0f).setDuration(800);
ObjectAnimator saX = ObjectAnimator.ofFloat(tView, "scaleX",
1.0f, 1.5f, 1.0f).setDuration(800);
ObjectAnimator saY = ObjectAnimator.ofFloat(tView, "scaleY",
1.0f, 1.5f, 1.0f).setDuration(800);
set.play(aa).with(animation);
set.play(saX).with(animation);
set.play(saY).with(animation);
}
for (RotateCardViewHolder _holder : viewHolderList) {
ObjectAnimator oa = ObjectAnimator.ofFloat(_holder, "angle",
_holder.getAngle(), _holder.getAngle() + angleDiff)
.setDuration(duration);
oa.setInterpolator(new DecelerateInterpolator());
set.play(oa).with(animation);
}
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
if (mOnItemClickListener != null && performClick) {
// 执行外部设置的点击事件
mOnItemClickListener.onItemClickListener(tView, index);
}
} @Override
public void onAnimationCancel(Animator animation) {
// TODO Auto-generated method stub
tView.setAlpha(1.0f);
holder.resetPositionAndScale();
}
});
set.start();
}
} /**
* 子view点击事件监听器OnItemClickListener
*
* RotateCard内部需要自行处理子vew的点击事件,OnItemClickListener监听器供外部代码使用
*/
public interface OnItemClickListener {
public void onItemClickListener(View view, int index);
} private OnItemClickListener mOnItemClickListener; /**
* 添加子view点击事件监听
*
* @param onItemClickListener
*/
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
}