仿三星S健康步數日趨勢圖

簡單的模仿,複雜的有點懶,以後無聊再繼續寫。
這裏只簡單模仿下ui,至於數據沒處理,默認都給個固定的步數,所以步數的線條沒處理


20181019_100951.gif

第一步

就是簡單分析下,都要做啥。
最下邊畫個灰色的背景,扣掉一個小箭頭
完事中間有個固定的黑圈,
再然後就是畫日子和步數線條了。我們這裏默認寬度分成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()
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章