Skip to content

Latest commit

 

History

History
775 lines (605 loc) · 26.1 KB

引导页面动画.md

File metadata and controls

775 lines (605 loc) · 26.1 KB

引导动画

项目地址:Github

动画效果:

引导动画

实现原理分析

  1. 清单文件

    <manifest package="cn.lry.animation"
              xmlns:android="http://schemas.android.com/apk/res/android">
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity
                android:name=".guide.MainActivity"
                android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    
            <activity android:name=".guide.GuideActivity"/>
        </application>
    
    </manifest>
    

    主要分为连个页面,一个是墨迹天气的引导页面,就是GIF中涨势的效果,另外一个也是差不多的动画效果的只不过是左右滑动的引导页面

  2. 主页面布局MainActivity,主页面主要有一个viewpager构成,滑动方式为vertical

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:app="http://schemas.android.com/apk/res-auto"
                  android:orientation="vertical"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent">
    
        <cn.lry.animation.viewpager.ViewPager
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:orientation="vertical"/>
    
    </LinearLayout>
    
  3. ViewPager的初始化,通过xml文件来初始化每个页面的视图,然后通过pagers.add(view1)添加到ViewPager中,总共有四个

           View view1 = LayoutInflater.from(this).inflate(R.layout.layout_tutorial_1, null);
            t1_icon1= (ImageView) view1.findViewById(R.id.t1_icon1);
            t1_icon2 = (ImageView) view1.findViewById(R.id.t1_icon2);
            t1_fixed = (ImageView) view1.findViewById(R.id.t1_fixed);
            t1_next = (ImageView) view1.findViewById(R.id.t1_next);
            pagers.add(view1);
            .........四个类似的页面
    
  4. 我们看其中一个页面的布局R.layout.layout_tutorial_1,也就是将页面上的几个图片分别用ImageView来进行布局分布。每个图片都是一个png的图片

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="@android:color/white"
        android:gravity="center"
        android:orientation="vertical" >
    
        <ImageView
            android:id="@+id/t1_fixed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="15.0dip"
            android:src="@mipmap/tutorial1_fixed" />
    
        <RelativeLayout
            android:id="@+id/center_layout"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true" >
    
            <ImageView
                android:id="@+id/t1_icon1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="180.0dip"
                android:layout_marginTop="53.0dip"
                android:background="@drawable/t1_frame_animation" />
    
            <ImageView
                android:id="@+id/t1_icon2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/t1_icon1"
                android:layout_centerHorizontal="true"
                android:layout_marginTop="30.0dip"
                android:src="@mipmap/tutorial1_icon3"
                android:visibility="visible" />
    
            <ImageView
                android:id="@+id/t1_static"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:src="@mipmap/tutorial1_static"
                android:visibility="visible" />
    
            <ImageView
                android:id="@+id/t1_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/t1_static"
                android:layout_centerHorizontal="true"
                android:layout_marginTop="10.0dip"
                android:src="@mipmap/tutorial1_text"
                android:visibility="visible" />
        </RelativeLayout>
    
        <ImageView
            android:id="@+id/t1_next"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20.0dip"
            android:src="@mipmap/tutorial_next" />
    
    </LinearLayout>
    

  5. 动画的实现主要靠private void animal(int position) 方法,在页面切换的时候,去启动或者禁用视图控件的动画

每个部分的动画实现

第一页分析

  • 极低耗电 这几个字的放大缩小动画

    极低耗电为一个图片 t1_fixed = (ImageView) view1.findViewById(R.id.t1_fixed);

     <ImageView
            android:id="@+id/t1_fixed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="15.0dip"
            android:src="@mipmap/tutorial1_fixed" />
    

    顶部动画加载

    animationTop = AnimationUtils.loadAnimation(MainActivity.this, R.anim.tutorail_scalate_top);
    

    启动动画

    t1_fixed.startAnimation(animationTop);
    

    主要分析这个动画的描述R.anim.tutorail_scalate_top,主要定义了两个动画效果scale,放大缩小,动画时长1000ms,x,y从1.0变为1.5倍大,重复模式reverse为倒序回复,如果设置为restart的话,表示从头回放,repeatcount负一表示无限循环;另外一个是alpha,时间也是1000,这样就是一边放大缩小,一边透明度渐变,透明度从1变为0.5

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <scale
            android:duration="1000"
            android:fromXScale="1.0"
            android:fromYScale="1.0"
            android:interpolator="@android:anim/decelerate_interpolator"
            android:pivotX="50%"
            android:pivotY="50%"
            android:toXScale="1.5"
            android:toYScale="1.5"
            android:repeatMode="reverse"
            android:repeatCount="-1"/>
        <alpha
            android:fromAlpha="1.0"
            android:toAlpha="0.5"
            android:duration="1000"
            android:repeatMode="reverse"
            android:repeatCount="-1"/>
    </set>
    
    <!-- 尺寸伸缩动画效果 scale
           属性:interpolator 指定一个动画的插入器
           常见动画插入器:
                accelerate_decelerate_interpolator  加速-减速 动画插入器
                accelerate_interpolator        加速-动画插入器
                decelerate_interpolator        减速- 动画插入器
                anticipate_interpolator	先回退一小步然后加速前进
                anticipate_overshoot_interpolator	在上一个基础上超出终点一小步再回到终点
                bounce_interpolator	最后阶段弹球效果
                cycle_interpolator	周期运动
                linear_interpolator	匀速
                overshoot_interpolator	快速到达终点并超出一小步最后回到终点
          浮点型值:
              
                fromXScale 属性为动画起始时 X坐标上的伸缩尺寸    
                toXScale   属性为动画结束时 X坐标上的伸缩尺寸     
             
                fromYScale 属性为动画起始时Y坐标上的伸缩尺寸    
                toYScale   属性为动画结束时Y坐标上的伸缩尺寸    
             
                说明:
                     以上四种属性值    
         
                        0.0表示收缩到没有 
                        1.0表示正常无伸缩     
                        值小于1.0表示收缩  
                        值大于1.0表示放大
             
                pivotX     属性为动画相对于物件的X坐标的开始位置
                pivotY     属性为动画相对于物件的Y坐标的开始位置
             
                说明:
                        以上两个属性值 从0%-100%中取值
                        50%为物件的X或Y方向坐标上的中点位置
             
            长整型值:
                duration  属性为动画持续时间
                说明:   时间以毫秒为单位
     
            布尔型值:
                fillAfter 属性 当设置为true ,该动画转化在动画结束后被应用
    -->
    
  • 中间电池图标的旋转动画

    电池图标

         <ImageView
                android:id="@+id/t1_icon2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/t1_icon1"
                android:layout_centerHorizontal="true"
                android:layout_marginTop="30.0dip"
                android:src="@mipmap/tutorial1_icon3"
                android:visibility="visible" />
    

    加载并开始动画

    Animation animation1 = AnimationUtils.loadAnimation(MainActivity.this, R.anim.tutorail_rotate);
                    LinearInterpolator lin = new LinearInterpolator();
                    animation1.setInterpolator(lin);
    
                    t1_icon2.setVisibility(View.VISIBLE);
                    t1_icon2.startAnimation(animation1);
    

    关于旋转动画的描述,刚开始的时候,电池图标是从无到有的一个从小到大的scale,android:fromXScale="0.0"到1.2,然后就结束了,同时会无限的旋转,rotate,从0度到359度旋转,时间为3000ms,pivotX,Y 50%表示从以中心点旋转

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android" >
    
        <scale
            android:duration="800"
            android:fillAfter="false"
            android:fromXScale="0.0"
            android:fromYScale="0.0"
            android:interpolator="@android:anim/accelerate_decelerate_interpolator"
            android:pivotX="50%"
            android:pivotY="50%"
            android:toXScale="1.2"
            android:toYScale="1.2" />
    
        <rotate
            android:duration="3000"
            android:fromDegrees="0"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="-1"
            android:toDegrees="359.0" />
        <!-- rotate 旋转动画效果
           属性:interpolator 指定一个动画的插入器
               
           浮点数型值:
                fromDegrees 属性为动画起始时物件的角度    
                toDegrees   属性为动画结束时物件旋转的角度 可以大于360度   
     
             
                说明:
                         当角度为负数——表示逆时针旋转
                         当角度为正数——表示顺时针旋转              
                         (负数from——to正数:顺时针旋转)   
                         (负数from——to负数:逆时针旋转) 
                         (正数from——to正数:顺时针旋转) 
                         (正数from——to负数:逆时针旋转)       
     
                pivotX     属性为动画相对于物件的X坐标的开始位置
                pivotY     属性为动画相对于物件的Y坐标的开始位置
                     
                说明:        以上两个属性值 从0%-100%中取值
                             50%为物件的X或Y方向坐标上的中点位置
     
            长整型值:
                duration  属性为动画持续时间
                说明:       时间以毫秒为单位
    -->
    
    </set>
    
  • 电池图片数字的闪烁变换

    闪烁的图片

     <ImageView
                android:id="@+id/t1_icon1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="180.0dip"
                android:layout_marginTop="53.0dip"
                android:background="@drawable/t1_frame_animation" />
    

    图片定义t1_frame_animation.xml,animation-list实现的是逐帧动画,oneshot表示不停的循环播放,每个帧的图片和时长定义

    <?xml version="1.0" encoding="utf-8"?>
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="false" >
    <!--   
        根标签为animation-list,其中oneshot代表着是否只展示一遍,设置为false会不停的循环播放动画  
        根标签下,通过item标签对动画中的每一个图片进行声明  
        android:duration 表示展示所用的该图片的时间长度  
     -->  
        <item
            android:drawable="@mipmap/tutorial1_icon1"
            android:duration="200"/>
        <item
            android:drawable="@mipmap/tutorial1_icon2"
            android:duration="200"/>
    
    </animation-list>
    

    开始启动动画

     t1_icon1.setImageResource(R.drawable.t1_frame_animation);
                    t1_icon1_animationDrawable = (AnimationDrawable) t1_icon1.getDrawable();
                    
     t1_icon1_animationDrawable.start();
    
  • 最下角箭头下移和透明度变换

    底部箭头

    
                <ImageView
            android:id="@+id/t1_next"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20.0dip"
            android:src="@mipmap/tutorial_next" />
    
    

    加载动画

    animationBottom = AnimationUtils.loadAnimation(MainActivity.this, R.anim.tutorail_bottom);
    

    启动

    t1_next.startAnimation(animationBottom);
    

    动画描述文件tutorail_bottom.xml,转移动画和透明度的组合

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:repeatMode="reverse" >
    
        <translate
            android:duration="1000"
            android:fromXDelta="0"
            android:fromYDelta="-15"
            android:repeatCount="infinite"
            android:toXDelta="0"
            android:toYDelta="20" />
        <!-- translate 平移动画效果
            整型值:
                fromXDelta 属性为动画起始时 X坐标上的位置    
                toXDelta   属性为动画结束时 X坐标上的位置
                fromYDelta 属性为动画起始时 Y坐标上的位置
                toYDelta   属性为动画结束时 Y坐标上的位置
                注意:
                         没有指定fromXType toXType fromYType toYType 时候,
                         默认是以自己为相对参照物  ,默认参考物最重要         
            长整型值:
                duration  属性为动画持续时间
                说明:   时间以毫秒为单位
    -->
    
        <alpha
            android:duration="1000"
            android:fromAlpha="1.0"
            android:repeatCount="infinite"
            android:toAlpha="0.3" />
        <!--
             透明度控制动画效果 alpha
            浮点型值:
                fromAlpha 属性为动画起始时透明度
                toAlpha   属性为动画结束时透明度
                说明: 
                    0.0表示完全透明
                    1.0表示完全不透明
                以上值取0.0-1.0之间的float数据类型的数字
             
            长整型值:
                duration  属性为动画持续时间
                说明:     
                    时间以毫秒为单位
            整型值:        
                repeatCount:重复次数
                说明:
                infinite:循环执行,
                具体正整数表示循环次数
                
                 repeatMode:重复模式,
                 说明:
                    restart:重新从头开始执行
                    reverse:反方向执行
                 
        -->
    
    </set>
    

其他页面的也都差不多原理实现

第三页分析

可以看一下火箭飞后面云朵的动画

每个云朵都是一个image view

 <ImageView
            android:id="@+id/t3_icon3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:layout_marginLeft="120.0dip"
            android:layout_marginTop="45.0dip"
            android:src="@mipmap/t3_could_left2"
            android:visibility="invisible"/>

启动动画

 t3_icon2.startAnimation(transAnimation2);

动画定义

final TranslateAnimation transAnimation2 = new TranslateAnimation(fx1, tx1, fy1, ty1);
                transAnimation2.setDuration(800);
                transAnimation2.setRepeatCount(Animation.INFINITE);
                transAnimation2.setRepeatMode(Animation.RESTART);
                lin = new LinearInterpolator();
                transAnimation2.setInterpolator(lin);

                final TranslateAnimation transAnimation3 = new TranslateAnimation(fx2, tx2, fy2, ty2);
                transAnimation3.setDuration(1200);
                transAnimation3.setRepeatCount(Animation.INFINITE);
                transAnimation3.setRepeatMode(Animation.RESTART);
                transAnimation3.setInterpolator(lin);

每个云朵的移动速度不一样,其实位置fx1,fy1到目标位置tx1,ty1也不一样

第四页分析

挂牌左右摇晃动画

挂牌

<ImageView
        android:id="@+id/t4_icon1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/t4_image"
        android:visibility="visible" />

左右摇晃动画

                int pivot = Animation.RELATIVE_TO_SELF;
                CycleInterpolator interpolator = new CycleInterpolator(3.0f);//执行多少次
                RotateAnimation animation = new RotateAnimation(0, 10, pivot, 0.47f, pivot, 0.05f);
                animation.setStartOffset(500);
                animation.setDuration(3000);
                animation.setRepeatCount(1);// Animation.INFINITE
                animation.setInterpolator(interpolator);
                t4_icon1.startAnimation(animation);

其他基本都是类似的实现:

小结

以上动画使用了Android的基本动画类型alpha,rotate,scale,translate,另外引入插值器完成动画

下面火箭的上升有一个自定义属性动画,我们现认识一下,后续在细细学习分析

火箭的上升动画

火箭view

  <ImageView
        android:id="@+id/page_one_rocket"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@mipmap/image_one_rocket"
        android:layout_below="@+id/page_one_center_bg"
        android:layout_toLeftOf="@+id/center"
        android:layout_marginRight="30dp"/>

加载动画

        animationRocket = ArcAnimator.createArcAnimator(page_one_rocket, centerX + r - x*3/2 + 20, centerY - r + 3*x/4 - 20 , 90f, Side.RIGHT);
        animationRocket.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                page_one_center_tj.startAnimation(animationCenterTJ);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
        animationRocket.setDuration(1500);
        animationRocket.start();

其中 ArcAnimator是自定义的属性动画

路径示意图

package cn.lry.animation.arcanimator;



import android.view.View;
import android.view.animation.Interpolator;

import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.view.ViewHelper;

import java.lang.ref.WeakReference;

public class ArcAnimator extends Animator {

    public static ArcAnimator createArcAnimator(View clipView, View nestView, float degree, Side side){
        return createArcAnimator(clipView, Utils.centerX(nestView), Utils.centerY(nestView), degree, side);
    }

    public static ArcAnimator createArcAnimator(View clipView, float endX, float endY, float degree, Side side){
        ArcMetric arcMetric = ArcMetric.evaluate(Utils.centerX(clipView), Utils.centerY(clipView), endX, endY, degree, side);
        return new ArcAnimator(arcMetric, clipView);
    }

    ArcMetric mArcMetric;
    WeakReference<View> mTarget;
    WeakReference<ObjectAnimator> mAnimator;
    float mValue;


    private ArcAnimator(ArcMetric arcmetric, View target) {
        mArcMetric = arcmetric;
        mTarget = new WeakReference<>(target);

        mAnimator = new WeakReference<>(
                ObjectAnimator.ofFloat(
                        ArcAnimator.this, // target
                        "degree", // property
                        arcmetric.getStartDegree(),
                        arcmetric.getEndDegree())
        );//其实主要就是一个ObjectAnimator,关注的是degree的值
    }

    void setDegree(float degree){
        mValue = degree;
        View clipView = mTarget.get();
        float x = mArcMetric.getAxisPoint().x + mArcMetric.mRadius * Utils.cos(degree);
        float y = mArcMetric.getAxisPoint().y - mArcMetric.mRadius * Utils.sin(degree);
        ViewHelper.setX(clipView,x - clipView.getWidth() / 2);
        ViewHelper.setY(clipView,y - clipView.getHeight() / 2);
    }

    float getDegree(){
        return mValue;
    }

    @Override
    public long getStartDelay() {
        Animator a = mAnimator.get();
        return a == null ? 0 : a.getDuration();
    }

    @Override
    public void setStartDelay(long startDelay) {
        Animator a = mAnimator.get();
        if(a != null) a.setStartDelay(startDelay);
    }

    @Override
    public ArcAnimator setDuration(long duration) {
        Animator a = mAnimator.get();
        if(a != null) a.setDuration(duration);
        return this;
    }

    @Override
    public long getDuration() {
        Animator a = mAnimator.get();
        return a == null ? 0 : a.getDuration();
    }

    @Override
    public void setInterpolator(Interpolator value) {
        Animator a = mAnimator.get();
        if(a != null) a.setInterpolator(value);
    }

    @Override
    public void start() {
        super.start();
        Animator a = mAnimator.get();
        if(a != null) a.start();
    }

    @Override
    public void end() {
        super.end();
        Animator a = mAnimator.get();
        if(a != null) a.end();
    }

    @Override
    public void cancel() {
        super.cancel();
        Animator a = mAnimator.get();
        if(a != null) a.cancel();
    }

    @Override
    public void addListener(AnimatorListener listener) {
        Animator a = mAnimator.get();
        if(a != null) a.addListener(listener);
    }

    @Override
    public void setupEndValues() {
        super.setupEndValues();
        Animator a = mAnimator.get();
        if(a != null) a.setupEndValues();
    }

    @Override
    public void setupStartValues() {
        super.setupStartValues();
        Animator a = mAnimator.get();
        if(a != null) a.setupStartValues();
    }

    @Override
    public boolean isRunning() {
        Animator a = mAnimator.get();
        return a != null && a.isRunning();
    }

    @Override
    public String toString() {
        return mArcMetric.toString();
    }
}

关注一下

ArcAnimator.createArcAnimator(page_one_rocket, centerX + r - x*3/2 + 20, centerY - r + 3*x/4 - 20 , 90f, Side.RIGHT);

centerX + r - x*3/2 + 20centerY - r + 3*x/4 - 20是计算出来火箭图标的最终目标位置的x,y坐标

看几条路线轨迹

01-10 04:57:03.822 11717-11717/cn.lry.animation I/GuideActivity: r=270.0中间背景的长宽
01-10 04:57:03.822 11717-11717/cn.lry.animation I/GuideActivity: x=120.0火箭图标的长宽
这两个只是用来算目标位置的坐标

01-10 04:57:03.832 11717-11717/cn.lry.animation I/ArcAnimator: createArcAnimator endx=349.0 endy=217.0degree90.0side =RIGHT
计算出目标位置的坐标为349,217

01-10 04:57:03.845 11717-11717/cn.lry.animation I/ArcAnimator: createArcAnimator starx=74.0 starty=381.0
当前位置的坐标,最后还进行了中心计算

01-10 04:57:03.884 11717-11717/cn.lry.animation I/ArcAnimator: setDegree=0.0

01-10 04:57:03.885 11717-11717/cn.lry.animation I/ArcAnimator: x=134.00002===ArcM.x + ArcM.r * cos(-88.82553): 129.5 + 219.54613 * 0.020496916
01-10 04:57:03.886 11717-11717/cn.lry.animation I/ArcAnimator: y=441.0===ArcM.y + ArcM.r * sin(-88.82553): 221.5 + 219.54613 * -0.9997899
01-10 04:57:03.887 11717-11717/cn.lry.animation I/ArcAnimator: setviewX=74.000015
01-10 04:57:03.887 11717-11717/cn.lry.animation I/ArcAnimator: setviewy=381.0
第一次偏移,当前

01-10 04:57:03.917 11717-11717/cn.lry.animation I/ArcAnimator: setDegree=-88.82553
01-10 04:57:03.919 11717-11717/cn.lry.animation I/ArcAnimator: x=134.00002===ArcM.x + ArcM.r * cos(-88.82553): 129.5 + 219.54613 * 0.020496916
01-10 04:57:03.920 11717-11717/cn.lry.animation I/ArcAnimator: y=441.0===ArcM.y + ArcM.r * sin(-88.82553): 221.5 + 219.54613 * -0.9997899
01-10 04:57:03.920 11717-11717/cn.lry.animation I/ArcAnimator: setviewX=74.000015
01-10 04:57:03.920 11717-11717/cn.lry.animation I/ArcAnimator: setviewy=381.0
01-10 04:57:03.965 11717-11717/cn.lry.animation I/ArcAnimator: setDegree=-88.82553
01-10 04:57:03.966 11717-11717/cn.lry.animation I/ArcAnimator: x=134.83453===ArcM.x + ArcM.r * cos(-88.60769): 129.5 + 219.54613 * 0.024298022
01-10 04:57:03.967 11717-11717/cn.lry.animation I/ArcAnimator: y=440.98132===ArcM.y + ArcM.r * sin(-88.60769): 221.5 + 219.54613 * -0.9997048

轨迹的示意图大概如下,90度是一个四分之一圆弧,180度就是一个半圆弧