功能需求:數(shù)字滾動動畫控件定義,并帶有單位,因ReactNative定義的動畫控件實現(xiàn)效果不佳,所以需要使用RN調(diào)用原生控件來實現(xiàn)。
具體實現(xiàn)如下:官方調(diào)用API

1.Android原生控件定義官方鏈接。
? ? ??
原生數(shù)字滾動自定義控件實現(xiàn)
class NumberRunningFontTextView @JvmOverloads constructor(
? ? context: Context, attrs: AttributeSet? = null
) : AppCompatTextView(context, attrs) {
? ? private var currentPlayAnima: ValueAnimator? = null
? ? private var delayPlayRunnable: Runnable? = null
? ? var duration: Int = 0
? ? var currentNum: Int? = null
? ? var fontFamily: String = ""http://字體
? ? var fontSize: Int = 0 //文本顏色
? ? var fontColor: String = "#000000" //文本顏色
? ? var suffixText: String = "" //后綴文本
? ? var suffixTextFontSize: Int = 0? // 后綴文本字體大小
? ? var suffixTextFontFamily: String = "" // 后綴文本字體
? ? var suffixTextFontColor: String = "" // 后綴文本文本顏色
? ? var toFixed: Int = 0 //保留小數(shù)幾位
? ? var mLayoutRunnable :Runnable ?= null
? ? override
? ? fun onDetachedFromWindow() {
? ? ? ? super.onDetachedFromWindow()
? ? ? ? delayPlayRunnable?.let { removeCallbacks(it)}
? ? ? ? delayPlayRunnable = null
? ? ? ? mLayoutRunnable?.let { removeCallbacks(it)}
? ? ? ? mLayoutRunnable = null
? ? ? ? cancelAnim()
? ? }
? ? private fun cancelAnim() {
? ? ? ? currentPlayAnima?.run {
? ? ? ? ? ? removeAllUpdateListeners()
? ? ? ? ? ? removeAllListeners()
? ? ? ? ? ? if (isRunning) cancel()
? ? ? ? }
? ? }
? ? fun setContent(data: Int, delayPlayTime: Long = 0L) {
? ? ? ? mLayoutRunnable =Runnable {
? ? ? ? ? ? if (data == currentNum) {
? ? ? ? ? ? ? ? text = convertNum(data)
? ? ? ? ? ? ? ? return@Runnable
? ? ? ? ? ? }
? ? ? ? ? ? if (delayPlayTime > 0) {
? ? ? ? ? ? ? ? delayPlayRunnable?.let { removeCallbacks(it)}
? ? ? ? ? ? ? ? text = convertNum(currentNum ?: 0)
? ? ? ? ? ? ? ? val runnable =Runnable { useAnimByType(data)}
? ? ? ? ? ? ? ? this.delayPlayRunnable = runnable
? ? ? ? ? ? ? ? postDelayed(runnable, delayPlayTime)
? ? ? ? ? ? ? ? return@Runnable
? ? ? ? ? ? }
? ? ? ? ? ? useAnimByType(data)
? ? ? ? }
? ? ? ? mLayoutRunnable?.let {
? ? ? ? ? ? post(it)
? ? ? ? }
? ? }
? ? private fun useAnimByType(num: Int) {
? ? ? ? cancelAnim()
? ? ? ? this.playNumAnim(num)
? ? ? ? currentNum = num
? ? }
? ? private fun playNumAnim(finalNum: Int) {
? ? ? ? try {
? ? ? ? ? ? val startNum = currentNum ?: 0
? ? ? ? ? ? val intAnimator = ValueAnimator.ofInt(*intArrayOf(startNum, finalNum))
? ? ? ? ? ? this.currentPlayAnima = intAnimator
? ? ? ? ? ? intAnimator.duration = this.duration.toLong()
? ? ? ? ? ? intAnimator.addUpdateListener{ animation->
? ? ? ? ? ? ? ? if (!this@NumberRunningFontTextView.isAttachedToWindow) return@addUpdateListener
? ? ? ? ? ? ? ? val currentNum = animation.animatedValue as Int
? ? ? ? ? ? ? ? text = convertNum(currentNum)
? ? ? ? ? ? }
? ? ? ? ? ? intAnimator.addListener(onEnd ={
? ? ? ? ? ? ? ? if (!this@NumberRunningFontTextView.isAttachedToWindow) return@addListener
? ? ? ? ? ? ? ? text = convertNum(finalNum)
? ? ? ? ? ? })
? ? ? ? ? ? intAnimator.start()
? ? ? ? } catch (var5: NumberFormatException) {
? ? ? ? ? ? var5.printStackTrace()
? ? ? ? ? ? text = convertNum(finalNum)
? ? ? ? }
? ? }
? ? private fun getPattern(): String {
? ? ? ? if (toFixed > 0) {
? ? ? ? ? ? val pattern: StringBuilder = StringBuilder()
? ? ? ? ? ? pattern.append("###################0.")
? ? ? ? ? ? for (i in 0until toFixed) {
? ? ? ? ? ? ? ? pattern.append("0")
? ? ? ? ? ? }
? ? ? ? ? ? return pattern.toString()
? ? ? ? }
? ? ? ? return "###################0"
? ? }
? ? private fun convertNum(num: Int): CharSequence {
? ? ? ? val df = DecimalFormat(getPattern())
? ? ? ? val formatNum = df.format(num / 100f)
? ? ? ? val spannableString: SpannableString?
? ? ? ? if (suffixText.notNullOrEmpty()) {
? ? ? ? ? ? val txt = formatNum + suffixText
? ? ? ? ? ? spannableString = SpannableString(txt)
? ? ? ? ? ? foregroundColorSpan(spannableString, Color.parseColor(fontColor), 0, txt.length)
? ? ? ? ? ? absoluteSizeSpan(
? ? ? ? ? ? ? ? spannableString,
? ? ? ? ? ? ? ? fontSize,
? ? ? ? ? ? ? ? startIndex = 0,
? ? ? ? ? ? ? ? endIndex = txt.length
? ? ? ? ? ? )
? ? ? ? ? ? if (fontFamily.notNullOrEmpty()) {
? ? ? ? ? ? ? ? val typeface = FontManager.getInstance(context).getFont(fontFamily)
? ? ? ? ? ? ? ? fontFamilySpan(spannableString, typeface, 0, txt.length)
? ? ? ? ? ? }
? ? ? ? ? ? foregroundColorSpan(spannableString, Color.parseColor(suffixTextFontColor), formatNum?.length ?: 0, txt.length)
? ? ? ? ? ? absoluteSizeSpan(
? ? ? ? ? ? ? ? spannableString,
? ? ? ? ? ? ? ? suffixTextFontSize,
? ? ? ? ? ? ? ? startIndex = formatNum?.length ?: 0,
? ? ? ? ? ? ? ? endIndex = txt.length
? ? ? ? ? ? )
? ? ? ? ? ? if (suffixTextFontFamily.notNullOrEmpty()) {
? ? ? ? ? ? ? ? val typeface = FontManager.getInstance(context).getFont(suffixTextFontFamily)
? ? ? ? ? ? ? ? fontFamilySpan(spannableString, typeface, formatNum?.length ?: 0, txt.length)
? ? ? ? ? ? }
? ? ? ? } else {
? ? ? ? ? ? spannableString = SpannableString(formatNum)
? ? ? ? ? ? foregroundColorSpan(spannableString, Color.parseColor(fontColor), 0, formatNum.length)
? ? ? ? ? ? absoluteSizeSpan(
? ? ? ? ? ? ? ? spannableString,
? ? ? ? ? ? ? ? fontSize,
? ? ? ? ? ? ? ? startIndex = 0,
? ? ? ? ? ? ? ? endIndex = formatNum.length
? ? ? ? ? ? )
? ? ? ? ? ? if (fontFamily.notNullOrEmpty()) {
? ? ? ? ? ? ? ? val typeface = FontManager.getInstance(context).getFont(fontFamily)
? ? ? ? ? ? ? ? fontFamilySpan(spannableString, typeface, 0, formatNum.length)
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return spannableString
? ? }
? ? private fun foregroundColorSpan(spannableString: SpannableString, color: Int, startIndex: Int = 0, endIndex: Int = 0) {
? ? ? ? val mForegroundColorSpan = ForegroundColorSpan(color)
? ? ? ? spannableString.setSpan(mForegroundColorSpan, startIndex, endIndex, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
? ? }
? ? private fun fontFamilySpan(spannableString: SpannableString, typeface: Typeface, startIndex: Int = 0, endIndex: Int = 0) {
? ? ? ? if (typeface == null) {
? ? ? ? ? ? return
? ? ? ? }
? ? ? ? spannableString.setSpan(typeface, startIndex, endIndex, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
? ? }
? ? private fun absoluteSizeSpan(spannableString: SpannableString, fontSize: Int, dip: Boolean = true, startIndex: Int = 0, endIndex: Int = 0) {
? ? ? ? if (fontSize <= 0) return
? ? ? ? val mAbsoluteSizeSpan = AbsoluteSizeSpan(fontSize, dip)
? ? ? ? spannableString.setSpan(mAbsoluteSizeSpan, startIndex, endIndex, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
? ? }
}
供ReactNativeAPI引用具體類方法聲明實現(xiàn)
class DUNumberLabelManager : SimpleViewManager<NumberRunningFontTextView>() {
? ? override fun getName(): String = "DUNumberLabel"? ? ?//具體控件名稱定義
? ? override fun createViewInstance(reactContext: ThemedReactContext): NumberRunningFontTextView {
? ? ? ? return NumberRunningFontTextView(reactContext)
? ? }
? ? /**
?? ? *字體大小
?? ? */
? ? @ReactProp(name = "fontSize" )
? ? fun setFontSize(numberRunningFontTextView: NumberRunningFontTextView, fontSize: Int) {
? ? ? ? numberRunningFontTextView.fontSize = fontSize
? ? }
? ? /**
?? ? *字體
?? ? */
? ? @ReactProp(name = "fontFamily")
? ? fun setFontFamily(numberRunningFontTextView: NumberRunningFontTextView, fontFamily: String) {
? ? ? ? numberRunningFontTextView.fontFamily = fontFamily
? ? }
? ? /**
?? ? *文本顏色
?? ? */
? ? @ReactProp(name = "fontColor")
? ? fun setFontColor(numberRunningFontTextView: NumberRunningFontTextView, fontColor: String) {
? ? ? ? numberRunningFontTextView.fontColor = fontColor
? ? }
? ? /**
?? ? *開始數(shù)字
?? ? */
? ? @ReactProp(name = "startNum")
? ? fun setStartNum(numberRunningFontTextView: NumberRunningFontTextView, startNum: Int) {
? ? ? ? numberRunningFontTextView.currentNum = startNum
? ? }
? ? /**
?? ? *結(jié)束數(shù)字
?? ? */
? ? @ReactProp(name = "endNum")
? ? fun setEndNum(numberRunningFontTextView: NumberRunningFontTextView, endNum: Int) {
? ? ? ? numberRunningFontTextView.setContent(endNum, 500L)
? ? }
? ? /**
?? ? *動畫時長,秒
?? ? */
? ? @ReactProp(name = "duration")
? ? fun setDuration(numberRunningFontTextView: NumberRunningFontTextView, duration: Int) {
? ? ? ? numberRunningFontTextView.duration = duration
? ? }
? ? /**
?? ? *文本
?? ? */
? ? @ReactProp(name = "text")
? ? fun setText(numberRunningFontTextView: NumberRunningFontTextView, text: String) {
? ? ? ? numberRunningFontTextView.text = text
? ? }
? ? /**
?? ? *保留小數(shù)幾位
?? ? */
? ? @ReactProp(name = "toFixed")
? ? fun setToFixed(numberRunningFontTextView: NumberRunningFontTextView, toFixed: Int) {
? ? ? ? numberRunningFontTextView.toFixed = toFixed
? ? }
? ? /**
?? ? *行數(shù)
?? ? */
? ? @ReactProp(name = "numberOfLines")
? ? fun setNumberOfLines(numberRunningFontTextView: NumberRunningFontTextView, numberOfLines: Int) {
? ? ? ? numberRunningFontTextView.maxLines = numberOfLines
? ? }
? ? /**
?? ? *后綴文本
?? ? */
? ? @ReactProp(name = "suffixText")
? ? fun setSuffixText(numberRunningFontTextView: NumberRunningFontTextView, suffixText: String) {
? ? ? ? numberRunningFontTextView.suffixText = suffixText
? ? }
? ? /**
?? ? *后綴文本字體大小
?? ? */
? ? @ReactProp(name = "suffixTextFontSize")
? ? fun setSuffixTextFontSize(numberRunningFontTextView: NumberRunningFontTextView, suffixTextFontSize: Int) {
? ? ? ? numberRunningFontTextView.suffixTextFontSize = suffixTextFontSize
? ? }
? ? /**
?? ? *后綴文本字體
?? ? */
? ? @ReactProp(name = "suffixTextFontFamily")
? ? fun setSuffixTextFontFamily(numberRunningFontTextView: NumberRunningFontTextView, suffixTextFontFamily: String) {
? ? ? ? numberRunningFontTextView.suffixTextFontFamily = suffixTextFontFamily
? ? }
? ? /**
?? ? *后綴文本文本顏色
?? ? */
? ? @ReactProp(name = "suffixTextFontColor")
? ? fun setSuffixTextFontColor(numberRunningFontTextView: NumberRunningFontTextView, suffixTextFontColor: String) {
? ? ? ? numberRunningFontTextView.suffixTextFontColor = suffixTextFontColor
? ? }
}
? ? ? ??
把定義好的控件橋接到RNAPI 供RN調(diào)用
override fun createViewManagers(reactContext: ReactApplicationContext) =
? ? listOf(? //支持定義許多個控件
? ? ? ? DUNumberLabelManager(),? ?// 目標(biāo)控件
? ? ? ? DUNumberLabelManager1(),??
? ? ? ? DUNumberLabelManager2(),
? ? ? ? DUNumberLabelManage3r()
? ? )
2.ReactNative控件屬性定義
// DUNumberLabel.js
import PropTypes from 'prop-types';
import React from 'react';
import { NativeModules, requireNativeComponent, UIManager, findNodeHandle } from 'react-native';
const NumberLabel = requireNativeComponent('DUNumberLabel', DUNumberLabel); // 通過定義名稱,獲取原生控件
export class DUNumberLabel extends React.Component {
? render() {
? ? return
? ? ? ref={ref => this.numberLab = ref}
? ? ? fontSize={14}
? ? ? fontColor={'#000000'}
? ? ? toFixed={0}
? ? ? numberOfLines={1}
? ? ? suffixTextFontSize={12}
? ? ? suffixTextFontColor={'#000000'}
? ? ? {...this.props}
? ? />;
? }
}
DUNumberLabel.propTypes = {? // 定義控件所需屬性
? /**
?? * 字體大小
?? */
? fontSize: PropTypes.number,
? /**
?? * 字體
?? */
? fontFamily: PropTypes.string,
? /**
?? * 文本顏色
?? */
? fontColor: PropTypes.string,
? /**
?? * 開始數(shù)字
?? */
? startNum: PropTypes.number,
? /**
?? * 結(jié)束數(shù)字
?? */
? endNum: PropTypes.number,
? /**
?? * 動畫時長,秒
?? */
? duration: PropTypes.number,
? /**
?? * 文本
?? */
? text: PropTypes.string,
? /**
?? * 保留小數(shù)幾位
?? */
? toFixed: PropTypes.number,
? /**
?? * 行數(shù)
?? */
? numberOfLines: PropTypes.number,
? /**
?? * 后綴文本
?? */
? suffixText: PropTypes.string,
? /**
?? * 后綴文本字體大小
?? */
? suffixTextFontSize: PropTypes.number,
? /**
?? * 后綴文本字體
?? */
? suffixTextFontFamily: PropTypes.string,
? /**
?? * 后綴文本文本顏色
?? */
? suffixTextFontColor: PropTypes.string,
};
3.ReactNative引用部分?
????<DUNumberLabel
?? ? ? ? ? style={{ ...styles.total, width: '100%' }}
?? ? ? ? ? fontSize={20}
?? ? ? ? ? fontFamily={DUFont.family.helveticaNeueCondensedBold}
?? ? ? ? ? fontColor={'#000fff'}
?? ? ? ? ? startNum={10000}
?? ? ? ? ? endNum={9999000}
?? ? ? ? ? duration={5000}
?? ? ? ? ? toFixed={2}
?? ? ? ? ? suffixText={"億萬"}
?? ? ? ? ? suffixTextFontSize={12}
?? ? ? ? ? suffixTextFontFamily={'HelveticaNeue-CondensedBold'}
?? ? ? ? ? suffixTextFontColor={'#14151A'}
?? ? ? />
注意事項:
? ? 1.注意ReatctNative調(diào)用通用控件封裝版本更新后, 本地也需要重新更新保持安裝版本內(nèi)代碼包含定義控件

????2.調(diào)用APK需包含上述原生控件封裝代碼否則會找不到原生控件
? ? 3.單位跳動問題:在原生自定義控件?NumberRunningFontTextView 新增? ? ?gravity = Gravity.RIGHT