07/09/2018, 09:28

Building a CustomView - TicTacToe (Part 3)

Ở bài trước ta đã bắt được event click của người dùng và điền vào ô đó giá trị tương ứng (X hoạc O). Trong bài này ta sẽ xác định người chiến thắng và highlight private val path = Path() .. override fun onDraw(canvas: Canvas) { super.onDraw(canvas) drawVerticalLines(canvas) ...

  • Ở bài trước ta đã bắt được event click của người dùng và điền vào ô đó giá trị tương ứng (X hoạc O). Trong bài này ta sẽ xác định người chiến thắng và highlight
private val path = Path()
..
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawVerticalLines(canvas)
        drawHorizontalLines(canvas)
        drawSquareStates(canvas)
        if (shouldAnimate) { <--------Check Here
            canvas.drawPath(path, paint) // path is storing our line
        }
        if (touching) {
            drawHighlightRectangle(canvas)
        }
    }
    
   fun animateWin(x1: Int, y1: Int, x3: Int, y3: Int) { // will be called from activity or fragment
        winCoordinates = arrayOf(x1, y1, x3, y3) // first and last coordinate of winning line
        if (winCoordinates[0] < 0) return
        val centerX = squares[winCoordinates[0]][winCoordinates[1]].exactCenterX()
        val centerY = squares[winCoordinates[0]][winCoordinates[1]].exactCenterY()
        val centerX2 = squares[winCoordinates[2]][winCoordinates[3]].exactCenterX()
        val centerY2 = squares[winCoordinates[2]][winCoordinates[3]].exactCenterY()

        path.reset()
        path.moveTo(centerX, centerY) // moving to centre of first square
        path.lineTo(centerX2, centerY2) // creating a line till centre of last square 
        shouldAnimate = true
        invalidate();
    }
  • Trong bài trước ta đã vẽ line sử dụng canvas.drawLine(), tuy nhiên trong bài này ta sẽ vẽ line sử dụng Path để dễ dàng trong việc chỉnh chiều dài của line
  • Phương thức animateWin() sẽ được gọi bởi Activity hay Fragment với đối số là tọa độ của 2 điểm đầu và cuối, như ảnh dưới đây đối số là 0,0,2,2

  • Tuy nhiên phương thức trên mới chỉ vẽ line mà chưa có animation
  • Để animate path ta sẽ sử dụng class ValueAnimatorDashPathEffect và ta phải measure path như sau
val measure = PathMeasure(path, false)
val lineLength = measure.length
  • path sẽ chứa tọa độ điểm đầu và cuối của line mình sẽ vẽ
  • DashPathEffect thông thường được dùng để vẽ line như sau ----
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Style.STROKE);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(1);
PathEffect effects = new DashPathEffect(new float[]{1,2,4,8} ,1);
paint.setPathEffect(effects);
canvas.drawLine(0, 40, mWidth, 40, paint);
  • mảng array truyền vào khi khởi tạo DashPathEffect sẽ là chiều dài của mỗi line được vẽ ra tương ứng. Như ví dụ trên sẽ vẽ ra line đầu tiên với length là 1, sau đó vẽ khoảng cách với length là 2, tiếp theo vẽ line với length là 4 và cuối cùng vẽ khoảng cách với length là 8. Đối số cuối trong khởi tạo DashPathEffect dùng để xác định vị trí bắt đầu vẽ line

  • Trong hình trên line sẽ đi từ 1st đến 2nd. 1st là tọa độ bắt đầu của path và 2nd là điểm cuối của path
  • Nếu offset bằng length thì điểm bắt đầu vẽ là từ 2nd đến điểm cuối 2nd nên ta sẽ không thấy gì trên màn hình
  • Nếu offset bằng 0 thì điểm bắt đầu vẽ là từ 1st đến điểm cuối 2nd nên ta sẽ thấy 1 line từ 1st đến 2nd
  • Tiếp theo để animate ta sẽ giảm offset từ length về 0.
private fun animateWin() {
        val valueAnimator = ValueAnimator.ofFloat(1f, 0f)
        valueAnimator.duration = 600
        valueAnimator.addUpdateListener(this)
        valueAnimator.start()
    }
    
override fun onAnimationUpdate(animation: ValueAnimator) {
        val measure = PathMeasure(path, false)
        val offset = (measure.length * (animation.animatedValue as Float))
        paint.pathEffect = createPathEffect(measure.length, offset)
        invalidate()
    }

    private fun createPathEffect(pathLength: Float, offset: Float): PathEffect {
        return DashPathEffect(floatArrayOf(pathLength, pathLength),
                offset)
    }
  • Trong đoạn code trên onAnimationUpdate() được gọi nhiều lần và mõi lần gọi ta sẽ giảm offset sau đó tạo lại PathEffect với offset mới và gọi hàm invalidate để view vẽ lại

nguồn

0