After getting the UI effect picture above, my first impression is that it is too simple to implement. SeekBar can be done easily. Change the thumb, add a gradient, and it’s done. Just do it, do it. I’m depressed when I’m doing it, and the bottom coordinate ruler can still be used. After equal proportion division, just set a few more TextViews under the SeekBar. What about the equal proportion small division line in the middle? Moreover, it needs to be available before and after sliding, and a small distance should be left for the left and right dividing lines. The gradient color should be displayed along with the sliding distance instead of the entire width. SeekBar is difficult to satisfy under various conditions. This demand, how to do? Can only be customized.
Still in accordance with the usual practice, make a rough outline:
1. Analyze the elements and determine the implementation plan
2. Analyze the main code
3. Open source address and usage
4. Summary
1. Analyze the elements and determine the implementation plan
Canvas draws such a draggable coordinate ruler, which can basically be split into four parts. The first part is the background and the default discrete interval, the second part is the moving background and the discrete interval, and the third part is the moving picture. thumb, the last part is the bottom text coordinates.
The four parts are basically drawn, but in addition to drawing, other factors need to be considered, such as height, such as finger movement events, etc.
1. Set the default height
The reason for setting the default height is to allow the View to better display a suitable size, so that it will not be displayed when wrap_content is set. The specific settings can be controlled according to the currently set mode. There are three modes. This is the previous one. It was introduced in the article, so I won’t go into details here. When the control sets wrap_content, the mode at this time is MeasureSpec.AT_MOST. In this mode, we need to give a default height.
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
var windowHeight = heightMeasureSpec
if (heightMode == MeasureSpec.AT_MOST) {
windowHeight = mDefaultHeight.toInt()//默认的高度
}
setMeasuredDimension(widthMeasureSpec, windowHeight)
}
2. Drag event
To achieve the drag effect, we need to monitor the user's finger movement event, that is, we need to rewrite the onTouchEvent method in the custom View. In this method, we need to do corresponding processing for the finger's pressing, lifting, and moving .
In onTouchEvent, I did the following processing. One is to return directly without executing the event consumption. The purpose is to make the custom View realize static display and dynamic display. It is controlled by a variable mProgressIsIntercept; the second is to solve the problem with For the sliding conflict event of the parent View, when there is a horizontal or vertical sliding event, there will inevitably be a conflict when dragging, so you need to notify the parent View not to consume the event, that is, execute the requestDisallowInterceptTouchEvent method.
All the dragging effects are realized in the move event, which constantly changes the coordinates to update the UI. mMoveProgress is the coordinates of the finger movement.
onTouchEvent(event: MotionEvent?): Boolean {
super.onTouchEvent(event)
//如果为true直接返回,不进行拖拽
if (mProgressIsIntercept) {
return mProgressIsIntercept
}
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
parent.requestDisallowInterceptTouchEvent(mDisallowIntercept)
val downX = getChangeX(event.x)
val startX = mMoveOldX - mProgressMarginLeftRight
val endX = mMoveOldX + mProgressMarginLeftRight
return downX in startX..endX
}
MotionEvent.ACTION_MOVE -> {
//移动
var moveX = getChangeX(event.x)
//滑动至最右边
//计算最后边的坐标
val viewWidth = getViewWidth()
if (moveX >= viewWidth) {
moveX = viewWidth
}
mMoveProgress = moveX
invalidate()
}
MotionEvent.ACTION_UP -> {
//手指谈起
mMoveOldX = getChangeX(event.x)
val viewWidth = getViewWidth()
if (mMoveOldX >= viewWidth) {
mMoveOldX = viewWidth
}
}
}
return true
}
Second, the main code for analysis
1. Draw the background
There is nothing to say about the background. It is a simple rounded rectangle, which can be drawn with drawRoundRect. What needs to be determined is the spacing between the upper left and the lower right.
/**
* AUTHOR:AbnerMing
* INTRODUCE:绘制背景
*/
private fun canvasBackground(canvas: Canvas) {
mPaint!!.color = mProgressBackground
val rect = RectF().apply {
left = mProgressMarginLeftRight
top = mProgressMarginTopBottom
right = width.toFloat() - mProgressMarginLeftRight
bottom = mProgressHeight + mProgressMarginTopBottom
}
canvas.drawRoundRect(rect, mProgressRadius, mProgressRadius, mPaint!!)
}
2. Draw discrete intervals
For discrete intervals, you need to determine the number of intervals, and then dynamically calculate the position of each interval according to the number of intervals. You can use drawLine to draw a small vertical line. The vertical line also needs to determine the distance from top to bottom and its own width; in special cases Next, discrete intervals, the color is different before and after sliding, so here is also a judgment to dynamically change the color.
/**
* AUTHOR:AbnerMing
* INTRODUCE:绘制离散间隔
*/
private fun canvasIntervalLine(canvas: Canvas, isCanvas: Boolean) {
val rect =
(width - mProgressMarginLeftRight * 2 - mIntervalParentLeftRight * 2) / mIntervalSize
if (isCanvas) {
mPaint!!.color = mIntervalSelectColor
} else {
mPaint!!.color = mIntervalColor
}
mPaint!!.strokeWidth = mIntervalWidth
for (a in 0..mIntervalSize) {
val x = (rect * a) + mProgressMarginLeftRight + mIntervalParentLeftRight
val y = mIntervalMarginTopBottom + mProgressMarginTopBottom
canvas.drawLine(
x,
y,
x,
mProgressHeight + mProgressMarginTopBottom - mIntervalMarginTopBottom,
mPaint!!
)
}
}
3. Draw the moving thumb
Regarding the thumb, the first thing to determine is the size. If the width and height are set, then you need to use Bitmap to reset the height and change the coordinates of the thumb. You only need to continuously change the left coordinate point of the picture, that is, through the above-mentioned Move event Set the moving coordinates in .
/**
* AUTHOR:AbnerMing
* INTRODUCE:绘制移动的图标
*/
private fun canvasMoveIcon(canvas: Canvas) {
mProgressThumb?.let {
var decodeResource = BitmapFactory.decodeResource(resources, it)
mProgressThumbWidth = decodeResource.width
if (mThumbWidth != 0f) {
val height: Int = decodeResource.height
// 设置想要的大小
val newWidth = mThumbWidth
val newHeight = mThumbHeight
// 计算缩放比例
val scaleWidth = newWidth / width
val scaleHeight = newHeight / height
// 取得想要缩放的matrix参数
val matrix = Matrix()
matrix.postScale(scaleWidth, scaleHeight)
// 得到新的图片
decodeResource =
Bitmap.createBitmap(decodeResource, 0, 0, width, height, matrix, true)
}
var mThumpLeft = mMoveProgress
if (mThumpLeft < (mProgressThumbWidth / 2 - mIntervalParentLeftRight + mProgressThumbSpacing)) {
mThumpLeft =
mProgressThumbWidth / 2 - mIntervalParentLeftRight + mProgressThumbSpacing
}
if (mThumpLeft > (getViewWidth() - mIntervalParentLeftRight + mProgressThumbSpacing)) {
mThumpLeft = getViewWidth() - mIntervalParentLeftRight + mProgressThumbSpacing
}
canvas.drawBitmap(
decodeResource, mThumpLeft, mThumbMarginTop, mIconPaint!!
)
}
}
4. Draw the progress of the movement
The progress of the movement is the same as the drawing of the background, except that it needs to move the distance bit by bit according to the coordinates of the finger, that is, to constantly change the coordinate value on the right. Similarly, it is also dynamic through the progress of mMoveProgress in the Move event. calculation. The gradient of the progress is relatively simple, using the shader property of the brush, and currently using the horizontal linear gradient LinearGradient.
/**
* AUTHOR:AbnerMing
* INTRODUCE:绘制进度
*/
private fun canvasMoveProgress(canvas: Canvas) {
//为空
if (mColorArray.isEmpty()) {
mColorArray = intArrayOf(
ContextCompat.getColor(context, R.color.text_ff3e3e93),
ContextCompat.getColor(context, R.color.text_ff8548d2),
)
}
val linearShader = LinearGradient(
0f,
0f,
mMoveProgress + mProgressMarginLeftRight,
mProgressHeight,
mColorArray,
floatArrayOf(0f, 1f),
Shader.TileMode.CLAMP
)
mProgressPaint!!.shader = linearShader
//等于0时
val rect = RectF()
rect.left = mProgressMarginLeftRight
rect.top = mProgressMarginTopBottom
rect.right = mMoveProgress + mProgressMarginLeftRight
rect.bottom = mProgressHeight + mProgressMarginTopBottom
canvas.drawRoundRect(rect, mProgressRadius, mProgressRadius, mProgressPaint!!)
//计算比例
mGraduationResult =
((mMoveProgress / getViewWidth()) * mMaxProgress).roundToInt()//(endProgress * mMaxProgress).roundToInt()
if (mGraduationResult < 1) {
mGraduationResult = if (mGraduationSectionZero) {
0
} else {
1
}
}
if (mGraduationResult >= mMaxProgress) {
mGraduationResult = mMaxProgress
}
mMoveProgressCallback?.invoke(mGraduationResult)
}
5. Draw text scale
In fact, you can find that the discrete interval and the coordinate text scale at the bottom are actually in one-to-one correspondence. Since they are related to each other, we can put them together directly, that is, when traversing the discrete interval, we directly draw the coordinate scale at the bottom scale.
Coordinate scale, there are four effects, the first is no scale value, the second is only the start and end scale value, the third is to display all scale values, and the fourth is whether the scale value starts from 0 or 1 .
mIsGraduation is a variable used to determine whether a scale value is required. If it is true, it needs to be drawn, otherwise it will not be drawn, that is, no scale value is required. mHideGraduationSectionCenter is a variable that hides the middle scale. It is true to hide, otherwise it is not hidden. The specific code is as follows:
/**
* AUTHOR:AbnerMing
* INTRODUCE:绘制离散间隔
*/
private fun canvasIntervalLine(canvas: Canvas, isCanvas: Boolean) {
val rect =
(width - mProgressMarginLeftRight * 2 - mIntervalParentLeftRight * 2) / mIntervalSize
if (isCanvas) {
mPaint!!.color = mIntervalSelectColor
} else {
mPaint!!.color = mIntervalColor
}
mPaint!!.strokeWidth = mIntervalWidth
for (a in 0..mIntervalSize) {
val x = (rect * a) + mProgressMarginLeftRight + mIntervalParentLeftRight
val y = mIntervalMarginTopBottom + mProgressMarginTopBottom
canvas.drawLine(
x,
y,
x,
mProgressHeight + mProgressMarginTopBottom - mIntervalMarginTopBottom,
mPaint!!
)
//绘制刻度值
if (mIsGraduation && isCanvas) {
if (mHideGraduationSectionCenter && (a != 0 && a != mIntervalSize)) {
//隐藏中间
continue
}
var graduation = a * mGraduationSection
//是否从0开始记录
if (graduation == 0 && !mGraduationSectionZero) {
graduation = 1
}
//如果移动到了,改变颜色
if (mGraduationResult >= graduation && mGraduationResult < graduation + mGraduationSection) {
mGraduationPaint?.color = mGraduationSelectTextColor
} else {
mGraduationPaint?.color = mGraduationTextColor
}
val text = graduation.toString()
val rectText = Rect()
mGraduationPaint!!.getTextBounds(text, 0, text.length, rectText)
val textWidth = rectText.width()
val textHeight = rectText.height()
canvas.drawText(
text,
x - textWidth / 2,
mProgressHeight + mProgressMarginTopBottom * 2 + textHeight + mGraduationMarginTop,
mGraduationPaint!!
)
}
}
}
3. Open source address and usage
At present, it has been uploaded to Github. It is a simple class itself. There are not many things. If you need iron, you can directly view the source code.
Address: https://github.com/AbnerMing888/MoveProgress
If you are too lazy to download the source code and want to use it directly, there is no problem. I have uploaded it to the remote Maven, and you can rely on it.
1. Under the build.gradle file under your root project, import maven.
allprojects {
repositories {
maven { url "https://gitee.com/AbnerAndroid/almighty/raw/master" }
}
}
2. Introduce dependencies under the build.gradle file in the Module you need to use.
dependencies {
implementation 'com.vip:moveprogress:1.0.0'
}
3. Just import XML
<com.vip.moveprogress.MoveProgress
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:ms_graduation_hide_center="true" />
related attributes
Attributes |
type |
overview |
ms_height |
dimension |
The height of the View view |
ms_progress_height |
dimension |
the height of the progress bar |
ms_progress_thumb |
reference |
The icon of the progress bar |
ms_progress_margin_top_bottom |
dimension |
The up and down distance of the progress bar from the icon |
ms_progress_margin_left_right |
dimension |
The progress bar distance from the left and right margins |
ms_progress_radius |
dimension |
Rounded corners of the progress bar |
ms_progress_background |
color |
The background color of the progress bar |
ms_interval_color |
color |
Spacer color |
ms_interval_select_color |
color |
Interval selection color |
ms_interval_parent_margin_left_right |
dimension |
Spacer distance from parent left and right |
ms_interval_size |
integer |
Number of interval lines |
ms_interval_width |
dimension |
Spacer Width |
ms_interval_margin_top_bottom |
dimension |
Spacer top and bottom margins |
ms_progress_move_color |
reference |
defined mobile color |
ms_progress_max |
integer |
maximum progress |
ms_progress_default |
integer |
default progress |
ms_is_graduation |
boolean |
Whether to display the scale |
ms_graduation_text_size |
dimension |
scale text size |
ms_graduation_text_color |
color |
scale text color |
ms_graduation_select_text_color |
color |
Scale text selection color |
ms_graduation_section |
integer |
Scale value segment |
ms_graduation_section_zero |
boolean |
Scale value segments start at zero |
ms_graduation_hide_center |
boolean |
Whether to hide the middle of the scale value segment |
ms_graduation_margin_top |
dimension |
The distance from the scale value to the upper edge |
ms_progress_thumb_width |
dimension |
The width of the icon |
ms_progress_thumb_height |
dimension |
The height of the icon |
ms_progress_thumb_margin_top |
dimension |
The height of the icon from the top |
ms_progress_thumb_spacing |
dimension |
icon padding |
ms_progress_disallow_intercept |
boolean |
Whether to intercept |
ms_progress_is_intercept |
boolean |
Whether dragging is prohibited |
related method
method |
parameter |
overview |
getProgress |
No reference |
return current progress |
changeProgress |
Int |
change current progress |
getMoveProgress |
returns Int |
Callback |
setProgressIsIntercept |
Boolean |
Set whether to intercept |
Four. Summary
Regarding the gradient, it should be noted that the range of the gradient is not the default fixed distance from left to right, but the distance from left to finger sliding. This needs to be noted, that is, when setting the gradient, the X coordinate of the termination needs to be according to the gesture Dynamic settings on the left.
From this simple drag and drop progress bar, we can understand that canvas drawing lines, rounded rectangles, pictures and related knowledge points combined with gestures are not difficult in themselves.