Android自定义控件全览(二)

26 篇文章 0 订阅
26 篇文章 0 订阅

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.引入库


总结

本篇文章是自定义控件总结的第二篇文章,后续我会总结更多的自定义控件,以供大家学习参考!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值