package com.xw.common.custom import android.annotation.SuppressLint import android.content.Context import android.graphics.Color import android.text.TextPaint import android.text.TextUtils import android.util.AttributeSet import com.xw.common.utils.LogUtils import android.util.TypedValue import android.view.Gravity import android.view.View import android.widget.LinearLayout import androidx.appcompat.widget.AppCompatTextView import com.xw.common.R import com.xw.common.utils.ToolUtil import kotlin.math.max import kotlin.math.min /** * Created by zmc on 2017/5/15. * 自定义显示文本布局 */ class ShowTextLinearLayout : LinearLayout { val TAG = "zmc-ShowTextLinearLayout" // 是否能点击 private var canClickOn = true /** * 控件整体宽度 */ private var width = 0 /** * 默认背景 */ private var backGround = 0 /** * 默认字体颜色 */ private var textColor = 0 /** * 左右控件magrin */ private var margin_width = 0 /** * 最小的左右padding */ private var min_padding_width = 0 /** * 最大的左右padding */ private var max_padding_width = 0 /** * 上下padding */ private var padding_height = 0 /** * 左padding */ private var padding_left = 0 /** * 右padding */ private var padding_right = 0 /** * textview 字大小 */ private var text_size = 0 /** * 屏幕宽度 */ private var screenWith = 0 /** * 当前显示记录的行数 */ var index: Int = 1 /** * 当前行数显示的个数 */ var num: Int = 0 /** * 显示内容集合 */ val showList: MutableList = ArrayList() private var context: Context? = null /** * 需要选中的内容下标 */ val selectIndexList = mutableListOf() private var selectTextColor = 0 private var selectTextBg = 0 /** * 保存当前显示的textview */ private val tv_list: MutableList = ArrayList() /** * 保存显示textview的子布局集合 */ private val layout_list: MutableList = ArrayList() private var isNeedToLoadData = false constructor(context: Context) : super(context) { init(context) } fun setCanClickOn(canClickOn: Boolean) { this.canClickOn = canClickOn } @SuppressLint("UseKtx") constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { this.context = context val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ShowTextLinearLayout) // 此处获取的为px单位 margin_width = typedArray.getDimensionPixelSize(R.styleable.ShowTextLinearLayout_margin_width, 24) min_padding_width = typedArray.getDimensionPixelSize(R.styleable.ShowTextLinearLayout_min_padding_width, 24) max_padding_width = typedArray.getDimensionPixelSize(R.styleable.ShowTextLinearLayout_max_padding_width, 72) padding_height = typedArray.getDimensionPixelSize(R.styleable.ShowTextLinearLayout_padding_height, 16) padding_left = typedArray.getDimensionPixelSize(R.styleable.ShowTextLinearLayout_padding_left, 30) padding_right = typedArray.getDimensionPixelSize(R.styleable.ShowTextLinearLayout_padding_right, 30) //text_size = typedArray.getDimensionPixelSize(R.styleable.ShowTextLinearLayout_text_size, 13); text_size = typedArray.getDimension(R.styleable.ShowTextLinearLayout_text_size, 13.0.toFloat()) .toInt() textColor = typedArray.getColor(R.styleable.ShowTextLinearLayout_text_color, Color.BLACK) backGround = typedArray.getResourceId( R.styleable.ShowTextLinearLayout_text_bg, R.drawable.shape_60_back_bg ) LogUtils.e(TAG, "margin_width:" + margin_width) LogUtils.e(TAG, "min_padding_width:" + min_padding_width) LogUtils.e(TAG, "max_padding_width:" + max_padding_width) LogUtils.e(TAG, "padding_height:" + padding_height) LogUtils.e(TAG, "padding_left:" + padding_left) LogUtils.e(TAG, "padding_right:" + padding_right) LogUtils.e(TAG, "text_size:" + text_size) LogUtils.e(TAG, "=======================================================================") typedArray.recycle() init(context) } constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( context, attrs, defStyleAttr ) { this.context = context init(context) } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { super.onLayout(changed, l, t, r, b) if (width == 0) { width = getMeasuredWidth() } if (isNeedToLoadData) { Calculation() isNeedToLoadData = false } } /** * 初始化 */ private fun init(context: Context) { this.context = context } /** * 移除所有显示视图 */ fun remove() { this.removeAllViews() showList.removeAll(showList) layout_list.removeAll(layout_list) tv_list.removeAll(tv_list) index = 1 num = 0 selectIndexList.removeAll(selectIndexList) selectTextBg = 0 selectTextColor = 0 } /** * 设置选中样式 * * @param textColorSelect * @param drawable */ fun setSelectStyle(textColorSelect: Int, drawable: String?) { this.selectTextColor = textColorSelect this.selectTextBg = resources.getIdentifier(drawable, "drawable", context!!.packageName) } /** * tv的点击处理 * * @param position */ fun onTvClick(position: Int) { if (selectTextColor != 0 && selectTextBg != 0 && tv_list.size > position) { val indexOf = selectIndexList.indexOf(position) if (indexOf != -1) { tv_list[position]!!.setTextColor(textColor) tv_list[position]!!.setBackgroundResource(backGround) selectIndexList.removeAt(indexOf) } else { tv_list[position]!!.setTextColor(selectTextColor) tv_list[position]!!.setBackgroundResource(selectTextBg) selectIndexList.add(position) } } } /** * tv的点击处理 * * @param indexList 需要批量处理的下标集合 */ fun onTvClick(indexList: MutableList) { // 传入的 indexList 有可能为当前的 selectIndexList for循环内 操作 remove 或者 add 会有问题 val copyList: MutableList = ArrayList() for (i in indexList.indices) { copyList.add(indexList[i]) } for (i in copyList.indices) { val position: Int = copyList[i]!! if (selectTextColor != 0 && selectTextBg != 0 && tv_list.size > position) { val indexOf = selectIndexList.indexOf(position) if (indexOf != -1) { tv_list[position]!!.setTextColor(textColor) tv_list[position]!!.setBackgroundResource(backGround) selectIndexList.removeAt(indexOf) } else { tv_list[position]!!.setTextColor(selectTextColor) tv_list[position]!!.setBackgroundResource(selectTextBg) selectIndexList.add(position) } } } } /** * 设置数据源 * * @param lists * @param context * @param lists * @param color * @param drawable */ fun setData( lists: List, context: Context, selectIndex: List, color: Int, drawable: String? ) { this.showList.addAll(lists) this.context = context for (i in selectIndex.indices) { val index = selectIndex[i].toInt() if (index == -1 || index >= lists.size) { continue } this.selectIndexList.add(selectIndex[i].toInt()) } this.selectTextColor = color this.selectTextBg = resources.getIdentifier(drawable, "drawable", context.packageName) if (showList.isEmpty()) { return } if (width != 0) { Calculation() } else { isNeedToLoadData = true } } fun setData(lists: List, context: Context) { this.showList.addAll(lists) //该写法有版本限制 api 24 以上 //showList = lists.stream().collect(Collectors.toList()); this.context = context if (showList.isEmpty()) { return } if (width != 0) { Calculation() } else { isNeedToLoadData = true } } /** * 进行计算 */ private fun Calculation() { val linearLayout = LinearLayout(context) linearLayout.orientation = HORIZONTAL val lp = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) // 设置margin值 单位像素 lp.setMargins(0, 0, 0, 25) linearLayout.setLayoutParams(lp) linearLayout.setPadding(padding_left, 0, padding_right, 0) addView(linearLayout) layout_list.add(linearLayout) /** * 计算出需要添加的布局 动态添加子linearLayout */ screenWith = getScreenWith() LogUtils.e(TAG, "Calculation ---> 控件宽度为:" + screenWith) screenWith -= (padding_left + padding_right) LogUtils.e(TAG, "Calculation ---> 计算宽度为:" + screenWith) // 思路 先以最小左右padding间距计算出一行最多可以显示几个历史 在对字的宽度 屏幕宽度 去确认实际显示的左右padding为多少 然后去显示 var i = 0 while (i < showList.size) { LogUtils.e(TAG, "Calculation ---> 当前剩余宽度 = " + screenWith) val name = showList[i] val textWidth = getTextWidth(name, text_size.toFloat()) LogUtils.e(TAG, "Calculation ---> 文本:" + name + " , 宽度为:" + textWidth) /** * 单个item的宽度 应该为 文本宽度 + 文本左右内padding + 文本右margin * 但是当item为本行最后一个view时 不需要添加右margin */ val itemWidth: Float val bigItemWidth = textWidth + 2 * min_padding_width + margin_width LogUtils.e(TAG, "Calculation ---> bigItemWidth = " + bigItemWidth) if (screenWith >= bigItemWidth) { itemWidth = bigItemWidth } else { val smallItemWidth = textWidth + 2 * min_padding_width LogUtils.e(TAG, "Calculation ---> smallItemWidth = " + smallItemWidth) itemWidth = smallItemWidth } LogUtils.e(TAG, "Calculation ---> itemWidth = " + itemWidth) if (screenWith >= itemWidth) { //当前还能继续添加textview视图 screenWith = (screenWith - itemWidth).toInt() num++ if (i == showList.size - 1) { LogUtils.e(TAG, "Calculation ---> 满足条件 且没更多数据 显示界面") LogUtils.i(TAG, "Calculation ---> 当前剩余宽度为:" + screenWith) addTextView(layout_list.size - 1, num, i) /** * 对需要选中的tv做处理 */ if (selectIndexList.isNotEmpty() && selectTextColor != 0 && selectTextBg != 0) { for (j in selectIndexList.indices) { tv_list[selectIndexList[j]]!!.setTextColor(selectTextColor) /** * 此处使用id去设置背景 而不是drawable 原因使用drawable 屏幕在从不显示到显示时 会出现显示异常 */ // tv_list.get(selectIndesList.get(j)).setBackground(selectTextBg); tv_list[selectIndexList[j]]!! .setBackgroundResource(selectTextBg) } // selectIndesList.removeAll(selectIndesList); // selectTextBg = 0; // selectTextColor = 0; } num = 0 index = 1 } } else if (getScreenWith() - (padding_left + padding_right) == screenWith && screenWith < itemWidth) { // 当前列只有一个 并且一个就超出了长度 num++ LogUtils.e(TAG, "Calculation ---> 当前列只有一个 并且一个就超出了长度") LogUtils.w(TAG, "Calculation ---> i = " + i) addTextView(layout_list.size - 1, num, i) val linearLayouts = LinearLayout(context) linearLayouts.orientation = HORIZONTAL val lps = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) lps.setMargins(0, 0, 0, 25) linearLayouts.setLayoutParams(lps) linearLayouts.setPadding(padding_left, 0, padding_right, 0) addView(linearLayouts) layout_list.add(linearLayouts) index++ num = 0 screenWith = getScreenWith() screenWith -= (padding_left + padding_right) } else { // 当前不能再添加textview视图了 LogUtils.e(TAG, "Calculation ---> 不满足条件 添加到下一个视图中") LogUtils.w(TAG, "Calculation ---> i = " + i) i-- //当前不满足 则i不变 进行下一次循环在添加进去 addTextView(layout_list.size - 1, num, i) val linearLayouts = LinearLayout(context) linearLayouts.orientation = HORIZONTAL val lps = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) lps.setMargins(0, 0, 0, 25) linearLayouts.setLayoutParams(lps) linearLayouts.setPadding(padding_left, 0, padding_right, 0) addView(linearLayouts) layout_list.add(linearLayouts) index++ num = 0 screenWith = getScreenWith() screenWith -= (padding_left + padding_right) } i++ } } /** * 实际控件的左右padding */ private var Actual_padding_width = 0 /** * 添加textview到布局中 * * @param index 需要添加视图的布局下标 * @param num 当前布局中添加视图的个数 * @param i 当前添加数据在数据源中的下标 */ private fun addTextView(index: Int, num: Int, i: Int) { var i = i try { LogUtils.e(TAG, "addTextView ---> index = " + index) LogUtils.e(TAG, "addTextView ---> num = " + num) LogUtils.e(TAG, "addTextView ---> i = " + i) screenWith = getScreenWith() screenWith -= (padding_left + padding_right) i = i + 1 - num for (j in 0.. screenWith) { screenWith = 0 } else { screenWith = (screenWith - textWidth).toInt() } val tv = AppCompatTextView(context!!) // 长度限制 val maxLength = 30 val displayText = name tv.text = displayText tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, text_size.toFloat()) //设置字体大小 像素单位 tv.setTextColor(textColor) tv.setGravity(Gravity.CENTER_VERTICAL) tv_list.add(tv) tv.tag = tv_list.size - 1 tv.setOnClickListener { v -> if (onItemClick != null && canClickOn) { onItemClick!!.invoke(v) } } } screenWith -= ((num - 1) * margin_width) LogUtils.e(TAG, "addTextView ---> num = " + num) Actual_padding_width = screenWith / (num * 2) Actual_padding_width = min(Actual_padding_width, max_padding_width) Actual_padding_width = max(Actual_padding_width, 20) // 设置最小padding 20 LogUtils.e(TAG, "addTextView ---> Actual_padding_width = " + Actual_padding_width) i -= num for (j in 0.. index : " + index) LogUtils.e(TAG, "addTextView ---> i : " + i) LogUtils.e(TAG, "addTextView ---> num : " + num) LogUtils.e(TAG, "addTextView ---> e : " + e.message) } LogUtils.e(TAG, "===========================================================================") } /** * 获取屏幕宽度 */ private fun getScreenWith(): Int { if (width != 0) { return width } return ToolUtil.getDefaultDisplay(context!!).widthPixels } /** * 获取文字的宽度 * 这种方法获取到的宽度是像素单位的 */ private fun getTextWidth(text: String?, textSize: Float): Float { val paint = TextPaint() /** * 各单位的换算比例 * case COMPLEX_UNIT_PX: * return value; * case COMPLEX_UNIT_DIP: * return value * metrics.density; * case COMPLEX_UNIT_SP: * return value * metrics.scaledDensity; * case COMPLEX_UNIT_PT: * return value * metrics.xdpi * (1.0f/72); * case COMPLEX_UNIT_IN: * return value * metrics.xdpi; * case COMPLEX_UNIT_MM: * return value * metrics.xdpi * (1.0f/25.4f); * */ paint.textSize = textSize return paint.measureText(text) } var onItemClick: ((childView: View) -> Unit)? = null fun setListener(onItemClick: (childView: View) -> Unit) { this.onItemClick = onItemClick } }