草庐IT

如何用程序员的方式,营造七夕氛围感?

HMS Core 2023-03-28 原文

一个“冷”知识,又双叒叕到一年七夕了!

每年此时,送礼指南满天飞,选择困难症都给逼出来了,疑了个惑,能搞定爱情的人,却在送礼这件事儿上纠结万分!
要我说,七夕这种有情人的浪漫佳节,最重要的是图一个氛围!今天给大家介绍一个适合程序员的七夕“氛围感十足”的小礼物——借助华为图像服务,开发个超有爱七夕动图,它支持输入特殊关键词,触发“七夕”特效;同时,画面还可跟随指尖触碰产生动效……效果先睹为快↓↓↓

Demo效果

有对象的秀给对象看,单身的朋友也可先做个七夕氛围组成员,方法到手,也许下次就用上啦。

话不多说,开整!

开发步骤

1. 关键字动画播放

第一步:素材准备

首先找到一个合适的图片,这里我们选择了一张牛郎织女的图片:

然后将其中有动画效果的部件从图中取出来。我们取出了云朵,牛郎,织女,红心四个元素。

第二步:集成准备

先按照下面的指导完成开发者注册,创建应用,签名配置:
https://developer.huawei.com/consumer/cn/doc/development/Media-Guides/config-agc-0000001050199019?ha_source=hms1

再按照如下方式进行代码仓和编译依赖的配置:

  1. 在项目级“build.gradle”文件中配置:
buildscript {
    repositories {
        google()
        jcenter()
        // 配置HMS Core SDK的Maven仓地址。
        maven {url 'https://developer.huawei.com/repo/'}
    }
    dependencies {
        ...
        // 增加agcp插件配置。
        classpath 'com.huawei.agconnect:agcp:1.4.2.300'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        // 配置HMS Core SDK的Maven仓地址。
        maven {url 'https://developer.huawei.com/repo/'}
    }
}
  1. 在应用级“build.gradle”文件配置编译依赖(当前最新版本1.0.3.301):
dependencies { 
implementation 'com.huawei.hms:image-render: 1.0.3.301' 
  implementation 'com.huawei.hms:image-render-fallback: 1.0.3.301'
}
  1. 配置权限

在“AndroidManifest.xml”文件中配置应用所需权限。

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

第三步:功能开发

  1. 界面设计

这里用最简单的界面,一个FrameLayout中配置输入框和按钮:

我们将在这个FrameLayout中调试和展现动画。

  1. 配置存储权限申请

在MainActivity的onCreate()方法中,检查是否拥有写存储的权限,如果缺少,就调用requestPermission方法,对WRITE_EXTERNAL_STORAGE权限进行申请:

int permissionCheck = ContextCompat.checkSelfPermission(ImageKitRenderDemoActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
initData();
initImageRender();
} else {
ActivityCompat.requestPermissions(ImageKitRenderDemoActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
}

如果已有权限,或权限申请成功后,对Image渲染模块进行初始化

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == PERMISSION_REQUEST_CODE) {
        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // The permission is granted.
            initData();
            initImageRender();
        } else {
            // The permission is rejected.
            Log.w(TAG, "permission denied");
            Toast.makeText(ImageKitRenderDemoActivity.this, "Please grant the app the permission to read the SD card", Toast.LENGTH_SHORT).show();
        }
    }
}
  1. Image渲染模块初始化

获取渲染实例,初始化,并获取渲染视图。 这里会指定动画元素的目录:

ImageRender.getInstance(context, new ImageRender.RenderCallBack() {
        // 获取场景动效服务实例成功回调,返回场景动效服务实例
        @Override
        public void onSuccess(ImageRenderImpl imageRender) {
            imageRenderAPI = imageRender;
            if (imageRenderAPI != null) {
                int initResult = imageRenderAPI.doInit(sourcePath, Utils.getAuthJson());
                Log.i(TAG, "DoInit result == " + initResult);
                if (initResult == 0) {
                    // Obtain the rendered view.
                    RenderView renderView = imageRenderAPI.getRenderView();
                    if (renderView.getResultCode() == ResultCode.SUCCEED) {
                        View view = renderView.getView();
                        if (null != view) {
                            // Add the rendered view to the layout.
                            contentView.addView(view);
                            hashCode = String.valueOf(view.hashCode());
                        } else {
                            Log.w(TAG, "GetRenderView fail, view is null");
                        }
            }
        }
        // 获取场景动效服务实例失败回调,返回错误码
        @Override
        public void onFailure(int errorCode) {
        ...
        }
    });
  1. 配置关键字播放动画

还记得前面留下的输入框和按钮吗?我们使用关键字“Love”来进行动画播放。只需要通过imageRenderAPI.playAnimation()即可触发:

wordInput = findViewById(R.id.textinput);
enterBtn = findViewById(R.id.enter);
enterBtn.setOnClickListener(v -> {
    String inputContent = wordInput.getText().toString();
    if (inputContent.contentEquals("Love")) {
        if (null != imageRenderAPI) {
            imageRenderAPI. playAnimation();;
            wordInput.setVisibility(View.GONE);
            enterBtn.setVisibility(View.GONE);
        }
    } else {
        Toast.makeText(this,"再想想?",Toast.LENGTH_SHORT).show();
    }
});
  1. 配置动画

框架搭好了,现在来到动画的部分。Image Kit提供了5种基础动效和9种高级动效,可以满足绝大部分场景的使用要求。
在这里我们使用到了透明度动画,位移动画,缩放动画,以及下落动效。

Image Kit的动画配置均在manifest.xml文件中完成。可不要和AndroidManifest.xml文件搞混了哦。

首先配置虚拟屏宽及背景图片。配置虚拟屏宽之后,系统会根据不同的分辨率对动画进行缩放,使效果保持一致。

<Root screenWidth="1080">
<Image src="background.png"/>

我们希望牛郎织女能够逐渐靠近直至相会,此时加入牛郎织女两个元素,分别配置动线,此时使用到位移动画的特效:

 <Image x="1000" y="1450" src="man.png">
       <PositionAnimation repeat="1">
           <Position x="0" y="0" time="0"/>
           <Position x="-450" y="-100" time="4000"/>
       </PositionAnimation>
    </Image>
    <Image x="-600" y="800" src="woman.png">
        <PositionAnimation repeat="1">
            <Position x="0" y="0" time="0"/>
            <Position x="700" y="300" time="4000"/>
        </PositionAnimation>
    </Image>

这样牛郎织女将从屏幕两侧向中间靠近,直至相会。

相会之后在中心将出现跳动的红心,这里用到了透明度动画及缩放动画的叠加特效:

 <Image x="520" y="1350"  src="heart.png" visibility="#show1">
        <AlphaAnimation delay="4000" repeat="1">
            <Alpha a="255" time="0"/>
            <Alpha a="0" time="3000"/>
        </AlphaAnimation>
        <SizeAnimation delay="4000" repeat="1">
            <Size w="127" h="95" time="0"/>
            <Size w="508" h="380" time="3000"/>
        </SizeAnimation>
    </Image>

到现在,关键元素已经有了,可是看起来还是有点干巴,要找点什么点缀一下。
天上的云朵也可以动起来,让它在小范围里动一下更灵动一些:

 <Image x="150" y="200" src="cloud.png">
        <PositionAnimation repeat="0">
            <Position x="0" y="0" time="0"/>
            <Position x="-50" y="0" time="3000"/>
            <Position x="0" y="0" time="6000"/>
        </PositionAnimation>
    </Image>

想再罗曼蒂克一些,撒一些花瓣,这里用到的就是下落动效:

 <DropPhysicalView gravityX="3" gravityY="10" airDensity="1000" delay="8800" visibility="#show2">
        <ItemGroup x="0" y="0" width="#screen_width" height="#screen_height">
            <Alpha x="0" y="#screen_height-1000" width="#screen_width" height="#screen_height" value="20"/>
            <Item count="20" src="follow.png">
                <Velocity isRandom="true" velocityX="0" velocityY="5"/>
                <Position isRandom="true"/>
                <AngleVelocity isRandom="true" angleVelocity="5"/>
                <Weight isRandom="true" value="0.5"/>
            </Item>
        </ItemGroup>
    </DropPhysicalView>
  1. 到这里已经大功告成。最后可以再将动画保留下来
// 开始录制
int resultCode = imageRenderAPI.startRecord(json, new IStreamCallBack () {
    // 在录制成功回调中,将视频或GIF字节数组保存成mp4或GIF文件
    @Override
    void onRecordSuccess(HashMap<String, Object> map) {
        ...
        String recordType = (String) hashMap.get("recordType");
        byte[] videoBytes = (byte[]) hashMap.get("videoBytes");
        byte[] gifBytes = (byte[]) hashMap.get("gifBytes");
        try {
            if (recordType.equals("1")) {
                if (videoBytes != null) {
                    // 保存mp4文件
                    saveFile(videoBytes, mp4Path);
                }
            } else if (recordType.equals("2")) {
                ...
            } else if (recordType.equals("3")) {
                ...
            }
        } catch (IOException e) {
            ...
        }
        ...
    }

    // 录制失败回调
    @Override
    void onRecordFailure(HashMap<String, Object> map) {
        ...
    }

    // 录制进度回调,progress取值范围为0-100
    @Override
    void onProgress(int progress) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                textProgress.setText("当前录制进度:" + progress + "%");
            }
        });
    }
});

以上就得到最终效果啦~

除了动效之外,Image Kit还提供了滤镜功能,可以给图片添上浪漫的色彩,贴纸花字的功能,也可以给用户的图片增加爱情的元素。

了解更多>>

访问华为图像服务官网

访问华为开发者联盟官网

获取开发指导文档

华为移动服务开源仓库地址:GitHubGitee

关注我们,第一时间了解** HMS Core **最新技术资讯~

有关如何用程序员的方式,营造七夕氛围感?的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  5. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  6. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  7. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  8. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  9. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  10. ruby-on-rails - 正确的 Rails 2.1 做事方式 - 2

    question的一些答案关于redirect_to让我想到了其他一些问题。基本上,我正在使用Rails2.1编写博客应用程序。我一直在尝试自己完成大部分工作(因为我对Rails有所了解),但在需要时会引用Internet上的教程和引用资料。我设法让一个简单的博客正常运行,然后我尝试添加评论。靠我自己,我设法让它进入了可以从script/console添加评论的阶段,但我无法让表单正常工作。我遵循的其中一个教程建议在帖子Controller中创建一个“评论”操作,以添加评论。我的问题是:这是“标准”方式吗?我的另一个问题的答案之一似乎暗示应该有一个CommentsController参

随机推荐