▲ Android 58同城的加载动画效果

今天要实现的效果是58同城的加载动画效果,还是主要以学习为主。

实现的效果

代码实现

(1)效果分析

这个效果是由三部分构成的,一个可变换形状的View ,一个底部阴影的View, 还一个TextView 组成的这个加载loadingView。 然后可变换形状的View 从上移动到下面(需要使用一个移动动画),动画执行完毕需要移动回原来的位置。
一个底部阴影的View在可变换形状的View移动的时候执行(缩小/放大的动画) 还有就是上抛的是时候可变换形状的View需要执行一个(可旋转的动画) 大体思路就是这样。

(2)自定义可变换形状的View ShapeView

ShapeView的测量方法

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //保证是正方形
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(Math.min(width,height),Math.min(width,height));
    }
	

接下来就是 onDraw方法了,这里值得说一下就是等边三角形的高的计算,思路就是等边三角形 勾股定理计算 a^2 + b^2 = c^2


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        switch (mCurrentShape) {
            case Circle:
			   
			   //圆形
                int center = getWidth()/2;
                mPaint.setColor(getResources().getColor(R.color.circle));
                canvas.drawCircle(center,center,center,mPaint);
                break;
            case Square:
			   
			   //正方形
                mPaint.setColor(getResources().getColor(R.color.rect));
                canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
                break;
            case Triangle:
               
			   // 三角形 Path 路径
                mPaint.setColor(getResources().getColor(R.color.triangle));
                if (mPath==null){
                    mPath = new Path();
                    mPath.moveTo(getWidth()/2,0);  //三角形的顶点位置
                    mPath.lineTo(0,(float)((getHeight()/2)*Math.sqrt(3)));  //左边位置 勾股定理计算
                    mPath.lineTo(getWidth(),(float)((getHeight()/2)*Math.sqrt(3)));   //右边位置
                    mPath.close();
                }

                canvas.drawPath(mPath,mPaint);
                break;
        }
    }

最后附上ShapeView的源码:

   public class ShapeView extends View {

    private Shape mCurrentShape = Shape.Circle;
    private Paint mPaint;
    private Path mPath;


    public ShapeView(Context context) {
        this(context, null);
    }

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

    public ShapeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //保证是正方形
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(Math.min(width,height),Math.min(width,height));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        switch (mCurrentShape) {
            case Circle:
                int center = getWidth()/2;
                mPaint.setColor(getResources().getColor(R.color.circle));
                canvas.drawCircle(center,center,center,mPaint);
                break;
            case Square:
                mPaint.setColor(getResources().getColor(R.color.rect));
                canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
                break;
            case Triangle:
                // 三角形 Path 路径
                mPaint.setColor(getResources().getColor(R.color.triangle));
                if (mPath==null){
                    mPath = new Path();
                    mPath.moveTo(getWidth()/2,0);
                    mPath.lineTo(0,(float)((getHeight()/2)*Math.sqrt(3)));
                    mPath.lineTo(getWidth(),(float)((getHeight()/2)*Math.sqrt(3)));
                    mPath.close();
                }

                canvas.drawPath(mPath,mPaint);
                break;
        }
    }


    // 改变形状
    public void exchange(){
        switch (mCurrentShape){
            case Circle:
                mCurrentShape = Shape.Square;
                break;
            case Square:
                mCurrentShape = Shape.Triangle;
                break;
            case Triangle:
                mCurrentShape = Shape.Circle;
                break;
        }
        invalidate();
    }

    public enum Shape {
        Circle, Square, Triangle
    }

    public Shape getmCurrentShape(){
        return mCurrentShape;
    }
}

(3)自定义LoadingView布局

这个自定义布局是为了将 ShapeView + 底部阴影的View, 还一个TextView 合并在一起

关于mShadowView的背景代码

 <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
    <solid android:color="#333"></solid>
 </shape>

LoadingView布局代码如下

   public class LoadingView extends LinearLayout {
    //形状
    private ShapeView mShapeView;
    //阴影
    private View mShadowView;

    private int mTranslationDistance = 0;


    public LoadingView(Context context) {
        this(context, null);
    }

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

    public LoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mTranslationDistance = dip2px(80);
        initLayout();
    }

    //像素转 dp
    private int dip2px(int dip) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
    }


    /**
     * 初始化加载
     */
    private void initLayout() {
        //加载ui_loading_view
        inflate(getContext(), R.layout.ui_loading_view, this);
        mShapeView = findViewById(R.id.shape_view);
        mShadowView = findViewById(R.id.shadow_view);

        startFallAnimator();
    }

    /**
     * 开始下落动画
     */
    private void startFallAnimator() {
        ObjectAnimator translation = ObjectAnimator.ofFloat(mShapeView, "translationY", 0, mTranslationDistance);
        //配合中间动画缩小
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(mShadowView, "ScaleX", 1f, 0.3f);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(translation, scaleX);
        animatorSet.setDuration(400);
        //下落越来越快的差值器
        animatorSet.setInterpolator(new AccelerateInterpolator());
        animatorSet.start();

        //监听动画结束
        animatorSet.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                startUpAnimator();
                //改变形状
                mShapeView.exchange();

            }
        });
    }


    /**
     * 执行上抛
     */
    private void startUpAnimator() {

        ObjectAnimator translation = ObjectAnimator.ofFloat(mShapeView, "translationY", mTranslationDistance,0 );

        //配合中间动画缩小
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(mShadowView, "ScaleX", 0.3f, 1f);

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(translation, scaleX);
        animatorSet.setDuration(400);
        animatorSet.setInterpolator(new DecelerateInterpolator());


        //监听动画结束
        animatorSet.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {

                startFallAnimator();
            }

            @Override
            public void onAnimationStart(Animator animation) {
               //开始旋转
                startRotationAnimation();

            }
        });

        animatorSet.start();
    }


    /**
     * 上抛的时候需要旋转
     */
    private void startRotationAnimation() {
        ObjectAnimator rotation =null;
        switch (mShapeView.getmCurrentShape()){
              case Circle:
              case Square:
                  rotation = ObjectAnimator.ofFloat(mShapeView, "Rotation",0,180);
                  break;
              case Triangle:
                  rotation = ObjectAnimator.ofFloat(mShapeView, "Rotation",0,-120);
                  break;
          }

        rotation.setDuration(400);
        rotation.setInterpolator(new DecelerateInterpolator());
        rotation.start();
    }

    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(View.INVISIBLE);  //不要再去排放/计算少走一些系统源码
        //清除动画
        mShapeView.clearAnimation();
        mShadowView.clearAnimation();

        //把loadingView从父布局中移除掉

        ViewGroup parent = (ViewGroup) getParent();

        if (parent!=null){
            parent.removeView(this);
            removeAllViews();
        }

    }
}

源码地址

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值