簡體   English   中英

Android 通過單擊外部使視圖消失

[英]Android make view disappear by clicking outside of it

我有一些視圖可以在按下按鈕時顯示。 如果我在這些視圖之外單擊,我希望它們消失。

這將如何在 Android 上完成?

此外,我意識到“后退按鈕”還可以幫助 Android 用戶使用此功能 - 我可能會將其用作關閉視圖的輔助方式 - 但有些平板電腦甚至不再使用“物理”后退按鈕了,它已經非常不重視了。

一個簡單/愚蠢的方法:

  • 創建一個虛擬的空視圖(假設 ImageView 沒有源),使其填充父視圖

  • 如果它被點擊,然后做你想做的事。

您需要將 XML 文件中的根標簽作為相對布局。 它將包含兩個元素:您的虛擬視圖(設置其 position 以align the Parent Top )。 另一個是包含視圖和按鈕的原始視圖(此視圖可能是 LinearLayout 或您制作的任何內容。不要忘記將其 position 設置為align the Parent Top

希望這對你有幫助,祝你好運!

找到視圖矩形,然后檢測點擊事件是否在視圖之外。

@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;
}

如果您想在其他地方使用觸摸事件,請嘗試

return super.dispatchTouchEvent(ev);

這是一個老問題,但我想我會給出一個不基於onTouch事件的答案。 正如 RedLeader 所建議的,也可以使用焦點事件來實現這一點。 我有一個案例,我需要顯示和隱藏排列在自定義彈出窗口中的一堆按鈕,即所有按鈕都放在同一個ViewGroup中。 要完成這項工作,您需要做一些事情:

  1. 您希望隱藏的視圖組需要設置View.setFocusableInTouchMode(true) 這也可以使用android:focusableintouchmode在 XML 中設置。

  2. 您的視圖根,即整個布局的根,可能是某種線性或相對布局,也需要能夠按照上面的#1 聚焦

  3. 當顯示視圖組時,您調用View.requestFocus()為其提供焦點。

  4. 您的視圖組需要覆蓋View.onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)或實現您自己的OnFocusChangeListener並使用View.setOnFocusChangeListener()

  5. 當用戶在您的視圖之外點擊時,焦點將轉移到視圖根(因為您在 #2 中將其設置為可聚焦)或另一個固有可聚焦的視圖( EditText或類似的)

  6. 當您使用 #4 中的一種方法檢測到焦點丟失時,您知道焦點已轉移到您的視圖組之外的東西上,您可以將其隱藏。

我猜這個解決方案並不適用於所有場景,但它適用於我的具體情況,聽起來好像它也適用於 OP。

我一直在尋找一種在觸摸外部時關閉視野的方法,但這些方法都不能很好地滿足我的需求。 我確實找到了解決方案,如果有人感興趣,我會在這里發布。

我有一個基本活動,幾乎我所有的活動都在擴展。 在里面我有:

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

因此,如果我的視圖可見,它就會關閉,如果不可見,它的行為就像正常的觸摸事件一樣。 不確定這是否是最好的方法,但它似乎對我有用。

基於王凱回答:我建議首先檢查您的視圖的可見性,根據我的場景,當用戶點擊 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);
}

我需要特定的能力,不僅可以在單擊外部視圖時刪除視圖,而且還需要允許單擊正常傳遞到活動。 例如,我有一個單獨的布局,notification_bar.xml,我需要動態膨脹並在需要時添加到當前活動的任何內容。

如果我創建一個屏幕大小的覆蓋視圖以接收 notification_bar 視圖之外的任何點擊並在點擊時刪除這兩個視圖,則父視圖(活動的主視圖)仍然沒有收到任何點擊,這意味着,當 notification_bar 可見時,單擊一個按鈕需要兩次單擊(一次關閉 notification_bar 視圖,一次單擊按鈕)。

要解決這個問題,您可以創建自己的 DismissViewGroup 擴展 ViewGroup 並覆蓋以下方法:

@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" ...

這將允許您與視圖交互,並且當您在視圖外部單擊時,您將關閉視圖並與活動正常交互。

第 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);
                        
                    }
                });

實現onTouchListener() 檢查觸摸的坐標是否在您的視圖坐標之外。

可能有某種方法可以使用onFocus()等 - 但我不知道。

我創建了自定義 ViewGroup 來顯示錨定到另一個視圖(彈出氣球)的信息框。 Child view 是實際的信息框,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);
}

我想分享我的解決方案,我認為它在以下情況下會很有用:

  • 您可以添加自定義 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>

這是完成工作的簡單方法:

第 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;
                                        }
                                    }
);

在視圖外執行單擊時隱藏視圖:

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)
}

感謝@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 之外觸摸時有效

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM