功能需求:数字滚动动画控件定义,并带有单位,因ReactNative定义的动画控件实现效果不佳,所以需要使用RN调用原生控件来实现。
具体实现如下:官方调用API
1.Android原生控件定义官方链接。
原生数字滚动自定义控件实现
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 = ""//字体
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 //保留小数几位
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引用具体类方法声明实现
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
}
/**
*开始数字
*/
@ReactProp(name = "startNum")
fun setStartNum(numberRunningFontTextView: NumberRunningFontTextView, startNum: Int) {
numberRunningFontTextView.currentNum = startNum
}
/**
*结束数字
*/
@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
}
/**
*保留小数几位
*/
@ReactProp(name = "toFixed")
fun setToFixed(numberRunningFontTextView: NumberRunningFontTextView, toFixed: Int) {
numberRunningFontTextView.toFixed = toFixed
}
/**
*行数
*/
@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调用
override fun createViewManagers(reactContext: ReactApplicationContext) =
listOf( //支持定义许多个控件
DUNumberLabelManager(), // 目标控件
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,
/**
* 开始数字
*/
startNum: PropTypes.number,
/**
* 结束数字
*/
endNum: PropTypes.number,
/**
* 动画时长,秒
*/
duration: PropTypes.number,
/**
* 文本
*/
text: PropTypes.string,
/**
* 保留小数几位
*/
toFixed: PropTypes.number,
/**
* 行数
*/
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调用通用控件封装版本更新后, 本地也需要重新更新保持安装版本内代码包含定义控件
2.调用APK需包含上述原生控件封装代码否则会找不到原生控件
3.单位跳动问题:在原生自定义控件 NumberRunningFontTextView 新增 gravity = Gravity.RIGHT