Android 使用Kotlin封装RecyclerView

文章目录

  • 1.概述
  • 2.运行效果图
  • 3.代码实现
    • 3.1 扩展RecyclerView
  • 3.2 扩展Adapter
    • 3.3 RecyclerView装饰绘制
      • 3.3.1 以图片实现分割线
      • 3.3.2 画网格线
      • 3.3.3空白的分割线
      • 3.3.4 不同方向上的分割线
    • 3.4 使用方法

1.概述

在一个开源项目上看到了一个Android Kotlin版的RecyclerView封装,个人觉得非常方便,所以就将这个封装摘了出来,记录下,方便以后使用,这个开源的项目叫DanDanPlayForAndroid点击链接可以查看具体的开源项目代码。

2.运行效果图

在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述

3.代码实现

3.1 扩展RecyclerView

我们可以通过Kotlin的扩展函数扩展RecycleView的布局方式,设置数据等功能,方便我们调用。代码如下:

fun RecyclerView.vertical(reverse: Boolean = false
): LinearLayoutManager {return LinearLayoutManager(context,LinearLayoutManager.VERTICAL,reverse)
}fun RecyclerView.horizontal(reverse: Boolean = false
): LinearLayoutManager {return LinearLayoutManager(context,LinearLayoutManager.HORIZONTAL,reverse)
}fun RecyclerView.grid(spanCount: Int
): GridLayoutManager {return GridLayoutManager(context, spanCount)
}fun RecyclerView.gridEmpty(spanCount: Int): GridLayoutManager {return GridLayoutManager(context, spanCount).also {it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {override fun getSpanSize(position: Int): Int {if (position == RecyclerView.NO_POSITION) {return 1}val viewType = adapter?.getItemViewType(position)if (viewType != -1) {return 1}return spanCount}}}
}fun RecyclerView.setData(itemData: List<Any>) {(adapter as RVBaseAdapter).setData(itemData)
}fun RecyclerView.requestIndexChildFocus(index: Int): Boolean {scrollToPosition(index)val targetTag = "tag_focusable_item"val indexView = layoutManager?.findViewByPosition(index)if (indexView != null) {indexView.findViewWithTag<View>(targetTag)?.requestFocus()return true}post {layoutManager?.findViewByPosition(index)?.findViewWithTag<View>(targetTag)?.requestFocus()}return true
}

3.2 扩展Adapter

在扩展Adapter之前,我们需要先定义一个我们自己的Adapter,然后再基于我们自己的Adapter去做扩展,代码如下:

class RVBaseAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {companion object{// the data of empty layoutval EMPTY_ITEM = Any()// view type of empty layoutconst val VIEW_TYPE_EMPTY = -1// number of max itemprivate const val NUMBER_OF_MAX_VIEW_TYPE = Int.MAX_VALUE -1}val itemData: MutableList<Any> = mutableListOf()private val typeHolders = SparseArrayCompat<BaseViewHolderCreator<out ViewDataBinding>>()override fun onCreateViewHolder(parent: ViewGroup,viewType: Int): RecyclerView.ViewHolder {return BaseViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.context),getHolderCreator(viewType).getResourceId(),parent,false))}private fun getHolderCreator(viewType: Int): BaseViewHolderCreator<out ViewDataBinding> {return typeHolders.get(viewType)?: throw java.lang.RuntimeException()}override fun getItemCount(): Int {return itemData.size}override fun onBindViewHolder(holder: RecyclerView.ViewHolder,position: Int) {getHolderCreator(holder.itemViewType).apply {initItemBinding(holder.itemView)onBindViewHolder(itemData[position],position,this)}}fun setData(dataList: List<Any>) {itemData.clear()itemData.addAll(dataList)// show the empty layout when data is emptyif(itemData.isEmpty() && typeHolders.containsKey(VIEW_TYPE_EMPTY)){itemData.add(EMPTY_ITEM)}notifyDataSetChanged()}fun register(creator: BaseViewHolderCreator<out ViewDataBinding>, customViewType: Int? = null) {apply {var viewType = customViewType ?: typeHolders.size()while (typeHolders.get(viewType) != null) {viewType++require(viewType < NUMBER_OF_MAX_VIEW_TYPE) {"the number of view type has reached the maximum limit"}}require(viewType < NUMBER_OF_MAX_VIEW_TYPE) {"the number of view type has reached the maximum limit"}typeHolders.put(viewType, creator)}}override fun getItemViewType(position: Int): Int {if(itemData[position] == EMPTY_ITEM&& typeHolders.containsKey(VIEW_TYPE_EMPTY)){return VIEW_TYPE_EMPTY}// only one viewHolderif(typeHolders.size() == 1){return typeHolders.keyAt(0)}// more than one viewHolderfor (i in 0 until typeHolders.size()){if(typeHolders.keyAt(i) == VIEW_TYPE_EMPTY){continue}val holder = typeHolders.valueAt(i)if(holder.isForViewType(itemData[position],position)){return typeHolders.keyAt(i)}}throw java.lang.IllegalStateException("no holder added that matches at position: $position in data source")}
}

与上面代码相关联的抽象类:

class BaseViewHolder(binding: ViewDataBinding) :RecyclerView.ViewHolder(binding.root) {
}
abstract class BaseViewHolderCreator<V : ViewDataBinding> {abstract fun isForViewType(data: Any?, position: Int): Booleanabstract fun getResourceId(): Intabstract fun onBindViewHolder(data: Any?,position: Int,creator: BaseViewHolderCreator<out ViewDataBinding>)lateinit var itemDataBinding: Vfun initItemBinding(itemView: View) {this.itemDataBinding = DataBindingUtil.getBinding(itemView)!!}
}

抽象类的实现:

class BaseViewHolderDSL<T : Any, V : ViewDataBinding>(private val resourceId: Int,private val clazz: KClass<T>
) : BaseViewHolderCreator<V>() {private var checkViewType: ((data: Any, position: Int) -> Boolean)? = nullprivate var viewHolder: ((data: T, position: Int, creator:BaseViewHolderCreator<out ViewDataBinding>) -> Unit)? = nullprivate var emptyViewHolder: (() -> Unit)? = nulloverride fun isForViewType(data: Any?, position: Int): Boolean {if(data == null){return false}if(checkViewType != null){return checkViewType!!.invoke(data,position)}return clazz.isInstance(data)}/*** judge the type of current item data according to position*/fun checkType(viewType:(data:Any,position:Int) ->Boolean){this.checkViewType = viewType}fun initView(holder:(data:T,position:Int,holder:BaseViewHolderCreator<out ViewDataBinding>)->Unit){this.viewHolder = holder}override fun getResourceId(): Int {return resourceId}override fun onBindViewHolder(data: Any?,position: Int,creator: BaseViewHolderCreator<out ViewDataBinding>) {// empty layoutif(data == RVBaseAdapter.EMPTY_ITEM){emptyViewHolder?.invoke()return}data ?: returnviewHolder?.invoke(data as T,position,creator)}
}

RVBaseAdapter类的扩展

fun buildAdapter(init: RVBaseAdapter.() -> Unit): RVBaseAdapter {return RVBaseAdapter().apply {init()}
}inline fun <reified T : Any, V : ViewDataBinding> RVBaseAdapter.addItem(resourceID: Int,init: BaseViewHolderDSL<T, V>.() -> Unit
) {register(BaseViewHolderDSL<T, V>(resourceID, T::class).apply { init() })
}inline fun RVBaseAdapter.addEmptyView(resourceID: Int,init: (BaseViewHolderDSL<Any, LayoutEmptyBinding>.() -> Unit) = {}
) {register(BaseViewHolderDSL<Any, LayoutEmptyBinding>(resourceID, Any::class).apply {init()},customViewType = RVBaseAdapter.VIEW_TYPE_EMPTY)setData(listOf(RVBaseAdapter.EMPTY_ITEM))
}

3.3 RecyclerView装饰绘制

RecyclerView可以继承自ItemDecoration类绘制自己想要的分割线和装饰,这里做了几个例子,代码如下:

3.3.1 以图片实现分割线

/*** 分割线(以图片实现)*/
class MyItemDecoration(divider: Drawable, dividerSize: Int) : 
RecyclerView.ItemDecoration() {private val mDivider = dividerprivate val mDividerSize = dividerSizeoverride fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {canvas.save()//居中显示val top = (parent.height - mDividerSize) / 2val bottom = top + mDividerSizeval mBounds = Rect()//只在中间绘制for (i in 0 until parent.childCount - 1) {val child = parent.getChildAt(i)parent.layoutManager!!.getDecoratedBoundsWithMargins(child, mBounds)val right = mBounds.right + child.translationX.roundToInt()val left = right - mDividerSizemDivider.setBounds(left, top, right, bottom)mDivider.draw(canvas)}canvas.restore()}override fun getItemOffsets(outRect: Rect,view: View,parent: RecyclerView,state: RecyclerView.State) {outRect.set(0, 0, mDividerSize, 0)}
}

3.3.2 画网格线

class ItemGridDecorationDrawable : ItemDecoration {private var leftRight: Intprivate var topBottom: Intprivate var mDivider: Drawable?constructor(spacePx: Int) {leftRight = spacePxtopBottom = spacePxmDivider = ColorDrawable(Color.WHITE)}constructor(leftRight: Int, topBottom: Int) {this.leftRight = leftRightthis.topBottom = topBottommDivider = ColorDrawable(Color.WHITE)}constructor(leftRight: Int, topBottom: Int, mColor: Int) {this.leftRight = leftRightthis.topBottom = topBottommDivider = ColorDrawable(mColor)}override fun onDraw(c: Canvas,parent: RecyclerView,state: RecyclerView.State) {val layoutManager = parent.layoutManager as GridLayoutManager? ?: returnval lookup = layoutManager.spanSizeLookupif (mDivider == null || layoutManager.childCount == 0) {return}//判断总的数量是否可以整除val spanCount = layoutManager.spanCountvar left: Intvar right: Intvar top: Intvar bottom: Intval childCount = parent.childCountif (layoutManager.orientation == GridLayoutManager.VERTICAL) {for (i in 0 until childCount) {val child = parent.getChildAt(i)//将带有颜色的分割线处于中间位置val centerLeft =((layoutManager.getLeftDecorationWidth(child) + layoutManager.getRightDecorationWidth(child)).toFloat()* spanCount / (spanCount + 1) + 1 - leftRight) / 2val centerTop =(layoutManager.getBottomDecorationHeight(child)+ 1 - topBottom) / 2f//得到它在总数里面的位置val position = parent.getChildAdapterPosition(child)//获取它所占有的比重val spanSize = lookup.getSpanSize(position)//获取每排的位置val spanIndex = lookup.getSpanIndex(position, layoutManager.spanCount)//判断是否为第一排val isFirst =layoutManager.spanSizeLookup.getSpanGroupIndex(position,spanCount) == 0//画上边的,第一排不需要上边的,只需要在最左边的那项的时候画一次就好if (!isFirst && spanIndex == 0) {left = layoutManager.getLeftDecorationWidth(child)right = parent.width - layoutManager.getLeftDecorationWidth(child)top = (child.top - centerTop).toInt() - topBottombottom = top + topBottommDivider!!.setBounds(left, top, right, bottom)mDivider!!.draw(c)}//最右边的一排不需要右边的val isRight = spanIndex + spanSize == spanCountif (!isRight) { //计算右边的left = (child.right + centerLeft).toInt()right = left + leftRighttop = child.topif (!isFirst) {top -= centerTop.toInt()}bottom = (child.bottom + centerTop).toInt()mDivider!!.setBounds(left, top, right, bottom)mDivider!!.draw(c)}}} else {for (i in 0 until childCount) {val child = parent.getChildAt(i)//将带有颜色的分割线处于中间位置val centerLeft =(layoutManager.getRightDecorationWidth(child) + 1 - leftRight) / 2fval centerTop =((layoutManager.getTopDecorationHeight(child) + layoutManager.getBottomDecorationHeight(child)).toFloat()
* spanCount / (spanCount + 1) - topBottom) / 2//得到它在总数里面的位置val position = parent.getChildAdapterPosition(child)//获取它所占有的比重val spanSize = lookup.getSpanSize(position)//获取每排的位置val spanIndex = lookup.getSpanIndex(position, layoutManager.spanCount)//判断是否为第一列val isFirst =layoutManager.spanSizeLookup.getSpanGroupIndex(position, spanCount) == 0//画左边的,第一排不需要左边的,只需要在最上边的那项的时候画一次就好if (!isFirst && spanIndex == 0) {left = (child.left - centerLeft).toInt() - leftRightright = left + leftRighttop = layoutManager.getRightDecorationWidth(child)bottom = parent.height - layoutManager.getTopDecorationHeight(child)mDivider!!.setBounds(left, top, right, bottom)mDivider!!.draw(c)}//最下的一排不需要下边的val isRight = spanIndex + spanSize == spanCountif (!isRight) { //计算右边的left = child.leftif (!isFirst) {left -= centerLeft.toInt()}right = (child.right + centerTop).toInt()top = (child.bottom + centerLeft).toInt()bottom = top + leftRightmDivider!!.setBounds(left, top, right, bottom)mDivider!!.draw(c)}}}}override fun getItemOffsets(outRect: Rect,view: View,parent: RecyclerView,state: RecyclerView.State) {val layoutManager = parent.layoutManager as GridLayoutManager? ?: returnval lp =view.layoutParams as GridLayoutManager.LayoutParamsval childPosition = parent.getChildAdapterPosition(view)val spanCount = layoutManager.spanCountif (layoutManager.orientation == GridLayoutManager.VERTICAL) { //判断是否在第一排if (layoutManager.spanSizeLookup.getSpanGroupIndex(childPosition,spanCount) == 0) { //第一排的需要上面outRect.top = topBottom}outRect.bottom = topBottom//这里忽略和合并项的问题,只考虑占满和单一的问题if (lp.spanSize == spanCount) { //占满outRect.left = leftRightoutRect.right = leftRight} else {outRect.left =((spanCount - lp.spanIndex).toFloat() / spanCount * leftRight).toInt()outRect.right =(leftRight.toFloat() * (spanCount + 1) / spanCount - outRect.left).toInt()}} else {if (layoutManager.spanSizeLookup.getSpanGroupIndex(childPosition,spanCount) == 0) { //第一排的需要leftoutRect.left = leftRight}outRect.right = leftRight//这里忽略和合并项的问题,只考虑占满和单一的问题if (lp.spanSize == spanCount) { //占满outRect.top = topBottomoutRect.bottom = topBottom} else {outRect.top =((spanCount - lp.spanIndex).toFloat() / spanCount * topBottom).toInt()outRect.bottom =(topBottom.toFloat() * (spanCount + 1) / spanCount - outRect.top).toInt()}}}
}

3.3.3空白的分割线

/*** 空白的分割线**/
class ItemDecorationSpace : ItemDecoration {private var top: Intprivate var left: Intprivate var right: Intprivate var bottom: Intprivate var spanCount: Intconstructor(space: Int) : this(space, space, space, space)constructor(spaceLR: Int, spaceTB: Int) : this(spaceTB, spaceLR, spaceLR,spaceTB)constructor(top: Int, left: Int, right: Int, bottom: Int) {this.top = topthis.left = leftthis.right = rightthis.bottom = bottomspanCount = 0}constructor(top: Int, left: Int, right: Int, bottom: Int, spanCount: Int) {this.top = topthis.left = leftthis.right = rightthis.bottom = bottomthis.spanCount = spanCount}override fun getItemOffsets(outRect: Rect, view: View,parent: RecyclerView, state: RecyclerView.State) {outRect.top = topoutRect.left = leftoutRect.bottom = bottomif (spanCount != 0) {val position = parent.getChildLayoutPosition(view)if ((position + 1) % spanCount == 0) {outRect.right = 0} else {outRect.right = right}} else {outRect.right = right}}
}

3.3.4 不同方向上的分割线

/*** 不同方向上的分割线*/class ItemDecorationOrientation : ItemDecoration {private val dividerPx: Intprivate val headerPx: Intprivate val footerPx: Intprivate val orientation: Intconstructor(dividerPx: Int, @RecyclerView.Orientation orientation: Int) : this(dividerPx,dividerPx,orientation)constructor(dividerPx: Int,headerFooterPx: Int,@RecyclerView.Orientation orientation: Int) : this(dividerPx, headerFooterPx, headerFooterPx, orientation)constructor(dividerPx: Int,headerPx: Int,footerPx: Int,@RecyclerView.Orientation orientation: Int) {this.dividerPx = dividerPxthis.headerPx = headerPxthis.footerPx = footerPxthis.orientation = orientation}override fun getItemOffsets(outRect: Rect,view: View,parent: RecyclerView,state: RecyclerView.State) {if (orientation == RecyclerView.VERTICAL) {getItemOffsetsVertical(outRect, view, parent)} else {getItemOffsetsHorizontal(outRect, view, parent)}}private fun getItemOffsetsVertical(outRect: Rect, view: View,parent: RecyclerView) {val itemCount = parent.adapter?.itemCount ?: returnval position = parent.getChildAdapterPosition(view)if (position == 0) {outRect.top = headerPx} else {outRect.top = position * dividerPx / itemCount}if (position == itemCount - 1) {outRect.bottom = footerPx} else {outRect.bottom = dividerPx - (position + 1) * dividerPx / itemCount}}private fun getItemOffsetsHorizontal(outRect: Rect, view: View, parent:RecyclerView) {val itemCount = parent.adapter?.itemCount ?: returnval position = parent.getChildAdapterPosition(view)if (position == 0) {outRect.left = headerPx} else {outRect.left = position * dividerPx / itemCount}if (position == itemCount - 1) {outRect.right = footerPx} else {outRect.right = dividerPx - (position + 1) * dividerPx / itemCount}}
}

3.4 使用方法

使用的时候去掉代码中对应的注释,体验各种风格

class RecyclerViewActivity : AppCompatActivity() {private lateinit var dataBinding: ActivityRecyclerViewBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)initDataBinding()initRV()val dataList = listOf<UserData>(UserData("walt zhong", 21),UserData("walt xian", 22),UserData("walt jian", 31),UserData("walt x", 22),UserData("walt y", 41),UserData("walt z", 26),UserData("walt 2", 29),)//   val dataList = emptyList<UserData>()dataBinding.rvList.setData(dataList)}private fun initRV() {dataBinding.rvList.apply {// layoutManager = gridEmpty(3) //网格布局// layoutManager = vertical(false) // 垂直布局layoutManager = horizontal(false) // 水平布局adapter = buildAdapter {addEmptyView(R.layout.layout_empty)addItem<UserData, RvItemBinding>(R.layout.rv_item) {initView { data, position, _ ->itemDataBinding.apply {tvName.text = data.nametvAge.text = data.age.toString()itemLayout.setOnClickListener {Log.d("zhongxj", "click item: $position")}}}}}//            val pxValue = dp2px(5)
//
//            addItemDecoration(
//                ItemGridDecorationDrawable(
//                    pxValue,
//                    pxValue,
//                    R.color.purple_200
//                )
//            )//            addItemDecoration(
//                ItemDecorationSpace(
//                    pxValue
//                )
//            )//            addItemDecoration(
//                ItemDecorationOrientation(
//                   dividerPx = pxValue,
//                    headerFooterPx = 0,
//                    orientation = RecyclerView.HORIZONTAL
//                )
//            )val dividerSize = dp2px(16)val divider =  ContextCompat.getDrawable(context, R.drawable.ic_arrow)if(divider != null){addItemDecoration(MyItemDecoration(divider,dividerSize))}}}private fun initDataBinding() {dataBinding = DataBindingUtil.setContentView(this,R.layout.activity_recycler_view)dataBinding.lifecycleOwner = this@RecyclerViewActivity}/*** 单位转换,将DP转为PX*/fun dp2px(dpValue: Int): Int {val scale = Resources.getSystem().displayMetrics.densityreturn (dpValue * scale + 0.5f).toInt()}
}data class UserData(var name:String,var age:Int)

布局文件:
RcyclerViewActivity布局

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"><data></data><LinearLayoutandroid:background="#eeeeee"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".RecyclerViewActivity"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_list"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout>
</layout>

RecyclerView item布局

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"><data></data><LinearLayoutandroid:background="@color/white"android:padding="10dp"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="100dp"android:id="@+id/item_layout"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:textSize="20sp"android:text="walt"android:id="@+id/tv_name"/><TextViewandroid:layout_marginTop="10dp"android:layout_width="match_parent"android:layout_height="wrap_content"android:textSize="20sp"android:text="24"android:id="@+id/tv_age"/></LinearLayout>
</layout>

没有数据时的空布局

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><ImageViewandroid:id="@+id/empty_iv"android:layout_width="200dp"android:layout_height="200dp"android:src="@mipmap/ic_empty_data"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.382" /><TextViewandroid:id="@+id/empty_tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="5dp"android:text="没有数据"android:textColor="@color/black"android:textSize="16sp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/empty_iv" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>

里面对应的图片读者自己找喜欢的替换上就可以啦,本文主要是记录,代码也不难,读者可以自行跟着敲一遍,加深映像,熟悉这种封装方法,后面可以使用在项目的其他部分的封装。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/145823.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Docker和Docker compose的安装使用指南

一&#xff0c;环境准备 Docker运行需要依赖jdk&#xff0c;所以需要先安装一下jdk yum install -y java-1.8.0-openjdk.x86_64 二&#xff0c;Docker安装和验证 1&#xff0c;安装依赖工具 yum install -y yum-utils 2&#xff0c;设置远程仓库 yum-config-manager --add-r…

26962-2011 高频电磁场综合水处理器技术条件

声明 本文是学习GB-T 26962-2011 高频电磁场综合水处理器技术条件. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了高频电磁场综合水处理器(以下简称处理器)的术语和定义、分类和型号、结构型式、 要求及检验、标志、包装和贮运…

Java | Maven(知识点查询)

文章目录 Maven知识速查1. Maven概述2. Maven的作用3. Maven的下载4. Maven的环境配置5. Maven 的基础组成5.1 Maven仓库5.1.1 本地仓库配置&#xff1a;5.1.2 中央仓库配置&#xff1a;5.1.3 镜像仓库配置 5.2 Maven坐标 6. Maven项目6.1 手工创建Maven项目6.2 自动构建项目 7…

前端JavaScript入门到精通,javascript核心进阶ES6语法、API、js高级等基础知识和实战 —— Web APIs(三)

思维导图 全选案例 大按钮控制小按钮 小按钮控制大按钮 css伪类选择器checked <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><…

理解自动驾驶感知技术

理解自动驾驶感知技术 文章目录 什么是自动驾驶感知技术&#xff1f;自动驾驶感知技术的关键组成部分1. 雷达&#xff08;Radar&#xff09;2. 摄像头&#xff08;Camera&#xff09;3. 激光雷达&#xff08;Lidar&#xff09;4. 超声波传感器&#xff08;Ultrasonic Sensors&a…

ElementUI实现登录注册啊,axios全局配置,CORS跨域

一&#xff0c;项目搭建 认识ElementUI ElementUI是一个基于Vue.js 2.0的桌面端组件库&#xff0c;它提供了一套丰富的UI组件&#xff0c;包括表格、表单、弹框、按钮、菜单等常用组件&#xff0c;具备易用、美观、高效、灵活等优势&#xff0c;能够极大的提高Web应用的开发效…

【Spring Cloud】深入探索 Nacos 注册中心的原理,服务的注册与发现,服务分层模型,负载均衡策略,微服务的权重设置,环境隔离

文章目录 前言一、初识 Nacos 注册中心1.1 什么是 Nacos1.2 Nacos 的安装&#xff0c;配置&#xff0c;启动 二、服务的注册与发现三、Nacos 服务分层模型3.1 Nacos 的服务分级存储模型3.2 服务跨集群调用问题3.3 服务集群属性设置3.4 修改负载均衡策略为集群策略 四、根据服务…

智慧公厕:探索未来公共厕所的创新设计

近年来&#xff0c;随着城市发展的不断科技化&#xff0c;智慧公厕的设计成为了一个备受关注的话题。作为城市基础设施的重要组成部分&#xff0c;公厕不仅仅是简单的功能性建筑&#xff0c;更是体现了城市形象和管理水平的重要指标。在这篇文章中&#xff0c;我们将以智慧公厕…

GitHub配置SSH key

GitHub配置SSH key Git配置信息并生成密钥 设置用户名和密码 设置用户名 git config --global user.name "用户名" 设置邮箱 git confir --global user.email "邮箱" 生成密钥 ssh-keygen -t rsa -C "邮箱" 查看密钥 到密钥所保存的位置 复…

docker安装MySQL 5.7

1.安装 Mysql 5.7 docker镜像 拉取官方镜像 docker pull mysql:5.7查看镜像库 docker images2.创建mysql容器 在本地创建mysql的映射目录 mkdir -p /root/mysql/data /root/mysql/logs /root/mysql/conf在/root/mysql/conf中创建 *.cnf 文件(叫什么都行) touch my.cnf创建…

CSS详细基础(四)显示模式

本帖开始介绍CSS中更复杂的内容 目录 一.显示模式 1.行内元素 2.块级元素 3.行内块元素 二.背景样式 一.显示模式 顾名思义&#xff0c;在CSS中&#xff0c;元素主要有3种显示模式&#xff1a;行内元素、块级元素、行内块元素~ 所谓块级元素&#xff0c;指的是该元素在…

蓝桥等考Python组别八级005

第一部分&#xff1a;选择题 1、Python L8 &#xff08;15分&#xff09; 运行下面程序&#xff0c;输出的结果是&#xff08; &#xff09;。 i 1 while i < 4: print(i, end ) i 1 1 2 30 1 2 31 2 3 40 1 2 3 4 正确答案&#xff1a;C 2、Python L8 &#…

【51单片机】10-蜂鸣器

1.蜂鸣器的原理 这里的“源”不是指电源。而是指震荡源。 也就是说&#xff0c;有源蜂鸣器内部带震荡源&#xff0c;所以只要一通电就会叫。 而无源内部不带震荡源&#xff0c;所以如果用直流信号无法令其鸣叫。必须用2K~5K的方波去驱动它。 有源蜂鸣器往往比无源的贵&#xff…

pandas--->CSV / JSON

csv CSV&#xff08;Comma-Separated Values&#xff0c;逗号分隔值&#xff0c;有时也称为字符分隔值&#xff0c;因为分隔字符也可以不是逗号&#xff09;&#xff0c;其文件以纯文本形式存储表格数据&#xff08;数字和文本&#xff09;。 CSV 是一种通用的、相对简单的文…

【Java 进阶篇】深入理解 JDBC:Java 数据库连接详解

数据库是现代应用程序的核心组成部分之一。无论是 Web 应用、移动应用还是桌面应用&#xff0c;几乎都需要与数据库交互以存储和检索数据。Java 提供了一种强大的方式来实现与数据库的交互&#xff0c;即 JDBC&#xff08;Java 数据库连接&#xff09;。本文将深入探讨 JDBC 的…

python二维码识别tesseract

window安装tesseract 下载路径&#xff1a; https://digi.bib.uni-mannheim.de/tesseract/ 选择 双击安装在D:\sore\teeseract-OCR后&#xff1a; 配置环境变量 配置环境变量Path&#xff1a;D:\sore\teeseract-OCR 配置语言包的环境变量TESSDATA_PREFIX&#xff1a; D:\s…

数据结构与算法基础-(5)---栈的应用-(1)括号匹配

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

ElasticSearch深度分页解决方案

文章目录 概要ElasticSearch介绍es分页方法es分页性能对比表方案对比 From/Size参数深度分页问题Scroll#性能对比向前翻页 总结个人思考 概要 好久没更新文章了&#xff0c;最近研究了一下es的深分页解决方案。和大家分享一下&#xff0c;祝大家国庆节快乐。 ElasticSearch介…

在MyBatisPlus中添加分页插件

开发过程中&#xff0c;数据量大的时候&#xff0c;查询效率会有所下降&#xff0c;这时&#xff0c;我们往往会使用分页。 具体操作入下&#xff1a; 1、添加分页插件&#xff1a; package com.zhang.config;import com.baomidou.mybatisplus.extension.plugins.Pagination…

安防监控/视频汇聚平台EasyCVR云端录像不展示是什么原因?该如何解决?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、…