基于自定义ViewGroup实现有趣的加载中动效

本文目的

平时项目中看到的加载中的动效基本上就是转圈的形式,有点审美疲劳。网上看到一个有趣的动效,参照着通过ViewGroup做了一个简单的实现。主要知识点基于ViewGroup实现自定义组合视图

效果介绍

话不多说,直接上视频。实现了一个物体上升/下落的动效,并且可以自动切换形状和颜色。颜色好好配一下效果预估更佳
在这里插入图片描述

代码工程结构

本次实现动效因为引用了res资源,单独设置了一个名称为loading的module存放相应的代码和资源,然后在app主Module中使用
在这里插入图片描述

绘制形状的自定义View代码

绘制形状的代码主要逻辑包括测量尺寸,根据形状类型决定绘制三角形/正方形/圆形3种形状。内部设置一个供外部调用的更换形状的接口,整体逻辑较为简单,如下所示

/**
 * 基于画图实现形状绘制
 */
public class ShapeView extends View {
    private int width , height;
    private int left , right , top ,bottom;
    private Paint shapePaint;
    //当前形状在全部形状中的序号
    private int index = 0;
    private Shape mShape = Shape.values()[index];
    //可以变化的颜色组合,长度需要与Shape长度相同
    private int[] colors = {R.color.purple_200 , R.color.purple_500 , R.color.purple_700};

    public ShapeView(Context context) {
        super(context);
        init();
    }

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

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

    public void setShape(Shape mShape) {
        this.mShape = mShape;
    }

    /**
     * 设置形状和颜色顺序切换
     */
    public void changeShape(){
        index++;
        index%=Shape.values().length;
        this.mShape = Shape.values()[index];
        shapePaint.setColor(getResources().getColor(colors[index],null));
        invalidate();
    }

    //测量应当绘制的形状的宽度,高度
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
        left = getPaddingLeft();
        right = left + width;
        top = getPaddingTop();
        bottom = top + height;
        Log.d("ShapeView" , "width = "+width+",height = "
                +height+",left = "+left+",right = "+right+",top = "+top+",bottom = "+bottom);
    }

    //绘制形状
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        switch (mShape){
            case CIRCLE:
                drawCircle(canvas);
                break;
            case TRIANGLE:
                drawTriangle(canvas);
                break;
            case RECTANGLE:
            default:
                drawRectangle(canvas);
                break;
        }
    }

    //绘制三角形
    private void drawTriangle(Canvas canvas){
        Path path = new Path();
        int topPoint = top + height - (int)(height*Math.sqrt(3)/2);
        path.moveTo(left/2+right/2 , topPoint);
        path.lineTo(right , bottom);
        path.lineTo(left , bottom);
        path.lineTo(left/2+right/2 , topPoint);
        path.close();
        canvas.drawPath(path , shapePaint);
    }

    //绘制正方形
    private void drawRectangle(Canvas canvas){
        int rectWidth = Math.min(width , height) - 20;
        int rectLeft = left/2 + right/2 - rectWidth/2;
        int rectRight = left/2 + right/2 + rectWidth/2;
        int rectTop = top/2 + bottom/2 - rectWidth/2;
        int rectBottom = top/2 + bottom/2 + rectWidth/2;
        RectF rect = new RectF(rectLeft , rectTop ,  rectRight , rectBottom);
        canvas.drawRoundRect(rect,5,5,shapePaint);
    }
    //绘制圆形
    private void drawCircle(Canvas canvas){
        canvas.drawCircle(left/2+right/2 , top/2+bottom/2 ,
                Math.min(width,height)/2 - 5 , shapePaint);
    }

    //初始化画笔
    private void init(){
        shapePaint = new Paint();
        shapePaint.setAntiAlias(true);
        shapePaint.setColor(getResources().getColor(colors[index],null));
        shapePaint.setStyle(Paint.Style.FILL_AND_STROKE);
    }

    //绘制的形状组合
    public enum Shape{
        CIRCLE,
        TRIANGLE,
        RECTANGLE
    }
}

自定义ViewGroup实现动效

整体的动效由提示文字,地面的倒影,上升/下降的形状物体3个部分构成,地面倒影是通过设置椭圆形状进行横向缩放实现,上升/下降的形状物体则是基于自定义View的物体增加上升和下降的动效。详细代码如下

/**
 * 加载中动效布局
 */
public class LoadingView extends LinearLayout {
    //运动小球
    private ShapeView shapeView;
    //地面倒影
    private ImageView imageViewReflection;
    //移动距离
    private int shiftDistance = 0;
    private AnimatorSet upAnimatorSet , downAnimatorSet;

    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);
    }

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

    private void init(Context context , AttributeSet attrs){
        setOrientation(VERTICAL);
        LayoutInflater.from(context).inflate(R.layout.layout_loading_view,this , true);
        shapeView = findViewById(R.id.shape_view);
        imageViewReflection = findViewById(R.id.imageview_reflect);
        shiftDistance = dp2px(60);
    }

    /**
     * 在视图加载完毕时开始执行动效
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if(getVisibility() == VISIBLE){
            startAnim();
        }
    }

    /**
     * 视图被移除时停止动效
     */
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        stopAnim();
    }

    private void startAnim(){
        toBottom();
    }

    private void stopAnim(){
        if(null != upAnimatorSet && upAnimatorSet.isRunning()){
            upAnimatorSet.cancel();
            upAnimatorSet.removeAllListeners();
        }
        if(null != downAnimatorSet && downAnimatorSet.isRunning()){
            downAnimatorSet.cancel();
            downAnimatorSet.removeAllListeners();
        }
    }

    /**
     * /减速上升
     */
    private void toTop(){
        shapeView.changeShape();
        ObjectAnimator upShiftAnimator = ObjectAnimator.ofFloat(shapeView,"translationY",
                shiftDistance,0);
        ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(imageViewReflection,"scaleX",1f,0.2f);
        ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(shapeView , "rotation",0,360);
        upAnimatorSet = new AnimatorSet();
        upAnimatorSet.playTogether(upShiftAnimator , scaleAnimator , rotateAnimator);
        upAnimatorSet.setDuration(1000);
        upAnimatorSet.setInterpolator(new DecelerateInterpolator());
        upAnimatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                toBottom();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        upAnimatorSet.start();
    }


    /**
     * 加速下降
     */
    private void toBottom(){
        ObjectAnimator bottomShiftAnimator = ObjectAnimator.ofFloat(shapeView,"translationY",
                0,shiftDistance);
        ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(imageViewReflection,"scaleX",0.2f,1f);
        ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(shapeView , "rotation",0,360);
        downAnimatorSet = new AnimatorSet();
        downAnimatorSet.playTogether(bottomShiftAnimator , scaleAnimator , rotateAnimator);
        downAnimatorSet.setDuration(1000);
        downAnimatorSet.setInterpolator(new AccelerateInterpolator());
        downAnimatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                toTop();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        downAnimatorSet.start();
    }

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

动效使用

在主Module的build.gradle文件中dependencies添加依赖

//引用加载中动效的module
implementation project(path:':loading')

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

<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"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

    <!--引入加载中动效-->
    <com.guo.loading.LoadingView
        android:layout_width="200dp"
        android:layout_height="200dp"/>

</LinearLayout>

源码位置

最后要感谢CSDN提供的设计效果和实现参照,只是我暂时没有找到链接注明,代码已共享之Gitee

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值