开始之前bb几句,虽然现在已经有类似的开源项目,但如果想真正理解它,还是要自己去手写一下,这不,趁着这两天空闲,公司项目不忙,就自己写了个demo,基本功能已完成,可能会有bug,回头再优化。
先说一下基本思路:
1.先创建一个类,继承View,先把刻度画出来。
2.再创建一个类,继承ViewGroup,这个类主要是控制内部尺子的滚动。
下面直接贴代码:
RulerView:
package com.test.demotestobject.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.util.Log
import android.view.View
import androidx.annotation.Nullable
open class RulerView : View{
private lateinit var mPaint : Paint
private lateinit var path: Path
private var color:String = "#FF4081"
//尺子Y轴水平基点
private var startYDraw : Float=0f
//矫正刻度起始位置
private var firststep : Float = 0f
//每个刻度起始位置
private var cachStep: Float = 0f
//尺子长度
private var ruleSize = 501
//一屏幕的刻度数
private var viewAll = 60
private lateinit var numListener:NumListener
private var isScrowComplent = false
constructor(context: Context?) : super(context) {
initView()
}
constructor(context:Context , @Nullable attrs: AttributeSet): super(context,attrs) {
initView()
}
constructor(context:Context , @Nullable attrs: AttributeSet,defStyleAttr:Int) : super(context,attrs,defStyleAttr){
initView()
}
private fun initView() {
mPaint = Paint()
path = Path()
mPaint.style=(Paint.Style.FILL)
mPaint.strokeWidth=(2f)
mPaint.color=(Color.parseColor(color))
mPaint.textAlign=(Paint.Align.CENTER)
mPaint.textSize=(14f)
mPaint.isAntiAlias=true
mPaint.isFilterBitmap=true
}
fun setScrollComplete(isScrollComplete:Boolean) {
this.isScrowComplent = isScrollComplete
invalidate()
}
/**
* 滚动时调用(给RuleViewGroup调用,不要私自调用)
*
* @param changeX
*/
fun setChangeX(changeX:Float) {
isLastComplete=false
firststep += changeX
invalidate()
}
/**
* 置为中点使用(给RuleViewGroup调用,不要私自调用)
*
* @param changeX
*/
var isLastComplete: Boolean=false
fun setCenterChangeX(changeX:Float) {
firststep += changeX
if(!isLastComplete) {
invalidate()
}
}
/**
* 手动输入时,设置当前刻度值
*
* @param rule
*/
fun setNowRule(rule:Int ) {
firststep = (width / 2 - (width / viewAll * rule)).toFloat()
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
startYDraw = ((height / 2 - 20).toFloat())
path.moveTo((width / 2).toFloat(), (startYDraw))
path.lineTo(((width / 2) + 10).toFloat(), (startYDraw) - 30)
path.lineTo(((width / 2) - 10).toFloat(), (startYDraw) - 30)
path.lineTo((width / 2).toFloat(), (startYDraw))
canvas.drawPath(path, mPaint)
//当起始位置超过中点时,则切换为中点,这样起始位置就不会跨过中点
if (firststep > width / 2) {
firststep = (width / 2).toFloat()
}
//-((width/viewAll)*(ruleSize-1)-(width/2))的计算结果为终点滑到中点时,起始位置的结果,
// 控制器不要超过此点,能保证终点不划过中点
if (firststep <= -((width / viewAll) * (ruleSize - 1) - (width / 2))) {
firststep = (-((width / viewAll) * (ruleSize - 1) - (width / 2))).toFloat()
}
cachStep = firststep
for ( i in 0..ruleSize) {
Log.e("测试cacheStep",cachStep.toString())
if(cachStep>width){
break
}
if(cachStep>=0 || cachStep<=width) {
if ((i % 5) == 0) {
canvas.drawLine(cachStep, startYDraw, cachStep, (startYDraw) + 80, mPaint)
canvas.drawText(i.toString(), cachStep, (startYDraw) + 120, mPaint)
} else {
canvas.drawLine(cachStep, startYDraw, cachStep, (startYDraw) + 55, mPaint)
}
//当前刻度在中点
if (cachStep <= (width / 2) && (cachStep + (width / viewAll)) >= (width / 2)) {
numListener.getnum(i)
//滚动完成
if (isScrowComplent) {
if (cachStep + (width / viewAll / 2) >= (width / 2)) {
setCenterChangeX((width / 2) - cachStep)
} else {
setCenterChangeX((width / 2) - (width / viewAll) - cachStep)
}
isLastComplete=true
}
}
}
cachStep += (width / viewAll)
}
}
public fun setNumListener(numListener : NumListener ) {
this.numListener = numListener
}
public interface NumListener {
fun getnum(num:Int)
}
}
RulerViewGroup:
package com.test.demotestobject.view
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.VelocityTracker
import android.widget.RelativeLayout
open class RulerViewGroup : RelativeLayout, Runnable {
private var mVelocityTracker: VelocityTracker? = null
//手指离开时的滚动速度
private var velocityX = 0f
//当前的触摸点的X值
private var nowtouchX = 0f
//加速度
private val speedAdd = 2
//单位时间
private val unitTime = 5
//手指滑动方向
private var isLeft = false
constructor(context: Context?) : super(context) {
intView()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
intView()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
intView()
}
private fun intView() {}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain()
}
var eventAddedToVelocityTracker = false
val vtev = MotionEvent.obtain(event)
when (event.action) {
MotionEvent.ACTION_DOWN -> nowtouchX = event.x
MotionEvent.ACTION_MOVE -> {
(getChildAt(0) as RulerView).setScrollComplete(false)
try {
(getChildAt(0) as RulerView).setChangeX(event.x - nowtouchX)
Log.e("ChangeX", (event.x - nowtouchX).toString() + "")
nowtouchX = event.x
} catch (e: Exception) {
Log.e("RuleViewGroup", "布局错误")
}
//计算实时滑动速度
mVelocityTracker!!.addMovement(vtev)
eventAddedToVelocityTracker = true
mVelocityTracker!!.computeCurrentVelocity(unitTime, 1000f)
velocityX = mVelocityTracker!!.getXVelocity(event.getPointerId(0))
//用于run方法判断加速度的正负
isLeft = velocityX < 0
}
MotionEvent.ACTION_UP -> //启动惯性滚动
postDelayed(this, unitTime.toLong())
MotionEvent.ACTION_CANCEL -> releaseVelocityTracker()
else -> {
}
}
if (!eventAddedToVelocityTracker) {
mVelocityTracker!!.addMovement(vtev)
}
vtev.recycle()
return true
}
override fun run() {
if (isLeft) {
velocityX += speedAdd
if (velocityX >= 0) {
velocityX = 0f
}
} else {
velocityX -= speedAdd
if (velocityX <= 0) {
velocityX = 0f
}
}
try {
(getChildAt(0) as RulerView).setChangeX(velocityX)
} catch (e: Exception) {
Log.e("RuleViewGroup", "布局错误")
}
if (velocityX != 0f) {
postDelayed(this, unitTime.toLong())
} else {
try {
(getChildAt(0) as RulerView).setScrollComplete(true)
} catch (e: Exception) {
Log.e("RuleViewGroup", "布局错误")
}
}
}
//释放VelocityTracker
private fun releaseVelocityTracker() {
if (null != mVelocityTracker) {
mVelocityTracker!!.clear()
mVelocityTracker!!.recycle()
mVelocityTracker = null
}
}
}
下面是布局文件:
以上代码放到项目里可以直接用