为了账号安全,请及时绑定邮箱和手机立即绑定

淘宝首页动画的实现

标签:
Android

今天来写一个淘宝的一个小动画,一看觉得挺简单的,但是实际操作起来,感觉有点麻烦,遇到的问题也比较多,不过好在模仿出来了,好了给大家看看效果。 
这是老版本的,模拟器上面的和现版本的不一致 
这个是新版本的,下面的布局Bi老版本要稍微复杂一点。

接下来看看主界面的布局结构,最外面的是一个ViewGroup,然后就是一个“+”的View,再就是文本。 
下面我们就来完成这个View的书写,先定义一个ViewGroup,在定义一个View画上“+”,然后ViewGroup里面加上该View和文本内容。

 @Override
    protected void onDraw(Canvas canvas) {        super.onDraw(canvas); //画矩形的背景       canvas.drawRect(0,getMeasuredWidth()/2,getMeasuredWidth(),getMeasuredHeight(),mPaint);
        RectF rectF = new RectF(0,0,getMeasuredWidth(),getMeasuredHeight()/2); //画圆       canvas.drawCircle(getMeasuredWidth()/2,getMeasuredWidth()/2,getMeasuredWidth()/2,mPaint);
    }//测量的话,因为我们这是特定的布局,所以就没考虑到硬编码的问题,因为该容器不具备通用性
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //测量子View,这里巨鼎父容器大小的是底部的TextView,所以就一该View为基准,设置父布局,其中TextView的上下左右的Margin都是可以控制的
        measureChildren(widthMeasureSpec,heightMeasureSpec);        int w = getChildAt(0).getMeasuredWidth();        int h = getChildAt(0).getMeasuredHeight();
        imgRadius = w/2;
        myLitterXView.init();        int width = w + marginLeftOrRightText * 2;        int height =  h + marginBottomText + marginTopText + imgRadius + width/2;        //最后将理想的宽高赋值上去。
        setMeasuredDimension(width,height);
    }    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {    //layout就是确定子View的位置的,这个就是个算数问题,没啥好说的
        View view = getChildAt(0);
        view.layout(
                marginLeftOrRightText,
                getMeasuredHeight()-view.getMeasuredHeight()-marginBottomText,
                marginLeftOrRightText+view.getMeasuredWidth(),
                getMeasuredHeight()-marginBottomText
                );
        View v = getChildAt(1);
        v.layout(
                marginLeftOrRightText,imgMarginTop,getMeasuredWidth()-marginLeftOrRightText,imgMarginTop+imgRadius*2
        );
    }

由于ViewGrou只是负责装载子View,并不承担绘制工作,但是我们这里需要其承担一定的绘制工作,那么就必须 setWillNotDraw(false);,让其调用draw()方法.

 private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(color);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);        //强制让容器执行绘制工作
        setWillNotDraw(false);
        textView = new TextView(getContext());
        textView.setText("发布");
        textView.setTextColor(Color.WHITE);
        LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        textView.setLayoutParams(lp);
        addView(textView);
        myLitterXView = new MyLitterXView(getContext());
        myLitterXView.setLayoutParams(lp);
        addView(myLitterXView);
        setOnClickListener(new OnClickListener() {            @Override
            public void onClick(View v) {                if (mClickListener!=null)mClickListener.click(v);
                myLitterXView.change(myLitterXView.model==TO_SPECIAL?TO_NOMAL:TO_SPECIAL);
            }
        });
    }

以上还只是完成了最基本的步骤,“+”View的定义

@Override 
protected void onDraw(Canvas canvas) { 
super.onDraw(canvas); 
canvas.drawColor(0x00000000); 
//画两条线,比较简单 canvas.drawLine(hLine[0].getX(),hLine[0].getY(),hLine[1].getX(),hLine[1].getY(),paint); 
canvas.drawLine(vLine[0].getX(),vLine[0].getY(),vLine[1].getX(),vLine[1].getY(),paint); 
canvas.restore(); 
}

说一下旋转动画的这几个参数,旋转的起始角度,最终角度,x轴的参照位置,参照点,y轴的参照位置,y轴参照点 
RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, 
int pivotYType, float pivotYValue)

animation2 = new RotateAnimation(45,0,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);

通过普通的补间动画完成该旋转功能即可。如此下来,就差不多一级界面差不多了,接下来分析点击动画的组成部分。

点击一下,会有一个全屏的Window,这是必须的,别的方法也可以但是,没有该效果好,在window上面操作动画,这里要特别注意,当我们点击了一级界面的按钮后,Window添加布局,然而该View何时测量完毕?什么时候去开启第一次的动画?这都值得我们思考 
如果对Window不太了解的可以看看我的第一篇博客WindowManger的应用

public class MyWindow {    private WindowManager mWindowManager;    private WindowManager.LayoutParams mLp;    private Activity context;    private View view;    //WindowManger需要基本的一些配置
    public MyWindow(Activity context) {        this.context = context;
        mWindowManager = context.getWindowManager();
        mLp = new WindowManager.LayoutParams();
        mLp.height = WindowManager.LayoutParams.MATCH_PARENT;        //这是表示Window的左上角的位置
        mLp.x = 0;
        mLp.y = 0;
        mLp.width = WindowManager.LayoutParams.MATCH_PARENT;
        mLp.flags = WindowManager.LayoutParams.FLAG_DITHER;
        mLp.format = PixelFormat.TRANSLUCENT;
    }    public void addView(View view){        this.view = view;
        mWindowManager.addView(view,mLp);
    }    public void removeView(){        if (mWindowManager==null)return;
        mWindowManager.removeView(view);
        context = null;
    }
}

Window有了,下面我们要定义动画View了。在此之前我们要将动画View设置在一级菜单的上方,那么我们就要得到一级菜单的高度或者是在屏幕当中的位置(可以使用getLocationOnWindow,参数是个长度为2的整型数组,分别为x,y的坐标)。

圆形的TextView通过canvas.drawCircle(radius, radius, radius, mPaint); 这里直接在调用父方法前绘制背景图层。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myMenuView = (MyMenuView) findViewById(R.id.menu_view);
        final List<String> list = new ArrayList<>();
        list.add("发布");
        list.add("报价");
        contentView = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.animator_view, null);
        myAnimatorView = (com.yzz.android.animator.MyAnimatorView) contentView.getChildAt(0);
        myAnimatorView.init(Color.rgb(12, 22, 44), Color.WHITE, list);
        myWindow = new MyWindow(this);
        myAnimatorView.setWindow(myWindow);
        myAnimatorView.setRadius(50);
        int count = myAnimatorView.getChildCount();
        for (int i = 0; i < count; i++) {
            View v = myAnimatorView.getChildAt(i);
            final int finalI = i;
            v.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this,list.get(finalI),Toast.LENGTH_LONG).show();
                }
            });
        }
        myMenuView.setClickListener(new MyMenuView.ClickListener() {
            @Override
            public void click(View view) {
//这里我是在点击事件里面设置给Window上的View的init数据的,这里只设置一次            
                if (isFirst) {
                    isFirst = false;
                    ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) myAnimatorView.getLayoutParams();//高度的话我们这里要设置到,必须在一级菜单的上方。                    
                    lp.height = myMenuView.getMeasuredHeight() + myMenuView.getWidth() / 2 + MyAnimatorView.MARGIN;
                    myAnimatorView.setLayoutParams(lp);
                    myAnimatorView.setRadius(myMenuView.getWidth() / 2);//设置上去菜单的高度,后面平移动画需要                    myAnimatorView.setHeight(myMenuView.getHeight());
                    myWindow.addView(contentView);
                } else {
                    myWindow.addView(contentView);
                    //myAnimatorView.doOpen();这里不可以开始动画,还没有加载完毕
                }
            }
        });

        contentView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myMenuView.change(MyMenuView.TO_NOMAL);
                myAnimatorView.docloes();
            }
        });
 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {    //这里会layout两次,第一次是没有设置高度时的layout,第二次是动态设置height是触发的layout
        if (radius <= 0) return;        int count = getChildCount();        int h = getMeasuredHeight() - menuHeight;        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            child.layout(instanceX / 2, h, instanceX / 2 + child.getMeasuredWidth(), h + child.getMeasuredHeight());
            child.setScaleX(0.5f);
            child.setScaleY(0.5f);
            child.setAlpha(0f);
        }        //这里当laout完毕的时候要进行第一次的动画
        doOpen();
    }1234567891011121314151612345678910111213141516

这里使用的属性动画,属性动画的有点就是在动画的过程中改变了View的位置属性,这样点击区域就会随之View的移动而改变。通过PropertyValuesHolder来操作一组动画,很方便,当然也可以时候AnimatorSet来实现,都可以。注意:动画的位置规则是相对的,相对于当前状态,平移的话,x>0,y>0表示这x右移,y下移。

 public void initAnimation() {
        instanceX = childList.get(0).getMeasuredWidth() + MARGIN;
        instanceY = getMeasuredHeight() - menuHeight;
        int translateX = instanceX / 2;

        holder0 = PropertyValuesHolder.ofFloat("translationX", -translateX, 0);
        holder1 = PropertyValuesHolder.ofFloat("translationY", -instanceY, 0);
        holder2 = PropertyValuesHolder.ofFloat("scaleX", 1, 0.5f);
        holder3 = PropertyValuesHolder.ofFloat("scaleY", 1, 0.5f);
        holder4 = PropertyValuesHolder.ofFloat("alpha", 1, 0f);
        closeLeft = ObjectAnimator.ofPropertyValuesHolder(childList.get(0), holder0, holder1, holder2, holder3, holder4);
        closeLeft.addListener(this);
        closeLeft.setDuration(2000);
        holder00 = PropertyValuesHolder.ofFloat("translationX", translateX, 0);
        closeRight = ObjectAnimator.ofPropertyValuesHolder(childList.get(1), holder00, holder1, holder2, holder3, holder4);
        closeRight.setDuration(2000);
        //还原
        holderN0 = PropertyValuesHolder.ofFloat("translationX", 0, -translateX);
        holderN1 = PropertyValuesHolder.ofFloat("translationY", 0, -instanceY);
        holderN2 = PropertyValuesHolder.ofFloat("scaleX", 0.5f, 1f);
        holderN3 = PropertyValuesHolder.ofFloat("scaleY", 0.5f, 1f);
        holderN4 = PropertyValuesHolder.ofFloat("alpha", 0f, 1f);

        openLeft = ObjectAnimator.ofPropertyValuesHolder(childList.get(0), holderN0, holderN1, holderN2, holderN3, holderN4);
        openLeft.addListener(this);
        openLeft.setDuration(2000);
        PropertyValuesHolder holderN00 = PropertyValuesHolder.ofFloat("translationX", 0, translateX);
        openRight = ObjectAnimator.ofPropertyValuesHolder(childList.get(1), holderN00, holderN1, holderN2, holderN3, holderN4);
        openRight.setDuration(2000);
    }

    public void docloes() {
//当当前View正在动画的时候,是不允许再次动画的
        if (isAnimationing)return;
        closeLeft.start();
        closeRight.start();
        model = CLOSE;
    }
 public void doOpen() {
        if (isAnimationing)return;
        openLeft.start();
        openRight.start();
        model = OPEN;
    }

好了,关键代码就是这些,要注意,何时init,这个时机很重要,当init有addView操作的,那么就必须放在onFinishLayoutInflate()方法中,原因是当从xm中加载完毕后,我们的ViewGroup才被创建,所以要在该方法中取添加或者操作子View,还有一点,就是在第一次动画的时候,要找准动画的时机,不然有一些数据还未初始化,就要开始动画了,这样会出现各种错误。好了,基本就是这么多,有兴趣的可以去下载源码看看GitHub地址https://github.com/yzzAndroid/Animator

原文链接:http://www.apkbus.com/blog-822415-68297.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消