Custom view trong android

View là một đối tượng được sử dụng để xây dựng giao diện cho ứng dụng android. Android đã xậy dựng một hệ thống View và các thư viện support rất mạnh, cung cấp cho các lập trình viên một hệ thống các component đa dạng cho việc phát triển ứng dụng. Không những vậy hiện nay các thư viện open source xậy dựng những đối tượng view hữu ích cũng xuất hiện rất nhiều. Tuy nhiên đôi khi chúng ta sẽ gặp những thành phần giao diện không có sẵn đòi hỏi chúng ta phải tự xây dựng dựa trên những view cơ bản mà android đã cung cấp cho chúng ta. Hôm nay chúng ta sẽ cùng tìm hiểu về việc custom một View trong android.

Để xây dựng một view do tự các bạn định nghĩa việc đầu tiên chúng ta cần kế thừa các đối tượng view có sẵ trong android như: View, LinearLayout, RelativeLayout, ListView, RecyclerView .... và thực hiện định nghĩa những phương thức cung cấp các thuộc tính cho View . Cụ thể chúng ta cần overide một số phương thức xác định kích thước, thuộc tính và phương thức vẽ của view. Sau đây là các bước thực hiện custom view

Custom các thuộc tính cho view

Chúng ta có thể tự định nghĩa các thuộc tính cho view bằng cách tạo ra một file attrs.xml và khai báo các thuộc tính các bạn muốn thêm cho view ở đây Ví dụ

<resources>
   <declare-styleable name="PieChart">
       <attr name="showText" format="boolean" />
       <attr name="labelPosition" format="enum">
           <enum name="left" value="0"/>
           <enum name="right" value="1"/>
       </attr>
   </declare-styleable>
</resources>

Thêm giá trị cho thuộc tính của view trong file layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews">
 <com.example.customviews.charting.PieChart
     custom:showText="true"
     custom:labelPosition="left" />
</LinearLayout>

Thực hiện đọc giá trị các thuộc tính được gán từ file layout.

public PieChart(Context context, AttributeSet attrs) {
   super(context, attrs);
   TypedArray a = context.getTheme().obtainStyledAttributes(
        attrs,
        R.styleable.PieChart,
        0, 0);

   try {
       mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
       mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
   } finally {
       a.recycle();
   }
}

Ở đây có một lưu ý đó là việc sử dụng TypeArray sau khi các bạn sử dụng xong cần phải gọi hàm recyle() để giải phóng bộ nhớ .

Custom Drawing

Sau khi các bạn đã có nhưng thuộc tính của view bước tiếp theo của việc custom view đó là chúng ta cần overide lại hàm onDraw() để thực hiện việc vẽ định nghĩa giao diện của view theo ý các bạn muốn. Tuy nhiên trước khi vẽ chúng ta cần xác định được kích thước của view bằng các override hàm onMeasure()

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mBitmap == null) {
            return;
        }

        int desiredWidth = mBitmap.getWidth();
        int desireHeight = mBitmap.getHeight();
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;

        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                width = widthSize;
                break;
            case MeasureSpec.AT_MOST:
                width = Math.min(desiredWidth, widthSize);
                break;
            default:
                width = desiredWidth;
                break;
        }

        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                height = heightSize;
                break;
            case MeasureSpec.AT_MOST:
                height = Math.min(desireHeight, heightSize);
                break;
            default:
                height = desireHeight;
                break;
        }
        setMeasuredDimension(width, height);
    }

Code trên la một đoạn code mình viết để xác định kích thước của một custom view, có một số constant mình sử dụng trong hàm này để xác định size của view:

  1. MeasureSpec.EXACTLY: Chó biết rằng width và height của view đã được set cho những giá trị cụ thể và chúng ta sẽ để size của view là size này
  2. MeasureSpec.AT_MOST: Có nghĩa là layout_width hoặc layout_height đã được gán cho giá trị là match_parent hoặc wrap_content.
  3. MeasureSpec.UNSPECIFIED: Cho biết layout_width hoặc layout_height đã được gán cho giá trị wrap_content và không được giới hạn . Trong trường họp này bạn nên định nghĩa một kích thước cho View.

Sau khi xác định được kích thước của view chúng ta sẽ thực viện vẽ giao diện cho view trên canvas bằng việc override hàm onDraw(). Có một số đối tượng chúng ta cần lưu ý khi vẽ:

  • Canvas: đối tượng này có cung cấp một số các hàm vẽ các hình khối đơn giản như đường thẳng, đoạn thẳng, hình vuông, hình tam giác, text, bitmap ... ở đây khi các bạn muốn vẽ một hình phức tạp buộc chúng ta phải xây dựng nó từ các hình khối đơn giản.
  • Paint: Nắm giữ các thuộc tính khi vẽ lên canvas như màu, độ rộng đường viền, kiểu vẽ, ....
  • Matrix: Một ma trận 3x3 quy định cách hiện thị và vị trí của View .
 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Matrix matrix = new Matrix();
        matrix.setRotate(mAngel, getWidth()/2, getHeight()/2);
        canvas.drawBitmap(mBitmap, matrix, null);
    }

đoạn code trên sẽ vẽ một bitmap xoay một góc quanh điểm giữa của view.

Thêm sự kiện cho view

Chúng ta thể custom sự kiện touch của view bằng việc override hàm onTouchEvent() và thực hiện định nghĩa sự kiện touch của riêng bạn. Ở đây có thể sự dụng GestureDetector giúp cho việc custom sự kiện touch trở nên dễ dàng hơn

   @Override
   public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
   }

code demo: https://github.com/Nghicv/rotate_button/blob/master/app/src/main/java/com/nghicv/rotatebutton/RotateButton.java