Android圓環選擇View
無奈産品喜歡在APP中加入各種動畫,再加上UI小姐姐的奇思妙想,然後就設計出了一大堆動畫,前兩周才把動畫寫完,故有了此篇部落格來記錄一下當時所遇到的坑,效果圖鎮樓,如下:
注:點選哪個那個旋轉到最下面,旋轉到最下面的為選中狀态。
哇,當時看到這效果,真的是有辭職的沖動,但是轉眼一想,哎,反正動畫也不太熟悉,那就把這個做出來吧,我們先不加動畫,就先實作靜态的,如下圖:
後面的圓環是怎麼畫出來的?
先實作簡單的吧,我們可以看到後面有個圓環,我們都知道圓環嘛,大圓套小圓,就可以實作,但是現在的圓環顔色是不一樣的,我們若還是用普通的圓來實作的話,就有點兒困難了。既然直接畫圓不行,那就用圓弧把這個圓拼出來吧,代碼如下:
//畫大圓
private void drawBackRound(Canvas canvas) {
initPaint();
roundWidth = Math.min(width, height) / * f;
rectF = new RectF(
(width > height ? Math.abs(width - height) / : ) + roundWidth / ,
(height > width ? Math.abs(height - width) / : ) + roundWidth / ,
width - (width > height ? Math.abs(width - height) / : ) - roundWidth / ,
height - (height > width ? Math.abs(height - width) / : ) - roundWidth / );
paint.setStrokeWidth(roundWidth * f);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(getResources().getColor(R.color.white_0));
paint.setAntiAlias(true);
canvas.drawArc(rectF, , , 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 = / (getChildCount() - );
// 設定center item位置
//居中
int cl = layoutRadius / - cView.getMeasuredWidth() / ;
int cr = cl + cView.getMeasuredWidth();
cView.layout(cl, cl, cr, cr);
} else {
angleDelay = / (getChildCount());
}
// 周遊去設定menuitem的位置
for (int j = ; 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 %= ;
// 計算,中心點到menu item中心的距離
float tmp = layoutRadius / f - cWidth / ;
// tmp cosa 即menu item中心點的橫坐标
left = layoutRadius
/
+ (int) Math.round(tmp
* Math.cos(Math.toRadians(mStartAngle)) - / f
* cWidth);
// tmp sina 即menu item的縱坐标
top = layoutRadius
/
+ (int) Math.round(tmp
* Math.sin(Math.toRadians(mStartAngle)) - / f
* 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 = ;
if (oldElect == && newElect == ) {
//目前選中的是0,未來選中的是3,則順時針旋轉180
angle = ROTATION_ANGLE;
} else if (oldElect == && newElect == ) {
//目前選中的是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 = ;
//将來的
int futureValue = ;
currentValue = (oldElect % == ) ? oldValue : newValue;
futureValue = (newElect % == ) ? oldValue : newValue;
//動畫
ValueAnimator oldAnim = ValueAnimator.ofInt(, currentValue);
ValueAnimator newAnim = ValueAnimator.ofInt(futureValue, );
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, , );
...
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, );
shrinkImageAnimHeight = ValueAnimator.ofInt(height, );
//縮小
AnimatorSet set = new AnimatorSet();
set.playTogether(shrinkImageAnimWidth, shrinkImageAnimHeight);
set.setDuration().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(, width);
bigImageAnimHeight = ValueAnimator.ofInt(, height);
set1.playTogether(bigImageAnimWidth, bigImageAnimHeight);
set1.setDuration().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 打造炫目的圓形菜單 秒秒鐘高仿建行圓形菜單,寫之前感覺好難,寫之後感覺,也就是一層窗戶紙的事兒,思路最重要,哈哈……
源碼連結