SVG

Sử dụng svg và animation render view trong react native

Trong bài viết lần trước nói về animation, mình có hứa bên dưới sẽ chia sẻ thêm về các hàm (Sequence , parallel...).

Tuy nhiên, mình lại có 1 ý tưởng hay ho hơn, vừa có thể chia sẻ tiếp về animation lại vừa giới thiệu một "chiêu trò" như này này nè =))) :

Sorry, đoạn gif trên mình đang k cut được 2s đầu 😦. Thông cảm cho mình nhé!

Nào, cũng thú vị chứ ạ?

Bài viết này mình sẽ chia sẻ về cách sử dụng svg và animation để làm một demo như vậy ạ!

Trước tiên, mình cùng tìm hiểu svg là gì nhé!

1. react-native-svg

Svg là một dạng định dạng ảnh sử dụng cấu trúc XML để hiển thị hình ảnh dưới dạng vector, vì vậy có thể co giãn thoả mái mà không làm giảm chất lượng hình ảnh.

Còn react-native-svg là một lib cung cấp hỗ trợ svg cho React Native trên Android và IOS. Về cách cài đặt về sử dụng, bạn có thể tham khảo ở link đầu mục.

2. Path trong react-native-svg

Để vẽ line có nhiều elements như: Line, Polyline,Path...

Trong bài này mình sẽ dùng Path để vẽ các đường cong từ hai điểm cho trước.

Lí do mình lựa chọn Path vì:

  • Line chỉ giúp tạo đường thẳng từ hai điểm cho trước
  • Polyline thì tạo hình dựa vào các đường thẳng
  • Path có thể tạo được đường thẳng, đường cong, tạo hình... từ các điểm (đúng ý mình rồi 😃)) ).

Cùng mình tìm hiểu thêm về cách sử dụng Path nhé!

a. Một số lệnh cần biết

  • M = moveto
  • L = lineto
  • H = horizontal lineto
  • V = vertical lineto
  • C = curveto
  • S = smooth curveto
  • Q = quadratic Bézier curve
  • T = smooth quadratic Bézier curveto
  • A = elliptical Arc
  • Z = closepath

Các dòng lệnh trên có thể sử dụng được cả viết hoa và viết thường. Tuy nhiên, chữ viết hoa có ý nghĩa là vị trí tuyệt đối còn chữ thường là vị trí tương đối.

Ví dụ : // line with 2 point (100,100) and (200,300)

const { width, height } = Dimensions.get('window');
return 
<Svg 
    width={width} 
    height={height} 
    style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
            <Path
              d={"M100 100 L200 300"} 
              stroke={"green"}
              strokeWidth={6}
              fill="none"
            />
 </Svg>

Vậy để tạo đường cong thì sao?

b. Sử dụng 'A' và 'a' để tạo đường cong

  • A (absolute) a (relative)
  • Đường cong được tạo ra dựa vào các cung tròn ( các phần của hình tròn hoặc hình eclipse ).
  • Sử dụng:
Lx1 y1 A rx ry x-axis-rotation large-arc-flag sweep-flag x y
Lx1 y1 a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy

Trong đó :

  1. Đối với A hay a các tham số :
  • x1, y1: điểm bắt đầu
  • rx, ry lần lượt là 2 bán kính của hình eclipse.
  • x-axis-rotation là góc quay của hình eclipse.
  • large-arc-flag là lựa chọn cung lớn (1) hay nhỏ (0).
  • sweep-flag là chiều quay ( 1 : cùng chiều kim đồng hồ và 0 là ngược lại).
  1. x ydx dy
  • Đối với A điểm cuối cùng sẽ là (x,y)
  • Đối với a, điểm cuối của cung sẽ tính bằng (x1 + dx, y1 + dy).

Để hiểu rõ hơn cách sử dụng A hay a cũng như param truyền vào. Xét hai ví dụ sau:

Ví dụ 1:

// using 'a' get line with 2 point (350,400) and (50,300) with radius 40

const { width, height } = Dimensions.get('window');
return 
<Svg 
    width={width} 
    height={height} 
    style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
             <Path
              d={"M350,400 L350 340 a 40,40 1 0 0 -40,-40 L50 300"}
              stroke={"green"}
              strokeWidth={6}
              fill="none" />
 </Svg>

Ví dụ 2:

// using 'A' get line with 2 point (350,400) and (50,300) with radius 40

const { width, height } = Dimensions.get('window');
return 
<Svg 
    width={width} 
    height={height} 
    style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
              <Path
              d={"M350,400 L350 340 A 40,40 1 0 0 310,300 L50 300"}
              stroke={"green"}
              strokeWidth={6}
              fill="none" />
 </Svg>

Và đây là kết quả của cả 2 ví dụ:

Giải thích : Trước hết, các bạn xem qua hình:

Hmm, thử ngẫm xem sao lại ra vậy nha 😄!

Và từ những kiến thức trên, bạn có thể vẽ được các hình như trên demo ví dụ đầu bài đấy 😄!

3. Vẽ line như video demo

Trước hết là data nhé:

Mình chỉ truyền vào 2 điểm và kiểu vẽ nó sẽ giúp mình vẽ được line.

const data = [
  {
    point1: { x: 350, y: 400 },
    point2: { x: 50, y: 300 },
    options: {
      type: 'Left', // giống hệt ví dụ trên nè 
    },
  },
  {
    point1: { x: 50, y: 300 },
    point2: { x: 300, y: 100 },
    options: {
      type: 'BoundedRight',
      // hơi khác một chút, hãy thử viết xem sao nhé =))
    },
  },
  {
    point1: { x: 50, y: 300 },
    point2: { x: 50, y: 100 },
    options: {
      type: 'BoundedLeft',
       // hơi khác một chút, hãy thử viết xem sao nhé =))
      distance: 125,  
      // mình thêm vào để lượn được đẹp hơn =)) 
    },
  },
];

Đây là cách viết của mình vẽ 3 type trên nhé!

Rất mong tham khảo thêm nhiều cách viết khác của bạn hơn ở dưới cmt ạ 😄!

4. Animation

Như bài trước mình đã chia sẻ và animation, bài này mình sẽ sử dụng sequence để chạy lần lượt từng line.

Đầu tiên mình khởi tạo 1 array chứa animatedValue

this.animatedValue = []
    data.map(() => {
      this.animatedValue.push(new Animated.Value(800))
    })

Đây là phần createAnimation và createTree // tạo line từ data và ghép animation

let path = require('svg-path-properties');

createAllTree() {
    const { data } = this.state
    data.map((item, index) => {
      const { options, point1, point2 } = item
      item.ref = index
      const d = getLine(point1, point2, { ...options, ...{ radius: 40 } })
      // hàm getLine từ 2 point vẽ line
      item.d = d
      const properties = path.svgPathProperties(d)
      const tl = properties.getTotalLength()
      item.tl = tl
    })
    this.setState({ data },
      () => {
        this.startAnimation(this.createAnimation())
      })
  }

  createAnimation() {
    const { data } = this.state
    return animations = data.map((item, index) => {
      this.animatedValue[index].addListener(dashOffset => {
        this.refs[index].setNativeProps({
          strokeDashoffset: dashOffset.value,
        });
      });
      return Animated.timing(
        this.animatedValue[index],
        {
          toValue: item.tl,
          duration: 300,
          useNativeDriver: true,
        }
      )
    })
  }

  startAnimation(animations) {
    Animated.sequence(animations).start(() => {
    })
  }

ở phần render cũng có thay đổi 1 chút:

  {data.map((item, index) => {
              console.log({ item })
              return (
                <Path
                  key={index}
                  ref={index}
                  d={item.d}
                  strokeDasharray={[800, 800]}
                  strokeLinecap="round"
                  strokeDashoffset={799}
                  stroke={item.options.color || "green"}
                  strokeWidth={6}
                  fill="none" />
              )
            })}

Done! Thế là đã giống với video đầu bài rồi 😄!

Đây là project chi tiết bạn có thể tham khảo để xem full code.

Hãy tham khảo code và tự tạo một project nhé 😃!

Cảm ơn các bạn đã đọc rất nhiều và rất mong nhận được nhiều sự chia sẻ của các bạn dưới cmt ạ!

Registration Login
Sign in with social account
or
Lost your Password?
Registration Login
Sign in with social account
or
A password will be send on your post
Registration Login
Registration