美文网首页Android开发Android进阶之路Android技术知识
View篇(二):玩一下自定义ViewGroup

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

作者: cff70524f5cf | 来源:发表于2019-02-22 17:12 被阅读8次

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

    三、添加动画

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

    静态 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技术知识的架构视频资料,还有职业生涯规划及面试指导。

    相关文章

      网友评论

        本文标题:View篇(二):玩一下自定义ViewGroup

        本文链接:https://www.haomeiwen.com/subject/algeyqtx.html