AR 眼镜之-拍照/录像动效切换-实现方案

目录

📂 前言

AR 眼镜系统版本

拍照/录像动效切换

1. 🔱 技术方案

1.1 技术方案概述

1.2 实现方案

1)第一阶段动效

2)第二阶段动效

2. 💠 默认代码配置

2.1 XML 初始布局

2.2 监听滑动对 View 改变

3. ⚛️ 拍照/录像动效切换实现

3.1 第一阶段动效

1)左移右边部分的 View

2)放大右边部分的 View

3.2 第二阶段动效

1)动态调整右边部分的约束

2)缩小右边部分的 View

3)从左往右移动左边部分

4)从 0 到 1 透明度增加左边部分

5)动画集实现

6)还原默认约束

4. ✅ 小结

附录1:动效帮助类代码


📂 前言

AR 眼镜系统版本

        W517 Android9。

拍照/录像动效切换

        实现效果如上 GIF 的左下角所示,我们看到主要分为:两部分、两阶段。

        两部分:左边部分为 Normal 状态 View,右边部分为带有文字描述的 View。

        两阶段:右边部分,分为变大阶段、缩小阶段;在右边部分的第二缩小阶段时,会触发左边部分的从左往右移动阶段、从 0 到 1 透明度增加阶段。

1. 🔱 技术方案

1.1 技术方案概述

        拍照/录像动效切换主要使用属性动画完成,同时对于放大和缩小的参考方向不同,所以需要动态调整约束,动态调整约束时还需注意 maigin 值,因为文字改变尺寸也会变化。

1.2 实现方案

1)第一阶段动效
  1. 左移右边部分的 View;

  2. 放大右边部分的 View。

2)第二阶段动效
  1. 动态调整右边部分的约束;

  2. 缩小右边部分的 View;

  3. 从左往右移动左边部分;

  4. 从 0 到 1 透明度增加左边部分。

2. 💠 默认代码配置

2.1 XML 初始布局

        norIcon 是左边部分 View,focLayout 是右边部分 View。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true">

    <ImageView
        android:id="@+id/norIcon"
        android:layout_width="80dp"
        android:layout_height="104dp"
        android:layout_marginStart="24dp"
        android:layout_marginBottom="24dp"
        android:background="@drawable/shape_34343a_corner_20dp"
        android:contentDescription="@null"
        android:paddingHorizontal="24dp"
        android:paddingVertical="36dp"
        android:src="@drawable/ic_camera_video_nor"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <LinearLayout
        android:id="@+id/focLayout"
        android:layout_width="wrap_content"
        android:layout_height="104dp"
        android:layout_marginStart="110dp"
        android:background="@drawable/shape_34343a_corner_20dp"
        android:gravity="center"
        android:minWidth="200dp"
        android:orientation="vertical"
        android:paddingStart="12dp"
        android:paddingEnd="16dp"
        app:layout_constraintBottom_toBottomOf="@id/norIcon"
        app:layout_constraintStart_toStartOf="parent">

        <ImageView
            android:id="@+id/focIcon"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:contentDescription="@null"
            android:src="@drawable/ic_camera_picture_foc" />

        <com.agg.ui.AGGTextView
            android:id="@+id/focText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="6dp"
            android:gravity="center"
            android:singleLine="true"
            android:text="@string/tap_to_photo"
            android:textColor="#FCC810"
            android:textSize="24sp"
            app:UITypeface="Bold" />
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

2.2 监听滑动对 View 改变

    /**
     * 往前滑动:切换为录像模式/拍照模式
     */
    override fun scrollForward() {
        if (AnimatorSwitchHelper.isAnimating) {
            Log.e(TAG, "scrollForward: 滑动过快")
            return
        }

        Log.i(TAG, "scrollForward: model=$mIsVideoModel,isRecordingVideo=${isRecording()}")

        if (mIsVideoModel) {
            if (isRecording()) stopRecord()

            switchToPhoto()
            mIsVideoModel = false
            binding.tips.text = getString(R.string.swipe_forward_to_video_model)
            binding.norIcon.setImageResource(R.drawable.ic_camera_video_nor)
            binding.focIcon.setImageResource(R.drawable.ic_camera_picture_foc)
            binding.focText.text = getString(R.string.tap_to_photo)
        } else {
            switchToVideo()
            mIsVideoModel = true
            binding.tips.text = getString(R.string.swipe_forward_to_photo_model)
            binding.norIcon.setImageResource(R.drawable.ic_camera_picture_nor)
            binding.focIcon.setImageResource(R.drawable.ic_camera_video_foc)
            binding.focText.text = getString(R.string.tap_to_record)
        }
        binding.tips.visibility = VISIBLE

        AnimatorSwitchHelper.startAnimator(binding)
    }

3. ⚛️ 拍照/录像动效切换实现

3.1 第一阶段动效

1)左移右边部分的 View
binding.focLayout.x = binding.focLayout.x - 86
2)放大右边部分的 View
val defWidth = binding.focLayout.width
val focBgBigAnim = ValueAnimator.ofInt(defWidth, defWidth + 86).apply {
            addUpdateListener { animation ->
                val width = animation.animatedValue as Int
                val layoutParams = binding.focLayout.layoutParams as ViewGroup.LayoutParams
                layoutParams.width = width
                binding.focLayout.layoutParams = layoutParams
            }
        }

3.2 第二阶段动效

1)动态调整右边部分的约束

        第一阶段在 XML 中默认配置的是 layout_constraintStart_toStartOf="parent",能保证放大时以左边为锚点从左往右放大;而第二阶段缩小时需要以右边为锚点,此时需要动态改变约束如下:

private fun changeConstraint(binding: ActivityMainBinding) {
    Log.i(TAG, "changeConstraint: ")
    val focLayoutId = R.id.focLayout
    val constraintLayout = binding.parent
    ConstraintSet().apply {
        // 修改约束
        clone(constraintLayout)
        // 清除原有的约束
        clear(focLayoutId, ConstraintSet.START)
        // 设置新的约束
        connect(
            focLayoutId,
            ConstraintSet.END,
            ConstraintSet.PARENT_ID,
            ConstraintSet.END,
            (binding.focLayout.context.resources.displayMetrics.widthPixels - binding.focLayout.x - binding.focLayout.width - 86).toInt()
        )
        // 自动播放过渡动画——取消播放,与自定义动画重复
//            TransitionManager.beginDelayedTransition(constraintLayout)
        // 应用新的约束
        applyTo(constraintLayout)
    }
}
2)缩小右边部分的 View
val focBgSmallAnim = ValueAnimator.ofInt(defWidth + 86, defWidth).apply {
    addUpdateListener { animation ->
        val width = animation.animatedValue as Int
        val layoutParams = binding.focLayout.layoutParams as ViewGroup.LayoutParams
        layoutParams.width = width
        binding.focLayout.layoutParams = layoutParams
    }
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(p0: Animator) {
            Log.i(TAG, "onAnimationEnd: focBgSmallAnim")
            isAnimating = false
        }
    })
}
3)从左往右移动左边部分
val norBgTransAnim = ObjectAnimator.ofFloat(binding.norIcon, "translationX", -80f, 0f)
4)从 0 到 1 透明度增加左边部分
val norBgAlphaAnim = ObjectAnimator.ofFloat(binding.norIcon, "alpha", 0f, 1f)
5)动画集实现
AnimatorSet().apply {
    playSequentially(focBgBigAnim, focBgSmallAnim)
    playTogether(focBgSmallAnim, norBgTransAnim, norBgAlphaAnim)
    duration = 1000
    start()
}
6)还原默认约束

        动效做完后需要还原默认约束,保证下次动效的正常进行。

if (!isFirstSwitch) restoreConstraint(binding)

private fun restoreConstraint(binding: ActivityMainBinding) {
    Log.i(TAG, "restoreConstraint: ")
    val focLayoutId = R.id.focLayout
    val constraintLayout = binding.parent
    ConstraintSet().apply {
        clone(constraintLayout)
        clear(focLayoutId, ConstraintSet.END)
        connect(
            focLayoutId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, 110
        )
        applyTo(constraintLayout)
    }
}

        具体动效类的代码,参考附录1。

4. ✅ 小结

        对于拍照/录像动效切换,本文只是一个基础实现方案,更多业务细节请参考产品逻辑去实现。

        另外,由于本人能力有限,如有错误,敬请批评指正,谢谢。


附录1:动效帮助类代码

object AnimatorSwitchHelper {

    private val TAG = AnimatorSwitchHelper::class.java.simpleName
    var isAnimating = false
    var isFirstSwitch = true

    fun startAnimator(binding: ActivityMainBinding) {
        Log.i(TAG, "startAnimator: isAnimating=$isAnimating,isFirstSwitch=$isFirstSwitch")
        isAnimating = true
        val defWidth = binding.focLayout.width
        if (!isFirstSwitch) restoreConstraint(binding)
        if (isFirstSwitch) binding.focLayout.x = binding.focLayout.x - 86

        // 1. 放大Foc的View
        val focBgBigAnim = ValueAnimator.ofInt(defWidth, defWidth + 86).apply {
            addUpdateListener { animation ->
                val width = animation.animatedValue as Int
                val layoutParams = binding.focLayout.layoutParams as ViewGroup.LayoutParams
                layoutParams.width = width
                binding.focLayout.layoutParams = layoutParams
            }
            addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(p0: Animator) {
                    Log.i(TAG, "onAnimationEnd: focBgBigAnim")
                    // 为绘制反向动画,需修改约束方向
                    changeConstraint(binding)
                    isFirstSwitch = false
                }
            })
        }
        // 2.1 缩小Foc的View
        val focBgSmallAnim = ValueAnimator.ofInt(defWidth + 86, defWidth).apply {
            addUpdateListener { animation ->
                val width = animation.animatedValue as Int
                val layoutParams = binding.focLayout.layoutParams as ViewGroup.LayoutParams
                layoutParams.width = width
                binding.focLayout.layoutParams = layoutParams
            }
            addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(p0: Animator) {
                    Log.i(TAG, "onAnimationEnd: focBgSmallAnim")
                    isAnimating = false
                }
            })
        }
        // 2.2 从左往右移动Nor的View
        val norBgTransAnim = ObjectAnimator.ofFloat(binding.norIcon, "translationX", -80f, 0f)
        // 2.3 透明度渐显Nor的View
        val norBgAlphaAnim = ObjectAnimator.ofFloat(binding.norIcon, "alpha", 0f, 1f)

        AnimatorSet().apply {
            playSequentially(focBgBigAnim, focBgSmallAnim)
            playTogether(focBgSmallAnim, norBgTransAnim, norBgAlphaAnim)
            duration = 1000
            start()
        }
    }

    private fun changeConstraint(binding: ActivityMainBinding) {
        Log.i(TAG, "changeConstraint: ")
        val focLayoutId = R.id.focLayout
        val constraintLayout = binding.parent
        ConstraintSet().apply {
            // 修改约束
            clone(constraintLayout)
            // 清除原有的约束
            clear(focLayoutId, ConstraintSet.START)
            // 设置新的约束
            connect(
                focLayoutId,
                ConstraintSet.END,
                ConstraintSet.PARENT_ID,
                ConstraintSet.END,
                (binding.focLayout.context.resources.displayMetrics.widthPixels - binding.focLayout.x - binding.focLayout.width - 86).toInt()
            )
            // 自动播放过渡动画——取消播放,与自定义动画重复
//            TransitionManager.beginDelayedTransition(constraintLayout)
            // 应用新的约束
            applyTo(constraintLayout)
        }
    }

    private fun restoreConstraint(binding: ActivityMainBinding) {
        Log.i(TAG, "restoreConstraint: ")
        val focLayoutId = R.id.focLayout
        val constraintLayout = binding.parent
        ConstraintSet().apply {
            clone(constraintLayout)
            clear(focLayoutId, ConstraintSet.END)
            connect(
                focLayoutId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, 110
            )
            applyTo(constraintLayout)
        }
    }

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTML5实现好看的端午节网页源码

HTML5实现好看的端午节网页源码 前言一、设计来源1.1 网站首页界面1.2 登录注册界面1.3 端午节由来界面1.4 端午节习俗界面1.5 端午节文化界面1.6 端午节美食界面1.7 端午节故事界面1.8 端午节民谣界面1.9 联系我们界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 H…

Android使用系统消息与定时器实现霓虹灯效果

演示效果: 界面设计: 在帧布局FrameLayout中添加6个TextView 依次设置这6个TextView的宽&#xff0c;高&#xff0c;权重 也可在XML中直接设置 添加自定义颜色 关联自定义颜色到数组变量 关联6个TextView控件到数组变量 处理自定义系统消息 Handler _sysHandler new Han…

多活架构的实现原理与应用场景解析

一、多活架构为何如此重要? 企业的业务运营与各类线上服务紧密相连,从日常的购物消费、社交娱乐,到金融交易、在线教育等关键领域,无一不依赖于稳定可靠的信息系统。多活架构的重要性愈发凸显,它宛如一位忠诚的卫士,为业务的平稳运行保驾护航。 回想那些因系统故障引发的…

【JVM-2.2】使用JConsole监控和管理Java应用程序:从入门到精通

在Java应用程序的开发和运维过程中&#xff0c;监控和管理应用程序的性能和资源使用情况是非常重要的。JConsole是Java Development Kit&#xff08;JDK&#xff09;自带的一款图形化监控工具&#xff0c;它可以帮助开发者实时监控Java应用程序的内存、线程、类加载以及垃圾回收…

《自动驾驶与机器人中的SLAM技术》ch2:基础数学知识

目录 2.1 几何学 向量的内积和外积 旋转矩阵 旋转向量 四元数 李群和李代数 SO(3)上的 BCH 线性近似式 2.2 运动学 李群视角下的运动学 SO(3) t 上的运动学 线速度和加速度 扰动模型和雅可比矩阵 典型算例&#xff1a;对向量进行旋转 典型算例&#xff1a;旋转的复合 2.3 …

如何使用高性能内存数据库Redis

一、详细介绍 1.1、Redis概述 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、内存中的数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。Redis支持多种类型的数据结构&#xff0c;如字符串&#xff08;strings&#xff09;、哈希&am…

C++ vtordisp的应用场景

文章目录 问题代码1. 基本概念回顾2. 应用场景虚继承与虚函数并存的类层次结构 3. 编译器相关考虑 问题代码 #include <iostream> using namespace std;class base { public:base() {}virtual void show() { cout << "base:: show"<<endl; } priv…

数据安全与隐私:Facebook在技术创新中的新挑战

在数字化高速发展的今天&#xff0c;数据安全与隐私保护成为社会关注的核心议题之一。作为全球最大的社交媒体平台之一&#xff0c;Facebook&#xff08;现为Meta&#xff09;在技术创新和用户体验优化的同时&#xff0c;也面临着前所未有的数据安全挑战。​ 技术创新中的数据…

SQL从入门到实战-2

高级语句 窗口函数 排序窗口函数 例题二十九 select yr,party,votes, rank() over (PARTITION BY yr ORDER BY votes desc) as pson from ge where constituency S14000021 order by party,yr 偏移分析函数 例题三十 select name,date_format(whn,%Y-%m-%d) data, confi…

爬虫基础之爬取歌曲宝歌曲批量下载

声明&#xff1a;本案列仅供学习交流使用 任何用于非法用途均与本作者无关 需求分析: 网站:邓紫棋-mp3在线免费下载-歌曲宝-找歌就用歌曲宝-MP3音乐高品质在线免费下载 (gequbao.com) 爬取 歌曲名 歌曲 实现歌手名称下载所有歌曲 本案列所使用的模块 requests (发送…

django基于Python对西安市旅游景点的分析与研究

基于Django框架和Python语言对西安市旅游景点进行的分析与研究&#xff0c;是一个结合现代Web技术和数据分析能力的综合性项目。 一、项目背景与意义 随着旅游业的快速发展&#xff0c;对旅游景点的深入分析和研究变得越来越重要。西安市作为中国历史文化名城&#xff0c;拥有…

spring boot 集成 knife4j

1、knife4j介绍以及环境介绍 knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名knife4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!其底层是对Springfox的封装&#xff0c;使用方式也和Springfox一致&#xff0c;只是对接口…

Apache Hadoop YARN框架概述

一、YARN产生和发展简史 1.1背景 数据、程序、运算资源&#xff08;内存、CPU&#xff09;三者组在一起&#xff0c;才能完成数据的计算处理过程。在单机环境下&#xff0c;三者之间协调配合不是太大问题。为了应对海量数据的处理场景&#xff0c;Hadoop软件出现并提供了分布…

妙用编辑器:把EverEdit打造成一个编程学习小环境

1 妙用编辑器&#xff1a;把EverEdit打造成一个编程学习小环境 1.1 应用场景 最近在学习Python语言&#xff0c;由于只是学习和练习&#xff0c;代码规模很小&#xff0c;不想惊动PyCharm、VSCode、WingIDE这些重型武器&#xff0c;只想轻快的敲些代码&#xff0c;记事本虽好&…

使用RSyslog将Nginx Access Log写入Kafka

个人博客地址&#xff1a;使用RSyslog将Nginx Access Log写入Kafka | 一张假钞的真实世界 环境说明 CentOS Linux release 7.3.1611kafka_2.12-0.10.2.2nginx/1.12.2rsyslog-8.24.0-34.el7.x86_64.rpm 创建测试Topic $ ./kafka-topics.sh --zookeeper 192.168.72.25:2181/k…

笔记本电脑 选购 回收 特权模式使用 指南

笔记本电脑 factor 无线网卡&#xff1a;有些笔记本无法检测到特定频段的信息&#xff0c;会导致连不上校园网 sudo iwlist wlp2s0 scan | grep Frequency > net.txt cat net.txt>表示用终端输出覆盖后续文件&#xff0c;>>表示添加到后续文件的末尾 一种更简…

【python A* pygame 格式化 自定义起点、终点、障碍】

pip install pygame 空格键&#xff1a;运行 A* 算法。CtrlC 键&#xff1a;清空路径。CtrlS 键&#xff1a;保存当前地图到 map.json 文件。CtrlL 键&#xff1a;从 map.json 文件加载地图。 import pygame import json from queue import PriorityQueue from tkinter import…

Mac——Docker desktop安装与使用教程

摘要 本文是一篇关于Mac系统下Docker Desktop安装与使用教程的博文。首先介绍连接WiFi网络&#xff0c;然后详细阐述了如何在Mac上安装Docker&#xff0c;包括下载地址以及不同芯片版本的选择。接着讲解了如何下载基础镜像和指定版本镜像&#xff0c;旨在帮助用户在Mac上高效使…

OpenCV的对比度受限的自适应直方图均衡化算法

OpenCV的对比度受限的自适应直方图均衡化&#xff08;CLAHE&#xff09;算法是一种图像增强技术&#xff0c;旨在改善图像的局部对比度&#xff0c;同时避免噪声的过度放大。以下是CLAHE算法的原理、步骤以及示例代码。 1 原理 CLAHE是自适应直方图均衡化&#xff08;AHE&…

解决Qt打印中文字符出现乱码

在 Windows 平台上&#xff0c;默认的控制台编码可能不是 UTF-8&#xff0c;这可能会导致中文字符的显示问题。 下面是在 Qt 应用程序中设置中文字体&#xff0c;并确保控制台输出为 UTF-8 编码&#xff1a; 1. Qt 应用程序代码 在 Qt 中&#xff0c;我们可以使用 QApplic…