SpringAnimation详解

Introduction to SpringAnimation with examples

SpringAnimation详解与例子(Android StudioJava 实现)

翻译部分(这部分是翻译,是翻译

说明

本文翻译自 原文链接
原文使用Kotlin实现的,源代码见SpringAnimation源代码Kotlin实现
本文改为了Java。
p1
r1
s1

你曾经想过在Android上做一个像上面这样的弹性动画吗?如果你有,那你一定会很开心!
(开不开心不知道,反正觉得还挺困难的,哈哈哈)

Dynamic-animation(动态动画)是Android支持库25.3.0版本中引入的一个新模块。它提供了几个类来制作基于物理的真实视图动画。

你可能会说“不管怎样,我只需要在我的动画上添加一个BounceInterpolator或OvershootInterpolator就可以了”。事实上,这两个效果看起来并不是很好。当然,你也可以编写自己的插值器或实现一个完整的自定义动画——但现在有一个更简单的方法。

Classes(它的类,也是文字说明)

在写这篇文章的时候,这个模块只包含4个类。让我们看看他们的文档描述.

1.DynamicAnimation<T extends DynamicAnimation<T>>
原文
实际上现在都使用AndroidX,这个库也不再维护了。
这个类是基于物理的动画的基础类。它管理动画的生命周期,比如start()和cancel()。这个基类还处理所有子类动画的公共设置。例如,DynamicAnimation库支持添加DynamicAnimation.OnAnimationEndListener 和DynamicAnimation.OnAnimationUpdateListener,以便重要的动画事件可以通过回调来监听。DynamicAnimation的任何子类的启动条件都可以使用setStartValue(float)和setStartVelocity(float)来设置。

2.DynamicAnimation.ViewProperty
原文
还是使用AndroidX,反正似乎android都不再维护这几个库了。且看看它是用来干啥的。
ViewProperty持有View属性的访问权限。也就是说当你使用DynamicAnimation创建动画时,视图的相应属性值将通过这个ViewProperty实例更新。

我们来看看经典动画的参数:

ALPHA(透明度), ROTATION(旋转度,一般是弧度),ROTATION_X, ROTATION_Y, 
SCALE_X(缩放x的倍数), SCALE_Y(缩放y的倍数), 
SCROLL_X, SCROLL_Y, (滑动,表示其子控件与左、上之间的差值,换句话说就比如你屏幕内容过多,使用这个可以让你滑动着看,比如说现在这行字已经很多了,你可以拖动黑色背景下的滑块来查看后面的文字)
TRANSLATION_X(平移x个单位), TRANSLATION_Y, TRANSLATION_Z, 
X, Y, Z(坐标)

3.SpringAnimation

什么叫SpringAnimation呢?春天动画吗?当然不是,看了很多资料,它被翻译为弹簧动画,说到弹簧,你又想起了胡克定律(力学弹性理论)。没错,SpringAnimation就是一个由SpringForce(弹力)驱动的动画。
弹簧力定义了弹簧的刚度、阻尼比以及静止位置。一旦启动了SpringAnimation,在每一帧上,弹簧力将更新动画的值和速度。动画将继续运行,直到弹簧力达到平衡。如果在动画中使用的弹簧是无阻尼的,动画将永远不会达到平衡。相反,它将永远振荡。

4 .SpringForce
弹簧力定义了动画中使用的弹簧的特性.
通过配置刚度和阻尼比,调用者可以创建一个外观和感觉适合他们的用例的弹簧。刚度对应于弹簧常数。弹簧越硬,拉伸就越困难,它经受的阻尼也就越快。
s

弹簧阻尼比描述了系统在受到扰动后振动是如何衰减的。当阻尼比> 1 (即过阻尼)时,物体将迅速返回到静止位置而不会超调(overshooting:或者翻译为过冲量)。如果阻尼比等于1(即临界阻尼),物体将在最短的时间内恢复平衡。当阻尼比小于1(即欠阻尼)时,质量倾向于超调,并返回,再次超调。没有任何阻尼(即阻尼比= 0),质量将永远振荡。
这个类有3个参数:

finalPosition:弹簧的静止位置(或角度/刻度)。

stiffness:刚度
刚度对应于弹簧常数。弹簧越硬,拉伸就越困难,它经受的阻尼也就越快。刚度越高,物体沉降越快。

dampingRatio:阻尼比。
弹簧阻尼比描述了系统在受到扰动后振动是如何衰减的。

SpringForce有4个预定义的浮动常量用于刚度和阻尼比,但也可以设置自定义值,自己实现。
根据值的不同,我们的对象将:

  1. 围绕静止位置永远振荡。
  2. 围绕其静止位置振荡,直到静止。
  3. 轻停,很快(肉眼不可见的快)。
  4. 快速停止,没有过冲。

正如你所看到的,这个包目前非常小。如果你在寻找一些更复杂的弹簧动态,看看 Facebook’s Rebound library,可能需要科学上网。

注意
DynamicAnimation不扩展Animation,所以你不能只是替换一个或在AnimationSet中使用它。不过不要担心,整个过程还是很简单的。

来张沙雕柴柴图,哈哈哈,开始代码过程了。以下代码均为自己实际操作了,Java版本的,要看Kotlin的自己去原文看,本文开头附上链接地址了哈。
s

样例以及详解

这部分为本人自己操作过程记录。源码会放在文章末尾。本来想赚点积分的(但是每次看到下载要积分就很崩溃,但是附上下载链接了,大家不要下载啊(反话)),想想还是传到GitHub上吧,免费开源最香,不给花积分的机会。

1.新建一个空项目

file->new project,选择空项目(empty),够简单的吧。
s

2.添加依赖项

依赖项,打开你的buil.gradle,注意,是app目录下的,不是gradle目录下的。将项目调成project模式,它长这样:
s
注意,添加完代码后需要同步一下,大概我写出来的sync红色位置,点击,如果build成功,它就成功了。

implementation 'androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03'

如果添加依赖项没成功怎么办,可能是版本不对。你这样添加:点击file,选择Project Structure
在这里插入图片描述
在搜索框里输入库名,搜索一下,添加进来即可(一般搜索到的就是和你android studio版本适配的库版本了。)
s

3.创建弹簧动画

//创建弹性动画类SpringAnimation
        SpringAnimation animation = new SpringAnimation(view, property);
        //SpringForce类,定义弹性特质
        SpringForce spring = new SpringForce(finalPosition);
        spring.setStiffness(stiffness);//刚度
        spring.setDampingRatio(dampingRatio);//阻尼比
        //关联弹性特质
        animation.setSpring(spring);
        animation.start();//启动动画

将其移动到函数中去,当要创建动画时调用它即可。

    @SuppressLint("Range")
    SpringAnimation createSpringAnimation(View view,
                                          DynamicAnimation.ViewProperty property,
                                          Float finalPosition,
                                          @FloatRange(from = 0.0) Float stiffness,
                                          @FloatRange(from = 0.0) Float dampingRatio) {
        //创建弹性动画类SpringAnimation
        SpringAnimation animation = new SpringAnimation(view, property);
        //SpringForce类,定义弹性特质
        SpringForce spring = new SpringForce(finalPosition);
        spring.setStiffness(stiffness);//
        spring.setDampingRatio(dampingRatio);
        //关联弹性特质
        animation.setSpring(spring);
        return animation;
    }

4.Position,第一幅动画演示。

PositionActivity.java

package com.example.mydef20;

import androidx.annotation.FloatRange;
import androidx.appcompat.app.AppCompatActivity;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;

public class PositionActivity extends AppCompatActivity {
    float stiffNess= SpringForce.STIFFNESS_MEDIUM;//硬度,可设
    float damp=SpringForce.DAMPING_RATIO_HIGH_BOUNCY;//阻尼比
    SpringAnimation xAnimation;
    SpringAnimation yAnimation;//坐标
    View mView;
    float dX=0f;
    float dY=0f;
    //创建一个动画对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_position);
        mView=findViewById(R.id.PView);
        mView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                xAnimation=createSpringAnimation(mView,SpringAnimation.X,mView.getX(),stiffNess,damp);
                yAnimation=createSpringAnimation(mView,SpringAnimation.Y,mView.getY(),stiffNess,damp);
                mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });//确定初始位置,确定后移除监听器
        mView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getActionMasked()){
                    case MotionEvent.ACTION_DOWN:
                        dX=v.getX()-event.getRawX();//计算距离
                        dY=v.getY()-event.getRawY();
                        xAnimation.cancel();
                        yAnimation.cancel();//按住图片
                        break;
                    case MotionEvent.ACTION_MOVE:
                        mView.animate()
                                .x(event.getRawX()+dX)
                                .y(event.getRawY()+dY)
                                .setDuration(0)
                                .start();
                        break;
                    case MotionEvent.ACTION_UP:
                        xAnimation.start();
                        yAnimation.start();
                        break;
                    default:
                        break;
                }
                return true;
            }
        });
    }
    @SuppressLint("Range")
    SpringAnimation createSpringAnimation(View view,
                                          DynamicAnimation.ViewProperty property,
                                          Float finalPosition,
                                          @FloatRange(from = 0.0) Float stiffness,
                                          @FloatRange(from = 0.0) Float dampingRatio) {
        //创建弹性动画类SpringAnimation
        SpringAnimation animation = new SpringAnimation(view, property);
        //SpringForce类,定义弹性特质
        SpringForce spring = new SpringForce(finalPosition);
        spring.setStiffness(stiffness);//
        spring.setDampingRatio(dampingRatio);
        //关联弹性特质
        animation.setSpring(spring);
        return animation;
    }
}

activity_position.xml
src那里是引入图片资源,img0是我自己放置的一张图片,你也可以放你自己的图片。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PositionActivity">
    <ImageView
        android:id="@+id/PView"
        android:layout_width="128dp"
        android:layout_height="128dp"
        android:layout_gravity="center"
        android:src="@drawable/img0"
        tools:ignore="ContentDescription"/>
</FrameLayout>

效果演示:
在这里插入图片描述
csdn不对劲,传了好久都失败额,图片不知道动不动得了,要不去GitHub上看吧。
附上这张阔爱的猫咪老师图片:这就是drawable下的img0了
img

5.Rotation,第二幅动画演示。

RotationActivity.java

package com.example.mydef20;

import androidx.annotation.FloatRange;
import androidx.appcompat.app.AppCompatActivity;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;

import android.annotation.SuppressLint;
import android.icu.lang.UProperty;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

public class RotationActivity extends AppCompatActivity {
    float stiffNess= SpringForce.STIFFNESS_MEDIUM;//硬度,可设
    float damp=SpringForce.DAMPING_RATIO_HIGH_BOUNCY;//阻尼比
    float spine=0f;//旋转角度,负表示逆时针,正代表顺时针
    TextView rTextView;
    ImageView rView;
    SpringAnimation rotationAnimation;
    float curRotation=0f,preRotation=0f;
    float x,y;
    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rotation);
        rTextView= findViewById(R.id.rTextView);
        rView=findViewById(R.id.RView);
        updateRotationText();
        rotationAnimation=createSpringAnimation(rView,SpringAnimation.ROTATION,spine,stiffNess,damp);
        rotationAnimation.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
            @Override
            public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
                updateRotationText();
            }
        });
        final float cx=rView.getWidth()/2f;
        final float cy=rView.getHeight()/2f;
        rView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getActionMasked()){
                    case MotionEvent.ACTION_DOWN:
                        x= event.getX();
                        y= event.getY();
                        //rotationAnimation.cancel();//
                        UCRotation(v,x,y,cx,cy);
                        rotationAnimation.cancel();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        x=event.getX();
                        y= event.getY();//检测鼠标移动,注意,会相对当前image的位置进行旋转
                        preRotation=curRotation;
                        UCRotation(v,x,y,cx,cy);
                        float angle=curRotation-preRotation;
                        float tmp=angle+v.getRotation();
                        v.setRotation(tmp);
                        updateRotationText();
                        break;
                    case MotionEvent.ACTION_UP:
                        rotationAnimation.start();
                        break;
                    default:
                        break;
                }
                return true;
            }
        });
    }
    public void UCRotation(View v,float tx,float ty,float ex,float ey){
        curRotation=v.getRotation()+(float) (Math.toDegrees(Math.atan2(tx - ex, ey - ty)));
    }
    private void updateRotationText(){
        @SuppressLint("DefaultLocale")
        String context=String.format("%.3f", rView.getRotation());
        rTextView.setText(context);
    }
    @SuppressLint("Range")
    SpringAnimation createSpringAnimation(View view,
                                          DynamicAnimation.ViewProperty property,
                                          Float rangle,
                                          @FloatRange(from = 0.0) Float stiffness,
                                          @FloatRange(from = 0.0) Float dampingRatio) {
        //创建弹性动画类SpringAnimation
        SpringAnimation animation = new SpringAnimation(view, property);
        //SpringForce类,定义弹性特质
        SpringForce spring = new SpringForce(rangle);
        spring.setStiffness(stiffness);//
        spring.setDampingRatio(dampingRatio);
        //关联弹性特质
        animation.setSpring(spring);
        return animation;
    }
}

activity_rotation.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".RotationActivity">

    <ImageView
        android:id="@+id/RView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@drawable/ic_rotation"
        tools:ignore="ContentDescription"/>
    <TextView
        android:id="@+id/rTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginBottom="192dp"/>
</FrameLayout>

演示:s

6.Scale,第三幅动画演示。

ScaleActivity.java

package com.example.mydef20;

import androidx.annotation.FloatRange;
import androidx.appcompat.app.AppCompatActivity;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.SeekBar;

public class ScaleActivity extends AppCompatActivity {
    private SeekBar damping;
    private SeekBar stiffness;//阻尼、硬度
    View sView;
    SpringAnimation xAnimation;
    SpringAnimation yAnimation;//坐标
    float dX=0f;
    float dY=0f;
    float sX;
    float sY;
    float vxDis;
    float vyDis;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scale);
        //调整seekbar,设置硬度范围
        stiffness=(SeekBar) findViewById(R.id.stiffness);
        stiffness.setMax((int) SpringForce.STIFFNESS_HIGH);
        stiffness.setProgress((int) SpringForce.STIFFNESS_VERY_LOW);
        //设置阻尼范围,因为阻尼系数范围是0.0-1.0,所以乘以1000可视化为seekbar,转化为整数
        damping=(SeekBar) findViewById(R.id.damping);
        //damping.setMax((int) (SpringForce.DAMPING_RATIO_NO_BOUNCY*1000));
        damping.setProgress((int) (SpringForce.DAMPING_RATIO_HIGH_BOUNCY*1000));

        sView=findViewById(R.id.SView);
        sView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                vxDis=sView.getRight()-sView.getLeft();
                vyDis=sView.getBottom()-sView.getTop();
                sView.setPivotX(vxDis/2);
                sView.setPivotY(vyDis/2);
                xAnimation=createSpringAnimation(sView,SpringAnimation.SCALE_X,sView.getScaleX(),getStiffness(),getDamp());
                yAnimation=createSpringAnimation(sView,SpringAnimation.SCALE_Y,sView.getScaleY(),getStiffness(),getDamp());
                sView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });//确定初始位置,确定后移除监听器
        sView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getActionMasked()){
                    case MotionEvent.ACTION_DOWN:

                        sX=vxDis/2-event.getRawX();//计算鼠标到当前视图中心的距离
                        sY=vyDis/2-event.getRawY();
                        xAnimation.getSpring().setStiffness(getStiffness());
                        yAnimation.getSpring().setStiffness(getStiffness());
                        xAnimation.getSpring().setDampingRatio(getDamp());
                        yAnimation.getSpring().setDampingRatio(getDamp());
//                        xAnimation.cancel();
//                        yAnimation.cancel();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if((vxDis/2-event.getRawX())/sX >= 0 && (vyDis/2-event.getRawY())/sY >= 0){
                            sView.animate()
                                    .scaleX((vxDis/2-event.getRawX())/sX)
                                    .scaleY((vyDis/2-event.getRawY())/sY)
                                    .setDuration(0)
                                    .start();
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        xAnimation.getSpring().setFinalPosition(1);
                        yAnimation.getSpring().setFinalPosition(1);
                        xAnimation.start();
                        yAnimation.start();
                        break;
                    default:
                        break;
                }
                return true;
            }
        });
    }

    @SuppressLint("Range")
    SpringAnimation createSpringAnimation(View view,
                                          DynamicAnimation.ViewProperty property,
                                          Float scaleChange,
                                          @FloatRange(from = 0.0) Float stiffness,
                                          @FloatRange(from = 0.0) Float dampingRatio) {
        //创建弹性动画类SpringAnimation
        SpringAnimation animation = new SpringAnimation(view, property);
        //SpringForce类,定义弹性特质
        SpringForce spring = new SpringForce(scaleChange);
        spring.setStiffness(stiffness);//
        spring.setDampingRatio(dampingRatio);
        //关联弹性特质
        animation.setSpring(spring);
        return animation;
    }
    private float getStiffness(){
        return Math.max(stiffness.getProgress(),1f);
    }
    private float getDamp(){
        return damping.getProgress()/1000f;
    }
}

activity_scale.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ScaleActivity"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/SView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scaleType="centerCrop"
        android:layout_marginTop="20dp"
        android:src="@drawable/img0"
        tools:ignore="ContentDescription"/>
<!--    android:layout_gravity="center"-->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_marginTop="20dp"
        android:text="stiffness"
        android:textSize="9sp"/>

    <SeekBar
        android:id="@+id/stiffness"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:maxHeight="20dp"
        android:minHeight="20dp"
        android:thumb="@drawable/ic_tag"
        android:progressTint="#CE3BF4" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="damping"
        android:textSize="9sp"/>

    <SeekBar
        android:id="@+id/damping"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:maxHeight="20dp"
        android:minHeight="20dp"
        android:thumb="@drawable/ic_tag"
        android:progressTint="#F4811C"
        />

</LinearLayout>

演示:
s

7.MainActivity

package com.example.mydef20;

import androidx.annotation.FloatRange;
import androidx.appcompat.app.AppCompatActivity;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    private Button poButton;
    private Button roButton;
    private Button scButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        poButton=(Button) findViewById(R.id.positionButton);
        roButton=(Button) findViewById(R.id.rotationButton);
        scButton=(Button) findViewById(R.id.scaleButton);
        poButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent();
                intent.setClass(MainActivity.this,PositionActivity.class);
                startActivity(intent);
            }
        });
        roButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent();
                intent.setClass(MainActivity.this,RotationActivity.class);
                startActivity(intent);
            }
        });
        scButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent();
                intent.setClass(MainActivity.this,ScaleActivity.class);
                startActivity(intent);
            }
        });
    }
}

activity_main.xml

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">
    <Button
        android:id="@+id/positionButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Position"
        android:textAllCaps="false"/>
    <Button
        android:id="@+id/rotationButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Rotation"
        android:textAllCaps="false"/>
    <Button
        android:id="@+id/scaleButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Scale"
        android:textAllCaps="false"/>

</LinearLayout>

我的源码

csdn下载链接:CSDN下载链接

GitHub:github链接

有什么问题欢迎指出。初学Android,有什么书籍大家也可以在评论区推荐一下,谢谢啦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值