touchListener 및 clickListener를 사용하여 강조 표시된 상태로 왼쪽 단추


10

다음을 수행 한 후 Button이 강조 표시된 상태로 유지되는 데 문제가 있습니다.

public class MainActivity extends AppCompatActivity {

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("Test", "calling onClick");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN: {
                        v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                        v.invalidate();
                        break;
                    }
                    case MotionEvent.ACTION_UP: {
                        v.getBackground().clearColorFilter();
                        v.invalidate();
                        v.performClick();
                        Log.d("Test", "Performing click");
                        return true;
                    }
                }
                return false;
            }
        });

    }
}

위의 코드와 관련하여 사용하면 버튼 클릭이 터치로 처리 될 것으로 예상하고 "true"를 반환하면 touchListener에서 처리가 중지됩니다.

그러나 이것은 사실이 아닙니다. 클릭이 호출 되더라도 버튼은 강조 표시된 상태로 유지됩니다.

내가 얻는 것은 :

Test - calling onClick
Test - Performing click

반면에, 다음 코드를 사용하는 경우 버튼이 클릭되고 동일한 인쇄가되지만 버튼이 강조 표시된 상태로 멈추지 않습니다.

public class MainActivity extends AppCompatActivity {

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("Test", "calling onClick");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN: {
                        v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                        v.invalidate();
                        break;
                    }
                    case MotionEvent.ACTION_UP: {
                        v.getBackground().clearColorFilter();
                        v.invalidate();
                        // v.performClick();
                        Log.d("Test", "Performing click");
                        return false;
                    }
                }
                return false;
            }
        });

    }
}

터치 이벤트에 대한 응답자 체인이 무엇인지 조금 혼란 스럽습니다. 내 생각 엔 :

1) TouchListener

2) ClickListener

3) ParentViews

누군가도 이것을 확인할 수 있습니까?


실제로하고 싶은 것은 터치로 처리하거나 누르기 만하면 색상이 변경됩니까?
Haider Saleem

터치로 로직을 실행 한 다음 performClick을 호출하여 버튼의 색상을 변경하지 않기를 원합니다.
Whitebear

@Whitebear 아래 답변을 확인하십시오. 더 많은 정보를 추가 할 수 있습니다.
GensaGames

터치 이벤트의 흐름을 이해하는 데 도움 될 수 있습니다. 무슨 일이 일어나고 있는지 명확하지 않습니다. 클릭 핸들러를 원하십니까? 클릭을 수행 하시겠습니까? 버튼을 초기 색상에서 컬러 필터로 설정된 상태로 전환 한 다음 초기 색상으로 되 돌리시겠습니까?
Cheticamp

무슨 뜻인지 설명하겠습니다. 버튼에 touchListener와 clickListener가 있습니다. 터치는 우선 순위에 따라 클릭보다 우선하며 이벤트를 처리 한 경우 true를 반환하므로 다른 사람이 처리하지 않아야합니다. 이것은 내가 터치를 통해하고, 클릭을 처리하고 true를 반환하는 것과 정확히 일치하지만 onclick 리스너가 호출되고 흐름이 올바르게 수행 되더라도 버튼은 여전히 ​​강조 표시됩니다.
Whitebear

답변:


10

이러한 사용자 지정은 프로그래밍 방식으로 수정할 필요가 없습니다. xml파일 에서 간단히 할 수 있습니다 . 우선, setOnTouchListener당신이 제공 하는 방법을 onCreate완전히 삭제하십시오 . 다음으로 res/color디렉토리 에서 선택기 색상을 다음과 같이 정의하십시오 . (디렉토리가 존재하지 않으면 작성하십시오)

입술 / 색상 /button_tint_color.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="#e0f47521" android:state_pressed="true" />
    <item android:color="?attr/colorButtonNormal" android:state_pressed="false" />
</selector>

이제 버튼의 app:backgroundTint속성으로 설정하십시오 .

<androidx.appcompat.widget.AppCompatButton
    android:id="@+id/mybutton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    app:backgroundTint="@color/button_tint_color" />


시각적 결과 :

여기에 이미지 설명을 입력하십시오



편집 : (터치 이벤트 문제를 해결하기 위해)

전체적인 관점에서, 터치 이벤트의 흐름은에서 시작 Activity하여 레이아웃 (부모에서 자식 레이아웃으로)과 흐름으로 차례로 흐릅니다. (다음 그림의 LRT 흐름)

여기에 이미지 설명을 입력하십시오

터치 이벤트가 목표에 도달하면 뷰 뷰 이벤트가 그 전에 배치 / 작동 여부 (복귀로 전달하기로 결정할 수 처리 falsetrueonTouch있어서). (위 그림에서 RTL 흐름)

이제 터치 이벤트 흐름에 대한 심층적 인 통찰력을 얻기 위해 View 의 소스 코드를 살펴 보겠습니다 . 의 구현을 살펴보면 뷰 dispatchTouchEvent를 설정 한 OnTouchListener다음 true해당 onTouch메소드 로 반환 onTouchEvent하면 뷰가 호출되지 않습니다.

public boolean dispatchTouchEvent(MotionEvent event) {
    // removed lines for conciseness...
    boolean result = false;    
    // removed lines for conciseness...
    if (onFilterTouchEventForSecurity(event)) {
        // removed lines for conciseness...
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) { // <== right here!
            result = true;
        }
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    // removed lines for conciseness...
    return result;
}

이제 onTouchEvent이벤트 조치가 있는 메소드를 보십시오 MotionEvent.ACTION_UP. 클릭-클릭 동작이 발생하는 것을 볼 수 있습니다. 그래서, 돌아 true에서 OnTouchListeneronTouch결과적으로 호출하지 onTouchEvent, 호출하지 원인 OnClickListener들 ' onClick.

을 호출하지 않는 또 다른 문제가 있습니다.이 문제 onTouchEvent는 눌린 상태와 관련이 있으며 질문에서 언급했습니다. 아래 코드 블록에서 볼 수 있듯이, 실행될 때 UnsetPressedState해당 호출 의 인스턴스 가 있습니다. 호출하지 않은 결과는 뷰가 눌린 상태에서 멈추고 드로어 블 상태가 변경되지 않는 결과입니다. setPressed(false)setPressed(false)

public boolean onTouchEvent(MotionEvent event) {
    // removed lines for conciseness...
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                // removed lines for conciseness...
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // removed lines for conciseness...
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // removed lines for conciseness...
                        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)) {
                                performClickInternal();
                            }
                        }
                    }
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
                    // removed lines for conciseness...
                }
                // removed lines for conciseness...
                break;
            // removed lines for conciseness...
        }
        return true;
    }
    return false;
}

UnsetPressedState :

private final class UnsetPressedState implements Runnable {
    @Override
    public void run() {
        setPressed(false);
    }
}


위의 설명과 관련하여 setPressed(false)이벤트 작업이 MotionEvent.ACTION_UP다음 과 같은 드로어 블 상태를 변경하도록 자신을 호출하여 코드를 변경할 수 있습니다 .

button.setOnTouchListener(new View.OnTouchListener() {

    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                v.invalidate();
                break;
            }
            case MotionEvent.ACTION_UP: {
                v.getBackground().clearColorFilter();
                // v.invalidate();
                v.setPressed(false);
                v.performClick();
                Log.d("Test", "Performing click");
                return true;
            }
        }
        return false;
    }
});

이것은 내가 친구를 찾는 것이 아닙니다. 위에서 설명한 두 상황에서 동작의 변화를 이해하려고합니다. 자세히 설명 할 내용이 있으면 알려주세요. 터치 핸들러 나 클릭 핸들러를 삭제하고 싶지 않습니다. 위의 답변에 대한 의견을 확인하십시오.
Whitebear

@ Whitebear : 대답을 업데이트했습니다. 확인 해봐, 친구
aminography

그것은 좋은 세부 답변이며, 나는 그것을 받아 들일 것입니다. 몇 가지 변경 사항 : Now, look at the onTouchEvent method where the event action is MotionEvent.ACTION_UP. We see that perform-click action happens there. So, returning true in the OnTouchListener's onTouch and consequently not calling the onTouchEvent, causes not calling the OnClickListener's onClick.내 경우에는 onClick이 호출됩니다. mUnsetPressedState는 false로 설정하기 전에 null인지 확인하고 prepressed 상태이면 실행 가능 파일이 실행되지 않을 수도 있습니다. 나는 그것이 거짓으로 설정되어야한다는 공제 방법을 잘 모르겠습니다
Whitebear

onClick당신이 호출하기 때문에라고합니다 v.performClick();. 위의 코드를 MotionEvent.ACTION_UP다시 확인하여 setPressed(false)어쨌든 호출되는지 여부, mUnsetPressedStatenull인지 여부, prepressedtrue 인지 여부를 확인하십시오 . 차이는 호출의 방법에 setPressed(false)를 통해 할 수있는을 post/ postDelayed직접.
aminography 2018

2

당신은 엉망 touch이고 focus이벤트입니다. 같은 색으로 행동을 이해하는 것으로 시작합시다. 기본적 으로 Android에서 Selector배경으로 지정됩니다 Button. 따라서 단순히 배경색을 변경하면 make는 정적입니다 (색상은 변경되지 않음). 그러나 기본 동작은 아닙니다.

Selector 이것처럼 보일 수 있습니다.

<?xml version="1.0" encoding="utf-8"?> 
  <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_focused="true"
        android:state_pressed="true"
        android:drawable="@drawable/bgalt" />

    <item
        android:state_focused="false"
        android:state_pressed="true"
        android:drawable="@drawable/bgalt" />

    <item android:drawable="@drawable/bgnorm" />
</selector>

위에서 볼 수 있듯이 state focused와 state가 pressed있습니다. 설정하여onTouchListener 하면와 관련이없는 터치 이벤트를 처리합니다 focus.

Selector버튼의 클릭 이벤트는 버튼의 클릭 이벤트 동안 focus이벤트를 대체해야 touch합니다. 그러나 코드의 첫 번째 부분에서touch 에서 (콜백에서 true를 반환 . 색 변경이 더 이상 진행되지 않고 같은 색으로 정지됩니다. 그리고 이것이 두 번째 변형 (차단없이)이 잘 작동하는 이유이며 이것이 바로 혼란입니다.

최신 정보

에 대한 동작과 색상을 변경하기 만하면 Selector됩니다. 예를 들어. 에 대한 다음 배경을 사용하여 Button. 그리고onTouchListener 구현에서 전혀 제거 하십시오.

<?xml version="1.0" encoding="utf-8"?> 
  <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_pressed="true"
        android:drawable="@color/color_pressed" />

    <item android:drawable="@color/color_normal" />
</selector>

그러면 첫 번째 예제를 어떻게 변경하여 잘 작동합니까?
Whitebear

구현에서 터치 리스너를 제거하지 않으려 고합니다. 터치 핸들러를 사용하여 특정보기에서 클릭을 잡은 다음 직접 처리하고 (clickClick을 사용하여) 직접 처리하고 터치 리스너에서 true를 반환하여 다른 처리기가 더 이상 처리되지 않는다고 알립니다. 내 예제에서는 로그가 잘 인쇄되었지만 버튼은 여전히 ​​강조 표시되어 있습니다.
Whitebear

@Whitebear 당신은 당신의 질문에 언급하지 않았습니다. 어쨌든 onTouchListener원하는만큼을 사용할 수 있습니다 . 에 의해 이벤트를 소비 할 필요가 없습니다 return true.
GensaGames

@Whitebear OR 선택기를 제거하고를 통해 버튼에 원색을 설정하십시오 backgroundColor.
GensaGames

얘들 아, 당신은 여전히 ​​원래 게시물에 쓴 내용을 다루고 있지 않습니다 ..
Whitebear

0

버튼에 배경을 지정하면 클릭시 색상이 변경되지 않습니다.

 <color name="myColor">#000000</color>

버튼에 backgrount로 설정하십시오.

android:background="@color/myColor"

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.