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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容