Android开发Android进阶之路Android技术知识

View篇(二):玩一下自定义ViewGroup

2019-02-22  本文已影响8人  cff70524f5cf

我们接着上一篇文章继续...

三、添加动画

下面这幅图应该不难吧,如果做不出来...下面的就当看风景吧...

静态 image.png 动态 image
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.位置交换的功能

这里实现和中心的交换,并且加入移动动画

无动画 image 有动画 image
---->[维护成员变量]-------------
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 家族使用指南

旋转 image 放大 image

三、你觉得无聊,玩点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.有了速度能干嘛?
惯性.gif

接下来的这部分源于陈小缘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进阶

image

Android前沿技术

image

Flutter

image

移动架构师

image image

需要这些安卓学习资料和面试资料的大伙需要的关注+点赞+加群:185873940 免费获取!

群内还有许多免费的关于高阶安卓学习资料,包括高级UI、性能优化、架构师课程、 NDK、混合式开发:ReactNative+Weex等多个Android技术知识的架构视频资料,还有职业生涯规划及面试指导。

上一篇下一篇

猜你喜欢

热点阅读