摘要:再來看前端,前端的動畫實現,經過多年的發展,已分為動畫和動畫。此外還探究了驅動動畫在實現方法上的不同之處。驅動的動畫接下來看驅動的動畫。改造后的函數如下關鍵修改,強制刷新。對于,函數是可行的,然而對無效。
引言
一直以來,動畫都是移動開發中極為特殊的一塊。一方面,動畫在交互體驗上有著不可替代的優越處,然而另一方面,動畫的開發又極為的耗時,需要消耗工程師大量的時間用于開發和調試。再來看前端,前端的動畫實現,經過多年的發展,已分為 CSS3 動畫和 JavaScript 動畫。
React Native 作為一個復用前端思想的移動開發框架,并沒有完整實現CSS,而是使用JavaScript來給應用添加樣式。這是一個有爭議的決定,可以參考這個幻燈片來了解 Facebook 做的理由。自然,在動畫上,因為缺少大量的 CSS 屬性,React Naive 中的動畫均為 JavaScript 動畫,即通過 JavaScript 代碼控制圖像的各種參數值的變化,從而產生時間軸上的動畫效果。
React Native 的官方文檔已經詳細地介紹了 React Native 一般動畫的使用方法和實例,在此不再贅述。然而閱讀官方文檔后可知,官方的動畫往往是給一個完整的物體添加各種動畫效果,如透明度,翻轉,移動等等。但是對于物體的自身變化,比如如下這個進度條,明顯是在旋轉的同時也在伸縮,則缺乏必要的實現方法。這是因為,動畫的本質既是圖形的各種參數的數值變化的過程,文檔中的 Animated.Value 就是用作被驅動的參數,可以,想要讓一個圓環能夠伸縮,就必須讓數值變化的過程,深入到圖形生成的過程中,而不是如官方文檔的例子一樣,僅僅是施加于圖形生成完畢后的過程,那么也就無法實現改變圖形自身的動畫效果了。
拙作初窺基于 react-art 庫的 React Native SVG已討論了 React Native 中靜態 SVG 的開發方法,本文則致力于探究 React Native 中 SVG 與 Animation 結合所實現的 SVG 動畫。也就是可以改變圖形自身的動畫效果。此外還探究了 Value 驅動動畫在實現方法上的不同之處。
Props 驅動的 SVG 動畫本節即以實現一個下圖所示的旋轉的進度條的例子,講述 React Native SVG 動畫的開發方法。
Wedge.art.js 位于 react-art 庫下 lib/ 文件夾內,提供了 SVG 扇形的實現,然而缺乏對 cx, cy 屬性的支持。另外拙作之前也提到了,Wedge中的扇形較為詭異,只有一條半徑,為了實現進度條效果我把另一條半徑也去掉了。我將 Wedge.art.js 拷貝到工程中,自行小修改后的代碼如下。
// wedge.js /** * Copyright 2013-2014 Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule Wedge.art * @typechecks * * Example usage: ** * Additional optional property: * (Int) innerRadius * */ "use strict"; var React = require("react-native"); var ReactART = React.ART; var $__0 = React,PropTypes = $__0.PropTypes; var Shape = ReactART.Shape; var Path = ReactART.Path; /** * Wedge is a React component for drawing circles, wedges and arcs. Like other * ReactART components, it must be used in a . */ var Wedge = React.createClass({displayName: "Wedge", propTypes: { outerRadius: PropTypes.number.isRequired, startAngle: PropTypes.number.isRequired, endAngle: PropTypes.number.isRequired, innerRadius: PropTypes.number, cx: PropTypes.number, cy: PropTypes.number }, circleRadians: Math.PI * 2, radiansPerDegree: Math.PI / 180, /** * _degreesToRadians(degrees) * * Helper function to convert degrees to radians * * @param {number} degrees * @return {number} */ _degreesToRadians: function(degrees) { if (degrees !== 0 && degrees % 360 === 0) { // 360, 720, etc. return this.circleRadians; } else { return degrees * this.radiansPerDegree % this.circleRadians; } }, /** * _createCirclePath(or, ir) * * Creates the ReactART Path for a complete circle. * * @param {number} or The outer radius of the circle * @param {number} ir The inner radius, greater than zero for a ring * @return {object} */ _createCirclePath: function(or, ir) { var path = Path(); path.move(this.props.cx, or + this.props.cy) .arc(or * 2, 0, or) .arc(-or * 2, 0, or); if (ir) { path.move(this.props.cx + or - ir, this.props.cy) .counterArc(ir * 2, 0, ir) .counterArc(-ir * 2, 0, ir); } path.close(); return path; }, /** * _createArcPath(sa, ea, ca, or, ir) * * Creates the ReactART Path for an arc or wedge. * * @param {number} startAngle The starting degrees relative to 12 o"clock * @param {number} endAngle The ending degrees relative to 12 o"clock * @param {number} or The outer radius in pixels * @param {number} ir The inner radius in pixels, greater than zero for an arc * @return {object} */ _createArcPath: function(startAngle, endAngle, or, ir) { var path = Path(); // angles in radians var sa = this._degreesToRadians(startAngle); var ea = this._degreesToRadians(endAngle); // central arc angle in radians var ca = sa > ea ? this.circleRadians - sa + ea : ea - sa; // cached sine and cosine values var ss = Math.sin(sa); var es = Math.sin(ea); var sc = Math.cos(sa); var ec = Math.cos(ea); // cached differences var ds = es - ss; var dc = ec - sc; var dr = ir - or; // if the angle is over pi radians (180 degrees) // we will need to let the drawing method know. var large = ca > Math.PI; // TODO (sema) Please improve theses comments to make the math // more understandable. // // Formula for a point on a circle at a specific angle with a center // at (0, 0): // x = radius * Math.sin(radians) // y = radius * Math.cos(radians) // // For our starting point, we offset the formula using the outer // radius because our origin is at (top, left). // In typical web layout fashion, we are drawing in quadrant IV // (a.k.a. Southeast) where x is positive and y is negative. // // The arguments for path.arc and path.counterArc used below are: // (endX, endY, radiusX, radiusY, largeAngle) path.move(or + or * ss + this.props.cx, or - or * sc + this.props.cy) // move to starting point .arc(or * ds, or * -dc, or, or, large) // outer arc // .line(dr * es, dr * -ec); // width of arc or wedge if (ir) { path.counterArc(ir * -ds, ir * dc, ir, ir, large); // inner arc } return path; }, render: function() { // angles are provided in degrees var startAngle = this.props.startAngle; var endAngle = this.props.endAngle; if (startAngle - endAngle === 0) { return; } // radii are provided in pixels var innerRadius = this.props.innerRadius || 0; var outerRadius = this.props.outerRadius; // sorted radii var ir = Math.min(innerRadius, outerRadius); var or = Math.max(innerRadius, outerRadius); var path; if (endAngle >= startAngle + 360) { path = this._createCirclePath(or, ir); } else { path = this._createArcPath(startAngle, endAngle, or, ir); } return React.createElement(Shape, React.__spread({}, this.props, {d: path})); } }); module.exports = Wedge;
然后就是實現的主體。其中值得關注的點是:
并非任何 Component 都可以直接用 Animated.Value 去賦值 Props,而需要對 Component 做一定的改造。Animated.createAnimatedComponent(Component component),是 Animated 庫提供的用于把普通 Component 改造為 AnimatedComponent 的函數。閱讀 React Native 源代碼會發現,Animated.Text, Animated.View, Animated.Image,都是直接調用了該函數去改造系統已有的組件,如Animated.createAnimatedComponent(React.Text)。
Easing 庫較為隱蔽,明明在react-native/Library/Animated/路徑下,卻又需要從React中直接引出。它為動畫的實現提供了許多緩動函數,可根據實際需求選擇。如 linear() 線性,quad() 二次(quad明明是四次方的意思,為毛代碼實現是t*t....),cubic() 三次等等。官方文檔中吹噓 Easing 中提供了 tons of functions(成噸的函數),然而我數過了明明才14個,233333。
該動畫由起始角度和終止角度兩個變化的參數來控制,因此,兩個Animated.Value需要同時啟動,這涉及到了動畫的組合問題。React Native 為此提供了 parallel, sequence,stagger 和 delay 四個函數。其主要實現均可在react-native/Library/Animated/Animate中找到,官方文檔中亦有說明。這里用的是Animated.parallel。
開發中遇到的問題有:
該動畫在 Android 上可以運行,但是刷新頻率看上去只有兩幀,無法形成一個自然過渡的動畫,筆者懷疑是 React Native Android 對 SVG 的支持仍有缺陷。
SVG 圖形和普通 React Native View 的疊加問題,目前我還沒有找到解決方法。感覺只能等 React Native 開發組的進一步支持。
動畫播放總會有一個莫名其妙的下拉回彈效果,然而代碼上沒有任何額外的控制。
// RotatingWedge.js "use strict"; var React = require("react-native"); var { ART, View, Animated, Easing, } = React; var Group = ART.Group; var Surface = ART.Surface; var Wedge = require("./Wedge"); var AnimatedWedge = Animated.createAnimatedComponent(Wedge); var VectorWidget = React.createClass({ getInitialState: function() { return { startAngle: new Animated.Value(90), endAngle: new Animated.Value(100), }; }, componentDidMount: function() { Animated.parallel([ Animated.timing( this.state.endAngle, { toValue: 405, duration: 700, easing: Easing.linear, } ), Animated.timing( this.state.startAngle, { toValue: 135, duration: 700, easing: Easing.linear, }) ]).start(); }, render: function() { return (Value 驅動的動畫); }, renderGraphic: function() { console.log(this.state.endAngle.__getValue()); return ( {this.renderGraphic()} ); } }); module.exports = VectorWidget;
接下來看 Value 驅動的 SVG 動畫。先解釋一下 Value 和 Props 的區別。
為什么要特意強調這一點呢,如果我們想要做一個如下圖所示的從10到30變動的數字,按照上節所述的方法,直接調用 Animated.createAnimatedComponent(React.Text)所生成的 Component ,然后給 Value 賦值一個Animated.Value(),然后Animated.timing...,是無法產生這樣的效果的。
必須要對庫中的createAnimatedComponent()函數做一定的改造。改造后的函數如下:
var AnimatedProps = Animated.__PropsOnlyForTests; function createAnimatedTextComponent() { var refName = "node"; class AnimatedComponent extends React.Component { _propsAnimated: AnimatedProps; componentWillUnmount() { this._propsAnimated && this._propsAnimated.__detach(); } setNativeProps(props) { this.refs[refName].setNativeProps(props); } componentWillMount() { this.attachProps(this.props); } attachProps(nextProps) { var oldPropsAnimated = this._propsAnimated; /** 關鍵修改,強制刷新。 原來的代碼是: var callback = () => { if (this.refs[refName].setNativeProps) { var value = this._propsAnimated.__getAnimatedValue(); this.refs[refName].setNativeProps(value); } else { this.forceUpdate(); } }; **/ var callback = () => { this.forceUpdate(); }; this._propsAnimated = new AnimatedProps( nextProps, callback, ); oldPropsAnimated && oldPropsAnimated.__detach(); } componentWillReceiveProps(nextProps) { this.attachProps(nextProps); } render() { var tmpText = this._propsAnimated.__getAnimatedValue().text; return ({Math.floor(tmpText)} ); } } return AnimatedComponent; }
為了獲取必須要用到的AnimatedProps,筆者甚至違背了道德的約束,訪問了雙下劃線前綴的變量Animated.__PropsOnlyForTests,真是罪惡啊XD。
言歸正傳,重要的修改有:
修改了 attachProps 函數。對于任何變動的 props,原來的代碼會試圖使用 setNativeProps 函數進行更新,若 setNativeProps 函數為空,才會使用 forceUpdate() 函數。對于 props,setNativeProps 函數是可行的,然而對 value 無效。我猜測,setNativeProps 方法在 Android 底層可能就是 setColor() 類似的 Java 方法,然而并沒有得到實證。目前這種 forceUpdate,由注釋知,是徹底更新了整個 Component,相當于先從 DOM 樹上取下一個舊節點,再放上一個新節點,在性能的利用上較為浪費。
使用 PropTypes.xxx.isRequired 來進行參數的類型檢查。PropTypes 檢查支持的類型可在 react-native/node_modules/react/lib/ReactPropTypes.js 中看到,在此不再贅述。
Animated.value() 從10到30變化的過程是一個隨機采樣的過程,并不一定會卡在整數值上,因此還需要做一些小處理。
值得注意的是,該動畫在 Android 上雖然可以正常運行,但也存在丟幀的問題,遠遠不能如 iOS 上流暢自然。對于這一點,只能等待 Facebook 的進一步優化。
全部的代碼如下:
// RisingNumber.js "use strict"; var React = require("react-native"); var { Text, Animated, Easing, PropTypes, View, StyleSheet, } = React; var AnimatedText = createAnimatedTextComponent(); var AnimatedProps = Animated.__PropsOnlyForTests; function createAnimatedTextComponent() { var refName = "node"; class AnimatedComponent extends React.Component { _propsAnimated: AnimatedProps; componentWillUnmount() { this._propsAnimated && this._propsAnimated.__detach(); } setNativeProps(props) { this.refs[refName].setNativeProps(props); } componentWillMount() { this.attachProps(this.props); } attachProps(nextProps) { var oldPropsAnimated = this._propsAnimated; var callback = () => { this.forceUpdate(); }; this._propsAnimated = new AnimatedProps( nextProps, callback, ); oldPropsAnimated && oldPropsAnimated.__detach(); } componentWillReceiveProps(nextProps) { this.attachProps(nextProps); } render() { var tmpText = this._propsAnimated.__getAnimatedValue().text; return ({Math.floor(tmpText)} ); } } return AnimatedComponent; } var RisingNumber = React.createClass({ propTypes: { startNumber: PropTypes.number.isRequired, toNumber: PropTypes.number.isRequired, startFontSize: PropTypes.number.isRequired, toFontSize: PropTypes.number.isRequired, duration: PropTypes.number.isRequired, upperText: PropTypes.string.isRequired, }, getInitialState: function() { return { number: new Animated.Value(this.props.startNumber), fontSize: new Animated.Value(this.props.startFontSize), }; }, componentDidMount: function() { Animated.parallel([ Animated.timing( this.state.number, { toValue: this.props.toNumber, duration: this.props.duration, easing: Easing.linear, }, ), Animated.timing( this.state.fontSize, { toValue: this.props.toFontSize, duration: this.props.duration, easing: Easing.linear, } ) ]).start(); }, render: function() { return (); }, }); var styles = StyleSheet.create({ kind: { fontSize: 15, color: "#01A971", }, number: { marginLeft: 15, }, }); module.exports = RisingNumber; {this.props.upperText}
====================================
如果您覺得我的文章對您有所啟迪,請點擊文末的推薦按鈕,您的鼓勵將會成為我堅持寫作的莫大激勵。 by DesGemini
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/79036.html
摘要:數據可視化庫超過的的可能是最流行和最廣泛的數據可視化庫。是一組組件,用于高效地渲染大型列表和表格數據。一種優雅而靈活的方式,可以利用組件來支持實際的數據可視化。 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! React Native 組件庫 1. NativeBase showImg(https://segmentfault.com/img/bVbrLHH?w=...
摘要:前端每周清單半年盤點之與篇前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。與求同存異近日,宣布將的構建工具由遷移到,引發了很多開發者的討論。 前端每周清單半年盤點之 React 與 ReactNative 篇 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為...
摘要:語法更近似于移動端。當參數為兩個時,等同于,繪制光滑二次貝塞爾曲線。有些精通的同學這時候可能就要問我了,不對啊,二次貝塞爾曲線和光滑三次貝塞爾曲線的參數都是個,你這里沒有光滑三次啊因為開發的同學留坑沒寫了呀微笑。和則是用于指定旋轉的原點。 技術背景 在移動應用的開發過程中,繪制基本的二維圖形或動畫是必不可少的。然而,考慮到Android和iOS均有一套各自的API方案,因此采用一種更普...
摘要:支持動畫狀態的,在動畫開始,執行中,結束時提供回調函數支持動畫可以自定義貝塞爾曲線任何包含數值的屬性都可以設置動畫倉庫文檔演示功能介紹一定程度上,也是一個動畫庫,適用所有的屬性,并且實現的能更方便的實現幀動畫,替代復雜的定義方式。 動畫調研-V1 前言:動畫從用途上可以分為兩種,一種是展示型的動畫,類似于一張GIF圖,或者一段視頻,另一種就是交互性的動畫。這兩種都有具體的應用場景,比如...
摘要:適用于,演示這是開發的一個簡單的可視化庫,它允許你創建所有常用的圖表類型條形圖,樹形圖,折線圖,面積圖等。可以輕松地對折線圖和條形圖進行混合和匹配以組合不同的數據集,這是非常棒的功能。 翻譯:瘋狂的技術宅原文:https://www.monterail.com/blo... 本文首發微信公眾號:jingchengyideng歡迎關注,每天都給你推送新鮮的前端技術文章 你的程序有多...
閱讀 2237·2021-09-24 10:31
閱讀 3888·2021-09-22 15:16
閱讀 3410·2021-09-22 10:02
閱讀 1025·2021-09-22 10:02
閱讀 1839·2021-09-08 09:36
閱讀 1986·2019-08-30 14:18
閱讀 617·2019-08-30 10:51
閱讀 1880·2019-08-29 11:08