属性动画:如何自定义View

道长今天说一下自定义View的实现,因为有很大一部分自定义View都带有动画,所以把自定义View放到属性动画里聊聊,let’s go……

一、需求明确

首先明确需求,根据需求创建布局,这里道长就自己定需求了:
1.拖动红色方块,红色沿着中心旋转并且颜色便为绿色,蓝色沿着Y轴旋转
2.拖动蓝色方块,红色沿着中心旋转并且颜色便为绿色,蓝色沿着Y轴旋转
3.拖动绿色方块,红色沿着中心旋转并且颜色便为绿色,蓝色沿着Y轴旋转
4.三种方块只可在屏幕内拖动

这里需求对布局的要求不明显,咱们把整个界面作为自定义View,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<com.yushan.animdemo.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.yushan.animdemo.MainActivity">

    <TextView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="#ff0000" />

    <TextView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="#0000ff" />

    <TextView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="#00ff00" />

</com.yushan.animdemo.DragLayout>


效果如下:
这里写图片描述

二、实现自定义View

  • 首先重写构造方法:
    public DragLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
    public DragLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    public DragLayout(Context context) {
        super(context);
        init();
    }

    private void init(){
        scroller = new Scroller(getContext());
        viewDragHelper = ViewDragHelper.create(this, callback);
    }
  • 获取子View
    /**
     * 当加载完布局xml的时候会执行该方法,所以执行该方法 的时候就能够知道当前的ViewGroup
     * 有多少个子View,但是此时并不知道子View的宽高是多少,因为还没有测量
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        redView = getChildAt(0);
        blueView = getChildAt(1);
        yellowView = getChildAt(2);
    }
  • 测量自己和子控件的宽高(这个不是必须的)
    /**
     * 测量自己和子控件的宽高
     * MeasureSpec: 测量规则,由size和mode组成
     * size:表示的是具体的大小值
     * mode:测量模式      封装的是我们在布局xml中的宽高参数
     *
     * MeasureSpec.AT_MOST: 对应的是wrap_content;
     * MeasureSpec.EXACTLY: 对应的是具体的dp值,match_parent;
     * MeasureSpec.UNSPECIFIED: 未定义的,一般只在adapter的测量中用到
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //构建测量规则
        //测量红孩子
        int measureSpec = MeasureSpec.makeMeasureSpec(redView.getLayoutParams().width,MeasureSpec.EXACTLY);
//      redView.measure(measureSpec, measureSpec);
//      //测量蓝精灵
//      blueView.measure(measureSpec, measureSpec);

        //更加简单的测量子View的方法是这样的:
        measureChild(redView, widthMeasureSpec, heightMeasureSpec);
        measureChild(blueView, widthMeasureSpec, heightMeasureSpec);
    }
  • 放置控件(这个是控件的初始位置,必须要有)
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left = 0;
        int top = 0;

        redView.layout(left,top,left+redView.getMeasuredWidth(), top+redView.getMeasuredHeight());

        blueView.layout(left,redView.getBottom(),left+blueView.getMeasuredWidth(), redView.getBottom()+blueView.getMeasuredHeight());

        yellowView.layout(left,blueView.getBottom(),left+yellowView.getMeasuredWidth(), blueView.getBottom()+yellowView.getMeasuredHeight());

        //将某个子View提到最上面
//      bringChildToFront(redView);
    }
  • 拦截事件(这里道长集成了NineOldAndroid并重写了Callback类中的方法)
  @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 让viewDragHelper帮助我们判断是否应该拦截
        boolean result = viewDragHelper.shouldInterceptTouchEvent(ev);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //将TouchEvent传递给viewDragHelper来处理
        viewDragHelper.processTouchEvent(event);
        return true;
    }

    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        /**
         * 是否捕获view的触摸
         * child: 表示当前所触摸的子VIew
         * return: true:会捕获      false:不会捕获,即忽略
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child==blueView || child==redView || child==yellowView;
        }
        /**
         * 当View被捕获的时候会回调
         */
        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {
            super.onViewCaptured(capturedChild, activePointerId);
//          Log.e("tag", "onViewCaptured");
        }
        /**
         * 获取view水平方向拖拽范围,但是目前并不起作用,但是最好还要实现下,不要
         * 返回0,它目前返回的值会用在计算view释放移动的动画时间计算上面
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            return DragLayout.this.getMeasuredWidth()-blueView.getMeasuredWidth();
        }
        /**
         * 控制child在水平方向的移动
         * child:当前所触摸的子View
         * left:表示ViewDragHelper帮你计算好的child的最终要变成的left值, left=child.getLeft()+dx
         * dx:表示本次水平移动的距离
         * return: 表示我们真正想让child的left变成的值
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if(left<0){
                left = 0;
            }
            return left;
        }
        /**
         * 控制child在垂直方向的移动
         * child:当前所触摸的子View
         * top:表示ViewDragHelper帮你计算好的child的最终要变成的top值, top=child.getTop()+dy
         * dy:表示本次垂直移动的距离
         * return: 表示我们真正想让child的top变成的值
         */
        public int clampViewPositionVertical(View child, int top, int dy) {
            if(top<0){
                top = 0;
            }
            return top;
        }

        /**
         * 当view位置改变的回调,一般用来实现view的伴随移动
         * changedView:表示当前位置改变了的view
         * left:changedView的最新的left
         * top:changedView的最新的top
         * dx:changedView本次水平移动距离
         * dy:changedView本次垂直移动距离
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                                          int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            if(changedView==blueView){
                //让redView跟随移动
                redView.layout(redView.getLeft()+dx,redView.getTop()+dy, redView.getRight()+dx, redView.getBottom()+dy);
            }else if (changedView==redView) {
                //让blueView跟随移动
                blueView.layout(blueView.getLeft()+dx,blueView.getTop()+dy, blueView.getRight()+dx, blueView.getBottom()+dy);
            } else if (changedView==yellowView){
                //让redView跟随移动
                redView.layout(redView.getLeft()+dx,redView.getTop()+dy, redView.getRight()+dx, redView.getBottom()+dy);
                //让blueView跟随移动
                blueView.layout(blueView.getLeft()+dx,blueView.getTop()+dy, blueView.getRight()+dx, blueView.getBottom()+dy);
            }

            //1.计算移动的百分比
            int maxLeft = DragLayout.this.getMeasuredWidth()-blueView.getMeasuredWidth();
            float fraction = changedView.getLeft()*1f/maxLeft;
            //2.根据移动的百分比执行很多的伴随动画
            executeAnim(fraction);
        }
        /**
         * 当View释放的时候执行,就是touch_up
         * releasedChild:当前抬起的子VIew
         * xvel:x方向移动的速度
         * yvel:y方向移动的速度
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
//          Log.e("tag", "xvel:"+xvel  +  "  yvel: "+yvel);
            //首先算出在正中间的left
            int centerLeft = getMeasuredWidth()/2-releasedChild.getMeasuredWidth()/2;
            if(releasedChild.getLeft()<centerLeft){
                //说明在左半边
                viewDragHelper.smoothSlideViewTo(releasedChild,0,releasedChild.getTop());
                ViewCompat.postInvalidateOnAnimation(DragLayout.this);

//              scroller.startScroll(startX, startY, dx, dy, duration);
//              invalidate();
            }else {
                //说明在右半边
                int finalLeft = getMeasuredWidth()-releasedChild.getMeasuredWidth();
                viewDragHelper.smoothSlideViewTo(releasedChild,finalLeft,releasedChild.getTop());
                ViewCompat.postInvalidateOnAnimation(DragLayout.this);
            }
        }
    };
  • 执行动画方法
    /**
     * 执行动画
     * @param fraction
     */
    private void executeAnim(float fraction){
        //旋转
//        blueView.setRotation(360*fraction);//设置旋转的角度
//      blueView.setRotationX(360*fraction);//设置围绕x轴旋转的角度
        blueView.setRotationY(360*fraction);//设置围绕Y轴旋转的角度

        //使用NineOldAndroid中的方法
        ViewHelper.setRotation(redView, 360*fraction);//设置旋转的角度
//        ViewHelper.setScaleX(redView, 1+fraction*0.5f);
//        ViewHelper.setScaleY(redView, 1+fraction*0.5f);

        //进行颜色的过度变化
        redView.setBackgroundColor((Integer) ColorUtil.evaluateColor(fraction, Color.RED,Color.GREEN));

    }

到这里就把View定义好了,当然这个View可以放到其他界面中,通过动画自定义View的好处是很明显的,就是实现简单灵活。希望这篇博客可以给你一些帮助。

源码下载

AnimDemo


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值