Android: Sự khác biệt giữa onInterceptTouchEvent và ClarkTouchEvent?


249

Sự khác biệt giữa onInterceptTouchEventdispatchTouchEventtrong Android là gì?

Theo hướng dẫn dành cho nhà phát triển Android, cả hai phương pháp đều có thể được sử dụng để chặn sự kiện chạm ( MotionEvent), nhưng sự khác biệt là gì?

Làm thế nào để onInterceptTouchEvent, dispatchTouchEventonTouchEventtương tác với nhau trong một hệ thống các lần xem ( ViewGroup)?

Câu trả lời:


272

Nơi tốt nhất để làm sáng tỏ điều này là mã nguồn. Các tài liệu rất thiếu sót về việc giải thích điều này.

ClarkTouchEvent thực sự được định nghĩa trên Activity, View và Viewgroup. Hãy nghĩ về nó như một bộ điều khiển quyết định cách định tuyến các sự kiện chạm.

Ví dụ: trường hợp đơn giản nhất là View.dispatchTouchEvent sẽ định tuyến sự kiện chạm tới OnTouchListener.onTouch nếu được xác định hoặc theo phương thức mở rộng onTouchEvent .

Đối với Viewgroup.dispatchTouchEvent, mọi thứ phức tạp hơn nhiều. Nó cần phải tìm ra một trong những khung nhìn con của nó sẽ nhận được sự kiện (bằng cách gọi child.dispatchTouchEvent). Về cơ bản, đây là một thuật toán thử nghiệm thành công trong đó bạn tìm ra hình chữ nhật giới hạn của khung nhìn con nào chứa tọa độ điểm chạm.

Nhưng trước khi nó có thể gửi sự kiện đến chế độ xem con thích hợp, phụ huynh có thể theo dõi và / hoặc chặn tất cả sự kiện lại với nhau. Đây là những gì onInterceptTouchEvent dành cho. Vì vậy, nó gọi phương thức này trước khi thực hiện kiểm tra lần truy cập và nếu sự kiện bị tấn công (bằng cách trả về đúng từ onInterceptTouchEvent), nó sẽ gửi ACTION_CANCEL cho các chế độ xem con để chúng có thể từ bỏ xử lý sự kiện chạm của chúng (từ các sự kiện chạm trước đó) và từ đó trở đi tất cả các sự kiện chạm ở cấp độ cha được gửi đến onTouchListener.onTouch (nếu được xác định) hoặc onTouchEvent (). Cũng trong trường hợp đó, onInterceptTouchEvent không bao giờ được gọi lại.

Bạn thậm chí có muốn ghi đè [Hoạt động | Viewgroup | Xem] .dispatchTouchEvent không? Trừ khi bạn đang thực hiện một số định tuyến tùy chỉnh, bạn có thể không nên.

Các phương thức mở rộng chính là Viewgroup.onInterceptTouchEvent nếu bạn muốn theo dõi và / hoặc chặn sự kiện chạm ở cấp độ cha mẹ và View.onTouchListener / View.onTouchEvent để xử lý sự kiện chính.

Tất cả trong tất cả các thiết kế imo quá phức tạp của nó, nhưng apis android nghiêng về sự linh hoạt hơn là sự đơn giản.


10
Đây là một câu trả lời tuyệt vời, súc tích. Để biết ví dụ chi tiết hơn, hãy xem chương trình đào tạo "Quản lý sự kiện chạm trong nhóm xem"
TalkLittle

1
@numan salati: "từ đó trở đi tất cả các sự kiện chạm ở cấp độ cha được gửi đến onTouchListener.onTouch" - Tôi muốn ghi đè phương thức cảm ứng trong nhóm xem của mình và làm cho nó gửi các sự kiện tới onTouchListener. Nhưng tôi không thấy làm thế nào nó có thể được thực hiện. Không có api cho điều đó như View.getOnTouchListener (). OnTouch (). Có một phương thức setOnTouchListener () nhưng không có phương thức getOnTouchListener (). Làm thế nào nó có thể được thực hiện sau đó?
Ashwin

@Ashwin suy nghĩ của tôi chính xác, cũng không có setOnInterceptTouchEvent. Bạn có thể ghi đè chế độ xem lớp con để sử dụng trong bố cục / thêm mã vào mã, nhưng bạn không thể loay hoay với rootView cấp độ phân đoạn / hoạt động, vì bạn không thể phân lớp các chế độ xem đó mà không phân lớp chính phân đoạn / hoạt động (như trong hầu hết khả năng tương thích Triển khai ProgressActivitiy). API cần một setOnInterceptTouchEvent để đơn giản. Mọi người đều sử dụng interceptTouch trên rootViews tại một số điểm trong Ứng dụng bán phức tạp
leRobot

244

Bởi vì đây là kết quả đầu tiên trên Google. Tôi muốn chia sẻ với bạn một bài nói chuyện tuyệt vời của Dave Smith trên Youtube: Làm chủ hệ thống cảm ứng Android và các slide có sẵn ở đây . Nó đã cho tôi một sự hiểu biết sâu sắc về Hệ thống cảm ứng Android:

Cách hoạt động xử lý cảm ứng:

  • Activity.dispatchTouchEvent()
    • Luôn luôn được gọi đầu tiên
    • Gửi sự kiện đến chế độ xem gốc được đính kèm với Window
    • onTouchEvent()
      • Được gọi nếu không có lượt xem tiêu thụ sự kiện
      • Luôn luôn được gọi là cuối cùng

Cách xử lý của View chạm:

  • View.dispatchTouchEvent()
    • Gửi sự kiện đến người nghe trước, nếu có
      • View.OnTouchListener.onTouch()
    • Nếu không tiêu thụ, xử lý cảm ứng
      • View.onTouchEvent()

Cách một Viewgroup xử lý cảm ứng:

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • Kiểm tra xem có nên thay thế trẻ em không
      • Truyền ACTION_CANCEL cho trẻ năng động
      • Nếu nó trả về đúng một lần, thì ViewGrouptiêu thụ tất cả các sự kiện tiếp theo
    • Đối với mỗi chế độ xem con (theo thứ tự ngược lại, chúng đã được thêm vào)
      • Nếu cảm ứng có liên quan (xem bên trong), child.dispatchTouchEvent()
      • Nếu nó không được xử lý bởi trước đó, gửi đến chế độ xem tiếp theo
    • Nếu không có trẻ em xử lý sự kiện, người nghe có cơ hội
      • OnTouchListener.onTouch()
    • Nếu không có người nghe, hoặc nó không được xử lý
      • onTouchEvent()
  • Các sự kiện bị chặn nhảy qua bước con

Ông cũng cung cấp mã ví dụ về cảm ứng tùy chỉnh trên github.com/devunwired/ .

Trả lời: Về cơ bản dispatchTouchEvent()được gọi trên mỗi Viewlớp để xác định xem a Viewcó hứng thú với một cử chỉ đang diễn ra không. Trong một ViewGroupsự ViewGroupcó khả năng đánh cắp những sự kiện liên lạc trong mình dispatchTouchEvent()-method, trước khi nó sẽ gọi dispatchTouchEvent()trên trẻ em. Điều ViewGroupnày sẽ chỉ dừng việc gửi đi nếu ViewGroup onInterceptTouchEvent()-method trả về đúng. Sự khác biệt là việc dispatchTouchEvent()gửi đi MotionEventsonInterceptTouchEventcho biết liệu nó có nên chặn (không gửi MotionEventcho trẻ em) hay không (gửi cho trẻ em) .

Bạn có thể tưởng tượng mã của Viewgroup thực hiện nhiều hơn hoặc ít hơn (rất đơn giản):

public boolean dispatchTouchEvent(MotionEvent ev) {
    if(!onInterceptTouchEvent()){
        for(View child : children){
            if(child.dispatchTouchEvent(ev))
                return true;
        }
    }
    return super.dispatchTouchEvent(ev);
}

64

Bổ sung trả lời

Dưới đây là một số bổ sung trực quan cho các câu trả lời khác. Câu trả lời đầy đủ của tôi là ở đây .

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

Các dispatchTouchEvent()phương pháp của một ViewGroupsử dụng onInterceptTouchEvent()để lựa chọn cho dù đó nên xử lý ngay lập tức sự kiện liên lạc (với onTouchEvent()) hoặc tiếp tục thông báo các dispatchTouchEvent()phương pháp của con của nó.


OnInterceptTouchEvent có thể được gọi trong hoạt động không ?? Tôi nghĩ rằng chỉ có thể có trong Viewgroup hoặc tôi sai? @Suragch
Federico Rizzo

@FedericoRizzo, bạn nói đúng! Cảm ơn rât nhiều! Tôi cập nhật sơ đồ và câu trả lời của tôi.
Suragch

20

Có rất nhiều nhầm lẫn về các phương pháp này, nhưng nó thực sự không phức tạp. Hầu hết sự nhầm lẫn là vì:

  1. Nếu con của bạn View/ViewGrouphoặc bất kỳ đứa con nào của nó không trở lại đúng onTouchEvent, dispatchTouchEventonInterceptTouchEventCHỈ sẽ được gọi cho MotionEvent.ACTION_DOWN. Nếu không có sự thật onTouchEvent, chế độ xem chính sẽ cho rằng chế độ xem của bạn không cần MotionEvents.
  2. Khi không ai trong số các con của Viewgroup trả về true trong onTouchEvent, onInterceptTouchEvent sẽ CHỈ được gọi MotionEvent.ACTION_DOWN, ngay cả khi Viewgroup của bạn trả về đúng onTouchEvent.

Trình tự xử lý là như thế này:

  1. dispatchTouchEvent được gọi là.
  2. onInterceptTouchEventđược gọi cho MotionEvent.ACTION_DOWNhoặc khi bất kỳ phần tử con nào của Viewgroup trả về đúng onTouchEvent.
  3. onTouchEventlần đầu tiên được gọi là con của Viewgroup và khi không có con nào trả về đúng thì nó được gọi trên View/ViewGroup.

Nếu bạn muốn xem trước TouchEvents/MotionEventsmà không vô hiệu hóa các sự kiện trên con bạn, bạn phải làm hai việc:

  1. Ghi đè dispatchTouchEventđể xem trước sự kiện và trở lại super.dispatchTouchEvent(ev);
  2. Ghi đè onTouchEventvà trả về true, nếu không, bạn sẽ không nhận được bất kỳ MotionEvent ngoại trừ nào MotionEvent.ACTION_DOWN.

Nếu bạn muốn phát hiện một số cử chỉ như sự kiện vuốt, mà không vô hiệu hóa các sự kiện khác trên con bạn miễn là bạn không phát hiện ra cử chỉ đó, bạn có thể làm như thế này:

  1. Xem trước MotionEvents như được mô tả ở trên và đặt cờ khi bạn phát hiện cử chỉ của mình.
  2. Trả lại đúng onInterceptTouchEventkhi cờ của bạn được đặt để hủy xử lý MotionEvent bởi con bạn. Đây cũng là một nơi thuận tiện để đặt lại cờ của bạn, bởi vì onInterceptTouchEvent sẽ không được gọi lại cho đến lần tiếp theo MotionEvent.ACTION_DOWN.

Ví dụ về ghi đè trong một FrameLayout(ví dụ của tôi là C # khi tôi lập trình với Xamarin Android, nhưng logic giống nhau trong Java):

public override bool DispatchTouchEvent(MotionEvent e)
{
    // Preview the touch event to detect a swipe:
    switch (e.ActionMasked)
    {
        case MotionEventActions.Down:
            _processingSwipe = false;
            _touchStartPosition = e.RawX;
            break;
        case MotionEventActions.Move:
            if (!_processingSwipe)
            {
                float move = e.RawX - _touchStartPosition;
                if (move >= _swipeSize)
                {
                    _processingSwipe = true;
                    _cancelChildren = true;
                    ProcessSwipe();
                }
            }
            break;
    }
    return base.DispatchTouchEvent(e);
}

public override bool OnTouchEvent(MotionEvent e)
{
    // To make sure to receive touch events, tell parent we are handling them:
    return true;
}

public override bool OnInterceptTouchEvent(MotionEvent e)
{
    // Cancel all children when processing a swipe:
    if (_cancelChildren)
    {
        // Reset cancel flag here, as OnInterceptTouchEvent won't be called until the next MotionEventActions.Down:
        _cancelChildren = false;
        return true;
    }
    return false;
}

3
Tôi không biết tại sao điều này không có nhiều upvote. Đó là một câu trả lời tốt (IMO) và tôi thấy nó rất hữu ích.
Đánh dấu Ormesher

8

Tôi đã đi qua lời giải thích rất trực quan tại trang web này http://doandroids.com/bloss/tag/codeexample/ . Lấy từ đó:

  • boolean onTouchEvent (MotionEvent ev) - được gọi bất cứ khi nào một sự kiện chạm với Chế độ xem này làm mục tiêu được phát hiện
  • boolean onInterceptTouchEvent (MotionEvent ev) - được gọi bất cứ khi nào một sự kiện chạm được phát hiện với Viewgroup này hoặc một đứa con của nó làm mục tiêu. Nếu chức năng này trả về đúng, MotionEvent sẽ bị chặn, có nghĩa là nó sẽ không được truyền cho trẻ mà thay vào đó là onTouchEvent của Chế độ xem này.

2
câu hỏi là về onInterceptTouchEvent và ClarkTouchEvent. Cả hai đều gọi trước onTouchEvent. Nhưng trong ví dụ đó, bạn không thể thấy công văn.
Dayerman

8

ClarkTouchEvent xử lý trước khi onInterceptTouchEvent.

Sử dụng ví dụ đơn giản này:

   main = new LinearLayout(this){
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            System.out.println("Event - onInterceptTouchEvent");
            return super.onInterceptTouchEvent(ev);
            //return false; //event get propagated
        }
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            System.out.println("Event - dispatchTouchEvent");
            return super.dispatchTouchEvent(ev);
            //return false; //event DONT get propagated
        }
    };

    main.setBackgroundColor(Color.GRAY);
    main.setLayoutParams(new LinearLayout.LayoutParams(320,480));    


    viewA = new EditText(this);
    viewA.setBackgroundColor(Color.YELLOW);
    viewA.setTextColor(Color.BLACK);
    viewA.setTextSize(16);
    viewA.setLayoutParams(new LinearLayout.LayoutParams(320,80));
    main.addView(viewA);

    setContentView(main);

Bạn có thể thấy rằng nhật ký sẽ giống như:

I/System.out(25900): Event - dispatchTouchEvent
I/System.out(25900): Event - onInterceptTouchEvent

Vì vậy, trong trường hợp bạn đang làm việc với 2 trình xử lý này, hãy sử dụng ClarkTouchEvent để xử lý sự kiện đầu tiên, sự kiện này sẽ được chuyển đến onInterceptTouchEvent.

Một sự khác biệt nữa là nếu ClarkTouchEvent trả về 'false' thì sự kiện không được truyền cho trẻ, trong trường hợp này là EditText, trong khi nếu bạn trả về false trong onInterceptTouchEvent thì sự kiện vẫn được gửi đến EditText


5

Câu trả lời ngắn: dispatchTouchEvent() sẽ được gọi đầu tiên .

Lời khuyên ngắn gọn: không nên ghi đè dispatchTouchEvent()vì khó kiểm soát, đôi khi nó có thể làm chậm hiệu suất của bạn. IMHO, tôi đề nghị ghi đè onInterceptTouchEvent().


Bởi vì hầu hết các câu trả lời đề cập khá rõ ràng về sự kiện chạm dòng trên hoạt động / nhóm xem / chế độ xem, tôi chỉ thêm chi tiết về mã trên các phương thức này trong ViewGroup(bỏ qua dispatchTouchEvent()):

onInterceptTouchEvent()sẽ được gọi đầu tiên, sự kiện ACTION sẽ được gọi tương ứng xuống -> di chuyển -> lên. Có 2 trường hợp:

  1. Nếu bạn trả về false trong 3 trường hợp (ACTION_DOWN, ACTION_MOVE, ACTION_UP), nó sẽ xem xét vì phụ huynh sẽ không cần sự kiện chạm này , vì vậy onTouch()cha mẹ không bao giờ gọi , nhưng onTouch()thay vào đó , trẻ em sẽ gọi ; tuy nhiên xin lưu ý:

    • Các onInterceptTouchEvent()vẫn tiếp tục nhận được sự kiện liên lạc, miễn là con của nó không gọi requestDisallowInterceptTouchEvent(true).
    • Nếu không có trẻ em nhận sự kiện đó (có thể xảy ra trong 2 trường hợp: không có trẻ em ở vị trí mà người dùng chạm vào, hoặc có trẻ em nhưng nó trả về sai tại ACTION_DOWN), cha mẹ sẽ gửi lại sự kiện đó cho onTouch()cha mẹ.
  2. Ngược lại, nếu bạn trở về đúng , phụ huynh sẽ đánh cắp sự kiện chạm này ngay lập tức và onInterceptTouchEvent()sẽ dừng ngay lập tức, thay vì onTouch()cha mẹ sẽ được gọi cũng như tất cả onTouch()trẻ em sẽ nhận được sự kiện hành động cuối cùng - ACTION_CANCEL (có nghĩa là cha mẹ lấy trộm sự kiện và trẻ em không thể xử lý nó từ đó trở đi). Luồng onInterceptTouchEvent()trả về false là bình thường, nhưng có một chút nhầm lẫn với trường hợp trả về true, vì vậy tôi liệt kê nó ở đây:

    • Quay trở lại đúng lúc ACTION_DOWN, onTouch()của các bậc phụ huynh sẽ nhận được ACTION_DOWN một lần nữa và hành động sau đây (ACTION_MOVE, ACTION_UP).
    • Trả về true tại ACTION_MOVE, onTouch()cha mẹ sẽ nhận được ACTION_MOVE tiếp theo (không phải cùng ACTION_MOVE in onInterceptTouchEvent()) và các hành động sau (ACTION_MOVE, ACTION_UP).
    • Trả lại đúng tại ACTION_UP, onTouch()cha mẹ sẽ KHÔNG gọi tất cả vì quá muộn để cha mẹ đánh cắp sự kiện chạm.

Một điều quan trọng nữa là ACTION_DOWN của sự kiện onTouch()sẽ xác định xem chế độ xem có muốn nhận thêm hành động từ sự kiện đó hay không. Nếu chế độ xem trả về đúng tại ACTION_DOWN onTouch(), điều đó có nghĩa là chế độ xem sẵn sàng nhận thêm hành động từ sự kiện đó. Mặt khác, trả về false tại ACTION_DOWN trong onTouch()sẽ ngụ ý rằng chế độ xem sẽ không nhận thêm bất kỳ hành động nào từ sự kiện đó.



3

Đoạn mã sau trong lớp con Viewgroup sẽ ngăn các bộ chứa mẹ của nó nhận các sự kiện chạm:

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    // Normal event dispatch to this container's children, ignore the return value
    super.dispatchTouchEvent(ev);

    // Always consume the event so it is not dispatched further up the chain
    return true;
  }

Tôi đã sử dụng điều này với lớp phủ tùy chỉnh để ngăn chế độ xem nền phản ứng với các sự kiện chạm.


1

Sự khác biệt chính :

• Activity.dispatchTouchEvent (MotionEvent) - Điều này cho phép Activity của bạn chặn tất cả các sự kiện chạm trước khi chúng được gửi đến cửa sổ.
• Viewgroup.onInterceptTouchEvent (MotionEvent) - Điều này cho phép Viewgroup xem các sự kiện khi chúng được gửi đến Chế độ xem con.


1
Vâng, tôi biết câu trả lời này từ hướng dẫn dành cho nhà phát triển Android - tuy nhiên chưa rõ ràng. Phương thức ClarkTouchEvent cũng tồn tại đối với Viewgroup không chỉ cho một Hoạt động. Câu hỏi của tôi là, làm thế nào ba phương thức ClarkTouchEvent, onInterceptTouchEvent và onTouchEvent tương tác với nhau trong một hệ thống phân cấp của Viewgroup, ví dụ như RelativeLayouts.
Anne Droid

1

Viewgroup onInterceptTouchEvent()luôn là điểm khởi đầu cho ACTION_DOWNsự kiện là sự kiện đầu tiên xảy ra.

Nếu bạn muốn Viewgroup xử lý cử chỉ này, hãy trả về true từ onInterceptTouchEvent(). Khi trở thành sự thật, ViewGroup của onTouchEvent()sẽ nhận được tất cả các sự kiện tiếp theo đến tiếp theo ACTION_UPhoặc ACTION_CANCEL, và trong hầu hết các trường hợp, các sự kiện liên lạc giữa ACTION_DOWNACTION_UPhoặc ACTION_CANCELACTION_MOVE, mà thông thường sẽ được công nhận là di chuyển / quăng ra cử chỉ.

Nếu bạn trả về false từ onInterceptTouchEvent(), khung nhìn đích onTouchEvent()sẽ được gọi. Nó sẽ được lặp lại cho các tin nhắn tiếp theo cho đến khi bạn trở về đúng onInterceptTouchEvent().

Nguồn: http://neevek.net/posts/2013/10/13/im Hiệning-onInterceptTouchEvent-and-onTouchEvent-for-Viewgroup.html


0

Cả Activity và View đều có phương thức ClarkTouchEvent () và onTouchEvent. Viewgroup cũng có phương thức này, nhưng có một phương thức khác gọi là onInterceptTouchEvent. Kiểu trả về của các phương thức đó là boolean, bạn có thể điều khiển tuyến gửi qua giá trị trả về.

Công văn sự kiện trong Android bắt đầu từ Activity-> Viewgroup-> View.


0
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume =false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
}

1
Bạn có thể thêm một số lời giải thích?
Paul Floyd

3
Mặc dù đoạn mã này có thể giải quyết câu hỏi, bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn.
Rosário Pereira Fernandes

-2

Câu trả lời nhỏ:

onInterceptTouchEvent xuất hiện trước setOnTouchListener.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.