一个漂亮的安卓动画控件(更新)

背景

用过很多APP了吧,很多一开始启动的时候总是显示一页广告,停着好几秒@( ̄- ̄)@……然后想关的话,还得手动地去向右上角那小小的、一点点的关闭按钮,只要稍微点错一点点就进入了广告,简直ZZ(╯‵□′)╯︵┻━┻。

这样的情景,真的是烦@( ̄- ̄)@哎……真不知道产品经理是怎么样的,比如像我这样的人,一想到打开一个APP就要看好几秒广告,就不想打开这个APP了@( ̄- ̄)@。所以这样的设计真的是反效果。

做不能一个漂亮的启动界面吗?这样用户看了之后觉得赏心悦目,打开APP的主动性也会高很多吧。
刚好最近在和导师撕逼ᐠ( ᐢ ᵕ ᐢ )ᐟ (用力怼,出奇迹~),闲得无聊地瞎搞APP,便搞出了一个控件,总之先放个GIF图看看效果~

这个效果调了好久,同事看了之后都觉得不错喵的说~
而且可以通过jitpack可以很方便地在Android Studio里添加依赖喵的说~
(迷之音:“不来一发star吗少年”,并且向你扔出了一个地址:https://github.com/dotomato/FringeLaunchView

代码/原理

本质上是一个完全自定义的View,所有效果在onDraw()里画出来。
首先定义一个Fringe类,用来帮助我们画单个的条纹。

  class Fringe {

            ...

            private float randF(){
                return (float) Math.random();
            }

            //初始化的时候要输入这个条纹的开始的横坐标、结束的横坐标,纵坐标,第几个条纹(虽然现在并没什么卵用),第几组条纹
            Fringe(int x1, int x2, int y, int fringeIndex, int groupIndex){
                this.x1 = x1;
                this.x2 = (int) (x2+ randF()*150);
                this.y = y;
                h = (int) (fringeHeight*(Math.random()*0.5+0.5));
                if (groupIndex==1){
                    color = HSBtoRGB(mainH1+(randF()-0.5f)*0.1f, 0.3f+ randF()*0.2f, 0.8f+ randF()*0.2f);
                }
                else if (groupIndex==2) {
                    color = HSBtoRGB(mainH2+(randF()-0.5f)*0.1f, 0.7f + randF() * 0.2f, 0.8f + randF() * 0.2f);
                }
                t1 = -Math.random()*0.3;
                ts = 0.8f+Math.random()*0.2f;
            }

由于整个效果是分成上下两层条纹的,所以在初始化的时候需要传递一些信息进去。
处于下层的条纹要暗一些,就比较符合视觉效果。然后每个条纹在需要对自条的高度、进入的延时、速度巴拉巴拉的做一下随机化,每个条纹的个性就出来了~这些参数在之后计算坐标的时候会用到。

然后是比较关键的绘制部分代码:

            private void drawFront(Canvas canvas, float t){
                double k = t*ts/enterDuration + t1;
                k = Math.max(0, k);
                k = Math.min(1, k);
                double k1 = Math.cos(Math.pow(k,1.3)*3*Math.PI);
                double k2 = (Math.cos(Math.pow(k,0.2)*Math.PI)+1)/2;
                double k3 = 1-k1*k2;

                double m = (1-k3)*50;

                int left = (int) (x1 + k3*(x2- x1));
                int right = left + fringeWidth;
                int top = (int) (y + m);
                int bottom = top + h;

                shadowRect.left = left-shadowWidth1;
                shadowRect.top = top-shadowWidth1;
                shadowRect.right = right+shadowWidth1;
                shadowRect.bottom = bottom+shadowWidth1;
                shadowPatch.draw(canvas, shadowRect);

                fringeRect.left = left;
                fringeRect.top = top;
                fringeRect.right = right;
                fringeRect.bottom = bottom;

                fringePaint.setColor(color);
                canvas.drawRect(fringeRect, fringePaint);
            }

            private void drawBack(Canvas canvas, float t){
                double k = t/exitDuration + t1*2;
                k = Math.max(0, k);
                k = Math.min(1, k);
                double k1 = Math.pow(1-k, 1);

                int left = (int) (x1 + k1*(x2- x1));
                int right = left + fringeWidth;
                int top = y;
                int bottom = top + h;

                shadowRect.left = left-shadowWidth1;
                shadowRect.top = top-shadowWidth1;
                shadowRect.right = right+shadowWidth1;
                shadowRect.bottom = bottom+shadowWidth1;
                shadowPatch.draw(canvas, shadowRect);

                fringeRect.left = left;
                fringeRect.top = top;
                fringeRect.right = right;
                fringeRect.bottom = bottom;
                fringePaint.setColor(color);
                canvas.drawRect(fringeRect, fringePaint);
            }

两个函数,分别是进入时的代码和退出时的代码。
先是做了一些曲线的计算工作,嘛,这里自己慢慢调就好,不要问我为什么这里是pow这里是cos。感觉,凭感觉~ᐠ( ᐢ ᵕ ᐢ )ᐟ (其实我是先python画图看一下大概曲线符合弹跳动画的形状,然后再在真机上慢慢调整@( ̄- ̄)@

然后再根据曲线计算当前时刻的坐标。
然后是绘制,绘制部份分为两步,先是用shadowPatch来绘制阴影层,shadowPatch是一个NinePatch。然后再用普通的canvas.drawRect来绘制彩色的条纹。

有了Fringe这个类之后,就可以再用一个FringeGroup类来管理每一层的绘制了,代码如下:

  class FringeGroup {

        private int degree;
        private long startTime;
        private ArrayList<Fringe> fringes = new ArrayList<>();

        FringeGroup(Context context, int left, int top, int degree, int index){

            startTime =0;
            Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.shadow);
            shadowPatch = new NinePatch(bitmap, bitmap.getNinePatchChunk(), null);
            this.degree = degree;

            for (int i = 0; i< fringeCount; i++){
                Fringe fringe = new Fringe(left+ slideLength, left, top+ intervalHeight *i, i, index);
                fringes.add(fringe);
            }
            Collections.shuffle(fringes);
        }

        void start(){
            startTime = System.currentTimeMillis();
        }

        void back(){
            startTime = System.currentTimeMillis();
        }

        void onDraw(Canvas canvas){
            if (isInEditMode()){
                canvas.rotate(degree);
                float t = previewTime;
                for (Fringe fringe : fringes) {
                    fringe.drawFront(canvas, t);
                }
                canvas.rotate(-degree);
                return;
            }

            if ((fringeState ==1 || fringeState ==2 || fringeState ==3) && startTime!=0) {
                canvas.rotate(degree);
                float t = (System.currentTimeMillis() - startTime)/1000.0f;
                for (Fringe fringe : fringes) {
                    fringe.drawFront(canvas, t);
                }
                canvas.rotate(-degree);
            }

            if (fringeState ==4){
                canvas.rotate(degree);
                float t = (System.currentTimeMillis() - startTime)/1000.0f;
                for (Fringe fringe : fringes) {
                    fringe.drawBack(canvas, t);
                }
                canvas.rotate(-degree);
            }


        }

每一个层有不同的旋转角度,以及原点坐标,这样子就能通过旋转移动两个层,把整个屏幕盖住(迷之音:简直是天才的设计wwwwww
以及fringeState这个是一个状态值 ,由于这个控制件涉及到进入过程、退出过程、用户点击,所以状态还是比较复杂的@( ̄- ̄)@

所需要实现的交互逻辑是:自动开始“进入”过程。如果在“进入”过程中用户点击了屏幕,则“进入”过程不被打断,在结束后自动到“退出”过程。如果在“进入”过程中用户没有点击屏幕,则“进入”过程完毕后,停留在完毕状态,等待用户点击,才到“退出”过程。

所以这就需要一个状态机,fringeState就是状态值,含义如下:

1:正在进入,用户没有点击
2:正在进入,用户已经点击
3:进入完毕,用户没有点击
4:正在退出
5:退出完毕

完成了FringeGroup之后,再在View里进行一下属性的获取,初始化,触模事件的处理就行了。关键代码如下:

属性的获取:

   private void getAttrs(Context context, AttributeSet attrs){
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.FringeLaunchView, 0, 0);
        try{
            mainH1 = a.getFloat(R.styleable.FringeLaunchView_mainH1, 0.9f);
            mainH2 = a.getFloat(R.styleable.FringeLaunchView_mainH2, 0.98f);
            degree = a.getInteger(R.styleable.FringeLaunchView_degree, 20);
            fringeCount = a.getInteger(R.styleable.FringeLaunchView_fringeCount, 15);
            intervalHeight = a.getInteger(R.styleable.FringeLaunchView_intervalHeight, 150);
            fringeHeight = a.getInteger(R.styleable.FringeLaunchView_fringeHeight, 300);
            fringeWidth = a.getInteger(R.styleable.FringeLaunchView_fringeWidth, 2500);
            slideLength = a.getInteger(R.styleable.FringeLaunchView_slideLength, 1500);
            overlapWidth = a.getInteger(R.styleable.FringeLaunchView_overlapWidth, 700);
            enterDuration = a.getFloat(R.styleable.FringeLaunchView_enterDuration, 1.5f);
            exitDuration = a.getFloat(R.styleable.FringeLaunchView_exitDuration, 0.7f);
            previewTime = a.getFloat(R.styleable.FringeLaunchView_previewTime, 2);
        }finally {
            a.recycle();
        }
    }

话说安卓这里真的没有一个更简单的方法来完成属性的获取吗?@( ̄- ̄)@ 我改个属性名,改个类型都很麻烦诶……

然后是两个FrigneGroup的初始化:

    private void init(Context context){
        fringePaint = new Paint();
        backgroundPaint = new Paint();
        canvasRect = new Rect();

        fringeGroup1 = new FringeGroup(context,0, 0, -degree, 1);
        fringeGroup2 = new FringeGroup(context, -overlapWidth, -fringeCount*intervalHeight, -degree-180, 2);

        ExitdelayMillis = (long) ((exitDuration+1) * 1000);

        exited = false;
        fringeState = 1;
        exitState = 0;
    }

在onDraw里View本身还需要画一个白底来盖住底下的View,在退出的时候白底渐变消失。

    @Override
    protected void onDraw(Canvas canvas)
    {
        canvasRect.right = canvas.getWidth();
        canvasRect.bottom = canvas.getHeight();

        if (exitState ==0) {
            backgroundPaint.setARGB(255, 255, 255, 255);
            canvas.drawRect(canvasRect, backgroundPaint);
        } else {
            float t =(System.currentTimeMillis() - startTime)/1000.0f;
            int a = (int) (Math.max(0, 1-t*2)*255);
            backgroundPaint.setARGB(a, 255, 255, 255);
            canvas.drawRect(canvasRect, backgroundPaint);
        }

        fringeGroup2.onDraw(canvas);
        fringeGroup1.onDraw(canvas);

        if (fringeState !=5) {
            invalidate();
        }
    }

触摸事件的处理以及对外接口的处理就不代码了,有兴趣的请移步github上下载源码@( ̄- ̄)@
(迷之音:不,请务必、烦请、劳驾、给大佬递茶、回旋180度,一个star~ᐠ( ᐢ ᵕ ᐢ )ᐟ
((再扔一次地址:https://github.com/dotomato/FringeLaunchView

你可能感兴趣的:(编程)