Android View.setOnclickListener(),View.onTouchEvent(),View.setOnTouchListener()关系源码分析

本文分析了Android中View的onclick、ontouch以及setOnTouchListener三者的关系。通过源码解析,揭示了点击事件在ACTION_DOWN和ACTION_UP中的处理,以及事件分发流程,从ViewRoot到DecorView,再到ViewGroup和View的dispatchTouchEvent方法。讨论了事件拦截和消费的过程,解释了为何OnClickListener只触发一次,而其他事件可能触发两次的情况。

同样是在上一篇文章中代码,我们给Acitvity中的mView添加一个监听器:

myView.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
	// TODO Auto-generated method stub
	Log.v(MyLinearLayout.Tag, "OnClickListener:onClick");
	Toast.makeText(MainActivity.this, "onClidk", 1).show();
	}
});
		  

当我们在单击mView时,发现没有Toast,通过测试代码发现:
这里写图片描述
OnClickListener:onClick在测试中也没有发现!
在这里我们就要思考了,明明为mView设置了监听器,为什么没有起到监听的作用呢???
然后再让大家看一个程序:现在把我们在MyView中重写 onTouchEvent注释掉,然后再运行程序
结果:
这里写图片描述

好了,现在我们发现OnClickListener:onClick出现了,而且Toast在界面上也出现了,这时候你想到了什么?

但此时,我们又发现了问题,为什么MainActicity:dispatchTouchEvent…OnTouchListener:onTouch;它们却出现了两次,而OnClickListener:onClick却出现了一次;
我们再从头分析一下,首先我们重写了MyView中的onTouchEvent(),我们发现 OnClickListener:onClick 不见了(也就是说没被调用);我们去掉重写的onTouchEvent(),此时OnClickListener:onClick 出现了(也就是说被调用啦);但又有个问题出现了,其他的都出现了两次,而OnClickListener:onClick 出现了一次。


现在让我们来分析为什么会OnClickListener:onClick出现一次,其他出现两次;
通过代码分析感觉有点小麻烦(还要附加代码),我们可以通过视觉的效果来观察出这个问题的出现。首先我们先通过手指触摸MyView(不要松手),通过观察LogCat我们可以发现:
这里写图片描述
然后把手松开,观察LogCat我们可以发现:
这里写图片描述
哈哈,你想到了什么?对,答案就如你所想。
首先我们先通过手指触摸MyView(不要松手),这是一个OnTouchEvent的ACTION_DOWN事件;手松开后,这是一个OnTouchEvent的ACTION_UP事件(在这里出现了OnClickListener:onClick)。


这两个问题联系到一起,你想到了什么? 对!答案正如你所想!(大胆去想,之后我们可以去验证);这两个问题联系到一起:在View.onTouchEvent()的ACTION_DOWN中消费了该事件;在ACTION_UP中我们调用了view.setOnclickListener()事件。

下面通过源码来分析一下onTouchEvent(event)函数到底做了什么吧。

/** 
 * Implement this method to handle touch screen motion events. 
 * 
 * @param event The motion event. 
 * @return True if the event was handled, false otherwise. 
 */
public boolean onTouchEvent(MotionEvent event) { 
  final int viewFlags = mViewFlags; 
  
  if ((viewFlags & ENABLED_MASK) == DISABLED)    // 1、判断该view是否enable 
    // 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)) // 2、是否是clickable或者long clickable 
    switch (event.getAction()) { 
      case MotionEvent.ACTION_UP:          // 抬起事件 
        boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; 
        if ((mPrivateFlags & 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 (!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))   // post 
                performClick();     // 3、点击事件处理 
              } 
            } 
          } 
  
          if (mUnsetPressedState == null) { 
            mUnsetPressedState = new UnsetPressedState(); 
          } 
  
          if (prepressed) { 
            mPrivateFlags |= PRESSED; 
            refreshDrawableState(); 
            postDelayed(mUnsetPressedState, 
                ViewConfiguration.getPressedStateDuration()); 
          } else if (!post(mUnsetPressedState)) { 
            // If the post failed, unpress right now 
            mUnsetPressedState.run(); 
          } 
          removeTapCallback(); 
        } 
        break; 
  
      case MotionEvent.ACTION_DOWN: 
        if (mPendingCheckForTap == null) { 
          mPendingCheckForTap = new CheckForTap(); 
        } 
        mPrivateFlags |= PREPRESSED; 
        mHasPerformedLongPress = false; 
        postDelayed(mPendingCheckForTap, Vi
package com.example.tapobulb import android.R.attr.startY import android.graphics.Color import android.graphics.drawable.ClipDrawable import android.os.Bundle import android.util.Log import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import com.example.tapobulb.databinding.ActivityMainBinding import com.google.android.material.bottomsheet.BottomSheetBehavior class MainActivity : AppCompatActivity() { private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) } private lateinit var lightBulb: ImageView private lateinit var percentText: TextView private lateinit var gestureDetector: GestureDetector private var startY = 0f // 用 float 更精确 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) lightBulb=binding.blubMaxInit percentText=binding.percentText var drawable = lightBulb.drawable as ClipDrawable var bulbHeight = 150 * resources.displayMetrics.density // 图片高度 // 手势检测器 gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { startY = e.y return true } override fun onScroll( e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float ): Boolean { val deltaY = startY - e2.y val percent = (deltaY / bulbHeight * 100).toInt().coerceIn(0..100) val level = percent * 100 // level 范围 0~10000 drawable.level = level percentText.text = "$percent%" startY = e2.y// 更新起始点,实现连续滑动 return true } }) // 给整个 FrameLayout 添加点击区域来接收触摸事件 // findViewById<FrameLayout>(android.R.id.content).setOnTouchListener { _, event -> // gestureDetector.onTouchEvent(event) // } val behavior = BottomSheetBehavior.from(binding.bottomSheet) Log.d("MainActivity", "BottomSheet ID: ${binding.bottomSheet.id}") behavior.peekHeight = 290 // 初始只显示图标部分 behavior.state = BottomSheetBehavior.STATE_COLLAPSED binding.blueButton.setOnClickListener { binding.maxLayout.backgroundTintList = ContextCompat.getColorStateList(this, R.color.blue) } binding.redButton.setOnClickListener { binding.maxLayout.backgroundTintList = ContextCompat.getColorStateList(this, R.color.red) } binding.orangeButton.setOnClickListener { binding.maxLayout.backgroundTintList = ContextCompat.getColorStateList(this, R.color.orange) } binding.deepblueButton.setOnClickListener { binding.maxLayout.backgroundTintList = ContextCompat.getColorStateList(this, R.color.deepblue) } binding.autoButton.setOnClickListener { binding.maxLayout.backgroundTintList = null } binding.whiteButton.setOnClickListener { binding.maxLayout.backgroundTintList = null } // 可选监听 behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { // 监听状态变化 when (newState) { BottomSheetBehavior.STATE_EXPANDED -> { // 菜单完全展开时,显示文字 binding.pullUp.setImageResource(R.drawable.push_down) } BottomSheetBehavior.STATE_DRAGGING -> { // 菜单折叠时,隐藏文字 binding.pullUp.setImageResource(R.drawable.push_down) } BottomSheetBehavior.STATE_COLLAPSED -> { binding.pullUp.setImageResource(R.drawable.pull) } } } override fun onSlide(bottomSheet: View, slideOffset: Float) { } }) } }app崩溃了
08-19
package com.example.tapobulb import android.R.attr.visibility import android.graphics.Outline import android.graphics.Path import android.graphics.drawable.ClipDrawable import android.os.Bundle import android.util.Log import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewOutlineProvider import android.widget.ImageView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModelProvider import com.example.tapobulb.databinding.ActivityMainBinding import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.button.MaterialButton import androidx.lifecycle.Observer class MainActivity : AppCompatActivity() { private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) } private val mainViewModel: MainViewModel by lazy { ViewModelProvider(this)[MainViewModel::class.java] } private lateinit var gestureDetector: GestureDetector private var startY = 0f private var totalDeltaY = 0f // private var i: Int = 0 private var switchState=true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) setupViewModelObservers() setupUI() } private fun setupViewModelObservers() { mainViewModel.selectedColorRes.observe(this) { colorRes -> colorRes?.let { binding.maxLayout.backgroundTintList = ContextCompat.getColorStateList(this, it) } } mainViewModel.brightnessPercent.observe(this) { percent -> val level = percent * 100 (binding.blubMaxInit.drawable as? ClipDrawable)?.level = level binding.percentText.text = "$percent%" binding.maskView.alpha = 0.4f * (1 - percent / 100f) } mainViewModel.showGuide.observe(this) { show -> if (show) { supportFragmentManager.beginTransaction() .add(android.R.id.content, GuideFragment(binding.orangeButton)) .addToBackStack(null) .commit() } } mainViewModel.isBulbOn.observe(this, Observer { isOn -> if (isOn) { binding.buttonGroup.visibility = View.VISIBLE binding.percentText.visibility = View.VISIBLE binding.blubMaxInit.visibility = View.VISIBLE binding.maskView.visibility = View.VISIBLE } else { binding.buttonGroup.visibility = View.GONE binding.percentText.visibility = View.GONE binding.blubMaxInit.visibility = View.GONE binding.maskView.visibility = View.GONE } }) } private fun setupUI() { // 设置圆角裁剪 val pullImageView = binding.pullUp pullImageView.clipToOutline = true pullImageView.outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { val cornerRadius = 80f val path = Path() val width = view.width.toFloat() val height = view.height.toFloat() path.moveTo(0f, height) path.lineTo(0f, cornerRadius) path.quadTo(width / 2, 0f, width, cornerRadius) path.lineTo(width, height) path.close() outline.setConvexPath(path) } } // 初始化灯泡高度 val bulbHeight = 150 * resources.displayMetrics.density Log.e("blub", "blubHeight=$bulbHeight") // 手势检测器 gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { startY = e.y Log.e("blub", "startY=$startY") return true } override fun onScroll( e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float ): Boolean { // i++ // Log.e("检测到滑动次数", "i=$i") totalDeltaY += distanceY val percent = (totalDeltaY / bulbHeight * 100).toInt() mainViewModel.updateBrightness(percent) return true } }) //TODO binding.switchOn.setOnClickListener { val isOn = switchState if (isOn) { // 当前是 on -> 想要关闭 mainViewModel.setBulbOn(false) binding.buttonGroup.visibility = View.INVISIBLE binding.percentText.visibility = View.INVISIBLE binding.blubMaxInit.visibility = View.INVISIBLE binding.maskView.visibility = View.INVISIBLE binding.off.visibility= View.VISIBLE binding.maxLayout.setBackgroundResource(R.drawable.bulb_close) binding.switchOn.setImageResource(R.drawable.switch_off) } else { // 当前是 off -> 想要打开 mainViewModel.setBulbOn(true) binding.buttonGroup.visibility = View.VISIBLE binding.percentText.visibility = View.VISIBLE binding.blubMaxInit.visibility = View.VISIBLE binding.maskView.visibility = View.VISIBLE binding.off.visibility= View.GONE binding.maxLayout.setBackgroundResource(R.drawable.bg_blue) binding.switchOn.setImageResource(R.drawable.switch_on) } switchState = !switchState } binding.bulbImage.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) } // 设置 BottomSheet val behavior = BottomSheetBehavior.from(binding.bottomSheet) behavior.peekHeight = 290 behavior.state = BottomSheetBehavior.STATE_COLLAPSED behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { when (newState) { BottomSheetBehavior.STATE_EXPANDED -> { binding.pullUp.setImageResource(R.drawable.push_down) } BottomSheetBehavior.STATE_COLLAPSED -> { binding.pullUp.setImageResource(R.drawable.pull) } } } override fun onSlide(bottomSheet: View, slideOffset: Float) { // 可选实现 } }) // 颜色按钮点击逻辑 val buttons = listOf<MaterialButton>( binding.blueButton, binding.redButton, binding.whiteButton, binding.orangeButton, binding.deepblueButton ) for (button in buttons) { button.setOnClickListener { for (btn in buttons) { btn.icon = null btn.isSelected = false } button.isSelected = !button.isSelected button.icon = getDrawable(R.drawable.color_edit) val colorRes = when (button.id) { R.id.blue_button -> R.color.blue R.id.red_button -> R.color.red R.id.orange_button -> R.color.orange R.id.deepblue_button -> R.color.deepblue R.id.auto_button, R.id.white_button -> R.color.white else -> return@setOnClickListener } mainViewModel.selectColor(colorRes) } } } } 为什么上面iobserve了还是得在下面进行listen
08-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值