Tombol di kiri dalam keadaan yang disorot dengan touchListener dan clickListener

10

Saya mengalami masalah dengan Tombol saya tetap dalam keadaan yang disorot, setelah melakukan hal berikut:

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

    }
}

Mengenai kode di atas, ketika menggunakannya, saya mengharapkan klik tombol untuk ditangani oleh sentuhan, dan dengan mengembalikan "benar" penanganannya harus berhenti di touchListener.

Tapi ini bukan masalahnya. Tombol tetap dalam kondisi yang disorot, meskipun klik dipanggil.

Apa yang saya dapatkan adalah:

Test - calling onClick
Test - Performing click

di sisi lain, jika saya menggunakan kode berikut, tombol diklik, cetakan yang sama, tetapi tombol tidak berakhir terjebak dalam keadaan yang disorot:

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

    }
}

Saya agak bingung apa rantai responden untuk acara sentuhan. Dugaan saya adalah:

1) TouchListener

2) DaftarKlik

3) ParentViews

Bisakah seseorang mengonfirmasi hal ini juga?

Shirohige
sumber
Apa yang sebenarnya ingin Anda lakukan, apakah menangani dengan menyentuh atau mengubah warna hanya dengan menekan?
Haider Saleem
Saya ingin menjalankan beberapa logika yang berhubungan dan kemudian memanggil performClick dan agar tidak mengubah warna tombol.
Shirohige
@ Whitebear Silakan periksa jawabannya di bawah ini. Mungkin saya dapat menambahkan informasi lebih lanjut.
GensaGames
Ini mungkin membantu Anda dengan memahami aliran acara sentuhan. Tidak jelas apa yang Anda inginkan terjadi. Apakah Anda ingin penangan klik dan melakukan klik melakukan? Apakah Anda ingin tombol untuk beralih dari warna awal ke kondisi yang ditetapkan oleh filter warna lalu kembali ke warna awal?
Cheticamp
Izinkan saya menjelaskan apa yang saya maksud. Saya memiliki touchListener dan clickListener pada tombolnya. Sentuhan mendahului klik menurut prioritas dan mengembalikan true jika itu menangani acara, yang berarti bahwa tidak ada orang lain yang harus menanganinya. Ini persis apa yang saya lakukan melalui sentuhan, menangani klik dan mengembalikan true, namun tombol masih tetap disorot meskipun pendengar onclick disebut dan aliran dilakukan dengan benar.
Shirohige

Jawaban:

10

Kustomisasi semacam itu tidak memerlukan modifikasi terprogram. Anda dapat melakukannya hanya dalam xmlfile. Pertama-tama, hapus setOnTouchListenermetode yang Anda berikan onCreatesepenuhnya. Selanjutnya, tentukan warna pemilih dalam res/colordirektori seperti berikut. (jika direktori tidak ada, buat itu)

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>

Sekarang, atur ke app:backgroundTintatribut tombol :

<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" />


Hasil Visual:

masukkan deskripsi gambar di sini



Diedit: (untuk mengatasi masalah acara sentuh)

Dari sudut pandang keseluruhan, aliran acara sentuh dimulai dari Activity, kemudian mengalir ke tata letak (dari induk ke tata letak anak), lalu ke tampilan. (LTR mengalir dalam gambar berikut)

masukkan deskripsi gambar di sini

Ketika acara sentuhan mencapai pandangan sasaran, pandangan dapat menangani acara tersebut kemudian memutuskan untuk lulus ke sebelumnya layout / kegiatan atau tidak (kembali falsedari truedalam onTouchmetode). (Aliran RTL pada gambar di atas)

Sekarang mari kita lihat View 's kode sumber untuk mendapatkan wawasan yang lebih dalam sentuhan acara mengalir. Dengan melihat implementasi dari dispatchTouchEvent, kita akan melihat bahwa jika Anda mengatur OnTouchListenertampilan dan kemudian kembali truedalam onTouchmetodenya, onTouchEventtampilan tidak akan dipanggil.

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

Sekarang, lihat onTouchEventmetode di mana tindakan itu berada MotionEvent.ACTION_UP. Kami melihat bahwa tindakan melakukan-klik terjadi di sana. Jadi, kembali truedi OnTouchListener's onTouchdan akibatnya tidak memanggil onTouchEvent, menyebabkan tidak memanggil OnClickListener' s onClick.

Ada masalah lain dengan tidak memanggil onTouchEvent, yang terkait dengan keadaan ditekan dan Anda sebutkan dalam pertanyaan. Seperti yang dapat kita lihat di blok kode di bawah ini, ada sebuah instance dari UnsetPressedStatepanggilan itu ketika dijalankan. Hasil dari tidak menelepon adalah bahwa tampilan macet dalam keadaan tertekan dan gambar yang dapat ditarik tidak berubah. 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);
    }
}


Mengenai uraian di atas, Anda dapat mengubah kode dengan memanggil setPressed(false)diri Anda untuk mengubah status yang dapat digambar di mana tindakan tersebut berada 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;
    }
});
aminografi
sumber
Ini bukan yang saya cari teman saya. Saya ingin memahami perubahan perilaku dalam kedua situasi yang saya jelaskan di atas. Jika ada sesuatu yang bisa saya uraikan, beri tahu saya. Saya tidak ingin menghapus handler sentuh atau handler klik. Silakan periksa komentar saya untuk jawaban di atas.
Shirohige
@ Whebebear: Saya sudah memperbarui jawabannya. Silakan periksa, bung.
aminografi
Itu jawaban terperinci yang bagus, dan saya akan menerimanya. Beberapa petunjuk untuk berubah: 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.Dalam kasus saya onClick dipanggil. mUnsetPressedState memeriksa apakah itu nol sebelum disetel ke false dan juga runnable tidak yakin untuk berjalan jika kita melakukan prapengaturan. Saya tidak begitu mengerti bagaimana Anda menyimpulkan bahwa itu harus disetel ke salah
Whitebear
The onClickdisebut karena Anda panggil v.performClick();. Silakan periksa kode di atas pada MotionEvent.ACTION_UPbagian lagi, setPressed(false)dipanggil pula, apakah mUnsetPressedStateitu nol atau tidak, apakah prepressedbenar atau tidak. Perbedaannya terletak pada cara menelepon setPressed(false)yang bisa melalui post/ postDelayedatau langsung.
aminografi
2

Anda mengacau touchdan focusacara. Mari kita mulai dengan memahami perilaku dengan warna yang sama. Secara default, ada yang Selectorditetapkan sebagai latar belakang Buttondi Android. Jadi cukup mengubah warna latar belakang, make adalah statis (warna tidak akan berubah). Tapi itu bukan perilaku asli.

Selector mungkin terlihat seperti ini.

<?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>

Seperti yang Anda lihat di atas, ada negara bagian focuseddan negara bagian pressed. Dengan pengaturan onTouchListenerAnda akan menangani acara sentuh, yang tidak ada hubungannya dengan focus.

SelectorTombol harus menggantikan focusacara dengan touchselama acara klik pada tombol. Tetapi pada bagian pertama dari kode Anda, Anda mencegat acara untuk touch(mengembalikan true from callback). Perubahan warna tidak dapat dilanjutkan lebih lanjut dan membeku dengan warna yang sama. Dan itulah mengapa varian kedua (tanpa intersepsi) berfungsi dengan baik dan itu adalah kebingungan Anda.

MEMPERBARUI

Yang perlu Anda lakukan hanyalah mengubah perilaku dan warna untuk Selector. Misalnya dengan menggunakan latar belakang berikutnya untuk Button. DAN hapus sama sekali onTouchListenerdari implementasi Anda.

<?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>
GensaGames
sumber
Bagaimana Anda mengubah contoh pertama agar berfungsi dengan baik?
Shirohige
Saya tidak ingin menghapus pendengar sentuh dari implementasi saya. karena saya ingin menangkap klik pada tampilan tertentu menggunakan penangan sentuh, dan kemudian menanganinya sendiri (menggunakan performClick) dan mengembalikan true dari pendengar sentuh untuk memberi tahu penangan lain bahwa tidak ada penanganan lebih lanjut yang dilakukan. Log dicetak dengan baik dalam contoh saya, namun tombol masih tetap disorot.
Shirohige
@ Whebebear Anda tidak menyebutkan hal itu dalam pertanyaan Anda. Dengan cara apa pun, Anda dapat menggunakan sebanyak yang onTouchListenerAnda inginkan. Anda tidak perlu mengkonsumsi acara, oleh return true.
GensaGames
@ Whitebear ATAU Hapus pemilih dan atur warna mentah ke tombol via backgroundColor.
GensaGames
teman-teman, Anda masih belum membahas apa yang saya tulis di posting asli ..
Shirohige
0

jika Anda menetapkan latar belakang tombol, itu tidak akan mengubah warna saat klik.

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

dan atur sebagai backgrount ke tombol Anda

android:background="@color/myColor"
Haider Saleem
sumber
0

Anda bisa menggunakan Chips bahan untuk bukan tampilan Tombol. lihat: https://material.io/develop/android/components/chip di sana mereka menangani acara-acara hililghted dan Anda dapat menyesuaikan dengan menerapkan tema.

Prabudda Fernando
sumber