国际惯例,效果图
产品需求是做一个控制台,按照不同的状态显示(图片/数量),可以用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无效,从而报空指针。
完工!