簡單的模仿,複雜的有點懶,以後無聊再繼續寫。
這裏只簡單模仿下ui,至於數據沒處理,默認都給個固定的步數,所以步數的線條沒處理
第一步
就是簡單分析下,都要做啥。
最下邊畫個灰色的背景,扣掉一個小箭頭
完事中間有個固定的黑圈,
再然後就是畫日子和步數線條了。我們這裏默認寬度分成8份。
最後就是處理觸摸事件,移動控件即可,這裏自然就是修改要畫的東西的x座標拉。
那就一步一步的來,首先造50條數據。然後就開畫
畫灰色背景
比較簡單了,弄個箭頭,然後從canvas裏clip掉就ok拉
//draw the date background with an arrow at the center bottom
private fun drawDateBg(canvas: Canvas){
val arrow=space/3;
canvas.save()
val path=Path()
path.moveTo(width/2f,height-arrow)
path.lineTo(width/2f-arrow,height-0f)
path.lineTo(width/2f+arrow,height-0f)
path.close()
canvas.clipPath(path,Region.Op.DIFFERENCE)
paint.color=Color.parseColor("#55000000")
canvas.drawRect(0f,height-radius*2-space,width.toFloat(),height.toFloat(),paint)
canvas.restore()
}
畫黑圈
//draw the black circle
paint.color=Color.BLACK
var y=height-radius-space/2
canvas.drawCircle(width/2f,y,radius,paint)
畫日期和步數線條
從正中心開畫,往左邊的是有效數據,最中間的是當天的。
然後再往右邊畫4個灰色的無效數據。
//draw date
val centerX=width/2f+offsetX+currentChange;
y+=txtBounds.height()/2f
for(i in 0 until datas.size){
val stepBean=datas.get(i)
val x=centerX-intervalRange*i
canvas.drawLine(x,height/10f,x,height-radius*2-space-height/10f,paintLine)
paint.color= Color.BLACK
canvas.drawText(stepBean.getDate(),x,y,paint)
if(x>width/2f-radius-txtBounds.width()/2f&&x<width/2f+radius+txtBounds.width()/2f){
centerP=i
drawCenterDate(stepBean.getDate(),x,y,canvas)
}
if(x<0){
// println("centerX======$centerX===${intervalRange}*${i}")
}
}
val calendar=Calendar.getInstance()
paint.color=Color.GRAY
for(i in 1 ..4){
if(centerX+i*intervalRange>width){
break
}
calendar.add(Calendar.DAY_OF_YEAR,1)
canvas.drawText(StepBean(0, calendar.time).getDate(),centerX+i*intervalRange,y,paint)
}
畫在黑圈範圍內的白色日期
從動圖可以看到,日期文字在黑圈裏邊的時候是白色的,這個就是以前學的,文字漸變的原理
其實黑色文字照樣畫,就是上邊一步的代碼,
完事我們再畫個白色的蓋在上邊,因爲裁掉了黑色圓圈以外的部分,所以白色文字可能只有一部分。
private fun drawCenterDate(date:String,x:Float,y:Float,canvas: Canvas){
paint.color=Color.WHITE
canvas.save()
val path=Path().apply { addCircle(width/2f,y-txtBounds.height()/2f,radius,Path.Direction.CCW) }
canvas.clipPath(path)
canvas.drawText(date,x,y,paint)
canvas.restore()
}
處理滑動事件
如果你不想處理fling事件的話,可以直接在ontouch裏處理就完事了。邏輯和GestureDetector一樣的
就是記錄下x方向移動的距離,完事修改onDraw裏的x位置就完事了。
完整代碼
比較粗糙,就是簡單實現下,等以後再增加步數的處理
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.TypedValue
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.widget.OverScroller
import java.util.*
class AllStepsShow : View {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
val datas= arrayListOf<StepBean>()
val paint=Paint(Paint.ANTI_ALIAS_FLAG)
var txtBounds=Rect()
var intervalRange=0;
var radius=20f//the black circle radius
var space=radius*2
var offsetX=0f//the total changed x value
var oldX=0f//the motionevent action down x value
var currentChange=0f//from one touch down ,the change value
val paintLine=Paint(Paint.ANTI_ALIAS_FLAG).apply {
color=Color.BLUE
strokeWidth=8f;
style=Paint.Style.FILL_AND_STROKE
this.strokeCap=Paint.Cap.ROUND
}
var startFling=false;
val overScroller=OverScroller(this.context)
var lastX=0//remember the last x value of scroller
private val gestureListener=object : GestureDetector.SimpleOnGestureListener() {
override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
println("onFling=============${velocityX}")
startFling=true;
lastX=0
overScroller.fling(0,0,velocityX.toInt(),0,-width/2,width/2,0,0)
postInvalidateOnAnimation()
return super.onFling(e1, e2, velocityX, velocityY)
}
override fun onDown(e: MotionEvent): Boolean {
oldX=e.getX();
startFling=false;
offsetX+=currentChange
println("down==============${offsetX}=======$currentChange")
return super.onDown(e)
}
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
println("onScroll======${e1.getX()}/${e2.getX()}==$distanceX")
currentChange=e2.getX()-oldX;
postInvalidateOnAnimation()
return super.onScroll(e1, e2, distanceX, distanceY)
}
}
override fun computeScroll() {
super.computeScroll()
if(startFling){
if(overScroller.computeScrollOffset()){
val current=overScroller.currX
currentChange+=current-lastX
lastX=current
postInvalidateOnAnimation()
}else{
println("computeScroll=================stop")
//fling結束以後進行位置修正
startFling=false;
lastX=0
correctPosition()
}
}
}
init {
val calendar=Calendar.getInstance()
repeat(50){
datas.add(StepBean(100, calendar.time))
calendar.add(Calendar.DAY_OF_YEAR,-1)
}
paint.apply {
style=Paint.Style.FILL
textAlign=Paint.Align.CENTER
textSize=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,15f,resources.displayMetrics)
getTextBounds("31",0,2,txtBounds)
radius=textSize
space=radius*2
}
println("text size=============${paint.textSize}===${txtBounds.width()}/${txtBounds.height()}==${radius}")
initSomething()
}
lateinit var gesture:GestureDetector
private fun initSomething(){
gesture= GestureDetector(context,gestureListener)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if(changed){
intervalRange=(right-left)/8
}
}
var centerP=0;//the center item's index
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//draw the date bg
drawDateBg(canvas)
//draw the black circle
paint.color=Color.BLACK
var y=height-radius-space/2
canvas.drawCircle(width/2f,y,radius,paint)
//draw date
val centerX=width/2f+offsetX+currentChange;
y+=txtBounds.height()/2f
for(i in 0 until datas.size){
val stepBean=datas.get(i)
val x=centerX-intervalRange*i
canvas.drawLine(x,height/10f,x,height-radius*2-space-height/10f,paintLine)
paint.color= Color.BLACK
canvas.drawText(stepBean.getDate(),x,y,paint)
if(x>width/2f-radius-txtBounds.width()/2f&&x<width/2f+radius+txtBounds.width()/2f){
centerP=i
drawCenterDate(stepBean.getDate(),x,y,canvas)
}
if(x<0){
// println("centerX======$centerX===${intervalRange}*${i}")
}
}
val calendar=Calendar.getInstance()
paint.color=Color.GRAY
for(i in 1 ..4){
if(centerX+i*intervalRange>width){
break
}
calendar.add(Calendar.DAY_OF_YEAR,1)
canvas.drawText(StepBean(0, calendar.time).getDate(),centerX+i*intervalRange,y,paint)
}
}
private fun drawCenterDate(date:String,x:Float,y:Float,canvas: Canvas){
paint.color=Color.WHITE
canvas.save()
val path=Path().apply { addCircle(width/2f,y-txtBounds.height()/2f,radius,Path.Direction.CCW) }
canvas.clipPath(path)
canvas.drawText(date,x,y,paint)
canvas.restore()
}
//draw the date background with an arrow at the center bottom
private fun drawDateBg(canvas: Canvas){
val arrow=space/3;
canvas.save()
val path=Path()
path.moveTo(width/2f,height-arrow)
path.lineTo(width/2f-arrow,height-0f)
path.lineTo(width/2f+arrow,height-0f)
path.close()
canvas.clipPath(path,Region.Op.DIFFERENCE)
paint.color=Color.parseColor("#55000000")
canvas.drawRect(0f,height-radius*2-space,width.toFloat(),height.toFloat(),paint)
canvas.restore()
}
override fun onTouchEvent(event: MotionEvent): Boolean {
gesture?.onTouchEvent(event)
// val x=event.getX()
// when(event.action){
// MotionEvent.ACTION_DOWN->{
// oldX=x;
// offsetX+=currentChange
// }
// MotionEvent.ACTION_MOVE->{
// currentChange=x-oldX;
// }
// MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL->{
// currentChange=x-oldX;
// println("currentX======${width/2f+offsetX+currentChange-intervalRange*centerP}===${width/2}")
// currentChange-=width/2f+offsetX+currentChange-intervalRange*centerP-width/2
// }
// }
// postInvalidateOnAnimation()
println("onTouchEvent x=$x====old=${oldX}=====${event.action}")
if(event.action==MotionEvent.ACTION_UP||event.action==MotionEvent.ACTION_CANCEL){
if(!startFling){
correctPosition()//如果沒有進行fling操作,那麼手指擡起的時候就進行位置修正
}
}
return true
}
//手指鬆開以後,處理下最終的位置,保證有個item在最中間,位置進行修正
private fun correctPosition(){
//根據centerP的最後位置,減去中心座標,就是它的偏移量,把這部分去掉,它就回到最中間了。
currentChange-=width/2f+offsetX+currentChange-intervalRange*centerP-width/2
postInvalidateOnAnimation()
}
}