Android App开发实战项目之仿手机QQ动感影集动画播放(附源码和演示视频 可直接使用)

news2025/1/18 21:21:22

需要图片集和源码请点赞关注收藏后评论区留言~~~

动感影集就是只要用户添加一张图片,动感影集就能给每张图片渲染不同的动画效果,让原本静止的图片变得活泼起来,辅以各种精致的动画特效,营造一种赏心悦目的感觉。

一、需求描述

动感影集一边播放,一边穿插着其他动画特效,读者可前往QQ,点击左上角的头像打开个人菜单页,选择菜单项的我的相册,打开相册页面,点击相册页右上角的工具箱按钮,其中就有动感影集,进行效果测试

二、功能分析

动感影集的目的是使用动画技术呈现前后照片的动态切换效果,用到的动画必须承上启下,而且要求具备一定的视觉美感。效果包括以下几种

淡入淡出动画

灰度动画

平移动画

缩放动画

旋转动画

裁剪动画

集合动画

属性动画组合

其余动画 如百叶窗 马赛克等等

除了以上列举的动画技术,还需要考虑前后动画之间的无缝衔接,像补间动画可通过监听器AnimationListener侦听到播放完成事件,属性动画也是如此。但是对于淡入淡出动画而言,它属于图形类型,并非动画类型。因此无法通过动画事件的侦听来判断是否已经播放完成,只能利用处理器固定延迟一段时间开启下一个动画任务

动感影集的实现步骤主要包含以下三个步骤

1:编写动感影集刚开始的初始化代码

2:编写各种动画效果之间的承上启下衔接代码

3:编写动感影集末尾的集合动画代码 

 三、效果演示

演示视频如下  点击运行按钮后便会自动播放

动感影集

 

 

 

 四、代码

Java类

package com.example.animation;

import com.example.animation.widget.MosaicView;
import com.example.animation.widget.ShutterView;

import android.animation.Animator;
import android.animation.RectEvaluator;
import android.animation.Animator.AnimatorListener;
import android.animation.ObjectAnimator;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PorterDuff;
import android.graphics.Rect;
impo

import androidx.appcompat.app.AppCompatActivity;

import android.os.Looper;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class YingjiActivity extends AppCompatActivity implements AnimatorListener, AnimationListener {
    private RelativeLayout rl_yingji; // 声明一个相对布局对象
    private TextView tv_anim_title; // 声明一个文本视图对象
    private ImageView view1, view4, view5, view6; // 分别声明四个图像视图对象
    private ShutterView view2; // 声明一个百叶窗视图对象
    private MosaicView view3; // 声明一个马赛克视图对象
    // 定义一个用于播放动感影集的风景照片资源数组
    private int[] mImageArray = {
            R.drawable.bj01, R.drawable.bj02, R.drawable.bj03, R.drawable.bj04, R.drawable.bj05,
            R.drawable.bj06, R.drawable.bj07, R.drawable.bj08, R.drawable.bj09, R.drawable.bj10
    };
    private ObjectAnimator anim1, anim2, anim3, anim4; // 分别声明四个属性动画对象
    private Animation translateAnim, setAnim; // 分别声明两个补间动画对象
    private int mDuration = 5000; // 每个动画的播放时长

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_yingji);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 保持屏幕常亮
        findViewById(R.id.iv_back).setOnClickListener(v -> finish());
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText("动感影集");
        rl_yingji = findViewById(R.id.rl_yingji);
        tv_anim_title = findViewById(R.id.tv_anim_title);
        playYingji(); // 开始播放动感影集
    }

    // 开始
view1); // 往相对布局添加一个图像视图
        // 构造一个在灰度上变化的属性动画
        anim1 = ObjectAnimator.ofFloat(view1, "alpha", 0f, 1f);
        anim1.setDuration(mDuration); // 设置动画的播放时长
        anim1.addListener(this); // 给属性动画添加动画事件监听器
        anim1.start(); // 属性动画开始播放
    }

    private ImageView getImageView(LayoutParams params, int imageId) {
        ImageView iv = new ImageView(this);
        iv.setLayoutParams(params);
        iv.setImageResource(imageId);
        iv.setScaleType(ScaleType.FIT_START);
        return iv;
    }

    // 初始化各视图
    private void initView() {
        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        view1 = getImageView(params, mImageArray[0]);
        view1.setAlpha(0f); // 设置视图的灰度
        // 创建一个百叶窗视图
        view2 = new ShutterView(this);
        view2.setLayoutParams(params);
        view2.setImageBitmap(BitmapFactory.decodeResource(getResources(), mImageArray[1]));
        view2.setMode(PorterDuff.Mode.DST_OUT); // 设置百叶窗视图的绘图模式
        // 创建一个马赛克视图
        view3 = new MosaicView(this);
        view3.setLayoutParams(params);
        view3.setImageBitmap(BitmapFactory.decodeResource(getResources(), mImageArray[2]));
        view3.setMode(PorterDuff.Mode.DST_OUT); // 设置马赛克视图的绘图模式
        view3.setRatio(-5);
        view4 = getImageView(params, mImageArray[3]);
        view5 = getImageView(params, mImageArray[5]);
        view6 = getImageView(params, mImageArray[6]);
    }

    // 在属性动画开始播放时触发
    @Override
    public void onAnimationStart(Animator animation) {
        if (animation.equals(anim1)) {
            tv_anim_title.setText("正在播放灰度动画");
        } else if (animation.equals(anim2)) {
            tv_anim_title.setText("正在播放裁剪动画");
        } else if (animation.equals(anim3)) {
            tv_anim_title.setText("正在播放百叶窗动画");
        } else if (animation.equals(anim4)) {
            tv_anim_title.setText("正在播放马赛克动画");
        }
    }

    // 在属性动画结束播放时触发
    @Override
    public void onAnimationEnd(Animator animation) {
        if (animation.equals(anim1)) { // 灰度动画之后准备播放裁剪动画
            rl_yingji.addView(view2, 0);
            // 从指定资源编号的图片文件中获取位图对象
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mImageArray[0]);
            int width = view1.getWidth();
            int height = bitmap.getHeight() * width / bitmap.getWidth();
            // 构造一个从四周向中间裁剪的属性动画
            anim2 = ObjectAnimator.ofObject(view1, "clipBounds",
                    new RectEvaluator(), new Rect(0, 0, width, height),
                    new Rect(width / 2, height / 2, width / 2, height / 2));
            anim2.setDuration(mDuration); // 设置动画的播放时长
            anim2.addListener(this); // 给属性动画添加动画事件监听器
            anim2.start(); // 属性动画开始播放
        } else if (animation.equals(anim2)) { // 裁剪动画之后准备播放百叶窗动画
            rl_yingji.removeView(view1);
            rl_yingji.addView(view3, 0);
            // 构造一个按比率逐步展开的属性动画
            anim3 = ObjectAnimator.ofInt(view2, "ratio", 0, 100);
            anim3.setDuration(mDuration); // 设置动画的播放时长
            anim3.addListener(this); // 给属性动画添加动画事件监听器
            anim3.start(); // 属性动画开始播放
        } else if (animation.equals(anim3)) { // 百叶窗动画之后准备播放马赛克动画
            rl_yingji.removeView(view2);
            rl_yingji.addView(view4, 0);
            int offset = 5;
            view3.setOffset(offset); // 设置偏差比例
            // 构造一个按比率逐步展开的属性动画
            anim4 = ObjectAnimator.ofInt(view3, "ratio", 0 - offset, 101 + offset);
            anim4.setDuration(mDuration); // 设置动画的播放时长
            anim4.addListener(this); // 给属性动画添加动画事件监听器
            anim4.start(); // 属性动画开始播放
        } else if (animation.equals(anim4)) { // 马赛克动画之后准备播放淡入淡出动画
            rl_yingji.removeView(view3);
            // 淡入淡出动画需要先定义一个图形资源数组,用于变换图片
            Drawable[] drawableArray = {getDrawable(mImageArray[3]), getDrawable(mImageArray[4])};
            // 创建一个用于淡入淡出动画的过渡图形
            TransitionDrawable td_fade = new TransitionDrawable(drawableArray);
            td_fade.setCrossFadeEnabled(true); // 是否启用交叉淡入
            view4.setImageDrawable(td_fade); // 设置过渡图形
            td_fade.startTransition(mDuration); // 开始过渡转换
            tv_anim_title.setText("正在播放淡入淡出动画");
            // 延迟若干秒后启动平移动画的播放任务。平移动画跟在淡入淡出动画后面
            new Handler(Looper.myLooper()).postDelayed(() -> {
                rl_yingji.addView(view5, 0);
                // 创建一个平移动画
                translateAnim = new TranslateAnimation(0f, -view4.getWidth(), 0f, 0f);
                translateAnim.setDuration(mDuration); // 设置动画的播放时长
                translateAnim.setFillAfter(true); // 设置维持结束画面
                view4.startAnimation(translateAnim); // 平移动画开始播放
                translateAnim.setAnimationListener(this); // 给平移动画设置动画事件监听器
            }, mDuration);
        }
    }

    // 开始播放集合动画
    private void startSetAnim() {
        // 创建一个灰度动画
        Animation alpha = new AlphaAnimation(1.0f, 0.1f);
        alpha.setDuration(mDuration); // 设置动画的播放时长
        alpha.setFillAfter(true); // 设置维持结束画面
        // 创建一个平移动画
        Animation translate = new TranslateAnimation(1.0f, -200f, 1.0f, 1.0f);
        translate.setDuration(mDuration); // 设置动画的播放时长
        translate.setFillAfter(true); // 设置维持结束画面
        // 创建一个缩放动画
        Animation scale = new ScaleAnimation(1.0f, 1.0f, 1.0f, 0.5f);
        scale.setDuration(mDuration); // 设置动画的播放时长
        scale.setFillAfter(true); // 设置维持结束画面
        // 创建一个旋转动画
        Animation rotate = new RotateAnimation(0f, 360f, Animation.RELATIVE_TO_SELF,
                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        rotate.setDuration(mDuration); // 设置动画的播放时长
        rotate.setFillAfter(true); // 设置维持结束画面
        // 创建一个集合动画
        setAnim = new AnimationSet(true);
        ((AnimationSet) setAnim).addAnimation(alpha); // 给集合动画添加灰度动画
        ((AnimationSet) setAnim).addAnimation(translate); // 给集合动画添加平移动画
        ((AnimationSet) setAnim).addAnimation(scale); // 给集合动画添加缩放动画
        ((AnimationSet) setAnim).addAnimation(rotate); // 给集合动画添加旋转动画
        setAnim.setFillAfter(true); // 设置维持结束画面
        view5.startAnimation(setAnim); // 集合动画开始播放
        setAnim.setAnimationListener(this); // 给集合动画设置动画事件监听器
    }

    // 在属性动画取消播放时触发
    @Override
    public void onAnimationCancel(Animator animation) {}

    // 在属性动画重复播放时触发
    @Override
    public void onAnimationRepeat(Animator animation) {}

    // 在补间动画开始播放时触发
    @Override
    public void onAnimationStart(Animation animation) {
        if (animation.equals(translateAnim)) {
            tv_anim_title.setText("正在播放平移动画");
        } else if (animation.equals(setAnim)) {
            tv_anim_title.setText("正在播放集合动画");
        }
    }

    // 在补间动画结束播放时触发
    @Override
    public void onAnimationEnd(Animation animation) {
        if (animation.equals(translateAnim)) {
            rl_yingji.removeView(view4);
            rl_yingji.addView(view6, 0);
            startSetAnim(); // 开始播放集合动画
        } else if (animation.equals(setAnim)) {
            rl_yingji.removeView(view5);
            tv_anim_title.setText("动感影集播放结束,谢谢观看");
            view6.setOnClickListener(v -> playYingji());
        }
    }

    // 在补间动画重复播放时触发
    @Override
    public void onAnimationRepeat(Animation animation) {}

}

XML文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <include layout="@layout/title_yingji" />

    <TextView
        android:id="@+id/tv_anim_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:gravity="center"
        android:textColor="@color/black"
        android:textSize="17sp" />

    <RelativeLayout
        android:id="@+id/rl_yingji"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:background="@color/white" />

</LinearLayout>

创作不易 觉得有帮助请点赞关注收藏~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/23591.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Cadence Allegro PCB设计88问解析(十八) 之 Allegro中差分规则设置

一个学习信号完整性仿真的layout工程师 我们在进行layout设计时&#xff0c;进行会遇到差分信号的layout&#xff0c;像USB和HDMI等&#xff0c;是需要控制阻抗的&#xff0c;那么我们在走线的时候&#xff0c;也需要从电器规则和物理规则上设置差分要求&#xff0c;今天和大家…

K8S kube-scheduler-master CreateContainerError 问题解决及思路

错误信息1&#xff1a; kubectl get pods 发现pod状态一直在 runing-error-CrashLoopBackOff -循环 解决方法&#xff1a;1&#xff0c;查看日志。 kubectl logs pods web-674477549d-zx8gmkubectl describe pods web-674477549d-zx8gm 没有发现错误&#xff0c;并且服务器资源…

2023年MBA/MPA/MEM联考笔试答题抓分点

距离今年的管理类联考还有一个月左右的时间&#xff0c;在最后这个阶段&#xff0c;除了继续稳固的提升自身应试的基本能力之外&#xff0c;一些细节和技巧也要特别关注和留意&#xff0c;说不定可以在考场上帮自己更好的抓分。今天杭州达立易考教育为大家整理主观题答题的五个…

链表OJ题+牛客题

目录 206.反转链表 876.链表的中间节点 链表中倒数第k个节点 CM11链表分割 OR36 链表的回文 206.反转链表 给你单链表的头节点head,请你反转链表&#xff0c;并返回反转后的链表。 实现如下结果&#xff1a; 思路&#xff1a; 取链表中的节点头插&#xff1a; 代码&#…

操作系统的奋斗(二)

第二章 进程与线程2.1进程与线程2.1.1进程的概念、特征、状态与转换2.1.2进程的组织、控制、通信2.1.3进程和多线程模型2.2处理机调度2.2.1调度的概念、目标、实现2.2.2典型的调度算法2.2.3进程切换2.3同步与互斥2.3.1同步与互斥的基本概念2.3.2实现临界区互斥的基本办法2.3.3互…

IPv6与VoIP——配置Cisco CME实现VoIP实验

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.配置Cisco CME实现VoIP 1.实验环境 2.需要设备 Cisco P Co…

解决TypeError: type complex doesn‘t define __round__ method

在调整学习率的时候遇到的一个bug&#xff0c;现已解决。 首先看这个bug说的是&#xff1a;类型complex&#xff08;复数:ij&#xff09;类型不能使用round函数。 为啥不能使用呢&#xff1a; round函数是四舍五入&#xff0c;round(lr,10)就是取lr四舍五入后十位。而复数不能…

分享一套宾馆客房管理系统源码,功能完善,代码完整

淘源码&#xff1a;国内专业的免费源码下载平台 需要源码学习可私信 基本介绍&#xff1a; 本宾馆管理系统是一套成熟的客房管理软件,综合了国内多家同行业软件的优点。具有操作简单、功能全面。 适用于酒店、宾馆、招待所等提供住宿服务的企业。 功能简介&#xff1a; 包括入…

文献学习02_A Survey on Deep Learning for Named Entity Recognition_20221121

论文信息 Subjects: Computation and Language (cs.CL) &#xff08;1&#xff09;题目&#xff1a;A Survey on Deep Learning for Named Entity Recognition &#xff08;命名实体识别的深度学习研究综述&#xff09; &#xff08;2&#xff09;文章下载地址&#xff1a;ht…

私域流量对企业的好处

互联网商业人群在这种时代里&#xff0c;不断寻求突破&#xff0c;开拓创新&#xff0c;很好的将线上和线下结合起来&#xff0c;其中涌现了很多“互联网”模式以及一些新的概念。 比如社交电商、社群零售、私域流量等。这些新互联网商业概念&#xff0c;催生了很多大型企业&…

公众号免费网课查题方法

公众号免费网课查题方法 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xf…

charles抓包配置具体操作步骤

Charles主要功能 截取Http和Https网络封包 支持重发网络请求&#xff0c;方便后端调试 支持修改网络请求参数 支持网络请求的截获并动态修改 支持模拟慢速网络 Charles下载安装 charles下载地址&#xff1a;https://www.charlesproxy.com/download/ 注&#xff1a; 浏览…

匹配系统(下)

创建SpringCloud目录 目录 创建SpringCloud目录 创建我们的两个子项目 实现两个Interface Config网关 放行完事两个Api 封装后端逻辑 对接我们的匹配系统 修改数据库-天梯分 更改数据库对应的一些修改 实现我们匹配之后的逻辑的思路 具体实现过程 关于线程锁 来进…

【扩展阅读之编译和解释语言的区别】

扩展阅读之编译和解释语言的区别1 本节目标2 解释型语言和编译型语言3 标识符、关键字、保留字1 本节目标 知道解释型语言和编译型语言的特点知道标识符不能是关键字或保留字 2 解释型语言和编译型语言 程序语言翻译成机器语言的工具被称为翻译器。翻译器翻译的方式有两种&a…

# 自用集群搭建Cluster

Redis集群 MySQL集群 Zookeeper集群 server.1192.168.81.133:2881:3881 server.2192.168.81.133:2882:3882 server.3192.168.81.133:2883:3883 $ cd …/zkdata $ touch myid $ echo “1”>>myid echo “2”>>myid echo “3”>>myid vim zoo.cfg dataDi…

Windows操作系统 | CMD命令行查看当前用户名

文章目录概述一、定义介绍二、操作教程(一)、方法一&#xff1a;net命令查看(二)、方法二&#xff1a;echo命令查看概述 本节详细介绍在Windows操作系统下使用cmd命令查看当前的用户名 一、定义介绍 使用cmd命令查看windows系统的当前用户名。共有两种办法&#xff0c;一是自带…

swift枚举(一)

OC中的枚举 typedef NS_ENUM(NSUInteger, IFLEnum) {A, B, C}A,B, C分别默认代表0&#xff0c; 1&#xff0c; 2 关键字enum 声明枚举 而swift中的枚举则更加灵活&#xff0c;并且不需要给枚举中的每一个成员都提供值 enum IFLEnum {case onecase twocase three}let mEnum: I…

Omorn - NJ301-1100 AND NX102-9000 - Socket - TCP 通讯

目录 Omorn - NJ301-1100 AND NX102-9000 - Socket - TCP 通讯 测试案例IP 通讯验证 Omorn - NJ301-1100 AND NX102-9000 - Socket - UDP 通讯 测试案例IP 通讯验证 Omorn - NJ301-1100 AND NX102-9000 - Socket - TCP 通讯 说明&#xff1a; Socket通讯需要双方约定好…

【正点原子FPGA连载】 第四章Vivado软件的安装和使用 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第四章Vivado软件…

Design Compiler工具学习笔记(3)

目录 引言 知识储备 时钟创建 时钟偏差 时钟延迟 转换时间 输入路径约束 输出路径延迟 组合逻辑路径约束 时间预算 寄存器输出 总结 实际操作 设计文件 check_design reset_design 时序约束 check_timing compile report_constraint -all_violators remove_des…