我们接着上一篇文章继续...
三、添加动画
静态下面这幅图应该不难吧,如果做不出来...下面的就当看风景吧...

1.首先把排成圆的方法封装一下
/**
* @param start 第一个排成圆的View索引
* @param dθ 旋转角度
*/
private void layoutCircle(int start, float dθ) {
int count = getChildCount();
for (int i = start; i < count; i++) {
View childView = getChildAt(i);
int childW = childView.getMeasuredWidth();
int childH = childView.getMeasuredHeight();
int r = (getWidth() - childW) / 2;
float posX = childW / 2 + r - r * cos(i * 360.f / (count - 1) + dθ);
float posY = childH / 2 + r - r * sin(i * 360.f / (count - 1) + dθ);
int leftPos = (int) (posX - childW / 2);
int topPos = (int) (posY - childH / 2);
childView.layout(leftPos, topPos, leftPos + childW, topPos + childH);
}
}
复制代码
2.ValueAnimator走起
在点击的时候触发mAnimator.start()即可
mAnimator = ValueAnimator.ofInt(0, 360);
mAnimator.setDuration(3000);
mAnimator.addUpdateListener(a -> {
int deg = (int) a.getAnimatedValue();
layoutCircle(1, deg);
});
复制代码
3.位置交换的功能
无动画这里实现和中心的交换,并且加入移动动画
---->[维护成员变量]-------------
private int centerId = 0;//默认中心点
/**
* 交换两个View的位置
* @param positionMe 点击者
* @param positionHe 目标
*/
private void swap(int positionMe, int positionHe) {
View me = getChildAt(positionMe);
View he = getChildAt(positionHe);
int TempMeLeft = me.getLeft();
int TempMeTop = me.getTop();
int TempMeRight = me.getRight();
int TempMeBottom = me.getBottom();
me.layout(he.getLeft(), he.getTop(), he.getRight(), he.getBottom());
he.layout(TempMeLeft, TempMeTop, TempMeRight, TempMeBottom);
centerId = positionMe;
}
|--然后只需要在需要的时候触发即可:
swap(position, centerId);
复制代码
动画,刚才貌似写过了,直接拿来用
/**
* 交换两个View的位置
* @param positionMe 点击者
* @param positionHe 目标
*/
private void swapWithAnim(int positionMe, int positionHe) {
View me = getChildAt(positionMe);
View he = getChildAt(positionHe);
int TempMeLeft = me.getLeft();
int TempMeTop = me.getTop();
useLayoutAnimate(me, he.getLeft(), he.getTop());
useLayoutAnimate(he, TempMeLeft,TempMeTop);
centerId = positionMe;
}
private void useLayoutAnimate(View view, int x, int y) {
ObjectAnimator.ofInt(view, "Left", x).setDuration(500).start();
ObjectAnimator.ofInt(view, "Top", y).setDuration(500).start();
ObjectAnimator.ofInt(view, "Right", x + view.getMeasuredWidth()).setDuration(500).start();
ObjectAnimator.ofInt(view, "Bottom", y + view.getMeasuredHeight()).setDuration(500).start();
}
复制代码
旋转既然可以动画,那么则么玩都可以,比如旋转和放大
动画就不展开了,详情可见:Android 动画 Animator 家族使用指南
三、你觉得无聊,玩点6的
1.神技之一:VelocityTracker
这个类估计听过的人不多,翻译出来是
速度追踪器
,作为一个好用的类,在此拎出来讲一讲
它的作用是获取你滑动的x,y的速度x 左负,y上负
---->[FlowerLayout#init]---------------
private void init(AttributeSet attrs) {
...
velocityTracker = VelocityTracker.obtain();//1.VelocityTracker的创建
}
---->[FlowerLayout#onTouchEvent]---------------
@Override
public boolean onTouchEvent(MotionEvent event) {
View centerView = getChildAt(0);
velocityTracker.addMovement(event);//2.VelocityTracker与event结合
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_MOVE:
velocityTracker.computeCurrentVelocity(1000);//3.计算速度
//4.获取值
Log.e(TAG, "X velocity: " + velocityTracker.getXVelocity()+
"--Y velocity: " + velocityTracker.getYVelocity());
break;
case MotionEvent.ACTION_UP:
...
break;
}
return true;
}
|--注意第5点:在适当的地方取消和回收
velocityTracker.clear();//取消
velocityTracker.recycle();//回收
|---我们比较在意的是计算速度的方法,1000是搞嘛的?
/**
* Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
* velocity of Float.MAX_VALUE.
* 也就是说这里的第三参是Float的最大值,表示这个速度足以超光速
* @see #computeCurrentVelocity(int, float)
*/
public void computeCurrentVelocity(int units) {
nativeComputeCurrentVelocity(mPtr, units, Float.MAX_VALUE);
}
/**
* @param units The units you would like the velocity in. A value of 1
* provides pixels per millisecond, 1000 provides pixels per second, etc.
你想要的单位是速度。值1表示像素/毫秒,1000表示像素/秒,等等。
* @param maxVelocity The maximum velocity that can be computed by this method.
* This value must be declared in the same unit as the units parameter. This value
* must be positive. 该方法可以计算的最大值
*/
public void computeCurrentVelocity(int units, float maxVelocity) {
nativeComputeCurrentVelocity(mPtr, units, maxVelocity);
}
|-- native方法就不挖了
复制代码
2.有了速度能干嘛?
注
接下来的这部分源于陈小缘的Android实现圆弧滑动效果之ArcSlidingHelper篇
我认真研究了一下,并融入了本ViewGroup,他封装的非常好,我拆了一下截取了和惯性相关的部分
不懂的可以去深度一下,我就不卖弄唇舌了,GitHub在:ArcSlidingHelper
---->[FlowerLayout#onLayout]--------------------
private void initRotate() {
int width = getWidth();
int height = getHeight();
mPivotX = width/2;
mPivotY = height/2;
mVelocityTracker = VelocityTracker.obtain();
mScrollAvailabilityRatio = .3F;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
View centerView = getChildAt(0);
float x, y;
x = event.getRawX();
y = event.getRawY();
mVelocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mAnimator.start();
abortAnimation();
centerView.layout(x, y,
x + centerView.getMeasuredWidth(), y + centerView.getMeasuredHeight());
Log.e("EVENT", "onTouchEvent: " + x + "------" + y);
break;
case MotionEvent.ACTION_MOVE:
handleActionMove(x, y);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
mVelocityTracker.computeCurrentVelocity(1000);
mScroller.fling(0, 0,
(int) mVelocityTracker.getXVelocity(),
(int) mVelocityTracker.getYVelocity(),
Integer.MIN_VALUE, Integer.MAX_VALUE,
Integer.MIN_VALUE, Integer.MAX_VALUE);
startFling();
break;
}
mStartX = x;
mStartY = y;
return true;
}
//------------------------惯性旋转----------------------------
private Scroller mScroller = new Scroller(getContext());
private int mPivotX, mPivotY;
private float mStartX, mStartY;
private float mLastScrollOffset;
private float mScrollAvailabilityRatio;
private boolean isClockwiseScrolling;
private boolean isShouldBeGetY;
private boolean isRecycled;
private VelocityTracker mVelocityTracker;
private Handler mHandler = new Handler(msg -> {
computeInertialSliding();
return false;
});
/**
* 处理惯性滚动
*/
private void computeInertialSliding() {
checkIsRecycled();
if (mScroller.computeScrollOffset()) {
float y = ((isShouldBeGetY ? mScroller.getCurrY() : mScroller.getCurrX()) * mScrollAvailabilityRatio);
if (mLastScrollOffset != 0) {
float offset = fixAngle(Math.abs(y - mLastScrollOffset));
float deg = isClockwiseScrolling ? offset : -offset;
setRotation(getRotation() + deg);
}
mLastScrollOffset = y;
startFling();
} else if (mScroller.isFinished()) {
mLastScrollOffset = 0;
}
}
/**
* 计算滑动的角度
*/
private void handleActionMove(float x, float y) {
float l, t, r, b;
if (mStartX > x) {
r = mStartX;
l = x;
} else {
r = x;
l = mStartX;
}
if (mStartY > y) {
b = mStartY;
t = y;
} else {
b = y;
t = mStartY;
}
float pA1 = Math.abs(mStartX - mPivotX);
float pA2 = Math.abs(mStartY - mPivotY);
float pB1 = Math.abs(x - mPivotX);
float pB2 = Math.abs(y - mPivotY);
float hypotenuse = (float) Math.sqrt(Math.pow(r - l, 2) + Math.pow(b - t, 2));
float lineA = (float) Math.sqrt(Math.pow(pA1, 2) + Math.pow(pA2, 2));
float lineB = (float) Math.sqrt(Math.pow(pB1, 2) + Math.pow(pB2, 2));
if (hypotenuse > 0 && lineA > 0 && lineB > 0) {
float angle = fixAngle((float) Math.toDegrees(Math.acos((Math.pow(lineA, 2) + Math.pow(lineB, 2) - Math.pow(hypotenuse, 2)) / (2 * lineA * lineB))));
float deg = (isClockwiseScrolling = isClockwise(x, y)) ? angle : -angle;
setRotation(getRotation() + deg);
}
}
/**
* 打断动画
*/
public void abortAnimation() {
checkIsRecycled();
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
}
/**
* 释放资源
*/
public void release() {
checkIsRecycled();
mScroller = null;
mVelocityTracker.recycle();
mVelocityTracker = null;
isRecycled = true;
}
/**
* 检测手指是否顺时针滑动
*
* @param x 当前手指的x坐标
* @param y 当前手指的y坐标
* @return 是否顺时针
*/
private boolean isClockwise(float x, float y) {
return (isShouldBeGetY = Math.abs(y - mStartY) > Math.abs(x - mStartX)) ?
x < mPivotX != y > mStartY : y < mPivotY == x > mStartX;
}
/**
* 开始惯性滚动
*/
private void startFling() {
mHandler.sendEmptyMessage(0);
}
/**
* 调整角度,使其在360之间
*
* @param rotation 当前角度
* @return 调整后的角度
*/
private float fixAngle(float rotation) {
float angle = 360F;
if (rotation < 0) {
rotation += angle;
}
if (rotation > angle) {
rotation = rotation % angle;
}
return rotation;
}
/**
* 检查资源释放已经释放
*/
private void checkIsRecycled() {
if (isRecycled) {
throw new IllegalStateException(" is recycled!");
}
}
复制代码
OK,今天就到这里
最后附上小编整理出来的Android相关的学习思维导图,让大家有个学习的方向,早日拿到大厂的offer。
Android进阶
Android前沿技术
Flutter
移动架构师
需要这些安卓学习资料和面试资料的大伙需要的关注+点赞+加群:185873940 免费获取!
群内还有许多免费的关于高阶安卓学习资料,包括高级UI、性能优化、架构师课程、 NDK、混合式开发:ReactNative+Weex等多个Android技术知识的架构视频资料,还有职业生涯规划及面试指导。
网友评论