自定义App底部切换选择效果,支持新消息角标

1 篇文章 0 订阅
1 篇文章 0 订阅

首先,图片镇楼

支持根据集合动态生成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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值