美文网首页
手把手教你ReactNative如何调用原生自定义控件

手把手教你ReactNative如何调用原生自定义控件

作者: 善良的老农 | 来源:发表于2024-05-05 16:08 被阅读0次

功能需求:数字滚动动画控件定义,并带有单位,因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

相关文章

网友评论

      本文标题:手把手教你ReactNative如何调用原生自定义控件

      本文链接:https://www.haomeiwen.com/subject/skmkfjtx.html