Android EditText silme (geri silme) tuşu olayı


123

Bir editText için delete (geri al) tuşu olayını nasıl tespit edebilirim? TextWatcher'ı kullanmayı denedim, ancak editText boş olduğunda, sil tuşuna bastığımda hiçbir şey olmuyor. Silme tuşunu algılamak istiyorum, metin olmasa bile editText tuşuna basın.

Yanıtlar:


173

NOT: onKeyListenerYazılım klavyelerinde çalışmaz.

Sen ayarlayabilirsiniz OnKeyListenersizin için editTextherhangi bir tuşa basın algılayabilir böylece
EDIT: Biz kontrol ediyoruz yaygın bir hata KeyEvent.KEYCODE_BACKiçin backspace, ama gerçekten öyle KeyEvent.KEYCODE_DEL(Gerçekten adı çok kafa karıştırıcı olduğunu!)

editText.setOnKeyListener(new OnKeyListener() {                 
    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        //You can identify which key pressed buy checking keyCode value with KeyEvent.KEYCODE_
        if(keyCode == KeyEvent.KEYCODE_DEL) {  
            //this is for backspace
        }
        return false;       
    }
});

9
yeni denedim, ancak görünüşe göre onKeyListeners geri boşlukları kaydetmiyor.
stefs

3
Yumuşak klavye için çalışmayacaktır. Bu yalnızca donanım girişi için çalışacaktır.
Varundroid

6
Benim Nexus4 (çalıştıran stok KitKat) bu does yazılım klavye için çalışmalarını.
Matthias

10
Yumuşak tuşlar için doesnt iş eğer SO, neden android platform altında / içinde kabul Bu cevap ..
DJphy

33
kullanmak event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DELEğer ateşe olay istemiyorsanız, backspace basın başına iki kez
Fonix

83

Sordun uzun zaman oldu ama ben de aynı sorunu yaşadım. Estel tarafından daha önce de belirtildiği gibi, temel dinleyicilerle ilgili sorun, yalnızca donanım klavyeleriyle çalışmalarıdır. Bunu bir IME (yazılım klavyesi) ile yapmak için çözüm biraz daha karmaşıktır.

Biz aslında geçersiz kılma istediğiniz tek yöntemdir sendKeyEventiçinde EditTextbireyin InputConnectionsınıfında. Bu yöntem, bir IME'de önemli olaylar gerçekleştiğinde çağrılır. Ancak bunu geçersiz EditTextkılmak için, onCreateInputConnectionyöntemi geçersiz kılan ve varsayılan InputConnectionnesneyi bir proxy sınıfına saran bir özel uygulamamız gerekir ! : |

Kulağa karmaşık geliyor, ancak işte yapabileceğim en basit örnek:

public class ZanyEditText extends EditText {

    private Random r = new Random();

    public ZanyEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public ZanyEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ZanyEditText(Context context) {
        super(context);
    }

    public void setRandomBackgroundColor() {
        setBackgroundColor(Color.rgb(r.nextInt(256), r.nextInt(256), r
                .nextInt(256)));
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        return new ZanyInputConnection(super.onCreateInputConnection(outAttrs),
                true);
    }

    private class ZanyInputConnection extends InputConnectionWrapper {

        public ZanyInputConnection(InputConnection target, boolean mutable) {
            super(target, mutable);
        }

        @Override
        public boolean sendKeyEvent(KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN
                    && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
                ZanyEditText.this.setRandomBackgroundColor();
                // Un-comment if you wish to cancel the backspace:
                // return false;
            }
            return super.sendKeyEvent(event);
        }

    }

}

Çağrının olduğu satır, setRandomBackgroundColorözel geri alma eylemimin gerçekleştiği yerdir. Bu durumda, EditTextarka plan rengini değiştirir.

Bunu XML'den şişiriyorsanız, etiket olarak tam paket adını kullanmayı unutmayın:

<cc.buttfu.test.ZanyEditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/somefield"
></cc.buttfu.test.ZanyEditText>

27
Geçenlerde Jelly Bean'de aynı sorunla karşılaştım. SendKeyEvent (...) (hiç çağrılmayan) yerine deleteSurroundingText (...) 'i geçersiz kılmak zorunda kalmam dışında bu çözümün çoğunlukla işe yaradığını buldum. Umarım bu başka birine yardımcı olur!
Brandon

Yukarıdaki @Brandon yorumuyla birleştirilen bu cevap, bunu benim için çalıştı. Şimdi merak ettiğim şey, bunun JellyBean öncesi cihazlarda nasıl çalışacağı.
Christopher Perry

Benim için 2.2 ve 2.3 cihazlarda kabul edilen cevapla çalışıyor.
Christoph

/: o ... 2.3 iki kez backspace için anahtar olay ateş ediyor gibi görünüyor
Jeff

25
Bu, edittext boş olduğunda çalışmaz, edittext boş olduğunda ve metin olmadığında silme anahtarı için nasıl olay alınacağına dair herhangi bir fikir var mı? 4.2
Rickster

69

Bu, Idris'in cevabına sadece bir ektir ve deleteSurroundingText'e geçersiz kılmayı da ekler. Bununla ilgili daha fazla bilgiyi burada buldum: Android: WebView / BaseInputConnection'da Backspace

package com.elavon.virtualmerchantmobile.utils;

import java.util.Random;

import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.widget.EditText;

public class ZanyEditText extends EditText {

    private Random r = new Random();

    public ZanyEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public ZanyEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ZanyEditText(Context context) {
        super(context);
    }

    public void setRandomBackgroundColor() {
        setBackgroundColor(Color.rgb(r.nextInt(256), r.nextInt(256), r
                .nextInt(256)));
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        return new ZanyInputConnection(super.onCreateInputConnection(outAttrs),
                true);
    }

    private class ZanyInputConnection extends InputConnectionWrapper {

        public ZanyInputConnection(InputConnection target, boolean mutable) {
            super(target, mutable);
        }

        @Override
        public boolean sendKeyEvent(KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN
                    && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
                ZanyEditText.this.setRandomBackgroundColor();
                // Un-comment if you wish to cancel the backspace:
                // return false;
            }
            return super.sendKeyEvent(event);
        }


        @Override
        public boolean deleteSurroundingText(int beforeLength, int afterLength) {       
            // magic: in latest Android, deleteSurroundingText(1, 0) will be called for backspace
            if (beforeLength == 1 && afterLength == 0) {
                // backspace
                return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
                    && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
            }

            return super.deleteSurroundingText(beforeLength, afterLength);
        }

    }

}

3
Teşekkür ederim! deleteSurroundingTextBiraz ben sayısız diğer çözümleri denedikten sonra gereken şeydi.
Adam Rosenfield

5
Bu çözüm, önceki Android sürümlerinde benim için gerçekten iyi çalıştı, ancak maalesef deleteSurroundingText yalnızca 4.4 (KitKat) 'da beyaz boşluk kaldırılırken çağrılıyor. Hem Nexus4 hem de 7 üzerinde test ettim
Dean

1
EditText çok satırlı olduğunda deleteSurroundingText gerekli görünüyor. Tuhaf
Alex Sorokoletov

7
Çok teşekkürler adamım, deleteSurroundText ile çalışmadı. Android o kadar rastgele ki, onu androm olarak yeniden adlandırmaları gerekiyor.
Torsten Ojaperv

2
Benim için çalışıyor, ancak artık noktalama işaretlerini veya boşlukları silemiyorum!
jaytj95

29

İşte tüm API'ler için çalışan kolay çözümüm:

private int previousLength;
private boolean backSpace;

// ...

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    previousLength = s.length();
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}

@Override
public void afterTextChanged(Editable s) {
    backSpace = previousLength > s.length();

    if (backSpace) {

        // do your stuff ...

    } 
}

GÜNCELLEME 17.04.18 .
Yorumlarda belirtildiği gibi, bu çözüm EditText boşsa (diğer çözümlerin çoğunda olduğu gibi) backspace basını izlemez.
Ancak çoğu kullanım durumu için yeterlidir.
Not: Bugün benzer bir şey yaratmam gerekseydi, yapardım:

public abstract class TextWatcherExtended implements TextWatcher {

    private int lastLength;

    public abstract void afterTextChanged(Editable s, boolean backSpace);

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        lastLength = s.length();
    }

    @Override
    public void afterTextChanged(Editable s) {
        afterTextChanged(s, lastLength > s.length());
    }  
}

O zaman bunu normal bir TextWatcher olarak kullanın:

 editText.addTextChangedListener(new TextWatcherExtended() {
        @Override
        public void afterTextChanged(Editable s, boolean backSpace) {
           // Here you are! You got missing "backSpace" flag
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // Do something useful if you wish.
            // Or override it in TextWatcherExtended class if want to avoid it here 
        }
    });

Tam ihtiyacım olan şey! Teşekkür ederim!
DH28

9
TextWatcher boş bir EditText'i tetiklemiyor
Dan Neacșu

@Leo Droidcoder Benzer bir çözümde kullandım. Açık, öz ve mükemmel çalışıyor ... şerefe.
AJW

Bu algoritmanın, yazdıktan sonra boşluğa tıklarsınız gibi bir kusuru vardır, bu durumda önceki uzunluk s.uzunluktan büyüktür
Marcin S.

2
Seçimi (otomatik tamamlama) kullanmadığınız sürece çalışır
Javatar

13

Bir çözüm bulmak için 2 gün gönderdim ve işe yarayan bir çözüm buldum :) (yazılım tuşlarında)

public TextWatcher textWatcher = new TextWatcher() {
@Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {   } 

@Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (count == 0) {
        //Put your code here.
        //Runs when delete/backspace pressed on soft key (tested on htc m8)
        //You can use EditText.getText().length() to make if statements here
        }
    }

@Override
    public void afterTextChanged(Editable s) {
    }
}

Metin izleyiciyi EditText'inize ekledikten sonra:

yourEditText.addTextChangedListener(textWatcher);

Umarım başka bir android cihazda da çalışır (samsung, LG, vb.).


Cihaz HTC Desire (HTC yine de yaygındır: -P)
Junaid

beyaz boşluk
yazıldıysa

Brilliant no 1 answer bro :)
Gundu Bandgar

6
Bu tamamen çalışmıyor. count == 0 yalnızca edittext boş olduğunda olacaktır!
Leo Droidcoder

@MarcAlexander Bu cevaptan emin değilim, ancak çözümümü yukarıdaki cevapta kontrol edebilirsiniz
Leo Droidcoder

6

Mükemmel çalışan basit çözümüm. Bir bayrak eklemelisiniz. Kod pasajım:

editText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            if (after < count) {
                isBackspaceClicked = true;
            } else {
                isBackspaceClicked = false;
            }
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) { }

        @Override
        public void afterTextChanged(Editable s) {
            if (!isBackspaceClicked) {
                // Your current code
            } else {
                // Your "backspace" handling
            }
        }

textChangeListner hiçbir zaman emptTextview'u çağırmadı.
Janardhan R

3

TextWatcher ile EditText oluşturma örneği

EditText someEdit=new EditText(this);
//create TextWatcher for our EditText
TextWatcher1 TW1 = new TextWatcher1(someEdit);
//apply our TextWatcher to EditText
        someEdit.addTextChangedListener(TW1);

özel TextWatcher

public class TextWatcher1 implements TextWatcher {
        public EditText editText;
//constructor
        public TextWatcher1(EditText et){
            super();
            editText = et;
//Code for monitoring keystrokes
            editText.setOnKeyListener(new View.OnKeyListener() {
                @Override
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    if(keyCode == KeyEvent.KEYCODE_DEL){
                        editText.setText("");
                    }
                        return false;
                }
            });
        }
//Some manipulation with text
        public void afterTextChanged(Editable s) {
            if(editText.getText().length() == 12){
                editText.setText(editText.getText().delete(editText.getText().length() - 1, editText.getText().length()));
                editText.setSelection(editText.getText().toString().length());
            }
            if (editText.getText().length()==2||editText.getText().length()==5||editText.getText().length()==8){
                editText.setText(editText.getText()+"/");
                editText.setSelection(editText.getText().toString().length());
            }
        }
        public void beforeTextChanged(CharSequence s, int start, int count, int after){
        }
        public void onTextChanged(CharSequence s, int start, int before, int count) {



        }
    }

1

Kotlin kullanan biri için

addOnTextChanged bazı durumların üstesinden gelmek için yeterince esnek değildir (örn: metin düzenleme boşken kullanıcının sil tuşuna basıp basmadığını algılama)

setOnkeyListeneryumuşak klavye veya sabit klavye bile çalıştı! ama sadece bazı cihazlarda . Benim durumumda, Samsung s8'de çalışıyor ancak Xiaomi mi8 se'de çalışmıyor.

kotlin kullanıyorsanız, çapraz çizgi işlevini kullanabilirsiniz doOnTextChanged, bu aynıdır addOnTextChangedancak düzenleme metni boş olsa bile geri arama tetiklenir.

NOT: doOnTextChanged, Android KTX kitaplığının bir parçasıdır


2
Muhtemelen doOnTextChanged uzantı işlevinin Android KTX kitaplığında erişilebilir olduğunu belirtebilirsiniz
taş

2
Ancak geri aramanın "tetiklenmediği, hatta düzenleme metni boş olduğu" görülüyor. Boşluk için silme (geri alma) müdahalesi ile biraz pasaj sağlayabilir misiniz EditText? Peşin teşekkür ederiz
taş

1
ah, bir proje geliştirdiğimde test ettim. Benim durumumda xiaomi mi8se'de, edittext boş olduğunda ve sil tuşuna bastığınızda, geri arama yapılmadı. Bu cümle için pasaj arayacağım.
Mạnh Hoàng Huynh


0

Bu benim için çalışıyor gibi görünüyor:

public void onTextChanged(CharSequence s, int start, int before, int count) {
    if (before - count == 1) {
        onBackSpace();
    } else if (s.subSequence(start, start + count).toString().equals("\n")) {
        onNewLine();
    }
}

0

Dialog'da da aynı sorunla karşılaşıyorum .. çünkü setOnKeyListener kullanıyorum .. Ama varsayılan dönüşü true olarak ayarlıyorum. Aşağıdaki kod gibi değişiklikten sonra benim için iyi çalışıyor ..

    mDialog.setOnKeyListener(new Dialog.OnKeyListener() {

        @Override
        public boolean onKey(DialogInterface arg0, int keyCode,
                             KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                mDialog.dismiss();
                return true;
            }
            return false;//this line is important 

        }
    });

0

@Jiff dayanarak ZanyEditTextburada WiseEditTextbirliktesetSoftKeyListener(OnKeyListener)

package com.locopixel.seagame.ui.custom;

import java.util.Random;

import android.content.Context;
import android.graphics.Color;
import android.support.v7.widget.AppCompatEditText;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;

public class WiseEditText extends AppCompatEditText {

    private Random r = new Random();
    private OnKeyListener keyListener;

    public WiseEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public WiseEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public WiseEditText(Context context) {
        super(context);
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        return new MyInputConnection(super.onCreateInputConnection(outAttrs),
                true);
    }

    private class MyInputConnection extends InputConnectionWrapper {

        public MyInputConnection(InputConnection target, boolean mutable) {
            super(target, mutable);
        }

        @Override
        public boolean sendKeyEvent(KeyEvent event) {
            if (keyListener != null) {
                keyListener.onKey(WiseEditText.this,event.getKeyCode(),event);
            }
            return super.sendKeyEvent(event);
        }

        @Override
        public boolean deleteSurroundingText(int beforeLength, int afterLength) {       
            // magic: in latest Android, deleteSurroundingText(1, 0) will be called for backspace
            if (beforeLength == 1 && afterLength == 0) {
                // backspace
                return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
                    && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
            }

            return super.deleteSurroundingText(beforeLength, afterLength);
        }

    }

    public void setSoftKeyListener(OnKeyListener listener){
        keyListener = listener;
    }

}

Her silme tuşu olayı için iki kez çağrılmaktadır.
Pankaj Kumar

0

Benim sorunum, bir özelliğe sahip olmamdı Textwatcher, bu yüzden özel oluşturmak istemediğimden OnKeyListenerbir iyiye eklemek EditTextistemedim EditText. Yöntemimde geri al tuşuna basılıp basılmadığını tespit etmek istedim afterTextChanged, bu yüzden olayımı tetiklememeliyim.

Ben bunu böyle çözdüm. Umarım birisi için yararlı olur.

public class CustomTextWatcher extends AfterTextChangedTextWatcher {

private boolean backspacePressed;

@Override
public void afterTextChanged(Editable s) {
    if (!backspacePressed) {
        triggerYourEvent();
    }
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
    super.onTextChanged(s, start, before, count);
    backspacePressed = count == 0; //if count == 0, backspace is pressed
}
}

0

@ Jeff'in çözümünü 4.2, 4.4, 6.0 sürümlerinde test ettim. 4.2 ve 6.0'da iyi çalışıyor. Ancak 4.4'te çalışmıyor.

Bu sorunu aşmanın kolay bir yolunu buldum. Kilit nokta, başlangıçta EditText içeriğine görünmez bir karakter eklemek ve kullanıcının imleci bu karakterden önce hareket ettirmesine izin vermemek. Benim yöntemim, üzerinde Sıfır Genişlikli bir ImageSpan olan bir beyaz boşluk karakteri eklemektir. İşte kodum.

                @Override
                public void afterTextChanged(Editable s) {
                    String ss = s.toString();
                    if (!ss.startsWith(" ")) {
                        int selection = holder.editText.getSelectionEnd();
                        s.insert(0, " ");
                        ss = s.toString();
                        holder.editText.setSelection(selection + 1);
                    }
                    if (ss.startsWith(" ")) {
                        ImageSpan[] spans = s.getSpans(0, 1, ImageSpan.class);
                        if (spans == null || spans.length == 0) {
                            s.setSpan(new ImageSpan(getResources().getDrawable(R.drawable.zero_wdith_drawable)), 0 , 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                        }
                    }
                }

Ve bir SelectionChangeListener'a sahip özel bir EditText'e ihtiyacımız var.

public class EditTextSelectable extends android.support.v7.widget.AppCompatEditText {
public interface OnSelectChangeListener {
    void onSelectChange(int start, int end);
}

private OnSelectChangeListener mListener;

public void setListener(OnSelectChangeListener listener) {
    mListener = listener;
}

...constructors...

@Override
protected void onSelectionChanged(int selStart, int selEnd) {
    if (mListener != null) {
        mListener.onSelectChange(selStart, selEnd);
    }
    super.onSelectionChanged(selStart, selEnd);
}

}

Ve son adım

holder.editText.setListener(new EditTextSelectable.OnSelectChangeListener() {
                @Override
                public void onSelectChange(int start, int end) {
                    if (start == 0 && holder.editText.getText().length() != 0) {
                        holder.editText.setSelection(1, Math.max(1, end));
                    }
                }
            });

Ve şimdi, işimiz bitti ~ EditText gerçek içeriğe sahip olmadığında geri al tuşu olayını tespit edebiliriz ve kullanıcı hilemiz hakkında hiçbir şey bilmeyecektir.


0

Bu soru eski olabilir, ancak cevap bir TextWatcher kullanarak gerçekten basittir.

int lastSize=0;
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
    //2. compare the old length of the text with the new one
    //3. if the length is shorter, then backspace was clicked
    if (lastSize > charSequence.length()) {
        //4. Backspace was clicked
        //5. perform action
    }
    //1. get the current length of of the text
    lastSize = charSequence.length();
}

Önceki çözümlerde olduğu gibi, bu da otomatik tamamlama / önerilerle tetiklenebilir.
Stonz2

0

Yumuşak klavyeyle çalışan gerçekten basit bir çözüm buldum.

override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) {
    text?.let { 
        if(count < before) {
            Toast.makeText(context, "backspace pressed", Toast.LENGTH_SHORT).show()
            // implement your own code
        }
    }
}

-3

Etkinlik için bir anahtar dinleyici ayarlayabilir ve geri arama yönteminde, kullanıcının hangi tuşa bastığını tespit edebilirsiniz. Aşağıdaki kod referansınız içindir. Umarım yardımcı olur.

//after user hits keys, this method would be called.
public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (editText.isFocused()) {
            switch (keyCode) {
            case KeyEvent.KEYCODE_DEL:  //delete key
                Log.i("INFO", "delete key hit"); //you should see this log in ddms after you hit delete key
                break;
            }
        }
        return super.onKeyUp(keyCode, event);
    }

Bu çözüm işaretlendi - KEYCODE_DEL, yalnızca düzenleme metni bunu kendi başına ele almıyorsa etkinliğe atılacaktır. Örneğin, editText'te metin olmadığında veya bir metin olduğunda, ancak imleç en baştadır. Benim durumumda tam olarak bu davranışa ihtiyacım olması komik
Anton Kizema

Aktivitemde EditText yok ve sadece klavyenin programlı olarak görünmesini sağlıyorum. Tüm yumuşak klavye tuşlarını yakalamam gerekiyor ve bu çalışan tek çözüm gibi görünüyor. Diğeri, dispatchKeyEvent yöntemini geçersiz kılıyor. Maalesef JellyBean'den başlayarak IME, DELETE anahtarı için bir KeyEvent göndermez. developer.android.com/reference/android/view/KeyEvent.html
Bemipefe
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.