安卓属性动画实现图片慢慢在界面慢慢消失

时间:2024-05-23 10:56:45

在安卓开发过程中,突然遇到一个需求:需要模仿newsela的更改文章难度刷新文章内容的动画。实现思路:截取手机当前显示的屏幕,然后先展示给用户,然后通过动画的形式慢慢的让截屏消失,展示截图背后的屏幕内容。
然后一直百度,希望能看到别人实现的效果,但是很失望,一直都没有找到正好符合需求的实现效果,在安卓开源的这么大好情景下,我觉得需要把动画的动态图发出来,希望大家一起和我一样,让安卓开发更快更强。
下面是我学制作gif图的过程,手机下载一个“录屏大师”,然后把录制好的视频通过手机发到电脑,然后通过电脑在线mp4转gif图
安卓属性动画实现图片慢慢在界面慢慢消失
直接贴上实现代码:实现思路就是通过Imageview设置负的buttomMargin来达到慢慢不可见的效果,同时慢慢的改变外层控件的高度来达到不改变显示的布局文件。
布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <RelativeLayout
        android:id="@+id/rl_screen_shot_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <android.support.v7.widget.AppCompatImageView
            android:id="@+id/iv_screen_shot_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/timg"/>
    </RelativeLayout>
    <android.support.v7.widget.AppCompatButton
        android:id="@+id/btn_start_animation"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_50"
        android:text="动态改变图片大小"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"/>
</android.support.constraint.ConstraintLayout>

动画实现代码:

class ChangeBitmapHeightActivity : AppCompatActivity() {

    private var mTopToButtom: Boolean = false
    private lateinit var mShotScreenLayout: RelativeLayout
    private lateinit var mShotScreenView: AppCompatImageView
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_change_height)
        mShotScreenLayout = findViewById(R.id.rl_screen_shot_layout)
        mShotScreenView = findViewById(R.id.iv_screen_shot_image)


        val phoneScreenShot = ScreenShotUtil.getPhoneScreenShot(this)
        mShotScreenView.setImageBitmap(phoneScreenShot)
        findViewById<View>(R.id.btn_start_animation).setOnClickListener {
            //动态裁剪图片,然后重新设置给ImageView显示
            if(mTopToButtom) {
                mTopToButtom = false
                refreshPictureButtomToTop(mShotScreenLayout.height,0)
            }else{
                mTopToButtom = true
                refreshPictureTopToButtom(0, mShotScreenLayout.height)
            }
        }
    }

    private fun refreshPictureButtomToTop(startY: Int, endY: Int) {
        val valueAnimator = ValueAnimator.ofInt(startY, endY)
        valueAnimator.duration = 2000 * 6
        valueAnimator.addUpdateListener { animation ->
            val animatedValue = animation.animatedValue as Int
            Log.e(">>>>>>>>>>>>>", "animatedValue:$animatedValue")
            val layoutParams = ConstraintLayout.LayoutParams(-1, animatedValue)
            mShotScreenLayout.layoutParams = layoutParams
        }
        valueAnimator.start()
        
        val valueAnimatorTwo = ValueAnimator.ofInt(endY, startY)
        valueAnimatorTwo.duration = 2000 * 6
        valueAnimatorTwo.addUpdateListener { animation ->
            val animatedValue = animation.animatedValue as Int
            Log.e(">>>>>>>>>>>>>", "animatedValue:$animatedValue")
            val imageViewParams = RelativeLayout.LayoutParams(-1, -1)
            imageViewParams.bottomMargin = -animatedValue
            mShotScreenView.layoutParams = imageViewParams
        }
        valueAnimatorTwo.start()
    }

    private fun refreshPictureTopToButtom(startY: Int, endY: Int) {
        val valueAnimator = ValueAnimator.ofInt(startY, endY)
        valueAnimator.duration = 2000 * 6
        valueAnimator.addUpdateListener { animation ->
            val animatedValue = animation.animatedValue as Int
            Log.e(">>>>>>>>>>>>>", "animatedValue:$animatedValue")
            val layoutParams = ConstraintLayout.LayoutParams(-1, -1)
            layoutParams.topMargin = animatedValue
            mShotScreenLayout.layoutParams = layoutParams
        }
        valueAnimator.start()

        val valueAnimatorTwo = ValueAnimator.ofInt(startY, endY)
        valueAnimatorTwo.duration = 2000 * 6
        valueAnimatorTwo.addUpdateListener { animation ->
            val animatedValue = animation.animatedValue as Int
            Log.e(">>>>>>>>>>>>>", "animatedValue:$animatedValue")
            val imageViewParams = RelativeLayout.LayoutParams(-1, -1)
            imageViewParams.topMargin = -animatedValue
            mShotScreenView.layoutParams = imageViewParams
        }
        valueAnimatorTwo.start()
    }
}

截屏代码实现:

 /**
     * 截取屏幕的高度
     * @return
     */
    public static Bitmap getBitmapByView(View view) {
        Bitmap bitmap = null;
        if (view != null){
             bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.RGB_565);
              final Canvas canvas = new Canvas(bitmap);
              view.draw(canvas);
          }
          return bitmap;
    }

这里需要注意截屏的代码实现不能使用下面这两种:

 /**
     * 获取屏幕截屏
     * @param activity
     * @return
     */
    public static Bitmap getPhoneScreenShot(Activity activity) {
        if(activity != null) {
            View view = activity.getWindow().getDecorView();
            view.setDrawingCacheEnabled(true);
            view.buildDrawingCache();
            return view.getDrawingCache();
        }else{
            return null;
        }
    }

上面这种截屏获取方式只能获取一次手机截屏,下次获取到的截屏对象是同一个屏幕截图,图片不可变。
在安卓的动画中,我们知道有帧动画可以实现界面动画,比如星星一闪一闪的效果;平移动画可以让一个控件在屏幕的一个点移动到另一个点;旋转动画可以设置一个控件按某一个坐标点进行旋转;还有渐变动画可以让一个控件从透明状态到显示状态。到现在为止,我已经说了4种动画了,这四种动画我们可以知道是比较平常的动画效果的实现方式。当然我们也可以使用AnimationSet类设置多种动画的组合实现,但是前提是需要明确需求,动画的效果不能太过于复杂,否则我们是无法实现那么绚丽多彩的动画功能的,就算生搬硬套,实现后看起来也会很死板,无法再做过多的要求。
安卓3.0之后,我们迎来了安卓的属性动画,属性动画,顾名思义就是可以通过动态的设置控件的属性,从而来实现安卓的动态效果。这样我们会发现,安卓属性动画几乎可以实现前面我们说的所有动画的动画效果,而且实现代码并不复杂,只需要创建一个属性动画,然后设置一个你需要动态设置的控件属性范围,获取到属性动画的参数值设置给你需要实现动画效果的控件,然后开启动画,这样控件的动画效果就实现了。

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);  
anim.setDuration(300);  
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
    @Override  
    public void onAnimationUpdate(ValueAnimator animation) {  
        float currentValue = (float) animation.getAnimatedValue();  
        Log.d("TAG", "cuurent value is " + currentValue);  
    }  
});  
anim.start();

属性动画可以设置动画的运行速度:

android:interpolator="@android:anim/accelerate_interpolator" 设置动画为加速动画(动画播放中越来越快)  
  
android:interpolator="@android:anim/decelerate_interpolator" 设置动画为减速动画(动画播放中越来越慢)  
  
android:interpolator="@android:anim/accelerate_decelerate_interpolator" 设置动画为先加速在减速(开始速度最快 逐渐减慢)  
  
android:interpolator="@android:anim/anticipate_interpolator" 先反向执行一段,然后再加速反向回来(相当于我们弹簧,先反向压缩一小段,然后在加速弹出)  
  
android:interpolator="@android:anim/anticipate_overshoot_interpolator" 同上先反向一段,然后加速反向回来,执行完毕自带回弹效果(更形象的弹簧效果)  
  
android:interpolator="@android:anim/bounce_interpolator" 执行完毕之后会回弹跳跃几段(相当于我们高空掉下一颗皮球,到地面是会跳动几下)  
  
android:interpolator="@android:anim/cycle_interpolator" 循环,动画循环一定次数,值的改变为一正弦函数:Math.sin(2* mCycles* Math.PI* input)  
  
android:interpolator="@android:anim/linear_interpolator" 线性均匀改变  
  
android:interpolator="@android:anim/overshoot_interpolator" 加速执行,结束之后回弹