Android圆环选择View
无奈产品喜欢在APP中加入各种动画,再加上UI小姐姐的奇思妙想,然后就设计出了一大堆动画,前两周才把动画写完,故有了此篇博客来记录一下当时所遇到的坑,效果图镇楼,如下:
注:点击哪个那个旋转到最下面,旋转到最下面的为选中状态。
哇,当时看到这效果,真的是有辞职的冲动,但是转眼一想,哎,反正动画也不太熟悉,那就把这个做出来吧,我们先不加动画,就先实现静态的,如下图:
后面的圆环是怎么画出来的?
先实现简单的吧,我们可以看到后面有个圆环,我们都知道圆环嘛,大圆套小圆,就可以实现,但是现在的圆环颜色是不一样的,我们若还是用普通的圆来实现的话,就有点儿困难了。既然直接画圆不行,那就用圆弧把这个圆拼出来吧,代码如下:
//画大圆
private void drawBackRound(Canvas canvas) {
initPaint();
roundWidth = Math.min(width, height) / 2 * 0.15f;
rectF = new RectF(
(width > height ? Math.abs(width - height) / 2 : 0) + roundWidth / 2,
(height > width ? Math.abs(height - width) / 2 : 0) + roundWidth / 2,
width - (width > height ? Math.abs(width - height) / 2 : 0) - roundWidth / 2,
height - (height > width ? Math.abs(height - width) / 2 : 0) - roundWidth / 2);
paint.setStrokeWidth(roundWidth * 0.8f);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(getResources().getColor(R.color.white_0));
paint.setAntiAlias(true);
canvas.drawArc(rectF, 0, 360, false, paint);
}
额……若是RectF不知道是什么玩意儿的话…简单总结RectF、Rect 和Matrix ,还有Paint的使用方法这篇博客里面有讲。同理啦,圆环是大圆套小圆的,那么画小圆的方法和这个类似,只是改变了paint的颜色罢了,这就不贴代码啦,继续解决下一个问题~
圆环上的圆位置是怎么确定的?
在看完成的整体效果时,想必大家若是看到过鸿洋大神的Android 打造炫目的圆形菜单 秒秒钟高仿建行圆形菜单那这个位置解决的就soeasy啦~,其实我就是借鉴的鸿洋大神的这个例子,主要的代码如下:
/**
* 设置menu item的位置
*/
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
int layoutRadius = mRadius;
float angleDelay;
final int childCount = getChildCount();
int left, top;
// menu item 的尺寸
int cWidth = (int) (layoutRadius * RADIO_DEFAULT_CHILD_DIMENSION);
// 找到中心的view
View cView = findViewById(R.id.id_circle_menu_item_center);
// 根据menu item的个数,计算角度
if (cView != null) {
//如果中心View存在item个数减1
angleDelay = 360 / (getChildCount() - 1);
// 设置center item位置
//居中
int cl = layoutRadius / 2 - cView.getMeasuredWidth() / 2;
int cr = cl + cView.getMeasuredWidth();
cView.layout(cl, cl, cr, cr);
} else {
angleDelay = 360 / (getChildCount());
}
// 遍历去设置menuitem的位置
for (int j = 0; j < childCount; j++) {
final View child = getChildAt(j);
if (child.getId() == R.id.id_circle_menu_item_center)
continue;
if (child.getVisibility() == GONE) {
continue;
}
mStartAngle %= 360;
// 计算,中心点到menu item中心的距离
float tmp = layoutRadius / 2f - cWidth / 2;
// tmp cosa 即menu item中心点的横坐标
left = layoutRadius
/ 2
+ (int) Math.round(tmp
* Math.cos(Math.toRadians(mStartAngle)) - 1 / 2f
* cWidth);
// tmp sina 即menu item的纵坐标
top = layoutRadius
/ 2
+ (int) Math.round(tmp
* Math.sin(Math.toRadians(mStartAngle)) - 1 / 2f
* cWidth);
child.layout(left, top, left + cWidth, top + cWidth);
// 叠加尺寸
mStartAngle += angleDelay;
}
}
没错,就是重写onLayout来确定圆环上圆的位置,每当要旋转时就调用requestLayout()重新确定圆的位置~
圆环上的圆旋转的角度是怎么确定的?
在效果图上可以看到,点击哪个哪个就旋转到最下面,这中间有个问题,就是:我们人眼是可以看到那个在最上面,那个在左,在右,但是,在代码中实现的时候,我们并不知道它们的位置是在哪里,这就有个问题了,我们不知道位置,所以就没办法确定到底要旋转多少度,不知道要旋转的角度,那么这个效果就无法实现了。
后来想了半天,其实我们可以这样想,如下图:
首先我们可以获取的资源如下:当前选中的ImageView,要选中的ImageView,当前ImageView的在数组中的下标和要选中的ImageView在数组中的下标。这些我们是可以知道的,假如,现在选中了下标为1的ImageView,那么我不管是要选中下标为2还是0的都旋转90度,那么这个角度就可以用要选中的ImageView的下标来减去当前选中 X 90就好了,然后顺时针转还是逆时针转就看需求啦~但是还有一个要解决的,就是,当前选中的下标为0,那么左右的下标就是3和1了,这样(3-0) X 90要旋转的角度就不是我们想要的角度了,所以,在0和3的时候要有个判断,分析结束,主要的代码如下:
cl_group.setOnMenuItemClickListener(new CircleMenuLayout.OnMenuItemClickListener() {
@Override
public void itemClick(View iv, int pos, View tv) {
//旋转动画
groupRotating(pos);
...
oldElect = pos;
}
});
//旋转
private void groupRotating(int pos) {
ValueAnimator anim = new ValueAnimator();
int newElect = pos;
int angle = 0;
if (oldElect == 0 && newElect == 3) {
//当前选中的是0,未来选中的是3,则顺时针旋转180
angle = ROTATION_ANGLE;
} else if (oldElect == 3 && newElect == 0) {
//当前选中的是3,未来选中的是0,则逆时针旋转180
angle = -ROTATION_ANGLE;
} else {
//若不属于以上两种情况,按普通的处理
angle = (oldElect - newElect) * ROTATION_ANGLE;
}
//范围,基准角度----基准角度+要旋转的角度
anim = ValueAnimator.ofInt(tag, tag + angle);
anim.setDuration(SUCCESSFUL_ANIM_TIME).start();
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int angle = (int) valueAnimator.getAnimatedValue();
cl_group.setmStartAngle(angle);
}
});
tag = tag + angle;
}
ok,这里用了ValueAnimator属性动画,主要就是通过返回的数值,达到旋转的效果~
实现背景圆环同步旋转
我们在效果图上可以看到后面的圆环也是可以同步旋转的哈,其实要实现同步旋转很简单,在RingView加属性动画,然后外部调用就好啦!如下:
public void setRingRotating(int nowAngle, int futureAngle) {
ObjectAnimator.ofFloat(this, "rotation", nowAngle, futureAngle).setDuration(animTime).start();
}
外部调用:
//旋转
private void groupRotating(int pos) {
...
//背景圆环同步旋转
rl_view.setRingRotating(tag, tag + angle);
tag = tag + angle;
}
圆环颜色美化
在效果图上,可以看到当圆环在旋转的时候之前选中的颜色渐渐显示,要选中的渐渐隐藏,这个其实很简单,还是借助ValueAnimator属性动画就可以实现啦,同样的我们在外部调用它,在RingView里面添加如下代码:
//设置颜色要显示还是隐藏的参数
public void setElect(int oldElect, int newElect, int oldValue, int newValue) {
this.oldElect = oldElect;
this.newElect = newElect;
//当前的
int currentValue = 0;
//将来的
int futureValue = 0;
currentValue = (oldElect % 2 == 0) ? oldValue : newValue;
futureValue = (newElect % 2 == 0) ? oldValue : newValue;
//动画
ValueAnimator oldAnim = ValueAnimator.ofInt(0, currentValue);
ValueAnimator newAnim = ValueAnimator.ofInt(futureValue, 0);
oldAnim.setDuration(animTime).start();
newAnim.setDuration(animTime).start();
//显示
oldAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
oldAlpha = (int) valueAnimator.getAnimatedValue();
invalidate();
}
});
//隐藏
newAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
newAlpha = (int) valueAnimator.getAnimatedValue();
invalidate();
}
});
}
由于每段圆弧的颜色透明度不一样,所以要判断下当前的圆环和要选择的圆环的透明度。而它们的条件就是是否可以被2整除!
外部调用如下:
cl_group.setOnMenuItemClickListener(new CircleMenuLayout.OnMenuItemClickListener() {
@Override
public void itemClick(View iv, int pos, View tv) {
...
//背景圆弧换颜色动画
rl_view.setElect(oldElect, pos, 80, 50);
...
oldElect = pos;
}
});
圆弧上的ImageView美化
效果图上,可以看到圆弧上的ImageView是带有动画的,其实加动画也不难,主要的就是在点击后,有个缩放动画,主要代码如下:
private void zoomImageViewAnim(Bean oldBean, Bean newBean, final int width, final int height) {
//当前选中的缩小,换背景,字体换颜色
//将来选中的缩小,换背景,字体换颜色
final TextView oldTv = oldBean.getTv();
final ImageView oldIv = oldBean.getIv();
final TextView newTv = newBean.getTv();
final ImageView newIv = newBean.getIv();
ValueAnimator shrinkImageAnimWidth = new ValueAnimator();
ValueAnimator shrinkImageAnimHeight = new ValueAnimator();
shrinkImageAnimWidth = ValueAnimator.ofInt(width, 0);
shrinkImageAnimHeight = ValueAnimator.ofInt(height, 0);
//缩小
AnimatorSet set = new AnimatorSet();
set.playTogether(shrinkImageAnimWidth, shrinkImageAnimHeight);
set.setDuration(500).start();
shrinkImageAnimWidth.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int w = (int) animation.getAnimatedValue();
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) oldIv.getLayoutParams();
params.width = w;
//改变宽度
oldIv.setLayoutParams(params);
newIv.setLayoutParams(params);
}
});
shrinkImageAnimHeight.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int h = (int) animation.getAnimatedValue();
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) oldIv.getLayoutParams();
params.height = h;
//改变高度
oldIv.setLayoutParams(params);
newIv.setLayoutParams(params);
}
});
shrinkImageAnimWidth.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
//缩小动画,完成,开启放大动画
//文字换颜色
oldTv.setTextColor(Color.BLACK);
newTv.setTextColor(Color.WHITE);
//ImageView换背景
oldIv.setBackgroundResource(R.drawable.shape_circle_yellow_50);
newIv.setBackgroundResource(R.drawable.shape_circle_ring_yellow);
AnimatorSet set1 = new AnimatorSet();
ValueAnimator bigImageAnimWidth = new ValueAnimator();
ValueAnimator bigImageAnimHeight = new ValueAnimator();
bigImageAnimWidth = ValueAnimator.ofInt(0, width);
bigImageAnimHeight = ValueAnimator.ofInt(0, height);
set1.playTogether(bigImageAnimWidth, bigImageAnimHeight);
set1.setDuration(500).start();
bigImageAnimWidth.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int w = (int) animation.getAnimatedValue();
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) oldIv.getLayoutParams();
params.width = w;
//改变宽度
oldIv.setLayoutParams(params);
newIv.setLayoutParams(params);
}
});
bigImageAnimHeight.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int h = (int) animation.getAnimatedValue();
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) oldIv.getLayoutParams();
params.height = h;
//改变高度
oldIv.setLayoutParams(params);
newIv.setLayoutParams(params);
}
});
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
就不多做解释啦,里面都注释了。
END
其实最主要的是借鉴了,鸿洋大神的Android 打造炫目的圆形菜单 秒秒钟高仿建行圆形菜单,写之前感觉好难,写之后感觉,也就是一层窗户纸的事儿,思路最重要,哈哈……
源码链接