效果图:
1:先分析里面要的组合方式,是上面一个图片,下面一个文本,我们可以用自定义LInearLayout来实现是比较容易的:
自定义的方法看上两篇前,
我们需要的属性,但是系统又没有提供的,或者系统没能够满足我们的就可以自己去添加,添加的方法看上两篇前的,
这个是对每一个按钮的自定义的属性:当点下一个按钮是上一个按钮的选中状态就需要恢复为未选中状态,在这我是用两
组不同的照片来进行交换实现的,就是正常时的图片normalIcon,和选中时的图片selectedIcon,同样文本(title)的颜色的变化也有两种,
misselected是记录按钮是否被点击时的状态,
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Bottom_bar">
<attr name="normalIcon" format="reference"/>
<attr name="selectedIcon" format="reference"/>
<attr name="title" format="string"/>
<attr name="hightlighttitle" format="string"/>
<attr name="color" format="color"/>
<attr name="hightlightcolor" format="color"/>
<attr name="misselected" format="boolean"/>
</declare-styleable>
<declare-styleable name="myViewGroup">
<attr name="number" format="integer"/>
</declare-styleable>
</resources>
2:解析属性以得到自定义的属性,在解析属性之后就可以将属性取出应用了,我们需要xml的配置,
也需要代码的配置,所以在两个构造方法中都需要初始化:方法看上两篇
constructor(context: Context):super(context){
Log.v("lfl","代码")
initView() 初始化图片与文本
}
constructor(context: Context,attributeSet: AttributeSet?):super(context,attributeSet){
//获取自定义的属性
var typedArray = context.obtainStyledAttributes(attributeSet,
R.styleable.Bottom_bar)
//获取单个的属性
normalIconID = typedArray.getResourceId(R.styleable.Bottom_bar_normalIcon,
R.drawable.home)
selectedIconID = typedArray.getResourceId(R.styleable.Bottom_bar_selectedIcon,
R.drawable.home_selected)
title = typedArray.getString(R.styleable.Bottom_bar_title).toString()
Log.v("lfl","xml")
//初始化视图
initView()
//要改变的值
misselected = typedArray.getBoolean(R.styleable.Bottom_bar_misselected,false)
textColor = typedArray.getColor(R.styleable.Bottom_bar_color,Color.BLACK)
//释放
typedArray.recycle()
}
3:将解析出来的各种属性用起来,初始化图片与文本
用到的方法有:
(1):转换像素:
//获取手机的像素
fun dpTopx(dp:Int):Int = dp*(context.resources.displayMetrics.density).toInt()
(2):更新图片与文本 ,我们需要在多个地方更新,所以将其抽出
private fun UpdateUI(){
Log.v("lfl","******************************")
if (misselected){
//切换成没有被选中的状态
image?.setImageResource(selectedIconID)
textView?.setTextColor(Color.RED)
}else{
//选中状态
image?.setImageResource(normalIconID)
textView?.setTextColor(Color.BLACK)
}
}
//初始化
fun initView(){
orientation = VERTICAL 设置内容的填充方向
gravity = Gravity.CENTER_VERTICAL
//添加图片
image = ImageView(context).apply {
var layoutParams = LinearLayout.LayoutParams(dpTopx(32),dpTopx(32)) 设置图片的大小,
addView(this,layoutParams)
}
//添加文字
textView = TextView(context).apply {
//setText(title)
//text = title
}.also {
val layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
addView(it,layoutParams)
}
UpdateUI()
}
4:改变按钮的点击状态,当我们点击一个按钮时,若该按钮是点击状态则不响应,若该按钮没有被点击时
则将该按钮设置为被点击状态,并且更新图片与文本,对于一个按钮而言只需要这样,点击事件可以在点下去时
实现,也可以在手离开屏幕那一刻时实现,这是在点下去时实现的,并且这个事件已经被消费了,所以返回true
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event?.action == MotionEvent.ACTION_DOWN){
if (!misselected){
//改变状态
misselected = !misselected
selectCallback?.let {
it(index) //调用高阶函数,点下去时改变状态的同时要将上一个被点击
的按钮还原
}
}
}
//事件已经被消费了就返回true
return true
}
只要状态改变,那么就更新数据,因此可以重写misselected的set方法,
//记录按钮被点击的状态
var misselected:Boolean = false
set(value) {
field = value
Log.v("lfl","misselected")
UpdateUI()
}
一个按钮实现,接下来就实现多个按钮,多个按钮也可以通过自定义linearLayout来实现,一个按钮里面初始化的是图片和文本,
多个按钮的实现,就是将多个按钮放在一个容器里面,初始化按钮即可
(1)xml里面的应用:
<com.example.myapplication.myViewGroup
android:id="@+id/myviewGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:number="3" 有多少个按钮,是个自定义属性
/>
(2)初始化按钮,代码,xml皆要初始化
constructor(context: Context):super(context){
//初始化
initView()
}
constructor(context: Context,attributeSet: AttributeSet):super(context,attributeSet){
//解析
val typedArray = context.obtainStyledAttributes(attributeSet,
R.styleable.myViewGroup)
number = typedArray.getInteger(R.styleable.myViewGroup_number,4)
typedArray.recycle()
initView()
}
//初始化每一个视图
private fun initView() {
orientation = HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
for (i in 0 until number){
Bottom_bar(context).also {
val lp = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
weight = 1f
}
addView(it,lp)
Bottom_bar_array.add(it)
//为每一个按钮添加监听事件
it.selectCallback = {index->
//还原之前选中的栏目的状态
Bottom_bar_array[current].misselected = false
//保存索引
current = index
}
}
}
}
(3):通过一个模型将数据从外部传进来
a:需要先定义一个模型,模型里面放的全是属性,模型就作为传输数据的中间人,外部将数据给模型,模型在将数据传给对象
data class Bottom_bar_model (
var normalIconID:Int,
var selectedIconID:Int,
var title:String,
var misselected:Boolean
)
b:将通过模型的传输进来的数据给到每一个按钮
//更新每一个视图
private fun initBottom_bar(){
for ((i,iteam) in Bottom_bar_array.withIndex()){
iteam.index = i
iteam.title = model[i].title
iteam.normalIconID = model[i].normalIconID
iteam.selectedIconID = model[i].selectedIconID
iteam.misselected = model[i].misselected
}
在外部调用时就只需要将数据给模型就好了
myviewGroup.model = arrayOf(
Bottom_bar_model(
R.drawable.home,
R.drawable.home_selected,
"主页",
true
),
Bottom_bar_model(
R.drawable.home,
R.drawable.home_selected,
"主页",
false
),
Bottom_bar_model(
R.drawable.home,
R.drawable.home_selected,
"主页",
false
)
)
GitHub连接:https://github.com/luofangli/Bottom_bar