我报名参加金石计划1期应战——分割10万奖池,这是我的第4篇文章,点击检查活动详情

我自己公司的业务需求上是根本不会涉及动画相关的,也可以说根本两年没怎么做过动画相关的开发,这次写这篇文章是由于读到一篇比较好的文章,想把它记载下来,今后有做动画作用的需求的话就能快速上手。 参阅《Android 开发艺术探究》 7.3.4 对恣意特点做动画

众所周知Android里边动画有3种,View动画、帧动画和特点动画。不同的场景使用不同的动画,对控件而言,你想完结一个自定义动画的作用,也便是随心,那就使用特点动画会比较好,由于View动画只供给了简略的平移、翻转、缩放、透明度。 当然这儿也不会去介绍一些基础的内容。为什么独自说这一末节,由于我觉得这个地方是这章(第七章)最经典的地方。

一. 对恣意特点做动画

依照书上的逻辑来讲。 一开始举了一个栗子,给Button加个动画,让这个Button的宽度增加500px。 直接写特点动画的代码:

    ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start();

这儿是对这个Button的 width特点做动画,也便是对宽度这个特点做动画,可是并不能完结咱们想要的宽度增加500px的作用。这时候就先引出了一个重要的结论: 特点动画要求动画作用的目标供给该特点的get和set办法 这个很重要,意思便是说,这个mButton目标,没有供给getWidth和setWidth办法。其实这说法有点那啥,由于Button是有getWidth和setWidth的办法,可是为什么没生效呢? 这是展示了setWidth的源码

    public void setWidth(int pixels) {
        mMaxWidth = mMinWidth = pixels;
        mMaxWidthMode = mMinWidthMode = PIXELS;
        requestLayout();
        invalidate();
    }

发现这儿并不是设置Button的宽度,而是设置Button的最大宽度和最小宽度。 也便是说在上面的动画中:真的的是随着时刻的改动去改动控件的最大最小宽度,所以视觉看不出作用,由于实践作用的特点不是你想要去让它作用的特点 所以想要完结作用,应该要供给该特点正确的get/set办法

书上又写出了这样的一个结论(原话): 针对上诉问题,官方文档上告诉咱们有3种解决办法:

  • 给你的目标加上get和set办法,假如你有权限的话
  • 用一个类来包装原始目标,直接为其供给get和set办法
  • 选用ValueAnimator,监听动画过程,自己完结特点的变化

第一个计划必定不可行,系统的View不是咱们说改就改的,然后用两个Demo分别来描述办法2和办法3(不能仿制要手写,好不想写)

办法2:

    private static class ViewWrapper{
        private View mTarget;
        public ViewWrapper(View target){
            mTarget = target;
        }
        public int getWidth(){
            return mTarget.getLayoutParams().width;
        }
        public void setWidth(int width){
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }

调用的时候

ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper , "width", 500).setDuration(5000).start();

这样就能正常展示咱们想要的动画作用,目标是wrapper ,它确实为width特点供给了get/set办法。动画内部在履行过程中会拿到传给它的特点,然后用反射去调用它的get/sset办法。可是怎么说呢,具体的特点改动的算法仍是要你去手动写。

办法3:

        ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            private IntEvaluator mEvaluator = new IntEvaluator();
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int currentValue = (Integer) animation.getAnimatedValue();
                float fraction = animator.getAnimatedFraction();
                view.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
                view.requestLayout();
            }
        });

这个办法便是对动画进行监听,每监听到一帧的时候再做具体的操作。可以看到这儿也没写get/set办法,大约就那么一个意思就得了,也不是非得说一定要这样写,或者不必这两种办法也还有其它办法能完结。 总之,特点的具体的变化,是需求咱们自己去写逻辑(假如要完结自定义的作用),而ObjectAnimator、ValueAnimator这些特点广告的类,能告诉咱们这个动画履行的整个过程,简略的说便是咱们知道播放到哪一帧,就能做到在这一帧做什么作用。 这两个Demo也是我觉得这章最有意思的地方。

二. 自己完结一个自定义动画

光看必定是3天忘,自己动手写个简略的自定义动画作用。 我弄一个圆形View,可以拖动它,点击下去的时候圆会扩大并变成正方形,然后移动,松开时正方形会缩小并变回圆,而且咱们要让圆变成正方形不是秒变,要有个突变的作用(只完结了简略的突变作用,由于懒得去计算)。 终究的作用也没有做成gif,想看作用的直接仿制代码去试就行,不多。

####1. 制作view的初始方位 我这Demo再弥补一些细节的东西,仍是《Android 开发艺术探究》3.1.2 View的方位参数 不是什么难点,便是有些细节要注意,这个view的方位参数列举了3套 (1) Left, Right, Top, Bottom , 当成一个矩形看(圆也是矩形),便是4条边间隔父容器x\y轴的间隔。 (2) x, y 左上点的坐标 (3)translationX,translationY 间隔父View坐标的偏移量

我就直接贴代码讲吧。 先看Activity布局

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fl_content"
    android:padding="100px"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</FrameLayout>

这儿padding一个100px,是为了方便介绍这些方位,单位相同简略看,实践开发中必定最好用dp做单位。 然后看Activity代码

public class RsActivity extends Activity {
    private FrameLayout flContent;
    private RoundSquareView roundSquareView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rs);
        initView();
    }
    private void initView(){
        flContent = findViewById(R.id.fl_content);
        roundSquareView = new RoundSquareView(this);
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(150, 150);
        roundSquareView.setLayoutParams(lp);
        flContent.addView(roundSquareView);
        roundSquareView.setX(100);
        roundSquareView.setY(100);
    }
    @Override
    protected void onResume() {
        super.onResume();
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.v("mmp", "getLeft "+roundSquareView.getLeft());
                Log.v("mmp", "getTranslationX "+roundSquareView.getTranslationX());
                Log.v("mmp", "getX "+roundSquareView.getX());
                roundSquareView.setX(0);
            }
        }, 1000);
    }
}

RoundSquareView是一个自定义View, 先不必管它。 第一个问题来了,为什么要写个延迟,重点啊,由于getLeft ,便是获取第一套的那4个特点,要在view制作完结之后才干获取到,否则获取到的会是0,加个延迟是为了确保制作完结(这仅仅Demo,实在开发中判别制作完结必定不能用延时这样玩) 看看显现成果

Android简单实现自定义的动画效果
再看看打印成果

Android简单实现自定义的动画效果
间隔顶部和左边都是200px,可是left打印是100,假如把父布局中的padding去掉的话,left会打印0,这便是第二个地方,使用Left的时候要注意padding,而且它不算translationX偏移的部分。 然后translationX也打印的是100,他也不算是padding的部分。 最终getX是200,他是正常的相对于父布局的部分间隔,不受任何影响。 也证实了x = left + translationX 这儿仅仅为了介绍一些细节上的东西而已,onResume里边的代码对咱们要完结的功用没有任何含义,所以使用时请屏蔽掉。

2. 自定义View

看看代码

public class RoundSquareView extends View {
    private float oldRawX = 0;
    private float oldRawY = 0;
    private int currentValue = 0;
    private boolean isExpand;
    public RoundSquareView(Context context) {
        super(context);
    }
    public RoundSquareView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public RoundSquareView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(1);
        int w = getWidth() /2;
        int h = getHeight() /2;
        if (!isExpand) {
            // 非扩展状态下的制作
            canvas.drawCircle(w, h, w + currentValue , paint);
        }else {
            // 扩展状态下的制作
            canvas.drawCircle(w, h, w + (100 - currentValue) , paint);
        }
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                isExpand = false;
                expand();
                oldRawX = event.getRawX();
                oldRawY = event.getRawY();
                return true;
            case MotionEvent.ACTION_MOVE:
                moveView(event.getRawX(), event.getRawY());
                return true;
            case MotionEvent.ACTION_UP:
                oldRawX = 0;
                oldRawX = 0;
                isExpand = true;
                narrow();
                return true;
        }
        return super.onTouchEvent(event);
    }
    @Override
    public boolean performClick() {
        return super.performClick();
    }
    private void moveView(float rawX, float rawY){
        // 计算偏移量
        float offsetX = rawX - oldRawX;
        float offsetY = rawY - oldRawY;
        // 更改方位
        setX(getX()+offsetX);
        setY(getY()+offsetY);
        // 更新方位
        oldRawX = rawX;
        oldRawY = rawY;
    }
    /**
     *  扩展
     */
    private void expand(){
        int w = getWidth();
        int h = getHeight();
        ViewGroup.LayoutParams lp = this.getLayoutParams();
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentValue = (int) animation.getAnimatedValue();
                Log.v("mmp", "动画进展 "+currentValue);
                lp.width = w + (w/100 * currentValue);
                lp.height = h + (h/100 * currentValue);
                RoundSquareView.this.setLayoutParams(lp);
            }
        });
        valueAnimator.setDuration(300).start();
    }
    /**
     *  缩小
     */
    private void narrow(){
        int w = getWidth();
        int h = getHeight();
        ViewGroup.LayoutParams lp = this.getLayoutParams();
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentValue = (int) animation.getAnimatedValue();
                Log.v("mmp", "动画进展 "+currentValue);
                lp.width = w - (w/200 * currentValue);
                lp.height = h - (h/200 * currentValue);
                RoundSquareView.this.setLayoutParams(lp);
            }
        });
        valueAnimator.setDuration(300).start();
    }
}

自定义View,在draw中用Paint画个圆,这个,这个没什么好说的,都能看懂,最终

        if (!isExpand) {
            // 非扩展状态下的制作
            canvas.drawCircle(w, h, w + currentValue , paint);
        }else {
            // 扩展状态下的制作
            canvas.drawCircle(w, h, w + (100 - currentValue) , paint);
        }

先不必管,先看成

canvas.drawCircle(w, h, w, paint);

这样就画成一个圆了。 然后写onTouchEvent让view随手指移动而移动。记载改动前的手指的点击方位oldRawX,oldRawY,去获取当时的方位减去旧的方位就能得到一个偏移量,然后再用这个偏移量去改动x和y特点,这是最简略的view随手指移动的方法。 然后在按下时调用expand()做扩大的特点动画,再抬起时调用narrow()做缩小的特点动画。

这儿是用了上面的办法3去完结特点动画,这儿离就不多说了,定义了一个变量isExpand来记载当时是否处于扩大的状态。 最终,这个Demo仅仅简略的完结动画的作用,会存在一些问题,比如说在扩大动画的履行过程中抬起,这些都没做处理,时刻问题,就不打算花太多时刻在这个Demo上。

文章出自www.jianshu.com/p/ad80cea0a… 当然也是自己写的,是同一个号,仅仅最近打算转渠道

这是很早之前写的文章,当时仍是有点年轻,技能差点火候。这儿更正一下,onResume那不必延时,用post就行