-
使用一个自定义view记录思想
-
效果如下,实现方式有很多种,以此来回顾一下自定义view
-
使用Android Studio快速创建
-
删除一些无用的
(1)首先思考一下这个图片,共有三个一样,这时候我们只要实现抽出公共的,我们看到总共分三个模块,1个是矩形背景,2是倒计时文字,3是右边的文字显示,相应的,我们对每个模块进行颜色,文字大小等划分
(2)对应的attrs.xml
<!--倒计时-->
<declare-styleable name="TimeView">
<!--矩形背景-->
<attr name="re_color" format="color" />
<attr name="re_width" format="dimension" />
<attr name="re_height" format="dimension" />
<!--倒计时数字-->
<attr name="time_string" format="string" />
<attr name="time_color" format="color" />
<attr name="time_size" format="dimension" />
<!--日期文字-->
<attr name="date_text_string" format="string" />
<attr name="date_text_color" format="color" />
<attr name="date_text_size" format="dimension" />
</declare-styleable>
(3)我们再去java文件中将这些属性获取出来,都是一些固定操作
// 矩形背景
private var _rectangleColor: Int = Color.BLACK // 矩形背景颜色
private var _rectangleWidth: Float = 0f // 矩形宽度
private var _rectangleHeight: Float = 0f // 矩形高度
// 倒计时数字
private var _timeString: String = ""
private var _timeTextColor: Int = Color.WHITE // 倒计时颜色
private var _timeTextSize: Float = 13f // 倒计时数字大小
// 日期数字
private var _textString: String = ""
private var _textColor: Int = Color.BLACK // 倒计时颜色
private var _textSize: Float = 13f // 倒计时数字大小
// init中,最后别忘了a.recycle()
// Load attributes
val a = context.obtainStyledAttributes(
attrs, R.styleable.TimeView, defStyle, 0
)
// 矩形背景
_rectangleColor = a.getColor(
R.styleable.TimeView_re_color,
_rectangleColor
)
_rectangleWidth = a.getDimension(
R.styleable.TimeView_re_width,
_rectangleWidth
)
_rectangleHeight = a.getDimension(
R.styleable.TimeView_re_height,
_rectangleHeight
)
// 倒计时
_timeString = a.getString(
R.styleable.TimeView_time_string
).toString()
_timeTextSize = a.getDimension(
R.styleable.TimeView_time_size,
_timeTextSize
)
_timeTextColor = a.getColor(
R.styleable.TimeView_time_color,
_timeTextColor
)
// 文字
_textString = a.getString(
R.styleable.TimeView_date_text_string
).toString()
_textSize = a.getDimension(
R.styleable.TimeView_date_text_size,
_textSize
)
_textColor = a.getColor(
R.styleable.TimeView_date_text_color,
_textColor
)
a.recycle()
(4)这时候我们再分析图片,三个模块都需要一个画笔吧,然后我们再各自定义画笔和初始化画笔
private var rectanglePaint: Paint? = null // 矩形画笔
private var timePaint: TextPaint? = null // 时间画笔
private var textPaint: TextPaint? = null // 文字画笔
// 初始化它们
// Set up a default TextPaint object
rectanglePaint = Paint().apply {
flags = Paint.ANTI_ALIAS_FLAG
color = _rectangleColor
style = Paint.Style.FILL
}
// time
timePaint = TextPaint().apply {
flags = Paint.ANTI_ALIAS_FLAG
textAlign = Paint.Align.CENTER
textSize = _timeTextSize
color = _timeTextColor
}
// text
textPaint = TextPaint().apply {
flags = Paint.ANTI_ALIAS_FLAG
textAlign = Paint.Align.CENTER
textSize = _textSize
color = _textColor
}
(5)然后我们来进行绘制,可以自己动手画个草图,边调试边改
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// allocations per draw cycle.
val paddingLeft = paddingLeft
val paddingTop = paddingTop
val paddingRight = paddingRight
val paddingBottom = paddingBottom
val contentWidth = width - paddingLeft - paddingRight
val contentHeight = height - paddingTop - paddingBottom
var timeWidth = getTextWidth(_timeString, timePaint!!)
val timeHeight = getTextHeight(_timeString, timePaint!!)
val textWidth = getTextWidth(_textString, textPaint!!)
val textHeight = getTextHeight(_textString, textPaint!!)
val leftWidth = (contentWidth - (_rectangleWidth + 10 + textWidth)) / 2
with(canvas) {
drawRoundRect(RectF(leftWidth , (contentHeight - _rectangleHeight) / 2, leftWidth + _rectangleWidth, _rectangleHeight + (contentHeight - _rectangleHeight) / 2), 10f, 10f, rectanglePaint!!)
drawText(_timeString, leftWidth + _rectangleWidth / 2, (contentHeight / 2 + timeHeight / 2).toFloat(), timePaint!!)
drawText(_textString, leftWidth + _rectangleWidth + textWidth / 2 + 10, (contentHeight / 2 + textHeight / 2).toFloat(), textPaint!!)
}
}
private fun getTextWidth(text: String, paint: Paint): Float {
val rect = Rect() // 文字所在区域的矩形
paint.getTextBounds(text, 0, text.length, rect)
return rect.width().toFloat()
}
private fun getTextHeight(text: String, paint: Paint): Float {
val rect = Rect() // 文字所在区域的矩形
paint.getTextBounds(text, 0, text.length, rect)
return rect.height().toFloat()
}
(6)然后我们看下效果,接下来就是再LinearLayout布局中,水平放置三个就可以了
(7)代码改变各个属性的状态也别落下
/**
* 矩形背景
*/
var rectangleColor: Int
get() = _rectangleColor
set(value) {
_rectangleColor = value
invalidate()
}
var rectangleWidth: Float
get() = _rectangleWidth
set(value) {
_rectangleWidth = value
invalidate()
}
var rectangleHeight: Float
get() = _rectangleHeight
set(value) {
_rectangleHeight = value
invalidate()
}
/**
* 倒计时
*/
var timeString: String
get() = _timeString
set(value) {
_timeString = value
invalidate()
}
var timeTextColor: Int
get() = _timeTextColor
set(value) {
_timeTextColor = value
invalidate()
}
var timeTextSize: Float
get() = _timeTextSize
set(value) {
_timeTextSize = value
invalidate()
}
/**
* 日期
*/
var textString: String
get() = _textString
set(value) {
_textString = value
invalidate()
}
var textColor: Int
get() = _textColor
set(value) {
_textColor = value
invalidate()
}
var textSize: Float
get() = _textSize
set(value) {
_textSize = value
invalidate()
}
- 在我们感觉快大功告成时候发现,宽度怎么只能设置具体值,比如100,200,设置android:layout_width=“wrap_content” android:layout_width="match_parent"效果是一样的,我们百度一下,发现父类View中onMeasure()中默认的使用方式
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:使用的是一样的,这时候我们需要自己来重写,进行区分
// 给个默认值
private val DEFAULT_WITH = 140
private val DEFAULT_Height = 140
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
//处理wrap_contentde情况
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(DEFAULT_WITH, DEFAULT_Height)
} else if (widthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(DEFAULT_WITH, heightSize)
} else if (heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSize, DEFAULT_Height)
}
}
-
我们再去试一下使用wrap_content,这次正常了,然后再在项目中使用
LinearLayout水平放置
-
源码
attrs.xml
<!--倒计时-->
<declare-styleable name="TimeView">
<!--矩形背景-->
<attr name="re_color" format="color" />
<attr name="re_width" format="dimension" />
<attr name="re_height" format="dimension" />
<!--倒计时数字-->
<attr name="time_string" format="string" />
<attr name="time_color" format="color" />
<attr name="time_size" format="dimension" />
<!--日期文字-->
<attr name="date_text_string" format="string" />
<attr name="date_text_color" format="color" />
<attr name="date_text_size" format="dimension" />
</declare-styleable>
TimeView
class TimeView : View {
// 矩形背景
private var _rectangleColor: Int = Color.BLACK // 矩形背景颜色
private var _rectangleWidth: Float = 0f // 矩形宽度
private var _rectangleHeight: Float = 0f // 矩形高度
private var rectanglePaint: Paint? = null // 矩形画笔
// 倒计时数字
private var _timeString: String = ""
private var _timeTextColor: Int = Color.WHITE // 倒计时颜色
private var _timeTextSize: Float = 13f // 倒计时数字大小
private var timePaint: TextPaint? = null
// 日期数字
private var _textString: String = ""
private var _textColor: Int = Color.BLACK // 倒计时颜色
private var _textSize: Float = 13f // 倒计时数字大小
private var textPaint: TextPaint? = null
private val DEFAULT_WITH = 140
private val DEFAULT_Height = 140
/**
* 矩形背景
*/
var rectangleColor: Int
get() = _rectangleColor
set(value) {
_rectangleColor = value
invalidate()
}
var rectangleWidth: Float
get() = _rectangleWidth
set(value) {
_rectangleWidth = value
invalidate()
}
var rectangleHeight: Float
get() = _rectangleHeight
set(value) {
_rectangleHeight = value
invalidate()
}
/**
* 倒计时
*/
var timeString: String
get() = _timeString
set(value) {
_timeString = value
invalidate()
}
var timeTextColor: Int
get() = _timeTextColor
set(value) {
_timeTextColor = value
invalidate()
}
var timeTextSize: Float
get() = _timeTextSize
set(value) {
_timeTextSize = value
invalidate()
}
/**
* 日期
*/
var textString: String
get() = _textString
set(value) {
_textString = value
invalidate()
}
var textColor: Int
get() = _textColor
set(value) {
_textColor = value
invalidate()
}
var textSize: Float
get() = _textSize
set(value) {
_textSize = value
invalidate()
}
constructor(context: Context) : super(context) {
init(null, 0)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(attrs, 0)
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
context,
attrs,
defStyle
) {
init(attrs, defStyle)
}
private fun init(attrs: AttributeSet?, defStyle: Int) {
// Load attributes
val a = context.obtainStyledAttributes(
attrs, R.styleable.TimeView, defStyle, 0
)
// 矩形背景
_rectangleColor = a.getColor(
R.styleable.TimeView_re_color,
_rectangleColor
)
_rectangleWidth = a.getDimension(
R.styleable.TimeView_re_width,
_rectangleWidth
)
_rectangleHeight = a.getDimension(
R.styleable.TimeView_re_height,
_rectangleHeight
)
// 倒计时
_timeString = a.getString(
R.styleable.TimeView_time_string
).toString()
_timeTextSize = a.getDimension(
R.styleable.TimeView_time_size,
_timeTextSize
)
_timeTextColor = a.getColor(
R.styleable.TimeView_time_color,
_timeTextColor
)
// 文字
_textString = a.getString(
R.styleable.TimeView_date_text_string
).toString()
_textSize = a.getDimension(
R.styleable.TimeView_date_text_size,
_textSize
)
_textColor = a.getColor(
R.styleable.TimeView_date_text_color,
_textColor
)
a.recycle()
// Set up a default TextPaint object
rectanglePaint = Paint().apply {
flags = Paint.ANTI_ALIAS_FLAG
color = _rectangleColor
style = Paint.Style.FILL
}
// time
timePaint = TextPaint().apply {
flags = Paint.ANTI_ALIAS_FLAG
textAlign = Paint.Align.CENTER
textSize = _timeTextSize
color = _timeTextColor
}
// text
textPaint = TextPaint().apply {
flags = Paint.ANTI_ALIAS_FLAG
textAlign = Paint.Align.CENTER
textSize = _textSize
color = _textColor
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
//处理wrap_contentde情况
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(DEFAULT_WITH, DEFAULT_Height)
} else if (widthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(DEFAULT_WITH, heightSize)
} else if (heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSize, DEFAULT_Height)
}
}
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// allocations per draw cycle.
val paddingLeft = paddingLeft
val paddingTop = paddingTop
val paddingRight = paddingRight
val paddingBottom = paddingBottom
val contentWidth = width - paddingLeft - paddingRight
val contentHeight = height - paddingTop - paddingBottom
var timeWidth = getTextWidth(_timeString, timePaint!!)
val timeHeight = getTextHeight(_timeString, timePaint!!)
val textWidth = getTextWidth(_textString, textPaint!!)
val textHeight = getTextHeight(_textString, textPaint!!)
val leftWidth = (contentWidth - (_rectangleWidth + 10 + textWidth)) / 2
with(canvas) {
drawRoundRect(RectF(leftWidth , (contentHeight - _rectangleHeight) / 2, leftWidth + _rectangleWidth, _rectangleHeight + (contentHeight - _rectangleHeight) / 2), 10f, 10f, rectanglePaint!!)
drawText(_timeString, leftWidth + _rectangleWidth / 2, (contentHeight / 2 + timeHeight / 2).toFloat(), timePaint!!)
drawText(_textString, leftWidth + _rectangleWidth + textWidth / 2 + 10, (contentHeight / 2 + textHeight / 2).toFloat(), textPaint!!)
}
}
private fun getTextWidth(text: String, paint: Paint): Float {
val rect = Rect() // 文字所在区域的矩形
paint.getTextBounds(text, 0, text.length, rect)
return rect.width().toFloat()
}
private fun getTextHeight(text: String, paint: Paint): Float {
val rect = Rect() // 文字所在区域的矩形
paint.getTextBounds(text, 0, text.length, rect)
return rect.height().toFloat()
}
}