android源码分析——从button的点击事件看回调机制

目标

在Android中到处可见接口回调机制,尤其是UI事件处理方面。熟悉回调机制之后,我们就可以利用这个机制为自定义组件创建我们自己的事件监听接口和回调方法。

例子

举一个最常见的例子button点击事件,我们通常调用button.setOnClickListener(View.OnClickListener)并在接口View.OnClickListener中实现方法onClick(),我们知道onclick()是一个回调方法,当用户点击button时,系统框架就执行这个方法。

下面我贴出了为button注册click监听事件的代码:

public class SSSS extends Activity {  

 private Button button;  
 private OnClickListener clickListener = new OnClickListener() {  

      @Override  
      public void onClick(View v) {  
           // TODO Auto-generated method stub  

      }  
 };  

 @Override  
 protected void onCreate(Bundle savedInstanceState) {  
      // TODO Auto-generated method stub  
      super.onCreate(savedInstanceState);  
      button = (Button)findViewById(R.id.button1);  
      button.setOnClickListener(clickListener);    
 }  

}

onclick()回调过程

下面我们分析一下当button被点击时,系统framework找到onclick()回调方法并执行的。

首先,我们得知道button.setOnClickListener()方法其实继承自View超类,相关类图如下:
这里写图片描述

在View类中有如下源码:

public void setOnClickListener(OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

可见:接口OnClickListener的实现类的实例clickListener被赋值给了变量getListenerInfo().mOnClickListener。

在View类中还定义了内部接口OnClickListener,源码如下:

public interface OnClickListener {
    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    void onClick(View v);
}

此外,View类的中还定义了很多供系统framework调用的方法来响应屏幕触摸事件、键盘事件等等,例如这些方法:

public boolean onTouchEvent(MotionEvent event){}
 public boolean onKeyDown(int keyCode, KeyEvent event){}

由于button的点击属于屏幕触摸事件,所以我们来分析下 onTouchEvent()的源码:

public boolean onTouchEvent(MotionEvent event) {
    final int viewFlags = mViewFlags;

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }

    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // take focus if we don't have it already and we should in
                    // touch mode.
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }

                    if (prepressed) {
                        // The button is being released before we actually
                        // showed it as pressed.  Make it show the pressed
                        // state now (before scheduling the click) to ensure
                        // the user sees it.
                        setPressed(true);
                   }

                    if (!mHasPerformedLongPress) {
                        // This is a tap, so remove the longpress check
                        removeLongPressCallback();

                        // Only perform take click actions if we were in the pressed state
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
        ··············· 
                ···············
                break;

            case MotionEvent.ACTION_DOWN:
              ···········
          ···········
                break;

            case MotionEvent.ACTION_CANCEL:
                ·······················
                break;

            case MotionEvent.ACTION_MOVE:
                ·····················
                ·····················
                break;
        }
        return true;
    }

    return false;
}

我们知道,一般在用户松开button时,button的响应才开始执行,从以上源码的case MotionEvent.ACTION_UP代码片段中我们能够看到如下代码片段:

 if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();

可以推测,button的click响应应该在 performClick()方法中执行。 performClick()方法也View类中定义,源码如下:

public boolean performClick() {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        return true;
    }

    return false;
}

前面我们提到我们自定义的OnClickListener的实现类的实例变量clickListener被赋值给了变量mOnClickListener,因此上面代码片段中的

li.mOnClickListener.onClick(this);

实际上是调用的是

clickListener.onClick(this);

也就是接口OnClickListener中的onClick()方法:

private OnClickListener clickListener = new OnClickListener() {  

      @Override  
      public void onClick(View v) {  
           // TODO Auto-generated method stub  

      }  
 };  

总结

button按钮的点击事件回调过程可以简单地用如下流程表示:
这里写图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android应用中,我们经常需要为多个按钮添加点击事件监听器。如果每个按钮都有一个单独的监听器,这样会让我们的代码显得非常冗长。在这种情况下,我们可以使用单个监听器来监听多个按钮的点击事件。 要实现此功能,我们可以在Activity中实现OnClickListener接口,并在onClick方法中根据被点击的按钮来执行相应的操作。具体步骤如下: 1.为所有需要添加监听器的按钮设置相同的id,例如: ``` <Button android:id="@+id/btn1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button 1" /> <Button android:id="@+id/btn2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button 2" /> ``` 2.在Activity中实现OnClickListener接口,并在onClick方法中根据被点击的按钮来执行相应的操作,例如: ``` public class MainActivity extends AppCompatActivity implements View.OnClickListener{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 为所有需要添加监听器的按钮设置相同的id Button btn1 = findViewById(R.id.btn1); Button btn2 = findViewById(R.id.btn2); // 为所有按钮添加同一个监听器 btn1.setOnClickListener(this); btn2.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn1: // 处理按钮1的点击事件 break; case R.id.btn2: // 处理按钮2的点击事件 break; default: break; } } } ``` 通过这种方式,我们可以使用单个监听器来监听多个按钮的点击事件,代码更加简洁和易于维护。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值