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"><ImageViewandroid: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" /><LinearLayoutandroid: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"><ImageViewandroid:id="@+id/focIcon"android:layout_width="32dp"android:layout_height="32dp"android:contentDescription="@null"android:src="@drawable/ic_camera_picture_foc" /><com.agg.ui.AGGTextViewandroid: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 = falsebinding.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 = truebinding.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 = VISIBLEAnimatorSwitchHelper.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 Intval layoutParams = binding.focLayout.layoutParams as ViewGroup.LayoutParamslayoutParams.width = widthbinding.focLayout.layoutParams = layoutParams}}

3.2 第二阶段动效

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

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

private fun changeConstraint(binding: ActivityMainBinding) {Log.i(TAG, "changeConstraint: ")val focLayoutId = R.id.focLayoutval constraintLayout = binding.parentConstraintSet().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 Intval layoutParams = binding.focLayout.layoutParams as ViewGroup.LayoutParamslayoutParams.width = widthbinding.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 = 1000start()
}
6)还原默认约束

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

if (!isFirstSwitch) restoreConstraint(binding)private fun restoreConstraint(binding: ActivityMainBinding) {Log.i(TAG, "restoreConstraint: ")val focLayoutId = R.id.focLayoutval constraintLayout = binding.parentConstraintSet().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.simpleNamevar isAnimating = falsevar isFirstSwitch = truefun startAnimator(binding: ActivityMainBinding) {Log.i(TAG, "startAnimator: isAnimating=$isAnimating,isFirstSwitch=$isFirstSwitch")isAnimating = trueval defWidth = binding.focLayout.widthif (!isFirstSwitch) restoreConstraint(binding)if (isFirstSwitch) binding.focLayout.x = binding.focLayout.x - 86// 1. 放大Foc的Viewval focBgBigAnim = ValueAnimator.ofInt(defWidth, defWidth + 86).apply {addUpdateListener { animation ->val width = animation.animatedValue as Intval layoutParams = binding.focLayout.layoutParams as ViewGroup.LayoutParamslayoutParams.width = widthbinding.focLayout.layoutParams = layoutParams}addListener(object : AnimatorListenerAdapter() {override fun onAnimationEnd(p0: Animator) {Log.i(TAG, "onAnimationEnd: focBgBigAnim")// 为绘制反向动画,需修改约束方向changeConstraint(binding)isFirstSwitch = false}})}// 2.1 缩小Foc的Viewval focBgSmallAnim = ValueAnimator.ofInt(defWidth + 86, defWidth).apply {addUpdateListener { animation ->val width = animation.animatedValue as Intval layoutParams = binding.focLayout.layoutParams as ViewGroup.LayoutParamslayoutParams.width = widthbinding.focLayout.layoutParams = layoutParams}addListener(object : AnimatorListenerAdapter() {override fun onAnimationEnd(p0: Animator) {Log.i(TAG, "onAnimationEnd: focBgSmallAnim")isAnimating = false}})}// 2.2 从左往右移动Nor的Viewval norBgTransAnim = ObjectAnimator.ofFloat(binding.norIcon, "translationX", -80f, 0f)// 2.3 透明度渐显Nor的Viewval norBgAlphaAnim = ObjectAnimator.ofFloat(binding.norIcon, "alpha", 0f, 1f)AnimatorSet().apply {playSequentially(focBgBigAnim, focBgSmallAnim)playTogether(focBgSmallAnim, norBgTransAnim, norBgAlphaAnim)duration = 1000start()}}private fun changeConstraint(binding: ActivityMainBinding) {Log.i(TAG, "changeConstraint: ")val focLayoutId = R.id.focLayoutval constraintLayout = binding.parentConstraintSet().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.focLayoutval constraintLayout = binding.parentConstraintSet().apply {clone(constraintLayout)clear(focLayoutId, ConstraintSet.END)connect(focLayoutId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, 110)applyTo(constraintLayout)}}}

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

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

相关文章

Linux 下 Vim 环境安装踩坑问题汇总及解决方法(重置版)

导航 安装教程导航 Mamba 及 Vim 安装问题参看本人博客&#xff1a;Mamba 环境安装踩坑问题汇总及解决方法&#xff08;初版&#xff09;Linux 下Mamba 及 Vim 安装问题参看本人博客&#xff1a;Mamba 环境安装踩坑问题汇总及解决方法&#xff08;重置版&#xff09;Windows …

django解决跨域问题

# 1.安装django-cors-headers 库 pip install django-cors-headers -i https://pypi.tuna.tsinghua.edu.cn/simple2.添加到应用程序中 添加 corsheaders 到你的 INSTALLED_APPS 设置中&#xff1a; INSTALLED_APPS [...corsheaders,... ]3.添加中间件 MIDDLEWARE [...cor…

32单片机从入门到精通之用户界面——用户界面(十四)

不论你现在处于什么样的困境和挑战&#xff0c;不要放弃希望和努力。成功之路不会一帆风顺&#xff0c;但是只要你坚定信念&#xff0c;勇敢面对困难&#xff0c;努力奋斗&#xff0c;就一定能够战胜困难&#xff0c;迈向成功的道路。困难和挫折只是暂时的&#xff0c;而坚持和…

Ubuntu Bash工具

Ubuntu Bash工具 &#x1f4bb;⚡ Ubuntu Bash 工具&#x1f528; 指令列表1. &#x1f50b; 查看电池信息 (-b)2. &#x1f4bb; 查看 CPU 和内存使用情况 (-m)3. &#x1f504; 旋转屏幕 (-r)4. &#x1f513; 解锁屏幕 (-s)5. &#x1f310; 设置代理 (-p <proxy_url>…

【GoLang】两个字符串如何比较大小?以及字典顺序的比较规则

在 Go 语言中&#xff0c;字符串的比较是基于字典顺序进行的。 字典顺序的比较规则&#xff1a; 比较两个字符串从左到右逐个字符的Unicode码点值&#xff0c; 若比较结果不相等则将此结果作为字符串大小的结果&#xff0c; 若比较结果相等则比较下一位&#xff0c; 若其中一个…

为什么HTTP请求后面有时带一个sign参数(HTTP请求签名校验)

前言 最近在开发过程中&#xff0c;发现前端有很多的接口发送请求时都会携带signxxxx参数&#xff0c;但是后端明明没有写&#xff0c;也不需要这个参数&#xff0c;后面才知道&#xff0c;这个前面是为了给http请求签名&#xff0c;主要是为了防止请求体和请求参数被拦截篡改…

《机器学习》——贝叶斯算法

贝叶斯简介 贝叶斯公式&#xff0c;又称贝叶斯定理、贝叶斯法则&#xff0c;最初是用来描述两个事件的条件概率间的关系的公式&#xff0c;后来被人们发现具有很深刻的实际意义和应用价值。该公式的实际内涵是&#xff0c;支持某项属性的事件发生得愈多&#xff0c;则该属性成…

matlab离线安装硬件支持包

MATLAB 硬件支持包离线安装 本文章提供matlab硬件支持包离线安装教程&#xff0c;因为我的matlab安装的某种原因&#xff08;破解&#xff09;&#xff0c;不支持硬件支持包的安装&#xff0c;相信也有很多相同情况的朋友&#xff0c;所以记录一下我是如何离线安装的&#xff…

java中手机号,身份证号,邮箱,密码,银行卡号加密

1. 使用hutool工具依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-core</artifactId><version>5.8.11</version><scope>compile</scope></dependency> 2. 使用方式【使用工具类】 Data Equa…

Infineon PSoC 4 CapSense ModusToolbox IDE - 系统生态篇

本文档说明了 ModusToolbox 软体环境的 4 个层面&#xff0c;该环境为 CapSense 设备和生态系统提供支援。本文是 Infineon PSoC 4 CapSense ModusToolbox IDE-系统介绍的延伸篇 (Infineon PSoC 4 CapSense ModusToolbox IDE -系统介绍篇 - 大大通(简体站))。 什么是ModusToolb…

使用PVE快速创建虚拟机集群并搭建docker环境

安装Linux系统 这里以安装龙蜥操作系统AnolisOS8.9为例加以说明。 通过PVE后台上传操作系统ISO镜像。 然后在PVE上【创建虚拟机】&#xff0c;选定上传的龙蜥操作系统镜像进行系统安装。 注意&#xff1a;在安装过程中&#xff0c;要设定语言、时区、超管用户root的密码、普…

ElasticSearch内存占用率过高怎么办?

文章目录 1&#xff0c;先用top看看各个进程的内存占用情况2&#xff0c;不能简单的杀死进程&#xff0c;然后再重启。3&#xff0c;查看一下ElasticSearch进程的具体启动情况4&#xff0c;修改Elasticsearch 的Java堆内存 1&#xff0c;先用top看看各个进程的内存占用情况 先…

OpenHarmony AVScreenCaptureRecorder录屏开发指导

一、简介 OpenHarmony 5.0新增了AVScreenCaptureRecorder ArkTs API。用户可以调用录屏AVScreenCaptureRecorder API录制屏幕&#xff0c;采集音频源数据&#xff0c;获取封装后的音视频文件&#xff0c;然后通过文件的形式流转到其他模块进行播放或处理&#xff0c;用于以文件…

【Spring】构造方法注入 属性加final

在Spring框架中&#xff0c;构造方法注入是一种常见的依赖注入方式。通过构造方法注入&#xff0c;Spring容器会在创建Bean时自动调用相应的构造方法&#xff0c;并将所需的依赖作为参数传入。关于构造方法注入时属性是否加final关键字&#xff0c;主要有以下几点区别&#xff…

windows C#-泛型接口

为泛型集合类或表示集合中的项的泛型类定义接口通常很有用处。 为避免对值类型执行装箱和取消装箱操作&#xff0c;最好对泛型类使用泛型接口&#xff0c;例如 IComparable<T>。 .NET 类库定义多个泛型接口&#xff0c;以便用于 System.Collections.Generic 命名空间中的…

ios脚本巨魔商店多巴胺越狱基本操作教程

准备工作 确认设备兼容性&#xff1a;A9-A11&#xff08;iPhone6s&#xff0d;X&#xff09;&#xff1a;iOS15.0-16.6.1&#xff1b;A12-A14&#xff08;iPhoneXR&#xff0d;12PM&#xff09;&#xff1a;iOS15.0-16.5.1&#xff1b;A15-A16&#xff08;iPhone13&#xff0d…

一.MySQL程序简介

整体介绍 1.服务端mysqld(可执行文件) mysqld --verbose --help 2.客户端mysql(可执行文件) 3.其它工具包程序

算法练习03

一、题目 给你两个字符串 haystack和 needle&#xff0c;请你在haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从0开始)。如果 needle不是 haystack 的一部分&#xff0c;则返回-1。 示例 1:输入:haystack"sadbutsad",needle "sad"。输出…

G1垃圾回收器的FullGC

如何确定GarbageFirst回收器发生的是FullGC ? 必须出现FullGC字样才算是FUllGC&#xff0c;例如下图&#xff1a;因为内存分配失败&#xff08;Allocation Failure&#xff09;导致 如果不出现FullGC的字样说明它不是FUllGC&#xff0c;并不像Serial GC、ParallelGC的在老年代…

Hadoop常见面试题

题目摘录于博客https://blog.csdn.net/qq_42397330/article/details/130218083 1. HDFS的架构 HDFS采用主从架构&#xff0c;其中有两个重要节点Name Node和Data Node&#xff0c;前者负责管理节点以及命名空间和客户端的请求&#xff0c;后者是实际存储数据的节点&#xff0c;…