android多控件切换效果

控件上传到了github,https://github.com/jw20082009/piececode,主要实现的是一个继承自FrameLayout的容器控件,比较好的实现了FrameLayout的第一个子控件和最后一个子控件之前相互切换的动画(中心缩放效果),对于子控件没用任何侵入性,简单包裹即可,使用起来相当简单。可以比较方便的用于消息通知的展开和收拢以及类似效果。

先上一个效果图

android多控件切换效果_第1张图片

原理

原理并不复杂,主要是定义了一个动画开始的控件,一个动画结束的控件,拿到两个控件的占地面积,每次都使用占地面积大的控件的背景来做动画(因为小控件的切换细节不会被肉眼察觉,而大控件很容易被看出来),拿到背景后,就把这个背景在我们的当前控件(也就是起始控件和结束控件的父控件)的canvas上画图,动画一开始便把开始控件隐藏,动画结束后再把结束控件显示,从而形成了这一连串的效果。

代码

先看看调用有多方便

在xml中可以直接把他当成FrameLayout使用,子控件数量尽量为2个(一个开始控件,一个结束控件),且都有设置background。



        

        

            

            

            

            

            

            

            

            

            

            

            

        

    


切换的api主要有两个:scale01()和scale10,从命名上可以看出scale01是从第一个子控件向最后一个子控件做动画,scale10是从最后一个子控件向第一个子控件做动画。

boolean isExpand = false;

    public void expand(View view) {
        if (!isExpand) {
            transitionView.scale01();
            isExpand = true;
        }
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (isExpand) {
                    transitionView.scale10();
                    isExpand = false;
                }
                break;
        }
        return false;
    }

因为我的第一个子控件为通知收拢后的控件,第二个为展开的控件,所以expand方法中调用的是scale01(),并且为父控件设置了onTouchListener,且在其down事件中判断判断如果已展开后,就执行scale10()方法来收拢控件。之所以没有把这个touch事件也直接放在控件中,因为他实在是业务属性有点强,可能有人并不需要这么收拢。

控件代码(干货)

/**
 * 封装了第一个子控件和最后一个子空间之间的切换动画 created by jw200 at 2018/6/2 18:51
 **/
public class TransitionView extends FrameLayout {
    View child0, child1, viewToDraw;
    ScaleEntity mAnimEntity;
    boolean isScaling = false;
    private final int ANIMA_DURATION = 300;
    private boolean needAnimate = true;

    public TransitionView(@NonNull Context context) {
        this(context, null);
    }

    public TransitionView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TransitionView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public TransitionView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        setWillNotDraw(false);
    }

    public void scale(final View startView, final View endView) {
        if (needAnimate) {
            TransitionView.this.isScaling = true;
            startView.setVisibility(VISIBLE);
            startView.setAlpha(1.0f);
            endView.setVisibility(VISIBLE);
            endView.setAlpha(0f);

            int widthStart = startView.getWidth();
            int heightStart = startView.getHeight();
            int widthEnd = endView.getWidth();
            int heightEnd = endView.getHeight();

            ScaleEntity start = new ScaleEntity();
            if (widthStart * heightStart < widthEnd * heightEnd) {
                viewToDraw = endView;
            } else {
                viewToDraw = startView;
            }
            start.sx = viewToDraw == startView ? 1.0f : (1.0f * widthStart / widthEnd);
            start.sy = viewToDraw == startView ? 1.0f : (1.0f * heightStart / heightEnd);

            //calculate px
            float scale = viewToDraw == startView ? (1.0f * widthStart / widthEnd) : (1.0f * widthEnd / widthStart);
            int offset = viewToDraw == startView ? (startView.getLeft() - endView.getLeft()) : (endView.getLeft() - startView.getLeft());
            start.px = scale * offset / (1 - scale);

            //calculate py
            float scaley = viewToDraw == startView ? (1.0f * heightStart / heightEnd) : (1.0f * heightEnd / heightStart);
            int offsety = viewToDraw == startView ? (startView.getTop() - endView.getTop()) : (endView.getTop() - startView.getTop());
            start.py = scaley * offsety / (1 - scaley);

            ScaleEntity end = new ScaleEntity();
            end.sx = viewToDraw == startView ? (1.0f * widthEnd / widthStart) : 1.0f;
            end.sy = viewToDraw == startView ? (1.0f * heightEnd / heightStart) : 1.0f;
            end.px = start.px;
            end.py = start.py;

            ValueAnimator scaleAnimator = ValueAnimator.ofObject(new ScaleEvaluator(), start, end);
            scaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mAnimEntity = (ScaleEntity) animation.getAnimatedValue();
                    invalidate();
                }
            });

            scaleAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                    startView.setVisibility(GONE);
                    startView.setAlpha(0f);
                    isScaling = true;
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    endView.setVisibility(VISIBLE);
                    endView.setAlpha(1.0f);
                    isScaling = false;
                    invalidate();//clear screen
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                    endView.setVisibility(VISIBLE);
                    endView.setAlpha(1.0f);
                    isScaling = false;
                    invalidate();//clear screen
                }

                @Override
                public void onAnimationRepeat(Animator animation) {
                }
            });
            scaleAnimator.setDuration(ANIMA_DURATION);
            scaleAnimator.start();
        } else {
            endView.setAlpha(1.0f);
            endView.setVisibility(VISIBLE);
            startView.setAlpha(0f);
            startView.setVisibility(GONE);
            TransitionView.this.isScaling = false;
        }
    }

    public void scale01() {
        scale(child0, child1);
    }

    public void scale10() {
        scale(child1, child0);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (getChildCount() > 1 && (child0 == null || child1 == null)) {
            child0 = getChildAt(0);
            child1 = getChildAt(getChildCount() - 1);
        }
    }

    class ScaleEvaluator implements TypeEvaluator {

        @Override
        public ScaleEntity evaluate(float fraction, ScaleEntity startValue, ScaleEntity endValue) {
            ScaleEntity animEntity = new ScaleEntity();
            animEntity.sx = startValue.sx + fraction * (endValue.sx - startValue.sx);
            animEntity.sy = startValue.sy + fraction * (endValue.sy - startValue.sy);
            animEntity.px = startValue.px + fraction * (endValue.px - startValue.px);
            animEntity.py = startValue.py + fraction * (endValue.py - startValue.py);
            return animEntity;
        }
    }

    class ScaleEntity {
        float px, py, sx, sy;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mAnimEntity != null && isScaling && viewToDraw != null) {
            canvas.save();
            canvas.translate(viewToDraw.getLeft(), viewToDraw.getTop());
            canvas.scale(mAnimEntity.sx, mAnimEntity.sy, mAnimEntity.px, mAnimEntity.py);
            viewToDraw.getBackground().draw(canvas);
            canvas.restore();
        } else {
            super.onDraw(canvas);
        }
    }
}

代码相当简洁易懂,就不过多解释,有问题的可以在github,或者博客下留言

如需项目代码,可以移步github,方便的大佬可以顺手给个star。转场动画,切换动画,android切换效果,android控件切换

你可能感兴趣的:(android笔记)