两种Android判断view是否部分被遮挡(或者移出屏幕)的方法

部分内容参考http://blog.youkuaiyun.com/peidonghui/article/details/49583263,在此对原作者表示感谢。


在某些需求下,需要判断某一个View是否被遮挡,比如ListView的headerView中某一个View在ListView向上滑动到开始不可见时,在页面顶部固定显示一个View。在刚开始时,处理方式是在ListView的onScroll()中判断该View距离屏幕顶部的距离,来作为判断条件,网上好不容易找到解决方法,但测试的妹纸却在android 6.0的手机上测出兼容性BUG ,害得我周末跟妹纸约会都在想这个问题该怎么办,看来还是要仔细系统的研究一下自定义View和ViewGroup的相关问题


废话不多说,直接上代码。

1.当Android 的版本为6.0以下时


</pre><pre name="code" class="java">
</pre><pre name="code" class="java">
pullListView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {


            }


            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                Rect rect = new Rect();
                line.getGlobalVisibleRect(rect);
                if ((rect.top < statusBarHeight + DimenUtil.dip2px(mContext, 97)) ) {
                    if (rl_choosed_tag.getVisibility() == View.INVISIBLE)
                        rl_choosed_tag.setVisibility(View.VISIBLE);
                } else {
                    if (rl_choosed_tag.getVisibility() == View.VISIBLE)
                        rl_choosed_tag.setVisibility(View.INVISIBLE);
                }
            }
        });


以上line为listView的headerView布局中的一个view,通过line.getGlobalVisibleRect(rect);来判断该view距离屏幕顶部的距离是否小于指定值(此处该临界值为

statusBarHeight + DimenUtil.dip2px(mContext, 97))来作为另外一个View是否显示的条件


2.当Android 的S版本为6.0时

在onScroll()中这样处理

if (Build.VERSION.SDK_INT >= 23) {
    if (isViewCovered(mSortFlowLayout)) {
        if (rl_choosed_tag.getVisibility() == View.VISIBLE)
            rl_choosed_tag.setVisibility(View.INVISIBLE);
    } else {
        if (rl_choosed_tag.getVisibility() == View.INVISIBLE)
            rl_choosed_tag.setVisibility(View.VISIBLE);
    }
}


涉及的isViewCovered()函数如下:

public boolean isViewCovered(final View view) {
    View currentView = view;

    Rect currentViewRect = new Rect();
    boolean partVisible = currentView.getGlobalVisibleRect(currentViewRect);
    boolean totalHeightVisible = (currentViewRect.bottom - currentViewRect.top) >= view.getMeasuredHeight();
    boolean totalWidthVisible = (currentViewRect.right - currentViewRect.left) >= view.getMeasuredWidth();
    boolean totalViewVisible = partVisible && totalHeightVisible && totalWidthVisible;
    if (!totalViewVisible)//if any part of the view is clipped by any of its parents,return true
        return true;

    while (currentView.getParent() instanceof ViewGroup) {
        ViewGroup currentParent = (ViewGroup) currentView.getParent();
        if (currentParent.getVisibility() != View.VISIBLE)//if the parent of view is not visible,return true
            return true;

        int start = indexOfViewInParent(currentView, currentParent);
        for (int i = start + 1; i < currentParent.getChildCount(); i++) {
            Rect viewRect = new Rect();
            view.getGlobalVisibleRect(viewRect);
            View otherView = currentParent.getChildAt(i);
            Rect otherViewRect = new Rect();
            otherView.getGlobalVisibleRect(otherViewRect);
            if (Rect.intersects(viewRect, otherViewRect))//if view intersects its older brother(covered),return true
                return true;
        }
        currentView = currentParent;
    }
    return false;
}


private int indexOfViewInParent(View view, ViewGroup parent) {
    int index;
    for (index = 0; index < parent.getChildCount(); index++) {
        if (parent.getChildAt(index) == view)
            break;
    }
    return index;
}

虽然一个简单的判断却还需要分版本不同单独处理,不过应该有更加简洁优雅的解决方案,欢迎各位给出指导。


<think>我们正在讨论SeekBar在没有父视图(即没有嵌套在ScrollView等可滚动容器中)的情况下,ACTION_UP和ACTION_CANCEL事件的区别。 根据引用[1],在SeekBar的onTouchEvent中,对ACTION_CANCEL的处理是: case MotionEvent.ACTION_CANCEL: if (mIsDragging) { onStopTrackingTouch(); setPressed(false); } invalidate(); break; 而在ACTION_UP的处理中(虽然没有在引用中给出,但我们可以根据常见实现推断)通常也会调用onStopTrackingTouch()和setPressed(false)等,同时还会提交进度的改变。 因此,我们需要区分在没有父视图的情况下,这两个事件的发生场景和含义。 1. ACTION_UP:当用户抬起手指(正常结束一次触摸事件)时触发。 2. ACTION_CANCEL:当触摸事件被某个上层拦截(例如父视图或系统)而导致当前视图无法继续接收事件时触发。但在没有父视图的情况下,什么情况下会发生ACTION_CANCEL呢? 实际上,即使没有父视图,以下情况也可能触发ACTION_CANCEL: - 窗口失去焦点(例如来电、用户按了Home键等) - 用户触摸操作超出了SeekBar的边界(但系统设计上,一旦开始在一个View上触摸,后续事件都会发给该View,除非父容器干预。在没有父容器干预的情况下,通常不会因为移出边界而触发CANCEL,除非是系统级的操作) 然而,在没有父视图的情况下,ACTION_CANCEL事件相对较少发生,因为父视图的拦截是ACTION_CANCEL最常见的原因。但系统也可能在某些情况下发送ACTION_CANCEL,例如: - 当前视图所在的窗口被遮挡(如弹出Dialog) - 系统认为需要取消本次触摸序列(比如有更高级别的事件,如长按菜单弹出) 因此,对于单独使用的SeekBar,两者的区别主要体现在: - ACTION_UP:表示用户正常结束滑动,此时应该完成进度设置(比如提交到服务器、保存设置等)。 - ACTION_CANCEL:表示滑动被异常中断,此时应该回退到滑动开始前的状态(或者根据业务需求做其他处理),通常不会提交进度改变。 在SeekBar的源码中,对于ACTION_UP和ACTION_CANCEL的处理有相似之处(都会停止拖动,重置按压状态),但也有一些区别: - ACTION_UP会提交当前的进度(调用setProgress(),并触发OnSeekBarChangeListener的onStopTrackingTouch和onProgressChanged)。 - ACTION_CANCEL则不会提交进度,而是将进度重置到开始拖动前的值(根据引用[1]的处理,它只是停止了拖动,重置了按压状态,并重绘,但并没有重置进度?这里需要进一步分析)。 然而,实际上在标准的SeekBar中,进度在拖动过程中就已经通过onProgressChanged不断更新了。所以当ACTION_CANCEL发生时,进度可能已经改变,但此时我们可能希望回滚到拖动开始的值? 但观察标准SeekBar的行为:在拖动过程中,进度是实时改变的,即使最后取消,进度也不会回滚。这是因为在拖动过程中,每次进度改变都会更新进度值。所以ACTION_CANCEL的处理只是停止跟踪触摸,并不会重置进度。 那么,为什么还需要ACTION_CANCEL呢?在SeekBar中,ACTION_CANCEL的主要目的是结束拖动的状态(比如将滑块从按下状态恢复,并停止跟踪),但不会触发onStopTrackingTouch(注意引用[1]中,ACTION_CANCEL的处理中调用了onStopTrackingTouch)?实际上,在标准SeekBar中,ACTION_CANCEL和ACTION_UP都会触发onStopTrackingTouch,但是两者的语义不同: - ACTION_UP:用户主动结束,此时可以做一些最终确认(比如保存设置)。 - ACTION_CANCEL:非用户意愿结束,可能不需要做任何额外的操作(除了停止拖动状态)。 然而,在标准SeekBar的实现中,两者在停止拖动状态上的处理是一致的,都会调用onStopTrackingTouch。但是,我们可以通过OnSeekBarChangeListener来区分这两种情况吗?实际上,OnSeekBarChangeListener的onStopTrackingTouch方法并不区分是UP还是CANCEL。所以,如果我们确实需要区分,可能需要在自定义SeekBar中处理。 总结: 在没有父视图的情况下,ACTION_UP和ACTION_CANCEL在SeekBar中的处理相似,都会停止拖动状态(调用onStopTrackingTouch,重置按压状态)。但是,它们的触发场景不同: - ACTION_UP:用户正常释放手指,表明用户已经完成了滑动操作。 - ACTION_CANCEL:由于外部因素(如窗口失去焦点、系统中断)导致滑动被取消。 因此,在自定义SeekBar时,如果需要根据是正常结束还是取消结束来执行不同的逻辑,可以在onTouchEvent中分别处理这两个事件。 例如,如果我们希望在取消时恢复进度到拖动开始的值,我们可以这样做: - 在ACTION_DOWN时记录当前的进度(初始进度)。 - 在ACTION_CANCEL时,将进度重置为初始进度,并更新UI。 - 在ACTION_UP时,使用当前的进度(即用户最后设置的值)。 代码示例: ```java public class CustomSeekBar extends SeekBar { private int mInitialProgress; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mInitialProgress = getProgress(); break; case MotionEvent.ACTION_UP: // 正常结束,使用当前进度 // 这里不需要额外处理,因为进度已经在拖动中更新了 // 但可以执行一些确认操作 performFinalUpdate(); break; case MotionEvent.ACTION_CANCEL: setProgress(mInitialProgress); // 重置为初始进度 break; } return super.onTouchEvent(event); } private void performFinalUpdate() { // 执行最终确认操作,比如保存设置 } } ``` 注意:在ACTION_CANCEL中重置进度会覆盖拖动过程中的所有改变,这可能是用户期望的(因为取消意味着放弃本次操作)。 引用[1]中,SeekBar的ACTION_CANCEL处理并没有重置进度,所以如果我们希望重置,需要自定义。 因此,在没有父视图的情况下,两者的区别主要在于事件发生的原因,我们可以根据需求在自定义SeekBar中做不同的处理。 引用来源: [^1]: 在ACTION_CANCEL的处理中,SeekBar会停止拖动状态(如果正在拖动),并重置按压状态,然后重绘。 [^2][^3][^4]: 这些引用主要讨论垂直SeekBar的布局问题,与当前问题关系不大,但可以作为垂直SeekBar实现的参考。</think>在Android开发中,当SeekBar没有嵌套在父视图(如ScrollView)中时,`ACTION_UP`和`ACTION_CANCEL`事件的区别主要体现在**事件触发场景**和**逻辑处理目的**上: ### 核心区别对比 | **特性** | `ACTION_UP` | `ACTION_CANCEL` | |------------------|--------------------------------------|-------------------------------------| | **触发条件** | 用户主动抬起手指(正常结束操作) | 系统强制中断触摸事件(异常终止) | | **语义含义** | 用户完成滑动操作 | 外部因素导致操作中断 | | **典型场景** | 用户松开手指确认进度 | 来电/弹窗遮挡/系统资源回收等 | | **UI状态处理** | 保持当前进度状态 | 必须重置UI到安全状态[^1] | | **业务逻辑** | 提交最终进度值 | 放弃未完成操作 | ### 详细机制分析 #### 1. `ACTION_UP` (正常结束) - **触发时机** 用户手指从屏幕抬起,表明**主动完成**滑动操作 - **处理逻辑** ```java case MotionEvent.ACTION_UP: if (mIsDragging) { onStopTrackingTouch(); // 确认最终进度 setProgressFinalValue(); // 提交业务逻辑 } setPressed(false); // 解除按压状态 break; ``` - **典型操作** ✅ 保存最终进度值 ✅ 更新关联的文本显示 ✅ 触发进度提交回调 #### 2. `ACTION_CANCEL` (强制中断) - **触发时机** 系统强制终止触摸序列(即使手指仍在屏幕上) - **处理逻辑** ```java case MotionEvent.ACTION_CANCEL: if (mIsDragging) { onStopTrackingTouch(); resetToInitialState(); // 关键区别:重置UI[^1] } setPressed(false); // 必须解除按压状态 invalidate(); // 强制重绘界面 break; ``` - **核心操作** ❗ 重置滑块到操作前的安全状态 ❗ 放弃未确认的进度变更 ❗ 防止UI停留在"半完成"状态 ### 关键差异场景 1. **窗口失去焦点时** - 系统自动发送`ACTION_CANCEL`(非`ACTION_UP`) - 必须重置滑块位置避免状态不一致 2. **系统资源回收** - 当Activity进入后台时触发`ACTION_CANCEL` - 需确保下次显示时UI状态正常 3. **异常中断防护** ```java // 必须同时处理UP和CANCEL public boolean onTouchEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: // 双重保障 terminateTracking(); return true; } return super.onTouchEvent(event); } ``` ### 最佳实践建议 1. **状态重置必要性** `ACTION_CANCEL`必须重置UI状态,否则会导致: - 滑块卡在按压状态 - 进度显示与实际值不一致 - 后续触摸事件紊乱 2. **进度提交时机** ```java private void terminateTracking() { if (event.getAction() == MotionEvent.ACTION_UP) { saveProgress(); // 仅UP时保存进度 } resetThumbState(); // 两者都需重置UI } ``` 3. **自定义SeekBar扩展** 建议增加状态标志位: ```java @Override protected void onVisibilityChanged(View changedView, int visibility) { if (visibility != VISIBLE) { // 视图不可见时模拟CANCEL事件 sendCancelEvent(); } } ``` > 即使没有父视图,`ACTION_CANCEL`仍是**系统级安全机制**,用于处理不可控中断。忽略它的重置逻辑会导致UI状态错乱[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值