zoukankan      html  css  js  c++  java
  • 绚丽的loading动效的实现

    最近看到有个gif动画效果挺不错的,可以拿来当项目的LoadingView,所以就花点时间做了下。先来看下效果图:

    分析

    从效果上看,我们可以将其拆分成以下几部分:

    (1)底部框:带有黄色边框的圆角矩形和右边的圆形,为了方便,整个底部框切了,不需要我们去绘制圆角矩形和圆形了;

    (2)进度框:带有进度值和色值的圆角矩形框,特殊的是它的圆角只有左上角和左下角是,另外的两个角是直角;

    (3)风扇:在底部框的圆形位置中,绘制风扇,它可以旋转,直至进度加载完毕;

    (4)叶子:从风扇处飘出,有多个叶子,按照一定的曲线和频率飘荡,遇到了进度框,看起来似乎融入进去了。

    我们需要考虑的有几个问题

    1:叶子是随机产生的;

    2:叶子遇到进度框,像是融进去了,不在显示了;

    3:叶子是随着一条正余弦曲线移动;

    4:叶子飘出的角度是不一样的,而且移动的振幅也不一样,比较有美感。

    这样子,我们需要处理的有以下几部分:

    一是,绘制底部框;

    二是,不断往前绘制的进度条;

    三是,不断旋转的风扇;

    最后,不断飘出的叶子。

    =========================================================

    我们先处理第一部分

    private void drawBackground(Canvas canvas){
            Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.leaf_kuang);
            mPicWidth = bitmap.getWidth();
            mPicHeight = bitmap.getHeight();
            canvas.drawBitmap(bitmap,0,0,mBgPaint);
    
            mTotalProgressWidth = mPicWidth - 76;
        } 

    第二部分

    利用Path的addRoundRect方法绘制可定制圆角的圆角矩形。绘制进度值,它的宽度的计算是进度值/100*总进度的宽度。因为,我们刚刚先绘制了底部框,然后绘制进度框,两者会有相交的地方,遵循上层覆盖下层原则,会出现相交的部分显示在底部框之上,不符合我们的效果。所以给它设置下图片混合效果DST_OVER,就可以了。

    private void drawProgress(Canvas canvas){
            mProgressWidth = mProgress/100 * mTotalProgressWidth;
            RectF rectF = new RectF();
            rectF.left = 16;
            rectF.top = 16;
            rectF.right = mProgressWidth;
            rectF.bottom = mPicHeight-16;
    
            float[] radius = new float[8];
            radius[0] = 40;
            radius[1] = 40;
            radius[2] = 0;
            radius[3] = 0;
            radius[4] = 0;
            radius[5] = 0;
            radius[6] = 40;
            radius[7] = 40;
    
            Path path = new Path();
            path.addRoundRect(rectF,radius, Path.Direction.CW);
            //SRC 上层 DST 下层
            mProgressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));//设置图片混合显示效果
            canvas.drawPath(path,mProgressPaint);
        }

    第三部分

    我们通过Matrix矩阵操作图片,可以让图片旋转,缩放。首先,风扇图片是显示在底部框最右边的圆形位置(称为R位置),所以需要先位移坐标。原来的坐标原点是在(0,0),现在图片是要在R位置旋转,缩放,所以通过setTranslate设置图片的坐标位置,然后在进度未达到100%时,让图片需要不停的旋转;达到95%以上,这时图片会进行缩放;达到100%时,就显示文本。

    private void drawFan(Canvas canvas){
            int centerX = (int) mTotalProgressWidth;
            int centerY = 8;
            if(mProgress == 100){
                String text = "100%";
                canvas.drawText(text,centerX,mPicHeight/2+getTextHeight(text)/2,mFanPaint);
            }else{
                Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.fengshan);
                int bitmapWidth = bitmap.getWidth();
                int bitmapHeight = bitmap.getHeight();
    
                Matrix matrix = new Matrix();
                matrix.setTranslate(centerX, centerY);     //设置图片的原点坐标
                if (this.mProgress >= 95 && this.mProgress < 100){
                    float scale = Math.abs(this.mProgress - 100) * 0.2f;
                    //缩放 参数1:X轴缩放倍数,参数2:Y轴缩放倍数 参数3,4:缩放中心点
                    matrix.preScale(scale,scale,(float)bitmapWidth/2, (float)bitmapHeight/2);
                }else{
                    //旋转 参数1:角度,参数2,3:旋转中心点
                    matrix.preRotate(mAngle, (float)bitmapWidth/2, (float)bitmapHeight/2);
                }
                canvas.drawBitmap(bitmap, matrix, mFanPaint);
                if (this.mProgress != 100){
                    mAngle += 60;
                }
            }
        }

    最后一部分

    首先根据效果情况基本确定出曲线函数,标准函数方程为:y = A(wx+Q)+h,其中w影响周期,A影响振幅 ,周期T= 2 * Math.PI/w;

    根据效果可以看出,周期T大致为总进度长度,所以确定w=(float) ((float) 2 * Math.PI /mTotalProgressWidth);

    由以上的分析可知,叶子是有位置(x,y),有振幅的幅度type(低等幅度,中等幅度,高等幅度),有旋转的角度和方向rotateAngle和rotateDirection,它是随机产生的,有个起始时间startTime。

    叶子Leaf类就此产生:

    private enum StartType {
            LITTLE, MIDDLE, BIG
        }
    
        /**
         * 叶子对象,用来记录叶子主要数据
         *
         */
        private class Leaf {
            // 在绘制部分的位置
            float x, y;
            // 控制叶子飘动的幅度
            StartType type;
            // 旋转角度
            int rotateAngle;
            // 旋转方向--0代表顺时针,1代表逆时针
            int rotateDirection;
            // 起始时间(ms)
            long startTime;
        }
    

    叶子是可以随机产生一个或多个的,所以我们提供了一个LeafFactory类来生产叶子:

    private class LeafFactory {
            private static final int MAX_LEAFS = 8;
            Random random = new Random();
    
            // 生成一个叶子信息
            public Leaf generateLeaf() {
                Leaf leaf = new Leaf();
                int randomType = random.nextInt(3);
                // 随时类型- 随机振幅
                StartType type = StartType.MIDDLE;
                switch (randomType) {
                    case 0:
                        break;
                    case 1:
                        type = StartType.LITTLE;
                        break;
                    case 2:
                        type = StartType.BIG;
                        break;
                    default:
                        break;
                }
                leaf.type = type;
                // 随机起始的旋转角度
                leaf.rotateAngle = random.nextInt(360);
                // 随机旋转方向(顺时针或逆时针)
                leaf.rotateDirection = random.nextInt(2);
                // 为了产生交错的感觉,让开始的时间有一定的随机性
                mLeafFloatTime = mLeafFloatTime <= 0 ? LEAF_FLOAT_TIME : mLeafFloatTime;
                mAddTime += random.nextInt((int) (mLeafFloatTime));
                leaf.startTime = System.currentTimeMillis() + mAddTime;
                return leaf;
            }
    
            // 根据最大叶子数产生叶子信息
            public List<Leaf> generateLeafs() {
                return generateLeafs(MAX_LEAFS);
            }
    
            // 根据传入的叶子数量产生叶子信息
            public List<Leaf> generateLeafs(int leafSize) {
                List<Leaf> leafs = new LinkedList<Leaf>();
                for (int i = 0; i < leafSize; i++) {
                    leafs.add(generateLeaf());
                }
                return leafs;
            }
        }
    

    接下来,我们就可以获取到叶子的Y坐标了:

    // 通过叶子信息获取当前叶子的Y值
        private int getLocationY(Leaf leaf) {
            // y = A(wx+Q)+h
            float w = (float) ((float) 2 * Math.PI / mTotalProgressWidth);
            float a = mMiddleAmplitude;
            switch (leaf.type) {
                case LITTLE:
                    // 小振幅 = 中等振幅 - 振幅差
                    a = mMiddleAmplitude - mAmplitudeDisparity;
                    break;
                case MIDDLE:
                    a = mMiddleAmplitude;
                    break;
                case BIG:
                    // 小振幅 = 中等振幅 + 振幅差
                    a = mMiddleAmplitude + mAmplitudeDisparity;
                    break;
                default:
                    break;
            }
            return (int) (a * Math.sin(w * leaf.x)) + 40 * 2 / 3;//40是圆角半径
        }
    

    接下来,开始绘制叶子:

     /**
         * 绘制叶子
         *
         * @param canvas
         */
        private void drawLeaf(Canvas canvas) {
            mLeafRotateTime = mLeafRotateTime <= 0 ? LEAF_ROTATE_TIME : mLeafRotateTime;
            long currentTime = System.currentTimeMillis();
            for (int i = 0; i < mLeafInfos.size(); i++) {
                Leaf leaf = mLeafInfos.get(i);
                if (currentTime > leaf.startTime && leaf.startTime != 0) {
                    // 绘制叶子--根据叶子的类型和当前时间得出叶子的(x,y)
                    getLeafLocation(leaf, currentTime);
                    // 根据时间计算旋转角度
                    canvas.save();
                    // 通过Matrix控制叶子旋转
                    Matrix matrix = new Matrix();
                    float transX = leaf.x;
                    float transY = leaf.y;
                    Log.e("(x,y)=","("+transX+","+transY+")");
                    if (transX > mProgressWidth) {//叶子遇到进度框,就融入了不再显示
                        matrix.postTranslate(transX, transY);
                        // 通过时间关联旋转角度,则可以直接通过修改LEAF_ROTATE_TIME调节叶子旋转快慢
                        float rotateFraction = ((currentTime - leaf.startTime) % mLeafRotateTime)
                                / (float) mLeafRotateTime;
                        int angle = (int) (rotateFraction * 360);
                        // 根据叶子旋转方向确定叶子旋转角度
                        int rotate = leaf.rotateDirection == 0 ? angle + leaf.rotateAngle : -angle
                                + leaf.rotateAngle;
                        matrix.postRotate(rotate, transX
                                + mLeafWidth / 2, transY + mLeafHeight / 2);
                        canvas.drawBitmap(mLeafBitmap, matrix, mLeafPaint);
                        canvas.restore();
                    }
                } else {
                    continue;
                }
            }
        }
    /**
         * 获取叶子的x,y
         * @param leaf
         * @param currentTime
         */
        private void getLeafLocation(Leaf leaf, long currentTime) {
            long intervalTime = currentTime - leaf.startTime;
            mLeafFloatTime = mLeafFloatTime <= 0 ? LEAF_FLOAT_TIME : mLeafFloatTime;
            if (intervalTime < 0) {
                return;
            } else if (intervalTime > mLeafFloatTime) {
                leaf.startTime = System.currentTimeMillis()
                        + new Random().nextInt((int) mLeafFloatTime);
            }
    
            float fraction = (float) intervalTime / mLeafFloatTime;
            leaf.x = (int) (mTotalProgressWidth - mTotalProgressWidth * fraction);
            leaf.y = getLocationY(leaf);
        }

    最后,向外暴露几个方法:

    /**
         * 设置进度
         * @param progress
         */
        public void setProgress(float progress){
            mProgress = progress;
            invalidate();
        }
    
        /**
         * 进度框颜色
         * @param color
         */
        public void setProgressColor(int color){
            this.mProgressColor = color;
        }
    
        /**
         * 设置中等幅度值
         * @param amplitude
         */
        public void setAmplitude(int amplitude){
            this.mMiddleAmplitude = amplitude;
        }
    
        /**
         * 设置幅度差
         * @param amplitudeDisparity
         */
        public void setAmplitudeDisparity(int amplitudeDisparity){
            this.mAmplitudeDisparity = amplitudeDisparity;
        }
    

    使用方式

    (1)Activity:

    public class MainActivity extends AppCompatActivity {
        private final int REFRESH_PROGRESS = 1000;
        private float mProgress = 0;
        //利用handler实现动画
        private Handler mHandler = new Handler() {
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case REFRESH_PROGRESS:
                        if (mProgress <= 100) {
                            mProgress += 1;
                            // 随机100ms以内刷新一次
                            mHandler.sendEmptyMessageDelayed(REFRESH_PROGRESS,
                                    100);
                            mLoadingView.setProgress(mProgress);
                        }
                        break;
    
                    default:
                        break;
                }
            };
        };
    
        private LoadingLeafView mLoadingView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            findControl();
        }
    
        private void findControl(){
            mLoadingView  = (LoadingLeafView) findViewById(R.id.loading_leaf_view);
    //        mLoadingView.setProgressColor(Color.RED);
    //        mLoadingView.setAmplitude(16);
    //        mLoadingView.setAmplitudeDisparity(10);
            mHandler.sendEmptyMessageDelayed(REFRESH_PROGRESS, 100);
        }
    

      (2)layout:

     <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <com.ha.cjy.myproject.view.widget.LoadingLeafView
                android:id="@+id/loading_leaf_view"
                android:layout_marginTop="48dp"
                android:layout_marginLeft="24dp"
                android:layout_width="302dp"
                android:layout_height="61dp"/>
        </LinearLayout>

    Demo下载地址:https://github.com/hacjy/LeafLoadingView

  • 相关阅读:
    P1642 规划 [01分数规划]
    01分数规划学习笔记
    P1527 [国家集训队]矩阵乘法 [整体二分]
    P3292 [SCOI2016]幸运数字 [线性基+倍增]
    java中遍历集合的三种方式
    20190706中兴提前批专业面面经
    《java入门如此简单》——语句,函数和数组
    java中数组常见的操作
    2019 波克城市ava面试笔试题 (含面试题解析)
    2019 华云数据java面试笔试题 (含面试题解析)
  • 原文地址:https://www.cnblogs.com/hacjy/p/7412101.html
Copyright © 2011-2022 走看看