Android заставляет вид исчезать, щелкнув за его пределами

У меня есть некоторые виды, которые я делаю видимыми при нажатии кнопки. Я хочу, чтобы они исчезли, если я щелкну за пределами этих представлений.

Как это сделать на Android?

Кроме того, я понимаю, что «кнопка возврата» также может помочь пользователям Android в этом - я мог бы использовать это как дополнительный способ закрыть представления - но некоторые планшеты больше не используют даже «физическую» кнопку возврата, это был очень преуменьшен.


person CQM    schedule 13.07.2011    source источник


Ответы (12)


Простой / глупый способ:

  • Создайте фиктивное пустое представление (скажем, ImageView без источника), сделайте его заполненным родительским

  • Если по нему щелкнули, делайте то, что хотите.

У вас должен быть корневой тег в вашем XML-файле, чтобы он был RelativeLayout. Он будет содержать два элемента: ваше фиктивное представление (установите его позицию на align the Parent Top). Другой - ваше исходное представление, содержащее представления и кнопку (это представление может быть LinearLayout или чем-то еще. Не забудьте установить его позицию на align the Parent Top)

Надеюсь, это поможет вам, удачи!

person iTurki    schedule 13.07.2011
comment
РАБОТАЕТ КАК ОЧАРОВАНИЕ! спасибо :) Я скептически относился к распознаванию пустых изображений - person CQM; 14.07.2011
comment
Вы можете просто использовать View как фиктивное представление. Они немного легче, и просто увидеть их в XML - хорошее напоминание о том, что они не используются в качестве визуального элемента. - person zienkikk; 14.07.2011
comment
@RD. Рад это слышать :) . Я проделал подобные трюки с RelativeLayout. это больше, чем гибкий макет. - person iTurki; 14.07.2011
comment
сделайте фиктивный вид интерактивным, в противном случае он будет получать щелчки из заднего вида - person Shahab Rauf; 03.08.2017

Найдите прямоугольник представления, а затем определите, находится ли событие щелчка за пределами представления.

@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);
person Kai Wang    schedule 19.08.2015
comment
Следует отметить, что dispatchTouchEvent() должен возвращать логическое значение, но в вашем примере вы ничего не возвращаете. Кроме того, вы не определяете и не объясняете, что такое viewRect. - person CaptJak; 15.05.2016
comment
Спасибо, CaptJak. - person Kai Wang; 14.07.2016
comment
Что такое mToolTip? - person ToMakPo; 21.11.2019
comment
mToolTip - это представление, на которое можно щелкнуть, и поэтому вы сравниваете, находится ли событие за пределами Rect - person Killer; 27.01.2021

Это старый вопрос, но я подумал, что дам ответ, не основанный на onTouch событиях. Как было предложено RedLeader, этого также можно добиться с помощью событий фокуса. У меня был случай, когда мне нужно было показать и скрыть группу кнопок, расположенных в настраиваемом всплывающем окне, то есть все кнопки были помещены в один и тот же ViewGroup. Вот что вам нужно сделать, чтобы эта работа заработала:

  1. Для группы представлений, которую вы хотите скрыть, необходимо установить View.setFocusableInTouchMode(true). Это также можно установить в XML с помощью android:focusableintouchmode.

  2. Корень вашего представления, то есть корень всего вашего макета, возможно, какой-то линейный или относительный макет, также должен иметь возможность фокусировки согласно пункту 1 выше.

  3. Когда отображается группа представлений, вы вызываете View.requestFocus(), чтобы передать ей фокус.

  4. Ваша группа представлений должна либо переопределить View.onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect), либо реализовать собственное OnFocusChangeListener и использовать View.setOnFocusChangeListener()

  5. Когда пользователь касается вне вашего представления, фокус передается либо на корень представления (поскольку вы устанавливаете его как фокусируемый в # 2), либо на другое представление, которое по своей сути является фокусируемым (EditText или подобное)

  6. Когда вы обнаруживаете потерю фокуса одним из методов в №4, вы знаете, что фокус был перенесен на что-то за пределами вашей группы просмотра, и вы можете скрыть это.

Я предполагаю, что это решение работает не во всех сценариях, но оно сработало в моем конкретном случае, и похоже, что оно может работать и для OP.

person britzl    schedule 09.02.2013
comment
крошечное предложение: setFocusableInTouchMode гарантирует, что также установлен setFocusable. - person Edison; 14.08.2014
comment
Вы правы, я плохо читал документацию. Я обновлю ответ. - person britzl; 15.08.2014

Я искал способ закрыть свой обзор при прикосновении снаружи, и ни один из этих методов не соответствовал моим потребностям. Я нашел решение и просто опубликую его здесь, если кому-то интересно.

У меня есть базовая деятельность, которая распространяется практически на все мои занятия. В нем у меня есть:

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

Поэтому, если мой вид виден, он просто закроется, а если нет, он будет вести себя как обычное событие касания. Не уверен, что это лучший способ сделать это, но, похоже, у меня он работает.

person pickle_inspector    schedule 26.02.2014
comment
что происходит, когда ваш вид виден и вы нажимаете на него? похоже, что он все равно закроет - person Guillaume; 24.04.2014
comment
Очень полезно. См. Следующее, чтобы определить, находится ли касание внутри представления, которое должно исчезнуть, когда щелчок находится за его пределами: stackoverflow.com/questions/20700286/ - person dazed; 30.10.2016
comment
Я с @Guillaume, разве это не бесполезно, потому что вы никогда не можете щелкнуть свое представление? - person behelit; 27.02.2018

Основываясь на ответе Кая Ванга: я предлагаю сначала проверить видимость вашего представления, основываясь на моем сценарии, когда пользователь нажимает на 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);
}
person FxRi4    schedule 11.09.2018
comment
что ушел Anim (myView); - person DragonFire; 23.02.2021
comment
@DragonFire, чтобы изменить яркость вашего взгляда, может быть, вы захотите сделать это с помощью анимации :) - person FxRi4; 03.03.2021

Мне нужна была особая возможность не только удалять представление при щелчке за его пределами, но также позволять щелчку переходить в действие в обычном режиме. Например, у меня есть отдельный макет, notification_bar.xml, который мне нужно динамически раздувать и добавлять ко всему, что происходит в настоящее время, когда это необходимо.

Если я создаю оверлейное представление размером с экран, чтобы получать какие-либо клики за пределами представления 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" ...

Это позволит вам взаимодействовать с представлением, и в тот момент, когда вы щелкнете за пределами представления, вы одновременно закроете представление и начнете нормально взаимодействовать с действием.

person byebrianwong    schedule 02.09.2014

Реализовать onTouchListener(). Убедитесь, что координаты касания находятся за пределами координат вашего обзора.

Вероятно, есть какой-то способ сделать это с onFocus() и т. Д. - Но я этого не знаю.

person RedLeader    schedule 13.07.2011
comment
как мне определить, какие координаты X, Y моего представления динамически? Я использую пиксели или значения провала, с помощью которых я создал его размер - person CQM; 14.07.2011
comment
stackoverflow .com / questions / 2224844 /. - person RedLeader; 14.07.2011

Шаг 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);
                        
                    }
                });
person Gk Mohammad Emon    schedule 27.06.2020

Я создал настраиваемую 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);
}
person Levinthann    schedule 05.11.2015

Я хочу поделиться своим решением, которое, как мне кажется, может быть полезно, если:

  • вы можете добавить настраиваемую 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>
person Fartab    schedule 25.05.2020

Вот простой способ выполнить свою работу:

Шаг 1. Создайте идентификатор для внешнего контейнера вашего элемента, для которого вы хотите создать внешнее событие клика.

В моем случае это линейный макет, для которого я указал идентификатор как «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;
                                        }
                                    }
);
person Akshay Chavan    schedule 21.05.2021

спасибо @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

person vuhung3990    schedule 28.01.2016