Android通过点击外部使视图消失

时间:2011-07-13 21:07:22

标签: android view onclick back

我有一些观点,按下按钮后可以看到。如果我在这些视图之外点击,我希望它们消失。

如何在Android上完成?

此外,我意识到“后退按钮”也可以帮助Android用户 - 我可能会将其作为关闭视图的第二种方式 - 但有些平板电脑甚至没有使用“物理”后退按钮它已经非常不再强调了。

13 个答案:

答案 0 :(得分:32)

一种简单/愚蠢的方式:

  • 创建一个虚拟空视图(让我们说没有源的ImageView),让它填充父

  • 如果单击它,则执行您想要执行的操作。

您需要将XML文件中的根标记设置为RelativeLayout。它将包含两个元素:您的虚拟视图(将其位置设置为align the Parent Top)。另一个是包含视图和按钮的原始视图(此视图可能是LinearLayout或您创建的任何内容。不要忘记将其位置设置为align the Parent Top

希望这会对你有所帮助,祝你好运!

答案 1 :(得分:24)

这是一个老问题,但我想我会给出一个不基于onTouch事件的答案。正如RedLeader所建议的那样,使用焦点事件也可以实现这一点。我有一个案例,我需要显示和隐藏自定义弹出窗口中排列的一堆按钮,即按钮全部放在同一个ViewGroup中。您需要做的一些事情才能使其发挥作用:

  1. 您要隐藏的视图组需要设置View.setFocusableInTouchMode(true)。这也可以使用android:focusableintouchmode以XML格式设置。

  2. 您的视图根,即整个布局的根,可能是某种线性或相对布局,也需要能够按照上面#1

  3. 进行聚焦
  4. 当显示视图组时,您调用View.requestFocus()以使其获得焦点。

  5. 您的观看小组需要覆盖View.onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)或实施您自己的OnFocusChangeListener并使用View.setOnFocusChangeListener()

  6. 当用户点击视图外部时,焦点会转移到视图根目录(因为您将其设置为#2中的焦点)或另一个本身可聚焦的视图(EditText或类似的)< / p>

  7. 当您使用#4中的某个方法检测到焦点丢失时,您知道焦点已转移到视图组之外的某个位置,您可以隐藏它。

  8. 我想这个解决方案在所有情况下都不起作用,但它在我的特定情况下起作用,听起来好像它也适用于OP。

答案 2 :(得分:22)

找到视图矩形,然后检测click事件是否在视图之外。

df %>%
     filter_at(vars(target_columns), all_vars(. >= 0) )

  id  sth1 tg1_num sth2 tg2_num others
1  1  dave       2   ca      35    new
2  4 leroy       0   az      25    old
3  5 jerry       4   mi      55    old

如果您想在其他地方使用触摸事件,请尝试

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Rect viewRect = new Rect();
    mTooltip.getGlobalVisibleRect(viewRect);
    if (!viewRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
        setVisibility(View.GONE);
    }
    return true;
}

答案 3 :(得分:10)

我一直在寻找一种在外面触摸时关闭视角的方法,这些方法都不能很好地满足我的需求。我确实找到了一个解决方案,只是在这里发布以防万一有人感兴趣。

我有一个基本活动,几乎所有活动都延伸了。在其中我有:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (myViewIsVisible()){
            closeMyView();
        return true;
    }
    return super.dispatchTouchEvent(ev);
}

因此,如果我的视图可见,它将关闭,如果不是,它将表现得像普通的触摸事件。不确定这是否是最佳方式,但它似乎对我有用。

答案 4 :(得分:2)

我需要具体的功能,不仅可以在外部点击时删除视图,还可以让点击正常传递给活动。例如,我有一个单独的布局,notification_bar.xml,我需要动态膨胀并添加到需要时的当前活动。

如果我创建一个覆盖视图,屏幕大小可以在notification_bar视图之外接收任何点击,并在点击时删除这两个视图,那么父视图(活动的主视图)仍然没有收到任何点击,这意味着,当notification_bar可见时,点击一个按钮需要两次点击(一个用于关闭notification_bar视图,另一个用于点击按钮)。

要解决此问题,您只需创建自己的扩展ViewGroup的DismissViewGroup并覆盖以下方法:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    ViewParent parent = getParent();
    if(parent != null && parent instanceof ViewGroup) {
        ((ViewGroup) parent).removeView(this);
    }
    return super.onInterceptTouchEvent(ev);
}

然后您动态添加的视图看起来有点像:

<com.example.DismissViewGroup android:id="@+id/touch_interceptor_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent" ...
    <LinearLayout android:id="@+id/notification_bar_view" ...

这将允许您与视图进行交互,并且当您在视图外部单击时,您都会关闭视图并与活动正常交互。

答案 5 :(得分:0)

实施onTouchListener()。检查触摸的坐标是否在视图的坐标之外。

使用onFocus()等可能有某种方法可以做到这一点 - 但我不知道。

答案 6 :(得分:0)

我已创建自定义ViewGroup以显示锚定到另一个视图(弹出气球)的信息框。 子视图是实际信息框,BalloonView是全屏,用于孩子的绝对定位和拦截触摸。

public BalloonView(View anchor, View child) {
    super(anchor.getContext());
    //calculate popup position relative to anchor and do stuff
    init(...);
    //receive child via constructor, or inflate/create default one
    this.child = child;
    //this.child = inflate(...);
    //this.child = new SomeView(anchor.getContext());
    addView(child);
    //this way I don't need to create intermediate ViewGroup to hold my View
    //but it is fullscreen (good for dialogs and absolute positioning)
    //if you need relative positioning, see @iturki answer above 
    ((ViewGroup) anchor.getRootView()).addView(this);
}

private void dismiss() {
    ((ViewGroup) getParent()).removeView(this);
}

处理儿童内部的点击次数:

child.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //write your code here to handle clicks inside
    }
});

通过点击外部关闭我的视图,不要将触摸委托给基础视图:

BalloonView.this.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        dismiss();
    }
});

通过单击外部以及将触摸委派给基础视图来解除我的视图:

@Override
public boolean onTouchEvent(MotionEvent event) {
    dismiss();
    return false; //allows underlying View to handle touch
}

按下后退按钮时关闭:

//do this in constructor to be able to intercept key
setFocusableInTouchMode(true);
requestFocus();

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        dismiss();
        return true;
    }
    return super.onKeyPreIme(keyCode, event);
}

答案 7 :(得分:0)

基于Kai Wang的回答:我建议您首先检查您的视图的可见性,根据我的情况,当用户单击fab myView时可见,然后在用户单击myView之外时消失

  @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Rect viewRect = new Rect();
    myView.getGlobalVisibleRect(viewRect);
    if (myView.getVisibility() == View.VISIBLE && !viewRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
        goneAnim(myView);
        return true;
    }
    return super.dispatchTouchEvent(ev);
}

答案 8 :(得分:0)

我想分享我的解决方案,我认为,如果:

  • 您可以将自定义ViewGroup添加为根布局
  • 您想消失的视图也可以是自定义视图。

首先,我们创建一个自定义ViewGroup来拦截触摸事件:

class OutsideTouchDispatcherLayout @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private val rect = Rect()

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        if (ev.action == MotionEvent.ACTION_DOWN) {
            val x = ev.x.roundToInt()
            val y = ev.y.roundToInt()
            traverse { view ->
                if (view is OutsideTouchInterceptor) {
                    view.getGlobalVisibleRect(rect)
                    val isOutside = rect.contains(x, y).not()
                    if (isOutside) {
                        view.interceptOutsideTouch(ev)
                    }
                }
            }
        }
        return false
    }

    interface OutsideTouchInterceptor {
        fun interceptOutsideTouch(ev: MotionEvent)
    }
}

fun ViewGroup.traverse(process: (View) -> Unit) {
    for (i in 0 until childCount) {
        val child = getChildAt(i)
        process(child)
        if (child is ViewGroup) {
            child.traverse(process)
        }
    }
}

如您所见,OutsideTouchDispatcherLayout拦截触摸事件并通知每个后代视图,这些实现向OutsideTouchInterceptor表示某个触摸事件发生在该视图之外。

这是后代视图如何处理此事件的方法。请注意,它必须实现OutsideTouchInterceptor接口:

class OutsideTouchInterceptorView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr),
    OutsideTouchDispatcherLayout.OutsideTouchInterceptor {

    override fun interceptOutsideTouch(ev: MotionEvent) {
        visibility = GONE
    }

}

然后,仅通过儿童与父母的关系即可轻松进行外部触摸检测:

<?xml version="1.0" encoding="utf-8"?>
<com.example.touchinterceptor.OutsideTouchDispatcherLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.example.touchinterceptor.OutsideTouchInterceptorView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#eee"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</com.example.touchinterceptor.OutsideTouchDispatcherLayout>

答案 9 :(得分:0)

步骤1:通过Fragmelayout进行包装视图,该视图将覆盖您的主要布局。

 <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
     <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <!-- This is your main layout-->
     </RelativeLayout>
    
            <View
                android:id="@+id/v_overlay"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    <!-- This is the wrapper layout-->
            </View>
        </FrameLayout>

步骤2:现在在您的Java代码中添加逻辑-

         View viewOverlay = findViewById(R.id.v_overlay);
         View childView = findViewByID(R.id.childView);
         Button button = findViewByID(R.id.button);
    
         viewOverlay.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        childView.setVisibility(View.GONE);
                        view.setVisibility(View.GONE);
                    }
                });
    
          
         button.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                       childView.setVisibility(View.VISIBLE);
   // Make the wrapper view visible now after making the child view visible for handling the 
  // main visibility task. 
                       viewOverlay.setVisibility(View.VISIBLE);
                        
                    }
                });

答案 10 :(得分:0)

以下是完成工作的简单方法:

第 1 步:为要为其生成外部点击事件的元素的外部容器创建 ID。

就我而言,它是一个线性布局,我为其指定了 id 为“outsideContainer”

第 2 步: 为该外部容器设置一个 onTouchListener,它将简单地充当内部元素的点击外部事件!

outsideContainer.setOnTouchListener(new View.OnTouchListener() {
                                        @Override
                                        public boolean onTouch(View v, MotionEvent event) {
                                            // perform your intended action for click outside here
                                            Toast.makeText(YourActivity.this, "Clicked outside!", Toast.LENGTH_SHORT).show();
                                            return false;
                                        }
                                    }
);

答案 11 :(得分:0)

在视图外执行点击时隐藏视图:

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
      if (isMenuVisible) {
          if (!isWithinViewBounds(ev.rawX.toInt(), ev.rawY.toInt())) {
               hideYourView()
               return true
          }
      }
   return super.dispatchTouchEvent(ev)
}

创建一个方法来获取视图的边界(高度和宽度),因此当您在视图外部单击时,它将隐藏视图,而当单击视图时不会隐藏:

private fun isWithinViewBounds(xPoint: Int, yPoint: Int): Boolean {
        val l = IntArray(2)
        llYourView.getLocationOnScreen(l)
        val x = l[0]
        val y = l[1]
        val w: Int = llYourView.width
        val h: Int = llYourView.height
        return !(xPoint < x || xPoint > x + w || yPoint < y || yPoint > y + h)
}

答案 12 :(得分:-1)

感谢@ituki的想法

FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
android:clickable="true">

<LinearLayout
    android:clickable="true" // not trigger
    android:layout_width="match_parent"
    android:layout_height="300dp" 
    android:background="#FFF"
    android:orientation="vertical"
    android:padding="20dp">

    ...............

</LinearLayout>
</FrameLayout>

和java代码

mContainer = (View) view.findViewById(R.id.search_container);
    mContainer.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if(event.getAction() == MotionEvent.ACTION_DOWN){
                Log.d("aaaaa", "outsite");
                return true;
            }
            return false;
        }
    });

在LinearLayout外部触摸

时可以正常工作