利用Android属性动画实现有趣的加载中动效

本文目的

平时Android项目中看到的加载中的动效基本上就是转圈的形式,有点审美疲劳。前一篇文章通过ViewGroup做了一个简单的加载中的动效,上一篇文章的主要知识点基于ViewGroup实现自定义组合视图。本文仍然是基于ViewGroup实现自定义组合视图,将重点展示如何充分利用Android提供的属性动效实现一个有趣的加载中动效。文中较多的使用了位移动效,透明度动效。

效果介绍

实现了一个太阳升级/落下,月亮升起落下/星星眨眼的动效,组合起来之后可以作为加载中动效
在这里插入图片描述

自定义View代码

本次基于自定义视图绘制的形状包括太阳/月亮/星星三类自然景物。首先太阳绘制的基本思路是画完圆形之后,绘制横线并且进行画布的旋转,即可实现太阳光线。关键代码如下:

/**
 * 绘制太阳
 * @param canvas
 */
private void drawSun(Canvas canvas){
    //将画布平移至中心点
    canvas.translate(width/2+getPaddingLeft(),height/2+getPaddingTop());
    //画圆
    canvas.drawCircle(0,0,sunRadius,sunPaint);
    //画光线
    drawSunshine(canvas);
}
……
private void drawSunshine(Canvas canvas){
    /**
     * 逐个绘制光线,绘制10条线
     */
    if(index_draw_sunshine < 10 && index_draw_sunshine >= 0){
        for(int i=0 ;i<=index_draw_sunshine;i++){
            canvas.drawLine(sunRadius+10, 0, sunRadius+30,0,sunshinePaint);
            canvas.rotate(-360/MAX_COUNT);
        }
        index_draw_sunshine++;
        postInvalidateDelayed(20);
    }else{
        for(int i=0 ;i<index_draw_sunshine;i++){
            canvas.drawLine(sunRadius+10, 0, sunRadius+30,0,sunshinePaint);
            canvas.rotate(-360/MAX_COUNT);
        }
    }
}

绘制月亮的基本方法是通过绘制两端圆弧连接实现,过程中需要平移画布的位置实现,过程代码如下:

//绘制月亮
private void drawMoon(Canvas canvas){
    canvas.translate(width/2+getPaddingLeft(),height/2+getPaddingTop());
    //绘制大圆弧
    RectF oval = new RectF(-sunRadius , -sunRadius , sunRadius ,sunRadius);
    canvas.drawArc(oval , -120 , 240 , false , sunPaint);
    //绘制小圆弧
    RectF oval1 = new RectF(-2*sunRadius , -sunRadius , 0 ,sunRadius);
    canvas.drawArc(oval1 , -60 , 120 ,false ,sunPaint);
}

绘制星星的基本思路是先计算得到星星绘制所需要的10个点的横纵坐标,然后基于Path连接这些坐标点即可,代码如下

/**
 * 自定义视图实现星星
 */
public class StarView extends View {
    /**
     * 绘制星星所需的内径和外径
     */
    private int outRadius , innerRadius;
    private Paint mPaint;
    private int[][] points = new int[10][2];
    private static final int NUM_POINTS = 10;
    /**
     * 视图的宽度和高度
     */
    private int width,height;
    /**
     * 星星在父类视图的左上坐标,半径
     */
    private int x , y;
    private int size;
    public StarView(Context context) {
        super(context);
        initPaint();
    }

    public StarView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public StarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    private void initPaint(){
        if(null == mPaint){
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(3);
            mPaint.setColor(getResources().getColor(R.color.purple_200));
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
        outRadius = Math.min(width,height)/4;
        innerRadius = outRadius/2;
        getPositions();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawStar(canvas);
    }

    /**
     * 使用Path绘制星星
     * @param canvas
     */
    private void drawStar(Canvas canvas){
        Path path = new Path();
        for(int i = 0 ; i < NUM_POINTS ; i++){
            if(i == 0){
                path.moveTo(points[i][0] , points[i][1]);
            }else{
                path.lineTo(points[i][0] , points[i][1]);
            }
        }
        path.close();
        canvas.drawPath(path,mPaint);
    }

    /**
     * 计算星星的顶点坐标
     * @return
     */
    private int[][] getPositions(){
        int center_x = width/2 + getPaddingLeft();
        int center_y = height/2 + getPaddingTop();
        for(int i = 0 ; i < NUM_POINTS ; i++){
            int r = (i%2 == 0 ? outRadius : innerRadius);
            double angle = -Math.PI / 10 + i*2*Math.PI / 10 ;
            //横坐标
            points[i][0] = (int)(r*Math.cos(angle)) + center_x;
            //纵坐标
            points[i][1] = (int)(r*Math.sin(angle)) + center_y;
        }
        return points;
    }

    public int getPositionX() {
        return x;
    }

    public void setPositionX(int x) {
        this.x = x;
    }

    public int getPositionY() {
        return y;
    }

    public void setPositionY(int y) {
        this.y = y;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }
}

实现动效的代码

本次加载中动效分为4个步骤实现:1.太阳落下{@link #sunSet()};2.月亮升起并且星星显示{@link #moonRise()};月亮和星星收起{@link #moonStarVanish()};4.太阳升起{@link #sunRise()}。过程代码如下:

/**
 * 本次加载中动效分为4个步骤实现:1.太阳落下{@link #sunSet()};2.月亮升起并且星星显示{@link #moonRise()};
 * 3.月亮和星星收起{@link #moonStarVanish()};4.太阳升起{@link #sunRise()}
 */
public class LoadingView extends FrameLayout {
    private SunMoonView sunMoonView;
    /**
     * 全部的星星将通过addView添加,此处缓存添加的星星对象,用于对星星设置动效
     */
    private List<StarView> starViews = new ArrayList<>();
    /**
     * 整体布局宽度,高度
     */
    private int width , height;
    /**
     * 太阳的视图半径,星星最大半径
     */
    private int sunRadius , starMaxRadius;
    /**
     * 太阳和月亮落下,升起时的位移距离
     */
    private int shiftDistance;
    /**
     * 上升和下降的组合动效
     */
    private AnimatorSet upAnimatorSet,downAnimatorSet;
    private static final String TAG = "LoadingView";

    public LoadingView(Context context) {
        super(context);

    }

    public LoadingView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public LoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context , AttributeSet attrs){
        LayoutInflater.from(context).inflate(R.layout.layout_loading_view,this , true);
        sunMoonView = findViewById(R.id.sun_moon_view);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
        sunRadius = Math.min(width , height) / 5;
        starMaxRadius = sunRadius / 2;
        shiftDistance = height/2 + sunRadius;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        removeAllViews();
        post(new Runnable() {
            @Override
            public void run() {
                layoutSunMoon();
                sunSet();
            }
        });
    }

    /**
     * 设置太阳月亮初始位置
     */
    private void layoutSunMoon(){
        ViewGroup.MarginLayoutParams layoutParams  = (ViewGroup.MarginLayoutParams)sunMoonView.getLayoutParams();
        layoutParams.width = 2*sunRadius;
        layoutParams.height = layoutParams.width;
        layoutParams.leftMargin = width/2 - sunRadius;
        layoutParams.topMargin = height/2 - sunRadius;
        sunMoonView.setLayoutParams(layoutParams);
        addView(sunMoonView);
    }

    /**
     * 随机设置星星的位置
     */
    private void layoutStar(){
        Random random = new Random();
        int maxNum = height*2/5/2/starMaxRadius * width/2/starMaxRadius;
        int setNum = maxNum /2;
        //可以布置的行数
        int rows = height*2/5/2/starMaxRadius;
        //可以布置的列数
        int lines = width/2/starMaxRadius;
        starViews.clear();
        Log.d(TAG,"starMaxRadius = "+starMaxRadius+",sunRadius = "+sunRadius+",rows = "+rows + ",setNum = "+setNum);
        if(setNum > 0 ){
            for(int i = 0 ; i < setNum ; i++){
                StarView starView = new StarView(getContext());
                int x = i%lines*2*starMaxRadius + random.nextInt(starMaxRadius);
                int y = i/lines*2*starMaxRadius + random.nextInt(starMaxRadius);
                int size = starMaxRadius/2 + random.nextInt(starMaxRadius/2);
                starView.setPositionX(x);
                starView.setPositionY(y);
                starView.setSize(size);
                ViewGroup.MarginLayoutParams layoutParams = new ViewGroup.MarginLayoutParams(size*2,size*2);
                layoutParams.leftMargin = x;
                layoutParams.topMargin = y;
                starView.setLayoutParams(layoutParams);
                addView(starView);
                starViews.add(starView);
            }
        }
    }

    /**
     * 将dp值转为像素
     * @param dp 输入dp类型尺寸
     * @return
     */
    private int dp2px(int dp){
        float dpi = getContext().getResources().getDisplayMetrics().density;
        return (int)(dp*dpi + 0.5f);
    }

    /**
     * 1.日落
     */
    private void sunSet(){
        ObjectAnimator bottomShiftAnimator = ObjectAnimator.ofFloat(sunMoonView,"translationY",
                0,shiftDistance);
        ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(sunMoonView , "rotation",0,360);
        downAnimatorSet = new AnimatorSet();
        downAnimatorSet.playTogether(bottomShiftAnimator , rotateAnimator);
        downAnimatorSet.setDuration(3000);
        downAnimatorSet.setInterpolator(new DecelerateInterpolator());
        downAnimatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        moonRise();
                    }
                } , 1000);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        downAnimatorSet.start();
    }


    /**
     * 2.月出:月亮升起,星星出现和闪烁
     */
    private void moonRise(){
        layoutStar();
        sunMoonView.changeShape();
        ObjectAnimator upShiftAnimator = ObjectAnimator.ofFloat(sunMoonView,"translationY",
                shiftDistance,0);
        ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(sunMoonView , "rotation",-20,20);
        rotateAnimator.setRepeatCount(2);
        rotateAnimator.setRepeatMode(ObjectAnimator.REVERSE);
        upAnimatorSet = new AnimatorSet();
        upAnimatorSet.playTogether(upShiftAnimator , rotateAnimator);
        upAnimatorSet.setDuration(3000);
        upAnimatorSet.setInterpolator(new AccelerateInterpolator());
        upAnimatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        starShink();
                    }
                } , 1000);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        upAnimatorSet.start();
        starShow();
    }

    /**
     * 2.2设置星星闪烁
     */
    private void starShink(){
        for(int i = 0 ; i < starViews.size() ; i++){
            if(i % 2 == 0){
                ObjectAnimator alpha = ObjectAnimator.ofFloat(starViews.get(i),"alpha",
                        1.0f,0f);
                alpha.setDuration(1000);
                alpha.setRepeatCount(2);
                alpha.setRepeatMode(ValueAnimator.REVERSE);
                alpha.start();
                if((i == (starViews.size()-1)) | (i == (starViews.size()-1))){
                    alpha.addListener(new Animator.AnimatorListener() {
                        @Override
                        public void onAnimationStart(Animator animation) {

                        }

                        @Override
                        public void onAnimationEnd(Animator animation) {
                            postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    moonStarVanish();
                                }
                            } , 1000);
                        }

                        @Override
                        public void onAnimationCancel(Animator animation) {

                        }

                        @Override
                        public void onAnimationRepeat(Animator animation) {

                        }
                    });
                }
            }
        }
    }

    /**
     * 2.1设置星星显示
     */
    private void starShow(){
        for(int i = 0 ; i < starViews.size() ; i++){
            ObjectAnimator bottomShiftAnimator = ObjectAnimator.ofFloat(starViews.get(i),"translationY",
                    -3*starViews.get(i).getSize(),starViews.get(i).getPositionY());
            bottomShiftAnimator.setDuration(3000);
            bottomShiftAnimator.start();
        }

    }

    /**
     * 3.1设置星星隐藏
     */
    private void starHide(){
        for(int i = 0 ; i < starViews.size() ; i++){
            ObjectAnimator upShiftAnimator = ObjectAnimator.ofFloat(starViews.get(i),"translationY",
                    starViews.get(i).getPositionY(),-3*starViews.get(i).getSize());
            upShiftAnimator.setDuration(3000);
            upShiftAnimator.start();
        }
    }

    /**
     * 3.月亮落下,星星收起
     */
    private void moonStarVanish(){
        ObjectAnimator bottomShiftAnimator = ObjectAnimator.ofFloat(sunMoonView,"translationY",
                0,shiftDistance);
        ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(sunMoonView , "rotation",-20,20);
        rotateAnimator.setRepeatCount(1);
        rotateAnimator.setRepeatMode(ObjectAnimator.REVERSE);
        downAnimatorSet = new AnimatorSet();
        downAnimatorSet.playTogether(bottomShiftAnimator , rotateAnimator);
        downAnimatorSet.setDuration(3000);
        downAnimatorSet.setInterpolator(new DecelerateInterpolator());
        downAnimatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        sunRise();
                    }
                } , 1000);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        downAnimatorSet.start();
        starHide();
    }

    /**
     * 4.太阳升起
     * 结束后重新执行onFinishInflated()方法
     */
    private void sunRise(){
        sunMoonView.changeShape();
        ObjectAnimator upShiftAnimator = ObjectAnimator.ofFloat(sunMoonView,"translationY",
                shiftDistance,0);
        upShiftAnimator.setDuration(3000);
        upShiftAnimator.setInterpolator(new AccelerateInterpolator());
        upShiftAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //重写执行整个动效过程
                        onFinishInflate();
                    }
                } , 1000);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        upShiftAnimator.start();
    }
}

动效使用

在需要引用的布局文件中引用相应的类,如下示例

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:gravity="center"
    tools:context=".MainActivity">

    <!--引用的加载中动效-->
    <com.guo.loadinganim.LoadingView
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@color/gray"/>

</LinearLayout>

源码位置

https://gitee.com/com_mailanglidegezhe/loading-view2.git
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值