Android自定义控件总结(二)
目的:收集和整理所有的Android自定义控件
前言
后续会不断添加自定义控件实例,希望做成一个Android自定义控件大全
一、标签布局LaybelLayout
01.使用LaybelLayout
<...(包名)MyScrollView
android:layout_width="match_parent"
android:layout_height="@dimen/dp_200"
android:background="@drawable/member_bg">
<...(包名)LaybelLayout
android:id="@+id/laybelLayoutView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dp_15"
app:child_margin="8"
app:line_padding="8" />
</MyScrollView>
02.自定义控件
class LaybelLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ViewGroup(context, attrs, defStyleAttr), View.OnClickListener {
override fun onClick(v: View?) {
}
private val mChildView: MutableList<View>
private val mChildrenMsg: MutableMap<View, ChildLayoutMsg>
private var mLinePadding: Int = 0//行内上下边距
private var minWidth: Int = 0
private var minHeight: Int = 0//本控件的最小宽高
// private var onItemClickListener: OnItemClickListener? = null
private var childMargin: Int = 0
private var textBackground: Int = 0
private var mAdapter: Adapter? = null
init {
mChildView = ArrayList()
mChildrenMsg = HashMap()
initAttr(attrs)
}
private fun initAttr(attrs: AttributeSet?) {
val t = context.obtainStyledAttributes(attrs, R.styleable.LaybelLayout)
mLinePadding = dip2px(t.getInt(R.styleable.LaybelLayout_line_padding, 0).toFloat())
childMargin = dip2px(t.getInt(R.styleable.LaybelLayout_child_margin, 0).toFloat())
textBackground = t.getResourceId(R.styleable.LaybelLayout_text_background, R.drawable.member_name_bg)
t.recycle()
}
/**
* according to datas generate the child views
*/
private fun prepareView() {
for (i in 0 until mAdapter!!.count) {
val child = LayoutInflater.from(context).inflate(R.layout.item_select_tag,null)
mAdapter!!.onDataSet(child, mAdapter!!.getItem(i),i)
// var params: ViewGroup.MarginLayoutParams? = child!!.layoutParams as ViewGroup.MarginLayoutParams
var params = MarginLayoutParams(LayoutParams(LayoutParams.WRAP_CONTENT,dip2px(36f)))
if (params == null) {
params = generateDefaultLayoutParams() as ViewGroup.MarginLayoutParams
}
addView(child, params)
}
// if (onItemClickListener != null)
// for (i in 0 until childCount) {
// getChildAt(i).setOnClickListener(this)
// }
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
minWidth = paddingLeft + paddingRight
minHeight = paddingTop + paddingBottom
val count = childCount
for (i in 0 until count) {
val child = getChildAt(i)
measureChild(child, widthMeasureSpec, heightMeasureSpec)
// measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); 这里置为0时候,和 measureChild 是一回事
val layoutParams = child.layoutParams as ViewGroup.MarginLayoutParams
//如果单个View和本控件的padding加起来超过本控件的宽度,则让它的宽度 <= 本控件宽度 - Padding - margin
var defSize = (paddingLeft + layoutParams.leftMargin
+ child.measuredWidth + layoutParams.rightMargin + paddingRight)
if (defSize > measuredWidth) {
defSize = (measuredWidth - layoutParams.leftMargin
- layoutParams.rightMargin - paddingLeft - paddingRight)
layoutParams.width = defSize
measureChild(child, widthMeasureSpec, heightMeasureSpec)
}
if (!mChildView.contains(child))
mChildView.add(child)
}
writeViewMsg()
val widthMode = View.MeasureSpec.getMode(widthMeasureSpec)
val heightMode = View.MeasureSpec.getMode(heightMeasureSpec)
if (widthMode != View.MeasureSpec.EXACTLY && heightMode != View.MeasureSpec.EXACTLY)
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
else if (widthMode != View.MeasureSpec.EXACTLY)
setMeasuredDimension(minWidth, View.getDefaultSize(suggestedMinimumHeight, heightMeasureSpec))
else if (heightMode != View.MeasureSpec.EXACTLY)
setMeasuredDimension(View.getDefaultSize(suggestedMinimumWidth, widthMeasureSpec), minHeight)
}
private fun writeViewMsg() {
var lineHeight = 0//单行高度
var lineHeightSum = 0//前面总高度
var left = 0
var top = 0
var right = 0
var bottom = 0
var freeWidth = measuredWidth - paddingLeft - paddingRight//横向剩余空间
var isFirst = true//是否是某一行的第一个
var tmpWidth = 0
for (i in mChildView.indices) {
val view = mChildView[i]
val layoutParams = view.layoutParams as ViewGroup.MarginLayoutParams
//判断剩余空间是否够用,如果不够,则进入下一行
val childWidth = layoutParams.leftMargin + view.measuredWidth +
layoutParams.rightMargin + childMargin
if (childWidth > freeWidth) {
isFirst = true
lineHeightSum += lineHeight
lineHeight = 0
freeWidth = measuredWidth - paddingLeft - paddingRight//设为初始剩余
}
if (isFirst) {
left = paddingLeft + layoutParams.leftMargin + childMargin
isFirst = false
if (tmpWidth > minWidth)
minWidth = tmpWidth
tmpWidth = childWidth
} else {
val prView = mChildView[i - 1]
val ll = prView.layoutParams as ViewGroup.MarginLayoutParams
left += prView.measuredWidth + ll.rightMargin + layoutParams.leftMargin + childMargin
tmpWidth += childWidth
}
top = paddingTop + lineHeightSum + mLinePadding + layoutParams.topMargin
right = left + view.measuredWidth
bottom = top + view.measuredHeight
val tmpHeight = (mLinePadding * 2
+ layoutParams.topMargin
+ view.measuredHeight
+ layoutParams.bottomMargin)
if (tmpHeight > lineHeight)
//选出一行当中占用高度最多的作为行高
lineHeight = tmpHeight
freeWidth -= childWidth
if (mChildrenMsg.containsKey(view))
mChildrenMsg.remove(view)
mChildrenMsg[view] = ChildLayoutMsg(left, top, right, bottom)
}
lineHeightSum += lineHeight//加上最后一行的高度
minHeight += lineHeightSum
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
val set = mChildrenMsg.keys
for (child in set) {
val msg = mChildrenMsg[child]
child.layout(msg!!.left, msg.top, msg.right, msg.bottom)
}
}
override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams {
return ViewGroup.MarginLayoutParams(super.generateDefaultLayoutParams())
}
override fun generateLayoutParams(attrs: AttributeSet): ViewGroup.LayoutParams {
return ViewGroup.MarginLayoutParams(context, attrs)
}
override fun generateLayoutParams(p: ViewGroup.LayoutParams): ViewGroup.LayoutParams {
return ViewGroup.MarginLayoutParams(p)
}
private fun dip2px(dpValue: Float): Int {
val scale = context.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
// override fun onClick(v: View) {
// if (onItemClickListener != null) {
// onItemClickListener!!.onItemClick(mChildView.indexOf(v))
// }
// }
private class ChildLayoutMsg internal constructor(internal var left: Int, internal var top: Int, internal var right: Int, internal var bottom: Int)
fun setAdapter(adapter: Adapter) {
mAdapter = adapter
removeAllViews()
prepareView()
requestLayout()
}
/**
* @method changeAdapter
* @description 更新UI
* @date: 2019/11/18/0018 22:07
* @author: liuyang102
* @param
* @return
*/
fun changeAdapter() {
removeAllViews()
mChildrenMsg.clear()
mChildView.clear()
prepareView()
requestLayout()
}
fun changeAdapter(datas: ArrayList<Member>?) {
Log.i("changeAdapter","----------------------" +datas!!.size)
removeAllViews()
mChildrenMsg.clear()
mChildView.clear()
mAdapter!!.datas?.clear()
datas.let {
mAdapter!!.datas?.addAll(datas)
}
prepareView()
requestLayout()
}
/**
* @method Adapter
* @description 布局Adapter
* @date: 2019/11/18/0018 22:08
* @author: liuyang102
* @param
* @return
*/
class Adapter() {
//存放数据
var datas: ArrayList<Member>? = null
var context :Context? = null
private set
var isShowDelete: Boolean = true
var count: Int = 0
get() = if (datas == null) 0 else datas!!.size
constructor(context: Context, datas: ArrayList<Member>, isShowDelete: Boolean = true) : this() {
this.datas = datas
this.context = context
this.isShowDelete = isShowDelete
}
fun getItem(position: Int): String {
return if (datas == null) "" else datas!![position].Terminal!!.terminalName
}
//data -> terminalName
fun onDataSet(v: View, data: String,i: Int) {
val text : TextView = v.findViewById(R.id.tv_name) as TextView
val delete : TextView = v.findViewById(R.id.tv_delete) as TextView
Log.i(TAG, "GlobalCache.nickName = ${GlobalCache.nickName}")
if (data == GlobalCache.nickName) {
text.text = "本地终端"
delete.visibility = View.GONE
} else {
text.text = data
}
if (!isShowDelete) {
delete.visibility = View.GONE
}
delete.setOnClickListener {
datas = deleteItem(i,datas!!)
//回调删除的终端的名称以及终端名称所在的列表位置
delete(i,data)
}
}
private fun deleteItem(index:Int, itemArr :ArrayList<Member>) :ArrayList<Member>{
val item = ArrayList<Member>(itemArr.size - 1)
//去掉index对应的终端,其它的终端保留
for (i in 0 until itemArr.size ) {
if (i != index) {
item.add(itemArr[i])
}
}
return item
}
fun notifyDataChanged(i: () -> Unit){
this.onDataChange = i
}
var onDataChange: () -> Unit ={}
// var delete: (Int,String) -> Unit = {}
var delete: (Int,String) -> Unit = { i: Int, s: String -> }
fun setDeleteListener(i: (Int, String) -> Unit){
this.delete = i
}
}
companion object {
private val TAG = "LaybelLayout"
}
}
03.自定义控件的样式、Item的布局文件
<declare-styleable name="LaybelLayout">
<attr name="line_padding" format="integer"/>
<attr name="text_background" format="reference"/><!-- TextView的背景 -->
<attr name="child_margin" format="integer"/><!-- 子View的外边距 -->
</declare-styleable>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_42"
android:background="@drawable/member_name_bg"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_name"
android:layout_marginLeft="@dimen/dp_21"
android:layout_marginRight="@dimen/dp_16"
android:text="胡冉冉"
android:singleLine="true"
android:maxEms="12"
android:ellipsize="end"
android:layout_gravity="center"
android:textSize="@dimen/sp_22"
android:textColor="@color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_gravity="center"
android:id="@+id/tv_delete"
android:background="@drawable/search_icon_clean"
android:layout_marginRight="@dimen/dp_16"
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32" />
</LinearLayout>
二、使用步骤
1.引入库
总结
本篇文章是自定义控件总结的第二篇文章,后续我会总结更多的自定义控件,以供大家学习参考!!!