首先,图片镇楼
支持根据集合动态生成tab个数,小红点,可配置默认图片和选中图片,可配置字体颜色
先看 调用的代码
package com.example.myapplication
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import kotlinx.android.synthetic.main.activity_botttom_nav_bar.*
import org.jetbrains.anko.toast
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_botttom_nav_bar)
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
viewModel.apply {
setNavBarContent()
dataNabsLiveData.observe(this@MainActivity, Observer {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
navBar.apply {
setUnSelectedData(
it["dataNormal"] as Array<Int>,
it["dataSel"] as Array<Int>,
it["dataStr"] as Array<String>
)
//由于小圆点是依赖于集合的长度的,所以要先给集合赋值才可以
setBadgeView(true,0,R.mipmap.xyd)
itemClickListener = {
tvShow.text ="$it"
}
}
}
})
}
}
}
采用了viewmodel,把数据配置在了viewmodel中,尽量给界面以整洁,接下来看viewmodel里面的代码
package com.example.myapplication
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainViewModel : ViewModel() {
//存储显示的集合集合
var dataNabsLiveData = MutableLiveData<MutableMap<String, Any>>()
fun setNavBarContent() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val map: MutableMap<String, Any> = HashMap<String, Any>()
map["dataNormal"] = arrayOf(
R.mipmap.auction_rank_first,
R.mipmap.auction_rank_second,
R.mipmap.auction_rank_third,
R.mipmap.exp
)
map["dataSel"] = arrayOf(
R.mipmap.icon_vip_custom_avatar_privilege,
R.mipmap.icon_vip_diamond,
R.mipmap.icon_vip_home_5,
R.mipmap.icon_vip_home_6
)
map["dataStr"] = arrayOf("第一", "第二", "第三","粪土")
dataNabsLiveData.postValue(map)
}
}
}
}
引入了协程,赋值的操作,假设为一二个号网络请求,接下来,就是自定义的kt文件了
package com.example.myapplication
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.os.Build
import android.util.AttributeSet
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.core.content.ContextCompat
import org.jetbrains.anko.*
/**
* 自定义底部通用切换NavBar效果
*/
@Suppress("UNREACHABLE_CODE")
class BottomNavBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : LinearLayoutCompat(context, attrs) {
/**
* nav顶部线条颜色
*/
private var navBarDividerColor = Color.TRANSPARENT
/**
* nav字体默认颜色
*/
private var navBarTextNormalColor = Color.BLACK
/**
* nav字体选中颜色
*/
private var navBarTextSelColor = Color.GREEN
/**
* nav顶部线条高度大小
*/
private var navBarDividerSize = 1
/**
* nav字体的大小
*/
private var navBarTextSize = 15
/**
* 存储添加的集合
*/
private var viewList: ArrayList<LinearLayout> = ArrayList<LinearLayout>()
/**
* 声明点击的接口回调
*/
var itemClickListener: ((position: Int) -> Unit)? = null
/**
* 底部navBar高度
*/
var navBarHeight: Int = 0
/**
* 是否需要绘制小圆点
*/
private var haveBadgeView: Boolean = false
/**
* 绘制小圆点的位置
*/
private var badgeViewIndex: Int = -1
/**
* 绘制小圆点的宽度或者高度
*/
private var badgeWH: Int = -1
/**
* 小圆点当成一个bitMap处理
*/
private lateinit var bitmap: Bitmap
/**
* divider画笔
*/
private val dividerPaint by lazy { Paint() }
/**
* divider画笔
*/
private val textPaint by lazy {
Paint().apply {
color = Color.BLACK
textSize = context.dp2px(8).toFloat()
}
}
init {
orientation = HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
context.obtainStyledAttributes(attrs, R.styleable.BottomNavBar).apply {
navBarDividerColor =
getColor(R.styleable.BottomNavBar_nav_dividerColor, Color.TRANSPARENT)
navBarTextNormalColor =
getColor(R.styleable.BottomNavBar_nav_textNormalColor, Color.TRANSPARENT)
navBarTextSelColor =
getColor(R.styleable.BottomNavBar_nav_textSelColor, Color.TRANSPARENT)
navBarDividerSize = getDimensionPixelSize(R.styleable.BottomNavBar_nav_dividerSize, 0)
navBarTextSize = getDimensionPixelSize(R.styleable.BottomNavBar_nav_textSize, 0)
recycle()
}
}
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@SuppressLint("WrongConstant")
fun setUnSelectedData(data: Array<Int>, dataSel: Array<Int>, dataStr: Array<String>) {
if (data.isNullOrEmpty() or dataStr.isNullOrEmpty() or dataSel.isNullOrEmpty()) {
throw NullPointerException("集合不能为空")
}
if (data.size != dataStr.size || data.size != dataSel.size || dataSel.size != dataStr.size) {
throw NullPointerException("两个集合对应的个数不一致")
}
run {
data.forEachIndexed { index, i ->
val verticalLayout = verticalLayout {
orientation = VERTICAL
gravity = Gravity.CENTER
onClick {
viewList.let {
it.forEachIndexed { clickIndex, view ->
//重置所有drawable
(it[clickIndex].getChildAt(0) as ImageView).apply {
setImageDrawable(
ContextCompat.getDrawable(
context,
data[clickIndex]
)
)
}
//重置所有颜色
(it[clickIndex].getChildAt(1) as TextView).apply {
textColor = navBarTextNormalColor
}
}
//设置点击位置选中的字体颜色
(it[index].getChildAt(1) as TextView).apply {
textColor = navBarTextSelColor
}
//设置点击位置选中的drawable
(it[index].getChildAt(0) as ImageView).apply {
setImageDrawable(ContextCompat.getDrawable(context, dataSel[index]))
}
}
itemClickListener?.invoke(index)
}
imageView {
scaleType = ImageView.ScaleType.FIT_XY
setImageDrawable(ContextCompat.getDrawable(context, i))
}.lparams(width = dip(40), height = dip(40)) {
}
textView {
text = dataStr[index]
textSize = navBarTextSize.toFloat()
textColor = ContextCompat.getColor(context, android.R.color.black)
}.lparams(wrapContent, wrapContent) {
}
}
verticalLayout.layoutParams.width = context.screenWidthPx / data.size
viewList.add(verticalLayout)
}
//设置第一项默认选中
(viewList[0].getChildAt(1) as TextView).apply {
textColor = navBarTextSelColor
}
(viewList[0].getChildAt(0) as ImageView).apply {
setImageDrawable(ContextCompat.getDrawable(context, dataSel[0]))
}
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (navBarHeight == 0) navBarHeight = measuredHeight
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
dividerPaint.apply {
color = navBarDividerColor
style = Paint.Style.FILL_AND_STROKE
strokeWidth = navBarDividerSize.toFloat()
}
canvas?.drawLine(0F, 0F, context.screenWidthPx.toFloat(), 0F, dividerPaint)
if (haveBadgeView) {
canvas?.let {
val halfLength = context.screenWidthPx / viewList.size
it.drawBitmap(
bitmap,
halfLength.toFloat() * (badgeViewIndex + 1) - badgeWH * 1.95.toFloat(),
10f,
dividerPaint
)
/*it.drawText(
"+99",
halfLength.toFloat() * (badgeViewIndex + 1) - badgeWH * 1.55.toFloat(),
context.dp2px(10).toFloat(), textPaint
)*/
}
}
}
fun setBadgeView(
haveBadgeView: Boolean = false,
badgeViewIndex: Int = -1,
badgeViewBgResource: Int
) {
this.haveBadgeView = haveBadgeView
this.badgeViewIndex = badgeViewIndex
badgeViewBgResource.apply {
bitmap = BitmapFactory.decodeResource(context.resources, badgeViewBgResource)
badgeWH = bitmap.width
}
if (haveBadgeView) {
if (badgeViewIndex >= viewList.size) {
throw Exception("小圆点位置越界了")
}
if (badgeViewIndex < -1) {
throw Exception("小圆点位置传的不对")
}
invalidate()
}
}
val Context.screenWidthPx: Int
get() = resources.displayMetrics.widthPixels
fun Context.dp2px(dpValue: Int): Int {
val scale: Float = context.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
fun View.onClick(startValue: Float = 0.85F, block: (view: View) -> Unit) {
addClickAnim(startValue)
setOnClickListener(block)
}
fun View.addClickAnim(startValue: Float = 0.85F) {
setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
v.clearAnimation()
v.animate().scaleX(1.0F).scaleY(1.0F).setDuration(80L).start()
}
MotionEvent.ACTION_DOWN -> {
v.animate().scaleX(startValue).scaleY(startValue).setDuration(80L).start()
}
}
false
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
if (!bitmap.isRecycled) bitmap.recycle()
}
}
里面都有详细的注释,主要是canvas画小圆点,和顶部的nav线条,然后用了anko的动态生成布局,本来想实现角标消息数的效果,但是现在app一般不这么做了,咱也省事了不是,接下来是项目用到的资源
<?xml version="1.0" encoding="utf-8"?>
<com.example.myapplication.Drag xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:background="#e2e2e2"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvShow"
android:gravity="center"
android:text="这里展示点击的index"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<com.example.myapplication.BottomNavBar
android:id="@+id/navBar"
android:layout_width="match_parent"
android:layout_height="65dp"
android:layout_gravity="bottom"
android:background="@android:color/white"
android:gravity="center_vertical"
app:nav_dividerColor="#EEEEEE"
app:nav_dividerSize="1px"
app:nav_textNormalColor="#8a000000"
app:nav_textSelColor="#FFB400"
app:nav_textSize="12px" />
</com.example.myapplication.Drag>
<declare-styleable name="BottomNavBar">
<attr name="nav_dividerColor" format="color" />
<attr name="nav_textNormalColor" format="color" />
<attr name="nav_textSelColor" format="color" />
<attr name="nav_dividerSize" format="dimension" />
<attr name="nav_textSize" format="dimension" />
</declare-styleable>
package com.example.myapplication;
import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import androidx.customview.widget.ViewDragHelper;
public class Drag extends LinearLayout {
private ViewDragHelper mDraw;
private Point mPoint = new Point();
public Drag(Context context, AttributeSet attrs) {
super(context, attrs);
mDraw = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
int boundaryLeft = getPaddingLeft();
int boundaryRight = getWidth() - child.getWidth() - getPaddingRight();
left = Math.min(Math.max(left, boundaryLeft), boundaryRight);
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
Log.e("zxc", "top-------->" + top);
//垂直下拉弹性
if (top == 0) {
top = 0;
}
if (top > 300) {
top = 300;
}
//垂直上拉弹性
if (top < 0){
if (top < -300){
top = -300;
}
}
return top;
}
//手指释放的时候回调
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
mDraw.settleCapturedViewAt(0,0);
invalidate();
}
});
}
@Override
public void computeScroll()
{
if(mDraw.continueSettling(true))
{
invalidate();
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mDraw.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDraw.processTouchEvent(event);
return true;
}
}
上面的小圆点是图片,可以右键下载图片,如需下载,请参考:https://gitee.com/gitdaniua/app-bottom-switch