美文网首页学习之鸿蒙&Android
建立自己的王国:Android 自定义封装View(1)

建立自己的王国:Android 自定义封装View(1)

作者: 努尔江 | 来源:发表于2021-05-25 14:04 被阅读0次

国际惯例,效果图


效果图1 效果图2

产品需求是做一个控制台,按照不同的状态显示(图片/数量),可以用XML很容易就实现,但是众所周知,XML解析比代码慢,还得嵌套很多View(ConstraintLayout->LinearLayout->TextView->View),这样,在View的Measure,Layout,Draw 过程中的时间超过了16毫秒。

步骤1

实现View的构造方法。

class HomeHierarchyView : ConstraintLayout {

    constructor(context: Context) : super(context) {
      //默认方法,初始化时。
    }
    constructor(context:Context,attributeSet: AttributeSet):super(context,attributeSet){
        //获取资源
    }

    constructor(cn: Context,attributeSet: AttributeSet,style:Int):super(cn,attributeSet,style){
        //api>21时被调用
    }
}

步骤2

定义attrs属性。

   <!--要跟定义的View类名要相同-->
    <declare-styleable name="HomeHierarchyView">
        <!--是否文字,true 文字,false 是图片.-->
        <attr name="texture" format="boolean"/>
            <!--待量房-->
        <attr name="waitingMeasure" format="string"/>
    </declare-styleable>

步骤3

获取自定义属性变量(在类下面)以及绑定(第二个构造体内)

/**
     *  文字还是背景图
     */
    var textOrImage:Boolean=false

    /**
     *  待量房
     */
    var waitingMeasure:String?=""
    val ta = cn.obtainStyledAttributes(attributeSet, R.styleable.HomeHierarchyView)
    textOrImage = ta.getBoolean(R.styleable.HomeHierarchyView_texture, true)
   //最后保证,能被回收。
    ta.recycle()

步骤4

构建View.
主体是ConstraintLayout,在里面代码的形式加载有关的子View.
主要结构:
*HomeHierarchyView(ConstraintLayout)
**LinearLayout
***TextView(显示数量/图片)
***TextView(固定文案)

在这里LinearLayout一直重复着添加到主View里。LinearLayou内包含两个TextView,
有ConstraintLayout特性,位置关系需要提供View ID,所以把View ID 统统写在values->ids.xml文件里。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="ll_1" type="id"/>
    <item name="tv_top1" type="id"/>
</resources>

从内到外,从下到上添加View到主体内。
其中一个TextView

val textView=TextView(cn)
textView.id=R.id.tv_top1 //这个要跟定义的对应。
  textView.layoutParams = LinearLayout.LayoutParams(-2, DisplayUtils.dip2px(30F)) 
//-2代表WRAP_CONTENT, -1 代表MATCH_PARENT, 由于高度值需要Pixel,所以需要转换。
        textView.textSize = 22F
        textView.typeface = Typeface.DEFAULT_BOLD
        textView.textColor = Color.parseColor("#0166B3")
        textView.gravity = Gravity.CENTER
        textView.text = ""

相似的添加标签代码

        val label=TextView(cn)
        label.layoutParams = LinearLayout.LayoutParams(-2, -2)
        label.text = "待量房"
        label.textSize = 12F
        label.textColor = Color.parseColor("#3e3e3e")
      //注:会被加载到LinearLayout里,所以LayoutParams 是LinearLayout的,请注意。
        (label.layoutParams as LinearLayout.LayoutParams).topMargin = DisplayUtils.dip2px(5F)

把这两个TextView加载LinearLayou里。

      //ll1
        val l1 = LinearLayout(cn)
        l1.id = R.id.ll_1
        //这里不需要写ConstraintLayout.LayoutParams, 因为主体就是。
        val l1Params = LayoutParams(0, DisplayUtils.dip2px(80F))
        l1.gravity = Gravity.CENTER
        l1.orientation = LinearLayout.VERTICAL
        l1Params.leftToLeft = R.id.cl_parent
        l1Params.topToTop = R.id.cl_parent
        l1Params.matchConstraintPercentWidth = 0.33F
       l1.layoutParams = l1Params
        l1.addView(textView)
        l1.addView(label)

最后添加到主体里。

 this.addView(l1)

步骤5

整理代码。
由于两个TextView 除了ID,text 不一样意外,其他属性都一样。那么可以封装个方法。

  private fun addTextView(textView: TextView, id: Int): TextView {
        textView.layoutParams = LinearLayout.LayoutParams(-2, DisplayUtils.dip2px(30F))
        textView.textSize = 22F
        textView.typeface = Typeface.DEFAULT_BOLD
        textView.textColor = Color.parseColor("#0166B3")
        textView.id = id
        textView.gravity = Gravity.CENTER
        textView.text = ""
        return textView
    }

//这个textView不需要设置ID, 文案写死就ok。
    private fun addLabel(s: String): TextView {
        val textView = TextView(cn)
        textView.layoutParams = LinearLayout.LayoutParams(-2, -2)
        textView.text = s
        textView.textSize = 12F
        textView.textColor = Color.parseColor("#3e3e3e")
        (textView.layoutParams as LinearLayout.LayoutParams).topMargin = DisplayUtils.dip2px(5F)
        return textView
    }

步骤6

在XML里使用。

            <HomeHierarchyView
                android:id="@+id/hhv_controlPanel"
                android:layout_width="match_parent"
                android:layout_height="@dimen/name_dp160"
                android:layout_marginStart="15dp"
                app:texture="false"
                app:waitingMeasure="0"
                android:layout_marginTop="@dimen/dp_40"
                android:layout_marginEnd="@dimen/name_dp15" />

在自定义View里添加个方法,为了不同的状态展示文案或图片。
用Texture属性来控制展示图片或文案。

   fun updateStatus() {
        if (textOrImage) {
            tv_top1.background = null
            tv_top1.text = waitingMeasure
        } else {
            tv_top1.text = ""
//资源用的是UI切图的SVG, 怎么倒入请百度(谷歌也行)
            tv_top1.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_measure)
        }
    }

最后完整的代码:

HomeHierarchyView.kt

/**
 * @author hugo
 * @time 2021-5-24
 * Think twice, Code once.
 * 主页控制台
 */
class HomeHierarchyView : ConstraintLayout {

    /**
     *  文字还是背景图
     */
    var textOrImage: Boolean = false

    /**
     *  待量房
     */
    var waitingMeasure: String? = ""

    /**
     * 待预交底
     */
    var waitingSubmit: String? = ""

    /**
     *待签约
     */
    var waitingSign: String? = ""

    /**
     * 未开工
     */
    var statUnStart: String? = ""

    /**
     * 施工中
     */
    var statInProgress: String? = ""

    /**
     * 已竣工
     */
    var statOnFinished: String? = ""

    /**
     * 已结算
     */
    var statClearing: String? = ""

    /**
     * 文案1
     */
    lateinit var tv_top1: TextView
    lateinit var tv_top2: TextView
    lateinit var tv_top3: TextView
    lateinit var tv_top4: TextView
    lateinit var tv_top5: TextView
    lateinit var tv_top6: TextView
    lateinit var tv_top7: TextView

    var cn: Context = context

    constructor(context: Context) : super(context) {
        initView()
    }

    constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
        //获取资源
        initParams(attributeSet)
        initView()
    }

    constructor(cn: Context, attributeSet: AttributeSet, style: Int) : super(
        cn,
        attributeSet,
        style
    ) {
        //获取资源
        initParams(attributeSet)
        initView()
    }

    private fun initParams(attributeSet: AttributeSet) {
        val ta = cn.obtainStyledAttributes(attributeSet, R.styleable.HomeHierarchyView)
        textOrImage = ta.getBoolean(R.styleable.HomeHierarchyView_texture, true)
        waitingMeasure = ta.getString(R.styleable.HomeHierarchyView_waitingMeasure)
        waitingSubmit = ta.getString(R.styleable.HomeHierarchyView_waitingSubmit)
        waitingSign = ta.getString(R.styleable.HomeHierarchyView_waitingSign)
        statInProgress = ta.getString(R.styleable.HomeHierarchyView_statInProgress)
        statUnStart = ta.getString(R.styleable.HomeHierarchyView_statUnStart)
        statOnFinished = ta.getString(R.styleable.HomeHierarchyView_statOnFinished)
        statClearing = ta.getString(R.styleable.HomeHierarchyView_statClearing)
        ta.recycle()
    }

    private fun addTextView(textView: TextView, id: Int): TextView {
        textView.layoutParams = LinearLayout.LayoutParams(-2, DisplayUtils.dip2px(30F))
        textView.textSize = 22F
        textView.typeface = Typeface.DEFAULT_BOLD
        textView.textColor = Color.parseColor("#0166B3")
        textView.id = id
        textView.gravity = Gravity.CENTER
        textView.text = ""
        return textView
    }

    private fun addLabel(s: String): TextView {
        val textView = TextView(cn)
        textView.layoutParams = LinearLayout.LayoutParams(-2, -2)
        textView.text = s
        textView.textSize = 12F
        textView.textColor = Color.parseColor("#3e3e3e")
        (textView.layoutParams as LinearLayout.LayoutParams).topMargin = DisplayUtils.dip2px(5F)
        return textView
    }

    fun initView() {

        //初始化整个textview系列
        tv_top1 = TextView(cn)
        tv_top2 = TextView(cn)
        tv_top3 = TextView(cn)
        tv_top4 = TextView(cn)
        tv_top5 = TextView(cn)
        tv_top6 = TextView(cn)
        tv_top7 = TextView(cn)

        this.background = ContextCompat.getDrawable(cn, R.drawable.round_corder_w)
        val parentParams = ViewGroup.LayoutParams(-1, DisplayUtils.dip2px(160F))
        this.layoutParams = parentParams

        //ll1
        val l1 = LinearLayout(cn)
        l1.id = R.id.ll_1
        val l1Params = LayoutParams(0, DisplayUtils.dip2px(80F))
        l1.gravity = Gravity.CENTER
        l1.orientation = LinearLayout.VERTICAL
        l1Params.leftToLeft = R.id.cl_parent
        l1Params.topToTop = R.id.cl_parent
        l1Params.matchConstraintPercentWidth = 0.33F
        l1.addView(addTextView(tv_top1, R.id.tv_top1))
        l1.addView(addLabel("待量房"))
        l1.layoutParams = l1Params

        //ll2
        val l2 = LinearLayout(cn)
        l2.id = R.id.ll_2
        val l2Params = LayoutParams(0, DisplayUtils.dip2px(80F))
        l2.gravity = Gravity.CENTER
        l2.orientation = LinearLayout.VERTICAL
        l2Params.leftToRight = R.id.ll_1
        l2Params.topToTop = R.id.cl_parent
        l2Params.matchConstraintPercentWidth = 0.33F
        l2.addView(addTextView(tv_top2, R.id.tv_top2))
        l2.addView(addLabel("待预交底"))
        l2.layoutParams = l2Params


        //ll3
        val l3 = LinearLayout(cn)
        l3.id = R.id.ll_3
        val l3Params = LayoutParams(0, DisplayUtils.dip2px(80F))
        l3.gravity = Gravity.CENTER
        l3.orientation = LinearLayout.VERTICAL
        l3Params.leftToRight = R.id.ll_2
        l3Params.topToTop = R.id.cl_parent
        l3Params.matchConstraintPercentWidth = 0.33F
        l3.addView(addTextView(tv_top3, R.id.tv_top3))
        l3.addView(addLabel("待签约"))
        l3.layoutParams = l3Params

        //v1
        val v1 = View(cn)
        v1.id = R.id.v_1
        v1.layoutParams = LayoutParams(DisplayUtils.dip2px(1F), DisplayUtils.dip2px(60F))
        v1.backgroundColor = Color.parseColor("#EDEDED")
        (v1.layoutParams as LayoutParams).leftToRight = R.id.ll_1
        (v1.layoutParams as LayoutParams).topToTop = R.id.cl_parent
        (v1.layoutParams as LayoutParams).bottomToBottom = R.id.ll_1

        //v2
        val v2 = View(cn)
        v1.id = R.id.v_2
        v2.layoutParams = LayoutParams(DisplayUtils.dip2px(1F), DisplayUtils.dip2px(60F))
        v2.backgroundColor = Color.parseColor("#EDEDED")
        (v2.layoutParams as LayoutParams).leftToRight = R.id.ll_2
        (v2.layoutParams as LayoutParams).topToTop = R.id.cl_parent
        (v2.layoutParams as LayoutParams).bottomToBottom = R.id.ll_2

        //v3
        val v3 = View(cn)
        v1.id = R.id.v_3
        v3.layoutParams = LayoutParams(-1, DisplayUtils.dip2px(1F))
        v3.backgroundColor = Color.parseColor("#EDEDED")
        (v3.layoutParams as LayoutParams).marginEnd = DisplayUtils.dip2px(20F)
        (v3.layoutParams as LayoutParams).marginStart = DisplayUtils.dip2px(20F)
        (v3.layoutParams as LayoutParams).topToBottom = R.id.ll_1


        //v4
        val l4 = LinearLayout(cn)
        l4.id = R.id.ll_4
        val l4Params = LayoutParams(0, DisplayUtils.dip2px(80F))
        l4.gravity = Gravity.CENTER
        l4.orientation = LinearLayout.VERTICAL
        l4Params.leftToLeft = R.id.cl_parent
        l4Params.bottomToBottom = R.id.cl_parent
        l4Params.topToBottom = R.id.v_3
        l4Params.matchConstraintPercentWidth = 0.25F
        l4.addView(addTextView(tv_top4, R.id.tv_top4))
        l4.addView(addLabel("未开工"))
        l4.layoutParams = l4Params

        //ll5
        val l5 = LinearLayout(cn)
        l5.id = R.id.ll_5
        val l5Params = LayoutParams(0, DisplayUtils.dip2px(80F))
        l5.gravity = Gravity.CENTER
        l5.orientation = LinearLayout.VERTICAL
        l5Params.leftToRight = R.id.ll_4
        l5Params.bottomToBottom = R.id.cl_parent
        l5Params.topToBottom = R.id.v_3
        l5Params.matchConstraintPercentWidth = 0.25F
        l5.addView(addTextView(tv_top5, R.id.tv_top5))
        l5.addView(addLabel("施工中"))
        l5.layoutParams = l5Params

        //ll6
        val l6 = LinearLayout(cn)
        l6.id = R.id.ll_6
        val l6Params = LayoutParams(0, DisplayUtils.dip2px(80F))
        l6.gravity = Gravity.CENTER
        l6.orientation = LinearLayout.VERTICAL
        l6Params.bottomToBottom = R.id.cl_parent
        l6Params.leftToRight = R.id.ll_5
        l6Params.topToBottom = R.id.v_3
        l6Params.matchConstraintPercentWidth = 0.25F
        l6.addView(addTextView(tv_top6, R.id.tv_top6))
        l6.addView(addLabel("已竣工"))
        l6.layoutParams = l6Params

        //ll7
        val l7 = LinearLayout(cn)
        l7.id = R.id.ll_7
        val l7Params = LayoutParams(0, DisplayUtils.dip2px(80F))
        l7.gravity = Gravity.CENTER
        l7.orientation = LinearLayout.VERTICAL
        l7Params.bottomToBottom = R.id.cl_parent
        l7Params.leftToRight = R.id.ll_6
        l7Params.topToBottom = R.id.v_3
        l7Params.matchConstraintPercentWidth = 0.25F
        l7.addView(addTextView(tv_top7, R.id.tv_top7))
        l7.addView(addLabel("已结算"))
        l7.layoutParams = l7Params

        //v4
        val v4 = View(cn)
        v4.layoutParams = LayoutParams(DisplayUtils.dip2px(1F), DisplayUtils.dip2px(60F))
        v4.backgroundColor = Color.parseColor("#EDEDED")
        (v4.layoutParams as LayoutParams).leftToRight = R.id.ll_4
        (v4.layoutParams as LayoutParams).topToTop = R.id.ll_4
        (v4.layoutParams as LayoutParams).bottomToBottom = R.id.ll_4

        //v5
        val v5 = View(cn)
        v5.layoutParams = LayoutParams(DisplayUtils.dip2px(1F), DisplayUtils.dip2px(60F))
        v5.backgroundColor = Color.parseColor("#EDEDED")
        (v5.layoutParams as LayoutParams).leftToRight = R.id.ll_5
        (v5.layoutParams as LayoutParams).topToTop = R.id.ll_5
        (v5.layoutParams as LayoutParams).bottomToBottom = R.id.ll_5

        //v6
        val v6 = View(cn)
        v6.layoutParams = LayoutParams(DisplayUtils.dip2px(1F), DisplayUtils.dip2px(60F))
        v6.backgroundColor = Color.parseColor("#EDEDED")
        (v6.layoutParams as LayoutParams).leftToRight = R.id.ll_6
        (v6.layoutParams as LayoutParams).topToTop = R.id.ll_6
        (v6.layoutParams as LayoutParams).bottomToBottom = R.id.ll_6

        this.addView(l1)
        this.addView(l2)
        this.addView(l3)

        this.addView(v1)
        this.addView(v2)
        this.addView(v3)
        this.addView(v4)
        this.addView(v5)
        this.addView(v6)

        this.addView(l4)
        this.addView(l5)
        this.addView(l6)
        this.addView(l7)

        //注:切记,都加完了才去写入值的操作,不然会空指针。

        if (textOrImage) {
            tv_top1.background = null
            tv_top2.background = null
            tv_top3.background = null
            tv_top4.background = null
            tv_top5.background = null
            tv_top6.background = null
            tv_top7.background = null
            tv_top1.text = waitingMeasure
            tv_top2.text = waitingSubmit
            tv_top3.text = waitingSign
            tv_top4.text = statUnStart
            tv_top5.text = statInProgress
            tv_top6.text = statOnFinished
            tv_top7.text = statClearing
        } else {
            tv_top1.text = ""
            tv_top2.text = ""
            tv_top3.text = ""
            tv_top4.text = ""
            tv_top5.text = ""
            tv_top6.text = ""
            tv_top7.text = ""
            tv_top1.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_measure)
            tv_top2.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_send)
            tv_top3.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_sign)
            tv_top4.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_notstart)
            tv_top5.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_doing)
            tv_top6.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_done)
            tv_top7.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_money)
        }

        //下面的是对应的点击事件
        l1.singleClick {
            cn.startActivity<NewProjectListActivity>("status" to "2,3,4")
        }
        //
        l2.singleClick {
            cn.startActivity<NewProjectListActivity>("status" to "5,6,7,8")
        }

        l3.singleClick {
            cn.startActivity<NewProjectListActivity>("status" to "9")
        }

        l4.singleClick {
            cn.startActivity<NewProjectListActivity>("status" to "10,11")
        }

        l5.singleClick {
            cn.startActivity<NewProjectListActivity>("status" to "12")
        }

        l6.singleClick {
            cn.startActivity<NewProjectListActivity>("status" to "13")
        }

        l7.singleClick {
            cn.startActivity<NewProjectListActivity>("status" to "14")
        }
    }

    fun updateStatus() {
        if (textOrImage) {
            tv_top1.background = null
            tv_top2.background = null
            tv_top3.background = null
            tv_top4.background = null
            tv_top5.background = null
            tv_top6.background = null
            tv_top7.background = null
            tv_top1.text = waitingMeasure
            tv_top2.text = waitingSubmit
            tv_top3.text = waitingSign
            tv_top4.text = statUnStart
            tv_top5.text = statInProgress
            tv_top6.text = statOnFinished
            tv_top7.text = statClearing
        } else {
            tv_top1.text = ""
            tv_top2.text = ""
            tv_top3.text = ""
            tv_top4.text = ""
            tv_top5.text = ""
            tv_top6.text = ""
            tv_top7.text = ""
            tv_top1.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_measure)
            tv_top2.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_send)
            tv_top3.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_sign)
            tv_top4.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_notstart)
            tv_top5.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_doing)
            tv_top6.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_done)
            tv_top7.background = ContextCompat.getDrawable(cn, R.drawable.ic_main_money)
        }
    }
}

attrs.xml

  <declare-styleable name="HomeHierarchyView">
        <!--是否文字,true 文字,false 是图片.-->
        <attr name="texture" format="boolean"/>
            <!--待量房-->
        <attr name="waitingMeasure" format="string"/>
<!--待预交底-->
        <attr name="waitingSubmit" format="string"/>
        <!--        待签约-->
        <attr name="waitingSign" format="string"/>
<!--        未开工-->
        <attr name="statUnStart" format="string"/>
<!--       施工中-->
        <attr name="statInProgress" format="string"/>
<!--        已竣工-->
        <attr name="statOnFinished" format="string"/>
<!--        已结算-->
        <attr name="statClearing" format="string"/>

    </declare-styleable>

布局内使用。

            <HomeHierarchyView
                android:id="@+id/hhv_controlPanel"
                android:layout_width="match_parent"
                android:layout_height="@dimen/name_dp160"
                android:layout_marginStart="15dp"
                app:texture="false"
                app:waitingSign="0"
                app:statClearing="0"
                app:statInProgress="0"
                app:statOnFinished="0"
                app:statUnStart="0"
                app:waitingMeasure="0"
                app:waitingSubmit="0"
                android:layout_marginTop="@dimen/dp_40"
                android:layout_marginEnd="@dimen/name_dp15" />

ids.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="ll_1" type="id"/>
    <item name="ll_2" type="id"/>
    <item name="ll_3" type="id"/>
    <item name="ll_4" type="id"/>
    <item name="ll_5" type="id"/>
    <item name="ll_6" type="id"/>
    <item name="ll_7" type="id"/>

    <item name="tv_top1" type="id"/>
    <item name="tv_top2" type="id"/>
    <item name="tv_top3" type="id"/>
    <item name="tv_top4" type="id"/>
    <item name="tv_top5" type="id"/>
    <item name="tv_top6" type="id"/>
    <item name="tv_top7" type="id"/>

    <item name="v_1" type="id"/>
    <item name="v_2" type="id"/>
    <item name="v_3" type="id"/>
    <item name="v_4" type="id"/>
    <item name="v_5" type="id"/>
    <item name="v_6" type="id"/>
    <item name="v_7" type="id"/>

</resources>

动态更新

    //注:项目用的是Kotlin-Kapt,不需要findViewByID
                hhv_controlPanel.textOrImage = true
                    with(hhv_controlPanel) {
                        this.waitingMeasure = it.measure.toString()
                        this.waitingSubmit = it.prepaid.toString()
                        this.waitingSign = it.waitSigned.toString()
                        this.statUnStart = it.notStartWork.toString()
                        this.statInProgress = it.construction.toString()
                        this.statOnFinished = it.completion.toString()
                        this.statClearing = it.settlement.toString()
                }
                hhv_controlPanel.updateStatus()

最后DisplayUtils.java

public class DisplayUtils {
    /**
     * dp转px
     *
     * @param dipValue
     * @return
     */
    public static int dip2px(float dipValue) {
        final float scale = Resources.getSystem().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }


    public static int px2dip(int px) {
        final float scale = Resources.getSystem().getDisplayMetrics().density;
        return (int) (px / scale + 0.5f);
    }
}

注意事项:请不要在View里设置ID,如果被分配了ID,那么在xml里使用的时候的写的ID无效,从而报空指针。

完工!

相关文章

网友评论

    本文标题:建立自己的王国:Android 自定义封装View(1)

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