草庐IT

iOS "calculationMode"等效的 Android 动画

coder 2023-09-27 原文

我正在尝试为 Android 中的一些 drawables 设置动画,我已经使用 PathEvaluator 设置了路径沿完整路径沿某些曲线进行动画处理。

当我设置 duration(例如 6 秒)时,它会将持续时间拆分为我设置的曲线数量,而不管它们的长度如何导致动画在某些 fragment 上变慢对别人快。

iOS 上,可以使用以下方法修复此问题

animation.calculationMode   = kCAAnimationCubicPaced;
animation.timingFunction    = ...;

这让 iOS 可以将您的整个路径平滑到中间点,并根据每个 fragment 的长度跨越持续时间。 有什么方法可以在 Android 中获得相同的结果?

(除了将路径分成离散的段并手动为每个段分配自己的持续时间之外,这非常丑陋且无法维护)。

最佳答案

我不认为 ObjectAnimator 可以做任何事情,因为似乎没有可以调用的函数来断言某个动画 fragment 的相对持续时间。

我确实开发了一些与您前阵子需要的类似的东西,但它的工作方式略有不同 - 它继承自 Animation。

我已经修改了所有内容以满足您的曲线需求和 PathPoint 类。

这是一个概述:

  1. 我在构造函数中向动画提供点列表。

  2. 我使用一个简单的距离计算器计算所有点之间的长度。然后我将所有这些相加得到路径的总长度,并将分段长度存储在 map 中以备将来使用(这是为了提高运行时的效率)。

  3. 制作动画时,我使用当前插值时间来确定我在哪 2 个点之间制作动画,同时考虑时间比率和行进距离比率。

  4. 我根据它们之间的相对距离(与总距离相比)计算在这两个点之间制作动画所需的时间。

  5. 然后我使用 PathAnimator 类中的计算分别在这 2 个点之间进行插值。

代码如下:

曲线动画.java:

public class CurveAnimation extends Animation 
{
    private static final float BEZIER_LENGTH_ACCURACY = 0.001f; // Must be divisible by one. Make smaller to improve accuracy, but will increase runtime at start of animation.
    private List<PathPoint> mPathPoints;
    private float mOverallLength;
    private Map<PathPoint, Double> mSegmentLengths = new HashMap<PathPoint, Double>(); // map between the end point and the length of the path to it.

    public CurveAnimation(List<PathPoint> pathPoints)
    {
        mPathPoints = pathPoints;

        if (mPathPoints == null || mPathPoints.size() < 2)
        {
            Log.e("CurveAnimation", "There must be at least 2 points on the path. There will be an exception soon!");
        }

        calculateOverallLength();
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) 
    {       
        PathPoint[] startEndPart = getStartEndForTime(interpolatedTime);

        PathPoint startPoint = startEndPart[0];
        PathPoint endPoint = startEndPart[1];

        float startTime = getStartTimeOfPoint(startPoint);
        float endTime = getStartTimeOfPoint(endPoint);      
        float progress = (interpolatedTime - startTime) / (endTime - startTime);


        float x, y;
        float[] xy;
        if (endPoint.mOperation == PathPoint.CURVE) 
        {
            xy = getBezierXY(startPoint, endPoint, progress);
            x = xy[0];
            y = xy[1];
        } 
        else if (endPoint.mOperation == PathPoint.LINE) 
        {
            x = startPoint.mX + progress * (endPoint.mX - startPoint.mX);
            y = startPoint.mY + progress * (endPoint.mY - startPoint.mY);
        } 
        else 
        {
            x = endPoint.mX;
            y = endPoint.mY;
        }              

        t.getMatrix().setTranslate(x, y);
        super.applyTransformation(interpolatedTime, t);
    }


    private PathPoint[] getStartEndForTime(float time)
    {       
        double length = 0;

        if (time == 1)
        {
            return new PathPoint[] { mPathPoints.get(mPathPoints.size() - 2), mPathPoints.get(mPathPoints.size() - 1) }; 
        }

        PathPoint[] result = new PathPoint[2];

        for (int i = 0; i < mPathPoints.size() - 1; i++)
        {
            length += calculateLengthFromIndex(i);
            if (length / mOverallLength >= time)
            {
                result[0] = mPathPoints.get(i);
                result[1] = mPathPoints.get(i + 1);
                break;
            }
        }

        return result;
    }

    private float getStartTimeOfPoint(PathPoint point)
    {
        float result = 0;
        int index = 0;          

        while (mPathPoints.get(index) != point && index < mPathPoints.size() - 1)
        {
            result += (calculateLengthFromIndex(index) / mOverallLength);
            index++;
        }

        return result;

    }

    private void calculateOverallLength()
    {
        mOverallLength = 0;      
        mSegmentLengths.clear();

        double segmentLength;

        for (int i = 0; i < mPathPoints.size() - 1; i++)
        {
            segmentLength = calculateLengthFromIndex(i);
            mSegmentLengths.put(mPathPoints.get(i + 1), segmentLength);
            mOverallLength += segmentLength;
        }
    }

    private double calculateLengthFromIndex(int index)
    {
        PathPoint start = mPathPoints.get(index);
        PathPoint end = mPathPoints.get(index + 1);
        return calculateLength(start, end);
    }

    private double calculateLength(PathPoint start, PathPoint end)
    {
        if (mSegmentLengths.containsKey(end))
        {
            return mSegmentLengths.get(end);
        }       
        else if (end.mOperation == PathPoint.LINE)
        {
            return calculateLength(start.mX, end.mX, start.mY, end.mY);
        }
        else if (end.mOperation == PathPoint.CURVE)
        {
            return calculateBezeirLength(start, end);
        }
        else
        {
            return 0;
        }
    }

    private double calculateLength(float x0, float x1, float y0, float y1)
    {
        return Math.sqrt(((x0 - x1) * (x0 - x1)) + ((y0 - y1) * (y0 - y1)));
    }

    private double calculateBezeirLength(PathPoint start, PathPoint end)
    {
        double result = 0;  
        float x, y, x0, y0;
        float[] xy;

        x0 = start.mX;
        y0 = start.mY;              

        for (float progress = BEZIER_LENGTH_ACCURACY; progress <= 1; progress += BEZIER_LENGTH_ACCURACY)
        {
            xy = getBezierXY(start, end, progress);
            x = xy[0];
            y = xy[1];

            result += calculateLength(x, x0, y, y0);

            x0 = x;
            y0 = y;
        }

        return result;
    }

    private float[] getBezierXY(PathPoint start, PathPoint end, float progress)
    {
        float[] result = new float[2];

        float oneMinusT, x, y;  

        oneMinusT = 1 - progress;
        x = oneMinusT * oneMinusT * oneMinusT * start.mX +
                3 * oneMinusT * oneMinusT * progress * end.mControl0X +
                3 * oneMinusT * progress * progress * end.mControl1X +
                progress * progress * progress * end.mX;
        y = oneMinusT * oneMinusT * oneMinusT * start.mY +
                3 * oneMinusT * oneMinusT * progress * end.mControl0Y +
                3 * oneMinusT * progress * progress * end.mControl1Y +
                progress * progress * progress * end.mY;

        result[0] = x;
        result[1] = y;

        return result;
    }

}

下面是一个演示如何激 Activity 画的示例:

private void animate()
    {
        AnimatorPath path = new AnimatorPath();
        path.moveTo(0, 0);
        path.lineTo(0, 300);
        path.curveTo(100, 0, 300, 900, 400, 500);        

        CurveAnimation animation = new CurveAnimation(path.mPoints);
        animation.setDuration(5000);
        animation.setInterpolator(new LinearInterpolator());

        btn.startAnimation(animation);
    }

现在请记住,我目前正在根据近似值计算曲线的长度。这显然会导致速度出现一些轻微的误差。如果您觉得不够准确,请随时修改代码。此外,如果您想增加曲线的长度精度,请尝试减小 BEZIER_LENGTH_ACCURACY 的值。它必须能被 1 整除,因此可接受的值可以是 0.001、0.000025 等。

虽然您可能会注意到在使用曲线时速度会有一些轻微的波动,但我相信这比简单地将时间平均分配给所有路径要好得多。

希望对您有所帮助:)

关于iOS "calculationMode"等效的 Android 动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19076598/

有关iOS "calculationMode"等效的 Android 动画的更多相关文章

  1. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  2. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  3. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  4. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  5. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  6. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  7. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

  8. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  9. ruby - 安装 Ruby 时遇到问题(无法下载资源 "readline--patch") - 2

    当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub

  10. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

随机推荐