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

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

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

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

一、需求描述

动感影集一边播放,一边穿插着其他动画特效,读者可前往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>

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

相关文章
|
5月前
|
编解码 安全 Android开发
如何修复 Android 和 Windows 不支持视频编解码器的问题?
视频播放时遇到“编解码器不支持”错误(如0xc00d36c4或0xc00d5212)是常见问题,即使文件格式为MP4或MKV。编解码器是编码和解码数据的工具,不同设备和版本支持不同的编解码器。解决方法包括:1) 安装所需编解码器,如K-Lite Codec Pack;2) 使用自带编解码器的第三方播放器,如VLC、KMPlayer等。这些方法能帮助你顺利播放视频。
|
3月前
|
存储 文件存储 Android开发
仿第八区APP分发下载打包封装系统源码
该系统为仿第八区APP分发下载打包封装系统源码,支持安卓、iOS及EXE程序分发,自动判断并稳定安装。智能提取应用信息,自动生成PLIST文件和图标,提供合理的点数扣除机制。支持企业签名在线提交、专属下载页面生成、云端存储(阿里云、七牛云),并优化签名流程,支持中文包及合并分发,确保高效稳定的下载体验。 [点击查看源码](https://download.csdn.net/download/huayula/90463452)
240 22
|
4月前
|
机器学习/深度学习 存储 人工智能
MNN-LLM App:在手机上离线运行大模型,阿里巴巴开源基于 MNN-LLM 框架开发的手机 AI 助手应用
MNN-LLM App 是阿里巴巴基于 MNN-LLM 框架开发的 Android 应用,支持多模态交互、多种主流模型选择、离线运行及性能优化。
3114 21
MNN-LLM App:在手机上离线运行大模型,阿里巴巴开源基于 MNN-LLM 框架开发的手机 AI 助手应用
|
4月前
|
小程序 搜索推荐
2025同城线下陪玩APP开发/电竞游戏平台搭建游戏陪玩APP源码/语音APP开发
线下陪玩约玩APP旨在满足现代人的社交、兴趣分享、专业指导及休闲娱乐需求。用户可通过平台结识新朋友、找到志同道合的伙伴,并享受高质量的陪玩服务。平台提供用户注册登录、陪玩师筛选与预约、实时沟通等功能,支持个性化游戏体验和高效匹配。
150 0
2025同城线下陪玩APP开发/电竞游戏平台搭建游戏陪玩APP源码/语音APP开发
|
4月前
|
前端开发 Java 测试技术
语音app系统软件源码开发搭建新手启蒙篇
在移动互联网时代,语音App已成为生活和工作的重要工具。本文为新手开发者提供语音App系统软件源码开发的启蒙指南,涵盖需求分析、技术选型、界面设计、编码实现、测试部署等关键环节。通过明确需求、选择合适的技术框架、优化用户体验、严格测试及持续维护更新,帮助开发者掌握开发流程,快速搭建功能完善的语音App。
|
4月前
|
安全 JavaScript 前端开发
小游戏源码开发之可跨app软件对接是如何设计和开发的
小游戏开发团队常需应对跨平台需求,为此设计了成熟的解决方案。流程涵盖游戏设计、技术选型、接口设计等。首先明确游戏功能与特性,选择合适的技术架构和引擎(如Unity或Cocos2d-x)。接着设计通用接口,确保与不同App的无缝对接,并制定接口规范。开发过程中实现游戏逻辑和界面,完成登录、分享及数据对接功能。最后进行测试优化,确保兼容性和性能,发布后持续维护更新。
|
5月前
|
缓存 前端开发 IDE
【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
93 0
【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
5月前
|
小程序 IDE PHP
圈子源码如何打包生成App小程序/开发一个圈子系统软件所需要的费用体现在哪里?
将PHP源码打包成App的过程涉及多个步骤和技术选择。以圈子源码为例,首先明确需求,确定App功能和目标用户群体,并根据需求开发小程序页面,如用户注册、圈子列表等。源码准备阶段确保源码适用于小程序开发,环境配置需安装IDE(如微信开发者工具)及依赖库。最后在IDE中打包小程序并上传至管理平台,通过审核后发布。费用方面,模板开发成本较低,定制开发则更高,具体取决于需求复杂度和第三方服务费用。
176 0
|
6月前
|
PHP
全新uniapp小说漫画APP小说源码/会员阅读/月票功能
价值980的uniapp小说漫画APP小说源码/会员阅读/月票功能
268 20
|
4月前
|
Web App开发 前端开发 安全
语音交友app系统源码功能及技术研发流程剖析
语音交友App核心功能包括语音聊天(一对一、群聊、语音消息)、语音房间(直播、主题房、管理)、社交互动(好友、关注、打赏)、内容发现、音效美化、通知提醒及安全隐私等。开发流程涵盖需求分析、技术选型(前端、后端、数据库、实时通信)、UI/UX设计、前后端开发、实时通信集成、音效处理、测试优化、部署上线及运营维护,确保稳定高效运行并持续优化用户体验。

热门文章

最新文章