preface
This time free down, decided to project in the custom View with Kotlin to write all over, wank up
A TrendCurveView.
rendering
1. Paint the background
/** @param canvas */ private fun drawHorizontalLine(canvas: Canvas) { val baseHeight = mAvailableAreaHeight / 5for (i in0 until 6) { val startY = baseHeight * i + mAvailableAreaTop canvas.drawLine(0f, startY, mViewWidth, startY, MHorizontalLinePaint)} // Draw bottom line mPaint. Shader = null mPaint. Style = Paint mBottomLineHeight mPaint.color = mBottomLineColor canvas.drawLine( 0f, mTotalHeight - mBottomLineHeight, mViewWidth, mTotalHeight - mBottomLineHeight, mPaint ) }Copy the code
2. Draw unit text
/** @param canvas */ private fun drawUnitDes(canvas: canvas) {if(! TextUtils.isEmpty(mUnitDes)) { canvas.drawText( mUnitDes!! , width.toFloat() - mMarginRight - mUnitDesTextWidth / 2, mMarginTop + mUnitDesTextHeight / 2, mUnitDesPaint ) } }Copy the code
3. Data processing
{“value”:53.5126,”recordDate”:”2019-10-12″
Val diff = max-min // draw a line if the maximum and minimum values are equal //mAvailableAreaHeight * 0.8f because when calculating Bessel, the vertex coordinates will exceed val scale =if0.6 f (diff = = 0.0)elseMAvailableAreaHeight * 0.8f/diff.tofloat () val mCacheList = ArrayList<TextBean>()for (i inData. Indices) {val trendDataBean = data[I] * mEveryRectWidth). ToFloat () val y = (mAvailableAreaTop + (Max -) trendDataBean.value) * scale).toFloat() val pointF = PointF(x, y) val recordDate = trendDataBean.recordDate try { val parse = simpleDateFormat.parse(recordDate) calendar.time = parse / / calculate the coordinates all words val textBean = getTextBean (PM, trendDataBean. Value. The toString (), calendar, pointF) textBean.pointF = pointF mCacheList.add(textBean) } catch (e: ParseException) { e.printStackTrace() } }Copy the code
private inner class TextBean internal constructorToFloat () var centerX: Float = 0.tofloat () var centerY: Float = 0.tofloat () var centerStr: String? Var bottomX: Float = 0. ToFloat () var bottomY: Float = 0. ToFloat () var bottomY: Float = 0. ToFloat () var circleY: Float = 0.tofloat () var pointF: pointF? = null }Copy the code
Once the data is processed, bezier curves can be plotted. Scroller is used for sliding
init {
initSize()
initPaint()
mScroller = Scroller(getContext())
val configuration = ViewConfiguration.get(context)
mMinimumFlingVelocity = configuration.scaledMinimumFlingVelocity
mMaximumFlingVelocity = configuration.scaledMaximumFlingVelocity.toFloat()
}
override fun computeScroll() {
if(mScroller!! .computescrolloffSet ()) {// determine the left and right boundaries mMove = mscroll.currxif (mMove > mMaxMove) {
mMove = mMaxMove
} else if (mMove < 0) {
mMove = 0
}
invalidate()
}
}
Copy the code
4. Calculate curve points
According to the sliding distance, the current data to be drawn is calculated from the cacheList
/** ** Ensure that nub +3 +3 third-order Bessel three control points around each three * according to the sliding distance to calculate the display of items ** @param move */ private Fun calculateShowList(move: Int) {if (mCacheList.isEmpty()) {
return
}
val absMove = abs(move)
var start: Int
var end: Int
if (absMove < mCenterX) {
end = mTotalSize
start = mTotalSize - ((absMove + mCenterX) / mEveryRectWidth + 3)
} else{ val exceedStart = (absMove - mCenterX) / mEveryRectWidth end = mTotalSize - (exceedStart - 3) start = mTotalSize - ExceedStart + NUB + 3} // exceedStart end =if (mTotalSize > end) end else mTotalSize
start = if (start > 0) start else 0
mShowList.clear()
// mShowList.addAll(mCacheList.subList(start,end));
for (i in start until end) {
mShowList.add(mCacheList[i])
}
}
Copy the code
According to the mShowList obtained, the third-order Bessel curve is calculated
/** */ private Fun measurePath(pointFList: pointFList) List<TextBean>) { mPath.reset() var prePreviousPointX = java.lang.Float.NaN var prePreviousPointY = java.lang.Float.NaN var previousPointX = java.lang.Float.NaN var previousPointY = java.lang.Float.NaN var currentPointX = java.lang.Float.NaN var currentPointY = java.lang.Float.NaN var nextPointX: Float var nextPointY: Float val lineSize = pointFList.sizefor (i in 0 until lineSize) {
if(java.lang.Float.isNaN(currentPointX)) { val point = pointFList[i].pointF currentPointX = point!! .x + mMove currentPointY = point.y }if(java.lang.float. IsNaN (previousPointX)) {// is the first pointif(i > 0) { val point = pointFList[i - 1].pointF previousPointX = point!! .x + mMove previousPointY = point.y }elsePreviousPointX = currentPointX previousPointY = currentPointY}}if(java.lang.float. IsNaN (prePreviousPointX)) {// whether it is the first two pointsif(i > 1) { val point = pointFList[i - 2].pointF prePreviousPointX = point!! .x + mMove prePreviousPointY = point.y }elsePreviousPointX = previousPointX prePreviousPointY = previousPointY}if(i < lineSize - 1) { val point = pointFList[i + 1].pointF nextPointX = point!! .x + mMove nextPointY = point.y }elseNextPointX = currentPointX nextPointY = currentPointY}if(I == 0) {// Move Path to start mPath. MoveTo (currentPointX, currentPointY)}elseVal firstDiffY = currentPointy-prepreViouspointx val firstDiffY = currentPointy-prepreViouspointy val secondDiffX = nextPointX - previousPointX val secondDiffY = nextPointY - previousPointY val firstControlPointX = previousPointX + lineSmoothness * firstDiffX val firstControlPointY = previousPointY + lineSmoothness * firstDiffY val secondControlPointX = currentPointX - lineSmoothness * secondDiffX val secondControlPointY = currentPointY - LineSmoothness * secondDiffY // cubicTo(firstControlPointX, firstControlPointY, secondControlPointX, SecondControlPointY, currentPointX, currentPointY)} prePreviousPointX = previousPointX prePreviousPointY = previousPointY previousPointX = currentPointX previousPointY = currentPointY currentPointX = nextPointX currentPointY = nextPointY } }Copy the code
5. Draw Path and text
Once you have the Path and the data points you need to draw, easy is all that is left to draw
@param canvas */ private fun drawCurveLineAndBgPath(canvas: canvas) {if(mShowList.size > 0) { val firstX = mShowList[0].pointF!! .x + mMove val lastX = mShowList[mShowList.size - 1].pointF!! DrawPath (mPath, mCurvePaint) // Fill the background mPath. LineTo (lastX, mCurvePaint) mAvailableAreaTop + mAvailableAreaHeight) mPath.lineTo(firstX, mAvailableAreaTop + mAvailableAreaHeight) mPath.close() canvas.drawPath(mPath, mPathPaint) } }Copy the code
/ * * * map at the top of the rectangle and text and vertical line * * @ param canvas * / private fun drawTopAndVerticalLineView (canvas: Canvas) {val scrollX = abs(mMove) val baseWidth = mEveryRectWidth / 2f // Var nub = mTotalSize - 1 - ((scrollX + baseWidth)/mEveryRectWidth).toint ()if (nub > mTotalSize - 1) {
nub = mTotalSize - 1
}
if (nub < 0) {
nub = 0
}
val centerValue = mCacheList[nub].centerStr
val valueWidth = mTopTextPaint.measureText(centerValue)
val unitWidth = if (TextUtils.isEmpty(mUnit)) 0f elsemUnitPaint.measureText(mUnit) val centerTvWidth = valueWidth + unitWidth + 1f val topRectPath = getTopRectPath(centerTvWidth) mPaint.style = Paint.Style.FILL mPaint.color = mCurveLineColor Canvas. drawPath(topRectPath, mPaint) // Draw center line Canvas. drawLine(McEnterx.tofloat (), mAvailableAreaTop - mArrowBottomMargin, mCenterX.toFloat(), mTotalHeight.toFloat() - mBottomHeight - mBottomLineHeight, Set (McEnterx-centertvwidth / 2f, mMarginTop, mCenterX + centerTvWidth / 2, mMarginTop + mTopTvVerticalMargin * 2 + mTopTextHeight )if(mTopBaseLineY == 0) { val pm = mTextPaint.fontMetricsInt mTopBaseLineY = ((mRectF.bottom + mRectF.top - Pm.bottom.tofloat () -pm.top.tofloat ()) / 2f).toint ()} canvas. DrawText (centerValue!! , mRectF.centerX() - centerTvWidth / 2 + valueWidth / 2, mTopBaseLineY.toFloat(), mTopTextPaint )if(! Textutils.isempty (mUnit)) {// canvas. DrawText (mUnit!! , mRectF.centerX() + centerTvWidth / 2 - unitWidth / 2, mTopBaseLineY.toFloat(), MUnitPaint)} /** * top rectangle + Triangle ** @param rectWidth */ private fun getTopRectPath(rectWidth: Float): Path { mRectF.set( mCenterX.toFloat() - rectWidth / 2f - mTopTvHorizontalMargin, mMarginTop, mCenterX.toFloat() + rectWidth / 2f + mTopTvHorizontalMargin, MMarginTop + mTopTvVerticalMargin * 2 + mTopTextHeight) mtoppath.reset () // mtoppath.addroundRect (mRectF, Mtoppath.moveto (mrectf.centerx () -MarrowWidth / 2f, mTopRectRadius, mTopRectRadius, path.direction.ccw) mMarginTop + mRectF.height()) mTopPath.lineTo(mRectF.centerX(), mMarginTop + mRectF.height() + mArrowWidth / 2f) mTopPath.lineTo(mRectF.centerX() + mArrowWidth / 2f, mMarginTop + mRectF.height()) mTopPath.close()return** @param canvas */ private fun drawValueAndPoint(canvas: canvas) {for (i inMshowlist. indices) {val textBean = mShowList[I] val centerX = textBean.centerX + mMove canvas.drawText(textBean.centerStr!! , centerX, textBean.centerY, TextSize = mBottomTextSize canvas. DrawText (textBean.bottomstr!! , centerX, textBean.bottomY, mTextPaint) canvas.drawCircle(centerX, textBean.circleY, mInnerRadius, mInnerCirclePaint) canvas.drawCircle( centerX, textBean.circleY, mInnerRadius + mOuterRadiusWidth / 2, mOuterCirclePaint ) } }Copy the code
6.onTouchEvent
The last is gesture processing, and the scrolling rebound effect, the rebound effect is calculated according to scroll.finalx
var finalX = mScroller.finalX
val distance = abs(finalX % mEveryRectWidth)
if (distance < mEveryRectWidth / 2) {
finalX -= distance
} else {
finalX += (mEveryRectWidth - distance)
}
Copy the code
override fun onTouchEvent(event: MotionEvent): Boolean {
if(mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain() } mVelocityTracker!! .addMovement(event) val action = event.action val pointerUp = action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_POINTER_UP val skipIndex =if (pointerUp) event.actionIndex else -1
// Determine focal point
var sumX = 0f
var sumY = 0f
val count = event.pointerCount
for (i in 0 until count) {
if (skipIndex == i) continue
sumX += event.getX(i)
sumY += event.getY(i)
}
val div = if (pointerUp) count - 1 else count
val focusX = sumX / div
val focusY = sumY / div
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mLastFocusX = focusX
mDownFocusX = mLastFocusX
mLastFocusY = focusY
mDownFocusY = mLastFocusY
return true
}
MotionEvent.ACTION_MOVE ->
if(abs(mMove) <= mMaxMove) { val scrollX = (mLastFocusX - focusX).toInt() smoothScrollBy(-scrollX, 0) mLastFocusX = focusX mLastFocusY = focusY } MotionEvent.ACTION_UP -> { mVelocityTracker!! .computeCurrentVelocity(1000, mMaximumFlingVelocity) val velocityX = mVelocityTracker!! .xVelocity //if(abs(velocityX) > mMinimumFlingVelocity) { mScroller!! .fling( mMove, 0, velocityX.toInt(), mVelocityTracker!! .yVelocity.toInt(), 0, mMaxMove, 0, 0 ) var finalX = mScroller.finalX val distance = abs(finalX % mEveryRectWidth)if (distance < mEveryRectWidth / 2) {
finalX -= distance
} else {
finalX += (mEveryRectWidth - distance)
}
mScroller.finalX = finalX
} else {
setClick(event.x.toInt(), mDownFocusX)
}
getCurrentIndex()
if(mVelocityTracker ! = null) { // This may have been cleared when we called out to the // application above. mVelocityTracker!! .recycle() mVelocityTracker = null } }else -> {
}
}// invalidate();
return super.onTouchEvent(event)
}
private fun setClick(upX: Int, downX: Float) { var finalX = mScroller!! .finalX val distance: Intif (abs(downX - upX) > 10) {
distance = abs(finalX % mEveryRectWidth)
if (distance < mEveryRectWidth / 2) {
finalX -= distance
} else {
finalX += (mEveryRectWidth - distance)
}
} else {
val space = (mCenterX - upX).toFloat()
distance = abs(space % mEveryRectWidth).toInt()
val nub = (space / mEveryRectWidth).toInt()
if (distance < mEveryRectWidth / 2) {
if(nub ! = 0) { finalX =if (space > 0) {
(finalX + (space - distance)).toInt()
} else {
(finalX + (space + distance)).toInt()
}
}
} else {
if (space > 0) {
finalX += (nub + 1) * mEveryRectWidth
} else {
finalX = (finalX + space - (mEveryRectWidth - distance)).toInt()
}
}
}
if (finalX < 0) {
finalX = 0
} else if (finalX > mMaxMove) {
finalX = mMaxMove
}
smoothScrollTo(finalX, 0)
}
Copy the code
7. Fill in the data
val list = (0.. 1000).toList() val mutableList = mutableListOf<DataBean>()for (i in list) {
mutableList.add(
DataBean(
"2019-10-10", random.nextint (100) + 0.5))} trendCurveView.setData(mutableList,"kg")
Copy the code
This is the end of the question welcome to put forward corrections!!
Making the address