• Android动画学习笔记大集合


      其实动画这个东西我已经了解过很长一段时间了,但是一直没系统的整理过。关于android中的各种动画虽然都会用,但总怕自己会慢慢遗忘。这回看了几篇动画分析的文章,自己也学到了一些东西,在此就梳理一下。

    参考博文如下,感谢大神们的分享:

    http://www.open-open.com/lib/view/open1329994048671.html

    http://www.tuicool.com/articles/yeM3my

    http://blog.csdn.net/singwhatiwanna/article/details/17841165

    http://blog.csdn.net/lmj623565791/article/details/38067475

    http://blog.csdn.net/lmj623565791/article/details/38092093

    注意:所有view的动画就是被限制在它的父控件中的,即使你做了view的移动,它也不可能显示在父控件的外边。也就是说父控件是一个舞台,演员可以在舞台上到处走动,但如果超过了舞台,那么观众就看不到了。

    一、View Animation(Tween Animation)

    View Animation(Tween Animation):也可称为补间动画(Tween Animation),给出两个关键帧,通过一些算法将给定属性值在给定的时间内在两个关键帧间渐变。

    View animation只能应用于View对象,而且只支持一部分属性,如支持缩放旋转而不支持背景颜色的改变。

    对于View animation,它只是改变了View对象绘制的位置,注意这是“绘制”,而不是实际的位置。比如你让一个button变成它的两倍大小,但是它能接受你点击的区域还是原来的button区域,没有做任何改变。

    View Animation支持设定多种动画样式,也可以设定这些动画的执行顺序,支持通过xml和代码两种方式来设置动画效果。

    动画举例

    【 By XML 】

    <?xml version="1.0" encoding="utf-8"?>  
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:shareInterpolator="true"
        android:startOffset="50">  
    
        <alpha
            android:duration="200"
            android:fromAlpha="1.0"
            android:toAlpha="0.0" />
    </set>
         Animation aimation = AnimationUtils.loadAnimation(this, R.anim.anim);
            final ImageView imageView = (ImageView) findViewById(R.id.imageView_id);
            imageView.startAnimation(aimation);

    可以对动画添加监听器

        aimation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
    
                }
    
                @Override
                public void onAnimationEnd(Animation animation) {
                    animation = null;
    
                }
    
                @Override
                public void onAnimationRepeat(Animation animation) {
    
                }
            });

    如果你是用AnimationSet设置动画的话,animationSet也是继承自Animation,所以也有setAnimationListener的方法

    详细代码可以参考:http://www.cnblogs.com/tianzhijiexian/p/3983616.html

    【 By JAVA 】

    通过java代码来做的,可以参考这篇文章。其实也就是各种类继承Animation,然后有自己独特的方法,也可以通过AnimationSet进行各种设置。

    http://www.cnblogs.com/tianzhijiexian/p/3981241.html

    二、Drawable Animation(Frame Animation)

    Drawable Animation(Frame Animation):帧动画,就像GIF图片,通过一系列Drawable依次显示来模拟动画的效果。在XML中的定义方式如下:

    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="true">
        <item android:drawable="@drawable/rocket_thrust1" android:duration="200" />     <item android:drawable="@drawable/rocket_thrust2" android:duration="200" />     <item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
    </animation-list>

    必须以<animation-list>为根元素,以<item>表示要轮换显示的图片,duration属性表示各项显示的时间。XML文件要放在/res/drawable/目录下。

    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
    setContentView(R.layout.main); imageView
    = (ImageView) findViewById(R.id.imageView1); imageView.setBackgroundResource(R.drawable.drawable_anim); AnimationDrawable anim = (AnimationDrawable) imageView.getBackground(); } public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { anim.stop(); anim.start(); return true; } return super.onTouchEvent(event); }

     我在实验中遇到两点问题:

    • 要在代码中调用Imageview的setBackgroundResource方法,如果直接在XML布局文件中设置其src属性当触发动画时会FC。
    • 在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次。
    • 最后一点是SDK中提到的,不要在onCreate中调用start,因为AnimationDrawable还没有完全跟Window相关联,如果想要界面显示时就开始动画的话,可以在onWindowFoucsChanged()中调用start()。

    三、Property Animation

     属性动画,这个是在Android 3.0中才引进的,它更改的是对象的实际属性,如Button的缩放,Button的位置与大小属性值都改变了。而且Property Animation不止可以应用于View,还可以应用于任何对象(Object)。Property Animation只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是你自己决定的。

    在Property Animation中,可以对动画应用以下属性:

    • Duration:动画的持续时间,单位ms
    • TimeInterpolation:属性值的计算方式,如先快后慢,是一个接口。用来设置插值器
    • TypeEvaluator:根据属性的开始、结束值与TimeInterpolation计算出的因子计算出“当前”时间的属性值,这个值可以在AnimationUpdate中得到
    • Repeat Country and behavoir:重复次数与方式,如播放3次、5次、无限循环,可以此动画一直重复,或播放完时再反向播放
    • Animation sets:动画集合,即可以同时对一个对象应用几个动画,这些动画可以同时播放也可以对不同动画设置不同开始偏移
    • Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认为10ms,最终刷新时间还受系统进程调度与硬件的影响

     

    四、ValueAnimator

    ValueAnimator包含Property Animation动画的所有核心功能,如动画时间,开始、结束属性值,相应时间属性值计算方法等。它其实就是一个计算器,并不能实际执行动画效果。它可以计算出动画要执行的时间,每隔几毫秒刷新一次等等,但具体如何执行动画,它是不管的。你需要在ValueAnimator的ValueAnimator.onUpdateListener监听器中进行设置。

    这里给ValueAnimator对象设置了一个值,因为是一个值,所以会默认为是最终的结果值,动画默认从当前的值到你设定的这个值。如果你设定两个值,那么它就意味着是从第一个值到另一个值进行动画。

         // set one value,it is final value.
            // If you set two values in it.It means animation will start from first value to Second value.
            ValueAnimator animator = ValueAnimator.ofFloat(1f);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    
                }
            });
        animator.start();

    这个AnimationUpdateListener将会在动画执行过程中触发。如果你没在这里做任何处理,那么即使是执行了(start())也不会有任何动画的。

    实际运用——自由落体 & 抛物线

     

    如果我希望小球抛物线运动【实现抛物线的效果,水平方向100px/s,垂直方向加速度200px/s*s 】,分析一下,貌似只和时间有关系,但是根据时间的变化,横向和纵向的移动速率是不同的,我们该咋实现呢?此时就要重写TypeValue的时候了,因为我们在时间变化的同时,需要返回给对象两个值,x当前位置,y当前位置。

    /**
         * 抛物线
         * @param view
         */
        public void paowuxian(View view)
        {
    
            ValueAnimator valueAnimator = new ValueAnimator();
            valueAnimator.setDuration(3000);
            valueAnimator.setObjectValues(new PointF(0, 0));
            valueAnimator.setInterpolator(new LinearInterpolator());
            valueAnimator.setEvaluator(new TypeEvaluator<PointF>()
            {
                // fraction = t / duration
                @Override
                public PointF evaluate(float fraction, PointF startValue,
                        PointF endValue)
                {
                    Log.e(TAG, fraction * 3 + "");
                    // x方向200px/s ,则y方向0.5 * 10 * t
                    PointF point = new PointF();
                    point.x = 200 * fraction * 3;
                    point.y = 0.5f * 200 * (fraction * 3) * (fraction * 3);
                    return point;
                }
            });
    
            valueAnimator.start();
            valueAnimator.addUpdateListener(new AnimatorUpdateListener()
            {
                @Override
                public void onAnimationUpdate(ValueAnimator animation)
                {
                    PointF point = (PointF) animation.getAnimatedValue();
                    mBlueBall.setX(point.x);
                    mBlueBall.setY(point.y);
    
                }
            });
        }

    可以看到,因为ofInt,ofFloat等无法使用,我们自定义了一个TypeValue,每次根据当前时间返回一个PointF对象,(PointF和Point的区别就是x,y的单位一个是float,一个是int;RectF,Rect也是)PointF中包含了x、y的当前位置,然后我们在监听器中获取,动态设置属性。

    五、ObjectAnimator

    实际应用中一般都会用ObjectAnimator来产生某一对象的动画,但用ObjectAnimator有一定的限制,要想使用ObjectAnimator,应该满足以下条件:

    • 对象应该有一个setter函数:set<PropertyName>(驼峰命名法)
    • 如上面的例子中,像ofFloat之类的工场方法,第一个参数为对象名,第二个为属性名,后面的参数为可变参数,如果values…参数只设置了一个值的话,那么会假定为目的值,属性值的变化范围为当前值到目的值,为了获得当前值,该对象要有相应属性的getter方法:get<PropertyName>
    • 如果有getter方法,其应返回值类型应与相应的setter方法的参数类型一致。

      如果上述条件不满足,则不能用ObjectAnimator,应用ValueAnimator代替。

    下面是将imageview进行透明度渐变的例子。

        ObjectAnimator oa=ObjectAnimator.ofFloat(imageview, "alpha", 0f, 1f);
        oa.setDuration(3000);
        oa.start();

    根据应用动画的对象或属性的不同,可能需要在onAnimationUpdate函数中调用invalidate()函数刷新视图。

    下面分析下属性动画的原理:

    属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据你传递的该熟悉的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。总结一下,你对object的属性xxx做动画,如果想让动画生效,要同时满足两个条件:

    • object必须要提供setXxx方法,如果动画的时候没有传递初始值,那么还要提供getXxx方法,因为系统要去拿xxx属性的初始值(如果这条不满足,程序直接Crash)
    • object的setXxx对属性xxx所做的改变必须能够通过某种方法反映出来,比如会带来ui的改变啥的(如果这条不满足,动画无效果但不会Crash)

    以上条件缺一不可

     那么如果我们的object没办法满足这个条件呢?比如button的setWidth方法仅仅是设置它的最小宽度(minWidth),对于实际宽度不会产生任何影响。如果让它实际改变就需要用

    btn.getLayoutParams().width = xxx;

    来设置,但这种通过方法来获得public值的方法又不适合ObjecAnimator的应用场景,改怎么办呢?

    针对上述问题,Google告诉我们有3中解决方法:

    1. 给你的对象加上get和set方法,如果你有权限的话

    对于View我们没权限给其添加各种属性,所以这种方法仅仅适合于自己定义的Object对象

    2. 用一个类来包装原始对象,间接为其提供get和set方法

    这个方法很好,你可以写一个类继承那个Object对象,在子类中添加各种set、get方法,更好的办法是写一个包装类,传入一个object对象,然后给其添加各种方法。

        private void performAnimate() {
            ViewWrapper wrapper = new ViewWrapper(mButton);
            ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
        }
    
        @Override
        public void onClick(View v) {
            if (v == mButton) {
                performAnimate();
            }
        }
    
        private static class ViewWrapper {
            private View mTarget;
    
            public ViewWrapper(View target) {
                mTarget = target;
            }
    
            public int getWidth() {
                return mTarget.getLayoutParams().width;
            }
    
            public void setWidth(int width) {
                mTarget.getLayoutParams().width = width;
                mTarget.requestLayout();
            }
        }

    上面类中ViewWrapper这个内部类就是一个包装类,它的构造函数传入一个View对象,然后提供了set和get方法,这样我们就可以对它进行动画的操作了

    3. 采用ValueAnimator,监听动画过程,自己实现属性的改变

     这个方法就有些别扭了,但扩展性是最强的。ValueAnimator我们在上面已经介绍过了,下面是在动画执行过程中能的得到的东西。

    ValueAnimator animator = ValueAnimator.ofFloat(1, 1);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    Integer currentValue = (Integer)animation.getAnimatedValue(); // current Value(0~100)
    
                    float fraction = valueAnimator.getAnimatedFraction(); // progress of animation
                }
            });

    getAnimatedFraction() 这个可以得到当前动画的进度,得到的是浮点型数据很有用。api12中加入的,感觉比getAnimationValue有用

    getAnimatedValue(String propertyName) 得到某个特定属性当前的值,这个可用性就很高了。如果有特殊要求可以用它

    其余的属性都在下面了,看名字就知道是什么意思啦~

    实际运用

    这个例子是将一个view进行移动和拉伸的动画。涉及了多个属性,当你要让view选装或者是移动的时候,请务必设定PivotX,PivotY来指定坐标,如果不设定的话,移动和选择默认的中心点都是Object的中心

    public void startViewSimpleAnim(final View fromView,Rect finalBounds,int 
                startOffsetY,int finalOffsetY, float startAlpha, float finalAlpha) {
            Rect startBounds  = new Rect();
            startBounds.set(Position.getGlobalVisibleRect(fromView));
            //设置偏移量
            startBounds.offset(0, -startOffsetY);
            finalBounds.offset(0, -finalOffsetY);
            //设定拉伸或者旋转动画的中心位置,这里是相对于自身左上角
            fromView.setPivotX(0f);
            fromView.setPivotY(0f);
            //计算拉伸比例
            float scaleX = (float)finalBounds.width() / startBounds.width();
            float scaleY = (float)finalBounds.height() / startBounds.height();
            
            AnimatorSet set = new AnimatorSet();
            ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(fromView, "alpha", startAlpha, finalAlpha);
            ObjectAnimator xAnim = ObjectAnimator.ofFloat(fromView, "x", startBounds.left, finalBounds.left);
            ObjectAnimator yAnim = ObjectAnimator.ofFloat(fromView, "y", startBounds.top, finalBounds.top);
            ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(fromView, View.SCALE_X, 1f, scaleX);
            ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(fromView, View.SCALE_Y,1f, scaleY);
            
            set.play(alphaAnim).with(xAnim).with(yAnim).with(scaleXAnim).with(scaleYAnim);
            
            set.setStartDelay(mStartDelay);
            set.setDuration(mAnimTime);
            set.setInterpolator(mInterpolator);
            set.addListener(new AnimListener(fromView,null));
    
            set.start();
        }

    如果你操作对象的该属性方法里面,比如上例的setRotationX如果内部没有调用view的重绘,则你需要自己按照下面方式手动调用。

    anim.addUpdateListener(new AnimatorUpdateListener()
            {
                @Override
                public void onAnimationUpdate(ValueAnimator animation)
                {
    //                view.postInvalidate();
    //                view.invalidate();
                }
            });

    用xml文件来创建属性动画

    大家肯定都清楚,View Animator 、Drawable Animator都可以在anim文件夹下创建动画,然后在程序中使用,甚至在Theme中设置为属性值。当然了,属性动画其实也可以在文件中声明。

    首先在res下建立animator文件夹,然后建立res/animator/scalex.xml

    <?xml version="1.0" encoding="utf-8"?>
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="1000"
        android:propertyName="scaleX"
        android:valueFrom="1.0"
        android:valueTo="2.0"
        android:valueType="floatType" >
    </objectAnimator>

    然后通过动画工具类就可以加载xml中的动画文件了

     AnimatorInflater.loadAnimator(this, R.anim.anim);

    public void scaleX(View view)
        {
            // 加载动画
            Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scalex);
            anim.setTarget(mMv);
            anim.start();
        }

    纵向与横向同时缩放

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:ordering="together" >
    
        <objectAnimator
            android:duration="1000"
            android:propertyName="scaleX"
            android:valueFrom="1"
            android:valueTo="0.5" >
        </objectAnimator>
        <objectAnimator
            android:duration="1000"
            android:propertyName="scaleY"
            android:valueFrom="1"
            android:valueTo="0.5" >
        </objectAnimator>
    
    </set>

    使用set标签,有一个orderring属性设置为together,【还有另一个值:sequentially(表示一个接一个执行)】

    六、通过AnimatorSet来控制多个动画

    AnimatorSet和AnimationSet类似,可以操作多个动画属性。AnimationSet提供了一个把多个动画组合成一个组合的机制,并可设置组中动画的时序关系,如同时播放,顺序播放等。

    例子01:

    AnimatorSet bouncer = new AnimatorSet();
    bouncer.play(anim1).before(anim2);
    bouncer.play(anim2).with(anim3);
    bouncer.play(anim2).with(anim4)
    bouncer.play(anim5).after(amin2);
    animatorSet.start();
    1. 播放anim1;
    2. 同时播放anim2,anim3,anim4;
    3. 播放anim5。

    例子02:

    animSet.playTogether(anim1, anim2);  
    animSet.start();  

    使用playTogether两个动画同时执行,当然还有playSequentially依次执行

    例子03:

         /** 
             * anim1,anim2,anim3同时执行 
             * anim4接着执行 
             */  
            AnimatorSet animSet = new AnimatorSet();  
            animSet.play(anim1).with(anim2);  
            animSet.play(anim2).with(anim3);  
            animSet.play(anim4).after(anim3);  
            animSet.setDuration(1000);  
            animSet.start(); 

    如果我们有一堆动画,如何使用代码控制顺序,比如1,2同时;3在2后面;4在1之前

    注意animSet.play().with();也是支持链式编程的,但是不要想太多。

    比如:animSet.play(anim1).with(anim2).before(anim3).before(anim5);

    这样是不行的,系统不会根据你写的这一长串来决定先后的顺序,所以麻烦你按照上面例子的写法,多写几行

     

    七、ViewPropertyAnimator

     如果需要对一个View的多个属性进行动画可以用ViewPropertyAnimator类,该类对多属性动画进行了优化,会合并一些invalidate()来减少刷新视图,该类在3.1中引入。

    例子01:

    以下两段代码实现同样的效果

    PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
    PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
    ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
    myView.animate().x(50f).y(100f);

    例子02:

    在SDK12的时候,给View添加了animate方法,更加方便的实现动画效果。

     btn.animate().x(200).y(200).alpha(0f); 就可以实现动画效果了~

    例子03:

    简单的使用mBlueBall.animate().alpha(0).y(mScreenHeight / 2).setDuration(1000).start()就能实现动画~~不过需要SDK11,此后在SDK12,SDK16又分别添加了withStartAction和withEndAction用于在动画前,和动画后执行一些操作。当然也可以.setListener(listener)等操作。

    
    
    package com.example.zhy_property_animation;
    
    import android.animation.ObjectAnimator;
    import android.animation.PropertyValuesHolder;
    import android.app.Activity;
    import android.os.Bundle;
    import android.util.DisplayMetrics;
    import android.util.Log;
    import android.view.View;
    import android.widget.ImageView;
    
    public class ViewAnimateActivity extends Activity
    {
        protected static final String TAG = "ViewAnimateActivity";
    
        private ImageView mBlueBall;
        private float mScreenHeight;
    
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.view_animator);
    
            DisplayMetrics outMetrics = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
            mScreenHeight = outMetrics.heightPixels;
            mBlueBall = (ImageView) findViewById(R.id.id_ball);
    
        }
    
        public void viewAnim(View view)
        {
            // need API12
            mBlueBall.animate()//
                    .alpha(0)//
                    .y(mScreenHeight / 2).setDuration(1000)
                    // need API 12
                    .withStartAction(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            Log.e(TAG, "START");
                        }
                        // need API 16
                    }).withEndAction(new Runnable()
                    {
    
                        @Override
                        public void run()
                        {
                            Log.e(TAG, "END");
                            runOnUiThread(new Runnable()
                            {
                                @Override
                                public void run()
                                {
                                    mBlueBall.setY(0);
                                    mBlueBall.setAlpha(1.0f);
                                }
                            });
                        }
                    }).start();
        }                                                                                                                                                  }

    八、TypeEvalutors<T>

    根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值,Android提供了以下几个evalutor:

    • IntEvaluator:属性的值类型为int;
    • FloatEvaluator:属性的值类型为float;
    • ArgbEvaluator:属性的值类型为十六进制颜色值;
    • TypeEvaluator:一个接口,可以通过实现该接口自定义Evaluator。

      自定义TypeEvalutor很简单,只需要实现一个方法,如FloatEvalutor的定义:

    public class FloatEvaluator implements TypeEvaluator {
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            float startFloat = ((Number) startValue).floatValue();
            return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
        }
    }

    根据动画执行的时间跟应用的Interplator,会计算出一个0~1之间的因子,即evalute函数中的fraction参数,通过上述FloatEvaluator应该很好看出其意思。

    九、当Layout改变时应用动画

    ViewGroup中的子元素可以通过setVisibility使其Visible、Invisible或Gone,当有子元素可见性改变时,可以向其应用动画。

    通过LayoutTransition类的常量(第一个参数)可以区分动画的类型,第二个参数为Animator。

    • APPEARING        当一个元素变为Visible时对其应用的动画
    • CHANGE_APPEARING   当一个元素变为Visible时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画
    • DISAPPEARING      当一个元素变为InVisible时对其应用的动画
    • CHANGE_DISAPPEARING 当一个元素变为Gone时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画 disappearing from the container.
    mTransition.setAnimator(LayoutTransition.DISAPPEARING, customDisappearingAnim);

    更多的解释

    • 过渡的类型一共有四种:
    • LayoutTransition.APPEARING 当一个View在ViewGroup中出现时,对此View设置的动画
    • LayoutTransition.CHANGE_APPEARING 当一个View在ViewGroup中出现时,对此View对其他View位置造成影响,对其他View设置的动画
    • LayoutTransition.DISAPPEARING  当一个View在ViewGroup中消失时,对此View设置的动画
    • LayoutTransition.CHANGE_DISAPPEARING 当一个View在ViewGroup中消失时,对此View对其他View位置造成影响,对其他View设置的动画
    • LayoutTransition.CHANGE 不是由于View出现或消失造成对其他View位置造成影响,然后对其他View设置的动画。
    • 注意动画到底设置在谁身上,此View还是其他View。

    详细的例子,请参考:http://blog.csdn.net/lmj623565791/article/details/38092093

    顺便说下:Android 3.0已经原生支持了Animating Layout,当Layout变化的时候,系统会根据Layout变化前后,自动显示动画。

    只要给layout添加:android:animateLayoutChanges="true"即可,而且动画还可以通过setLayoutTransition()自己定义。

    参考博文如下:

    http://www.open-open.com/lib/view/open1329994048671.html

    http://www.tuicool.com/articles/yeM3my

    http://blog.csdn.net/singwhatiwanna/article/details/17841165

    http://blog.csdn.net/lmj623565791/article/details/38067475

    http://blog.csdn.net/lmj623565791/article/details/38092093

  • 相关阅读:
    vue-以文件流-blob-的形式-下载-导出文件
    vue-element-upload 文件上传打开选择文件弹框前进行提示或操作
    django-创建Template(模板)
    django-配置url
    django-创建页面
    django创建应用及应用模块解释
    django创建项目及目录介绍
    django的安装
    Python2X学习16-python-列表
    Request
  • 原文地址:https://www.cnblogs.com/tianzhijiexian/p/4156303.html
Copyright © 2020-2023  润新知