EditText dışında tıkladıktan sonra android yumuşak klavye gizlemek için?


355

Tamam, herkes bir klavyeyi gizlemek için uygulamanız gerektiğini biliyor:

InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);

Ancak buradaki en önemli şey, kullanıcı bir EditTextveya softKeyboard olmayan başka bir yere dokunduğunda veya seçtiğinde klavyeyi nasıl gizleyeceğinizdir ?

onTouchEvent()Ebeveynim üzerinde kullanmaya çalıştım, Activityancak yalnızca kullanıcı başka bir görünümün dışına dokunursa ve kaydırma görünümü yoksa çalışır.

Herhangi bir başarı olmadan dokunma, tıklama, odak dinleyici uygulamaya çalıştım.

Hatta dokunma olaylarını durdurmak için kendi kaydırma görünümümü uygulamaya çalıştım, ancak tıklatılan görünümü değil, yalnızca olayın koordinatlarını alabilirim.

Bunu yapmanın standart bir yolu var mı ?? iPhone'da gerçekten kolaydı.


Kaydırma görüntüsünün gerçekten sorun olmadığını, oradaki etiketler olduğunu fark ettim. Görünüm, TextView, EditText, TextView, EditText, vb. Gibi dikey bir mizanpajdır ve textViews, düzenleme metninin odaklamayı kaybetmesine ve klavyeyi gizlemesine izin vermez
htafoya

Bunun için bir çözüm bulabilirsiniz getFields(): stackoverflow.com/questions/7790487/…
Reto

Klavye bu çabaya değer olup olmadığını o şüpheli olduğunu söyleyebilirim yüzden basarak dönüş düğmesi ile kapatılabilir
gerrytan

4
Bu cevabı buldum: stackoverflow.com/a/28939113/2610855 En iyisi.
Loenix

Yanıtlar:


577

Aşağıdaki kod parçası klavyeyi gizler:

public static void hideSoftKeyboard(Activity activity) {
    InputMethodManager inputMethodManager = 
        (InputMethodManager) activity.getSystemService(
            Activity.INPUT_METHOD_SERVICE);
    inputMethodManager.hideSoftInputFromWindow(
        activity.getCurrentFocus().getWindowToken(), 0);
}

Bunu bir yardımcı program sınıfına koyabilir veya bir etkinlik içinde tanımlıyorsanız, etkinlik parametresinden kaçının veya çağırın hideSoftKeyboard(this).

En zor kısım ne zaman arayacağınızdır. ViewEtkinliğinizdeki her bir yolla yinelenen bir yöntem yazabilir ve bunun o bileşene instanceof EditTextbir kayıtlı değilse setOnTouchListenerve her şeyin yerinde olup olmadığını kontrol edebilirsiniz . Bunu nasıl yapacağınızı merak ediyorsanız, aslında oldukça basittir. Yaptığınız şey, aşağıdaki gibi özyinelemeli bir yöntem yazıyorsunuz, aslında bunu özel yazı tipleri vb. Gibi bir şey yapmak için kullanabilirsiniz ... İşte yöntem

public void setupUI(View view) {

    // Set up touch listener for non-text box views to hide keyboard.
    if (!(view instanceof EditText)) {
        view.setOnTouchListener(new OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                hideSoftKeyboard(MyActivity.this);
                return false;
            }
        });
    }

    //If a layout container, iterate over children and seed recursion.
    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            View innerView = ((ViewGroup) view).getChildAt(i);
            setupUI(innerView);
        }
    }
}

Hepsi bu setContentView, faaliyetinizde sizden sonra bu yöntemi çağırmanız yeterlidir. Hangi parametreyi geçeceğinizi merak ediyorsanız, bu idana konteynerin o parametresidir . Bir atama idgibi üst kabına göre

<RelativeLayoutPanel android:id="@+id/parent"> ... </RelativeLayout>

ve ara setupUI(findViewById(R.id.parent)), hepsi bu.

Bunu etkili bir şekilde kullanmak istiyorsanız, genişletilmiş bir Activityyöntem oluşturabilir ve uygulamanızdaki diğer tüm etkinliklerin bu etkinliği genişletmesini setupUI()ve onCreate()yöntemde çağırmasını sağlayabilirsiniz .

Umarım yardımcı olur.

Birden fazla etkinlik kullanıyorsanız, ana düzen için ortak kimliği tanımlayın. <RelativeLayout android:id="@+id/main_parent"> ... </RelativeLayout>

Daha sonra bir sınıfı genişletin Activityve setupUI(findViewById(R.id.main_parent))içinde tanımlayın OnResume()ve `` Etkinlik yerine bu sınıfı genişletinin your program


Yukarıdaki fonksiyonun Kotlin versiyonu:

@file:JvmName("KeyboardUtils")

fun Activity.hideSoftKeyboard() {
    currentFocus?.let {
        val inputMethodManager = ContextCompat.getSystemService(this, InputMethodManager::class.java)!!
        inputMethodManager.hideSoftInputFromWindow(it.windowToken, 0)
    }
}

Kendimi test etmedim ama işe yarayacak gibi görünüyor ve yüksek değerlendirmeleri var gibi ben bu kabul edilen cevabı değiştirir.
Mart'ta htafoya

4
Çok zor olmamalı mı? Şu anda Android programlama dışındayım, yanılıyorsam beni düzeltin. Bir şekilde odaklanmış EditText'i her an izleyebilir ve bir OnTouchEvent sırasında odağını kaybetmesini isteyebilirsiniz?
Navneeth G

25
Başka kimsenin bu sorunla karşılaşıp çalışmadığından emin değilim, ancak hiçbir şey odaklanmazsa hideSoftKeyboard'u çağırdığınızda uygulamanın çökmesine neden oluyor. Bunu, yöntemin ikinci satırını çevreleyerek çözebilirsinizif(activity.getCurrentFocus() != null) {...}
Frank Cangialosi

14
Bu yaklaşımdaki sorun, diğer tüm görüşlerin OnTouchListenerkendileri için bir ayar yapmaya gerek olmadığını varsaymasıdır . Bu mantığı ViewGroup.onInterceptTouchEvent(MotionEvent)bir kök görünümüne ayarlayabilirsiniz .
Alex.F

2
Klavyeyi açık tutarak diğer kontrolleri tıkladığımda çalışmıyor
mohitum

285

Aşağıdaki adımları uygulayarak bunu başarabilirsiniz:

  1. Aşağıdaki özellikleri ekleyerek üst görünümün (etkinliğinizin içerik görünümü) tıklanabilir ve odaklanabilir olmasını sağlayın

        android:clickable="true" 
        android:focusableInTouchMode="true" 
    
  2. HideKeyboard () yöntemini uygulama

        public void hideKeyboard(View view) {
            InputMethodManager inputMethodManager =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
            inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
    
  3. Son olarak, düzenleme metninizin onFocusChangeListener öğesini ayarlayın.

        edittext.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    hideKeyboard(v);
                }
            }
        });
    

Aşağıdaki yorumlardan birinde belirtildiği gibi, üst görünüm bir ScrollView ise bu işe yaramayabilir. Böyle bir durumda, tıklanabilir ve focusableInTouchMode doğrudan ScrollView altındaki görünüme eklenebilir.


67
Bence bu doğru cevap. Daha az kod, gereksiz yineleme yok ...
gattshjoty

14
Bu yanıtı çok beğendim. Unutulmaması gereken bir şey, kök öğeme eklerken clickableve focusableInTouchModebu ScrollViewöğeye benim için işe yaramadığı . Ben Sesimin doğrudan ebeveyne eklemek zorunda EditTextbir oldu LinearLayout.
Adam Johns

10
Benim için mükemmel çalıştı. Bununla birlikte, iki düzenleme metni widget'ınız varsa, onfocus'u her ikisine de doğru şekilde uyguladığınızdan emin olmanız gerekir, aksi takdirde gereksiz yere gizleme klavyesini değiştirirsiniz.
Marka A

1
@MarkaA Bu konuda hiçbir sorun yaşamadım, Diğer EditText'i tıkladığımda klavye arka planda tıklandığında olması gerektiği gibi gizlenir. OnFocusChange, sadece odak, hiçbir şey eskrim olduğunda EditText arka planını değiştirerek başka bir işlem var mıydı.
CularBytes

3
@Shrikant - Çoklu düzenleme metinleriyle de titreşim görüyordum. Ben sadece her düzenleme metni yerine üst üzerinde onFocusChangeListener ayarlayın ve (hasFocus) {hideKeyboard (v); } Btwn düzenleme metinlerini değiştirmenin artık titremediğini fark etmedim.
manisha

69

Etkinlik'teki kodu aşağıda geçersiz kılmanız yeterlidir

 @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (getCurrentFocus() != null) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
    }
    return super.dispatchTouchEvent(ev);
}

4
Basit bir çözüm, Aktivite ekleyin ve aynı zamanda parçalarla da ilgilenecek
Rohit Maurya

1
Başka bir çözüm, BaseActivity'yi oluşturmak ve tüm Etkinliklerde genişletmektir
sumit sonawane

1
Mükemmel çözüm
Ahmed Adel Ismail

1
klavyeyi kapatırken beni ekranımın titremesinden kurtardın. Başparmak havaya!
Dimas Mendes

1
Güzel, ama gerçek olamayacak kadar iyiydi: basit, çok kısa ve işe yaradı ... ne yazık ki, bir sorun var: klavye görüntülendiğinde, klavyeyi soran EditText'e her dokunduğumuzda, aşağı iniyor ve otomatik olarak yukarı.
Chrysotribax

60

Kabul edilen cevabı biraz karmaşık buluyorum.

İşte benim çözümüm. OnTouchListenerAna düzeninize bir ekleyin :

findViewById(R.id.mainLayout).setOnTouchListener(this)

ve onTouch yöntemine aşağıdaki kodu koyun.

InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);

Bu şekilde tüm görünümler üzerinde tekrar etmek zorunda kalmazsınız.


@roepit - im bir classCastexception alıyorum bir görünüm için bir düzen oluşturmak için. bir şey mi kaçırıyorum?
katzenhut

Kodunuzu bir yere başvurabilir misiniz? Düzeninize ve etkinlik / fragment et cetera kodunuza bakamadığımda neyin yanlış olduğunu söyleyemem.
roepit

Orada en iyi cevap, hala tho nasıl çalışır etrafında kafamı sarmaya çalışıyor.
user40797

uygulamanın başlık çubuğunu tıklarsanız bu işe yarar mı?
user1506104

1
Bu, klavyeyi gizlemek için mükemmel çalışır! Bunun EditText'i gerçekten odaklamadığını, sadece klavyeyi gizlediğini unutmayın. Düzenleme Metnini de odaktan çıkarmak için, örneğin android:onClick="stealFocusFromEditTexts"üst görünümün xml'sine ve ardından public void stealFocusFromEditTexts(View view) {}etkinliğine ekleyin . Tıklama yönteminin herhangi bir şey yapmasına gerek yoktur, yalnızca üst görünümün odaklanabilir / seçilebilir olması gerekir; bu, alt öğenin odağını çalmak için gereklidir EditText
Jacob R

40

Klavyeyi gizlemek için bir çözüm daha var:

InputMethodManager imm = (InputMethodManager) getSystemService(
    Activity.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);

İşte geçmesi HIDE_IMPLICIT_ONLYkonumunda showFlagve 0pozisyonunda hiddenFlag. Yumuşak klavyeyi zorla kapatacaktır.


3
Teşekkürler bu iş ..... her şeyden önce ben denedim ama im iletişim kutusundan değer alma sırasında çalışmıyor editext metin clo closoing iletişim kutusu ...
PankajAndroid

Teşekkürler, bu sadece çalışıyor ve yukarıdakilerden çok daha temiz! +1
Edmond Tamas

Beklendiği gibi çalışıyor, çözümünüz için teşekkür ederim +1
tryp

Benim için bir cazibe gibi çalışıyor. Zarif çözüm için +1.
saintjab

2
üzgünüm, ama bu yöntem geçiş, bu yüzden klavye durumu zaten kapalı ise, klavyeyi gösterecektir
HendraWD

16

Sorunu biraz çözmeyi başardım, aktivitemde dispatchTouchEvent'i aştım, orada klavyeyi gizlemek için aşağıdakileri kullanıyorum.

 /**
 * Called to process touch screen events. 
 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            touchDownTime = SystemClock.elapsedRealtime();
            break;

        case MotionEvent.ACTION_UP:
            //to avoid drag events
            if (SystemClock.elapsedRealtime() - touchDownTime <= 150){  

                EditText[] textFields = this.getFields();
                if(textFields != null && textFields.length > 0){

                    boolean clickIsOutsideEditTexts = true;

                    for(EditText field : textFields){
                        if(isPointInsideView(ev.getRawX(), ev.getRawY(), field)){
                            clickIsOutsideEditTexts = false;
                            break;
                        }
                    }

                    if(clickIsOutsideEditTexts){
                        this.hideSoftKeyboard();
                    }               
                } else {
                    this.hideSoftKeyboard();
                }
            }
            break;
    }

    return super.dispatchTouchEvent(ev);
}

EDIT: getFields () yöntemi, görünümde metin alanları olan bir dizi döndüren bir yöntemdir. Her dokunuşta bu diziyi oluşturmaktan kaçınmak için, getFields () yönteminde döndürülen sFields adlı statik bir dizi oluşturdum. Bu dizi aşağıdaki gibi onStart () yöntemlerinde başlatılır:

sFields = new EditText[] {mUserField, mPasswordField};


Mükemmel değil, sürükleme olay süresi sadece sezgisel tarama dayanmaktadır, bu yüzden bazen uzun klipsler yaparken gizlenmez ve ayrıca görünüm başına tüm editTexts'i almak için bir yöntem oluşturarak bitirdim; aksi takdirde klavye diğer EditText'e tıklandığında gizlenir ve gösterilir.

Yine de, daha temiz ve daha kısa çözümler bekliyoruz


8
Gelecekte başkalarına yardımcı olmak için, yanıtınızdaki kodu getFields()yönteminizi içerecek şekilde düzenlemeyi düşünür müsünüz ? Kesin olmak zorunda değil, sadece bir EditTextnesne dizisini döndürdüğünü gösteren bazı yorumlara sahip bir örnek .
Squonk

14

OnFocusChangeListener kullanın .

Örneğin:

editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (!hasFocus) {
            hideKeyboard();
        }
    }
});

Güncelleme : Ayrıca onTouchEvent()etkinliğinizde geçersiz kılabilir ve dokunmanın koordinatlarını kontrol edebilirsiniz. Koordinatlar EditText dışındaysa klavyeyi gizleyin.


9
Sorun, bir Etikete veya odaklanamayan diğer görünümlere tıkladığımda düzenleme metninin odağı kaybetmemesidir.
htafoya

Bu durumda bir çözüm daha var. Cevabı güncelledim.
Sergey Glotov

2
onTouchEvent birçok kez çağırdı, bu yüzden bu da iyi bir uygulama değil
Jesus Dimrix

13

Bunu yapmak için etkinlikte dispatchTouchEvent uyguladım:

private EditText mEditText;
private Rect mRect = new Rect();
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);

    int[] location = new int[2];
    mEditText.getLocationOnScreen(location);
    mRect.left = location[0];
    mRect.top = location[1];
    mRect.right = location[0] + mEditText.getWidth();
    mRect.bottom = location[1] + mEditText.getHeight();

    int x = (int) ev.getX();
    int y = (int) ev.getY();

    if (action == MotionEvent.ACTION_DOWN && !mRect.contains(x, y)) {
        InputMethodManager input = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        input.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
    }
    return super.dispatchTouchEvent(ev);
}

ve test ettim, mükemmel çalışıyor!


işe yarıyor, ama sorun şu ki, eğer birden fazla EditText varsa, bunu da düşünmeliyiz, ama cevabınızı beğendim :-)
Lalit Poptani

getActionMasked (ev) kullanımdan kaldırıldı, şimdi şunu kullanın: final int action = ev.getActionMasked (); ilk satır için.
Andrew

12

TextInputEditText kullanarak daha fazla Kotlin & Material Design yolu (bu yaklaşım EditTextView ile de uyumludur ) ...

Aşağıdaki özellikleri ekleyerek üst görünümün (etkinliğinizin / parçanızın içerik görünümü) tıklanabilir ve odaklanabilir olmasını sağlayın

android:focusable="true"
android:focusableInTouchMode="true"
android:clickable="true"

2.Tüm Görünüm için bir uzantı oluşturun (örneğin, bir ViewExtension.kt dosyasının içinde):

fun View.hideKeyboard(){
    val inputMethodManager = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
    inputMethodManager.hideSoftInputFromWindow(this.windowToken, 0)
}

3. TextInputEditText öğesini devralan bir BaseTextInputEditText oluşturun. Görünüm odaklanmadığında klavyeyi gizlemek için onFocusChanged yöntemini uygulayın:

class BaseTextInputEditText(context: Context?, attrs: AttributeSet?) : TextInputEditText(context, attrs){
    override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect)
        if (!focused) this.hideKeyboard()
    }
}

4.Sadece XML'nizde yepyeni özel görünümü arayın:

<android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayout"
        ...>

        <com.your_package.BaseTextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            ... />

    </android.support.design.widget.TextInputLayout> 

Bu kadar. Bu tekrarlayan durumu ele almak için denetleyicilerinizi (parça veya aktivite) değiştirmenize gerek yoktur .


11

Herhangi bir Etkinlikteki (veya Etkinlik sınıfını genişletme) genel boolean dispatchTouchEvent (MotionEvent olayı) geçersiz kıl

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    View view = getCurrentFocus();
    boolean ret = super.dispatchTouchEvent(event);

    if (view instanceof EditText) {
        View w = getCurrentFocus();
        int scrcoords[] = new int[2];
        w.getLocationOnScreen(scrcoords);
        float x = event.getRawX() + w.getLeft() - scrcoords[0];
        float y = event.getRawY() + w.getTop() - scrcoords[1];

        if (event.getAction() == MotionEvent.ACTION_UP 
 && (x < w.getLeft() || x >= w.getRight() 
 || y < w.getTop() || y > w.getBottom()) ) { 
            InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(getWindow().getCurrentFocus().getWindowToken(), 0);
        }
    }
 return ret;
}

Ve tüm yapmanız gereken bu


çalışmasını sağlamanın en kolay yolu buydu. Birden EditTexts ve Scrollview eserleri
RJH

Birden çok EditTexts ile test edilmiştir; işe yarıyor! Tek dezavantajı, sürükleme hareketi yaptığınızda da gizlenmesidir.
RominaV

9

Andre Luis IM'in çözümünü değiştirdim, bunu başardım:

Yumuşak klavyeyi Andre Luiz IM'in yaptığı gibi gizlemek için bir yardımcı program yöntemi oluşturdum:

public static void hideSoftKeyboard(Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager)  activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    inputMethodManager.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0);
}

Ancak, kötü bir performans veren her görünüm için bir OnTouchListener kaydetmek yerine, OnTouchListener'ı yalnızca kök görünüm için kaydettirdim. Etkinlik tüketilene kadar kabarcıklar oluşturduğundan (EditText varsayılan olarak tüketen görünümlerden biridir), kök görünümüne ulaşırsa, bunun nedeni tüketilmemesidir, bu nedenle yumuşak klavyeyi kapatırım.

findViewById(android.R.id.content).setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Utils.hideSoftKeyboard(activity);
        return false;
    }
});

1
Bu benim için daha güvenli görünüyor.
superarts.org

9

Bu konunun oldukça eski olduğunu, doğru cevabın geçerli göründüğünü ve orada çok fazla çalışma çözümü olduğunu biliyorum, ancak aşağıda belirtilen yaklaşımın verimlilik ve zarafet konusunda ek bir yararı olabileceğini düşünüyorum.

Tüm etkinliklerim için bu davranışı gerekir, bu yüzden sınıf Etkinlik devralma bir sınıf CustomActivity oluşturdu ve " dispatchTouchEvent işlevini çengel" . Dikkat edilmesi gereken başlıca iki koşul vardır:

  1. Odak değişmezse ve birisi geçerli giriş alanının dışına dokunuyorsa, IME'yi kapatın
  2. Odak değiştiyse ve bir sonraki odaklanılan öğe herhangi bir giriş alanının örneği değilse, IME'yi kapatın

Bu benim sonucum:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if(ev.getAction() == MotionEvent.ACTION_UP) {
        final View view = getCurrentFocus();

        if(view != null) {
            final boolean consumed = super.dispatchTouchEvent(ev);

            final View viewTmp = getCurrentFocus();
            final View viewNew = viewTmp != null ? viewTmp : view;

            if(viewNew.equals(view)) {
                final Rect rect = new Rect();
                final int[] coordinates = new int[2];

                view.getLocationOnScreen(coordinates);

                rect.set(coordinates[0], coordinates[1], coordinates[0] + view.getWidth(), coordinates[1] + view.getHeight());

                final int x = (int) ev.getX();
                final int y = (int) ev.getY();

                if(rect.contains(x, y)) {
                    return consumed;
                }
            }
            else if(viewNew instanceof EditText || viewNew instanceof CustomEditText) {
                return consumed;
            }

            final InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

            inputMethodManager.hideSoftInputFromWindow(viewNew.getWindowToken(), 0);

            viewNew.clearFocus();

            return consumed;
        }
    }       

    return super.dispatchTouchEvent(ev);
}

Yan not: Ayrıca, bu nitelikleri her bir giriş alanına odaklanmayı mümkün kılan ve girdi alanlarının etkinlik başlangıcına odaklanmayı önleyen (içeriğin "odak yakalayıcı" haline getirilmesi) kök görünümüne atarım:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    final View view = findViewById(R.id.content);

    view.setFocusable(true);
    view.setFocusableInTouchMode(true);
}

Süper onun iyi çalışıyor teşekkürler !! Cevabınız için bir oy verin.
vijay

2
Bence bu karmaşık düzenler için en iyi çözüm. Ama şimdiye kadar 2 dezavantaj buldum: 1. EditText içerik menüsü tıklanamaz - herhangi bir tıklama EditText 2 odak kaybetmek neden olur. "tıklama noktamızın" EditText'te değil, klavyede olduğunu gösteren klavye - bu yüzden odağı tekrar kaybediyoruz: /
sosite

Sanırım cevabımdaki bu sınırlamalara değindim; Bir göz at.
Andy Dennie

6

Ben dispatchTouchEventhtafoya tarafından yapılan arama yaklaşımını sevdim , ama:

  • Zamanlayıcı kısmını anlamadım (kesinti süresini ölçmenin neden gerekli olduğunu bilmiyor musunuz?)
  • Tüm görünüm değişiklikleriyle tüm EditTexts'leri kaydettirmek / kaydını silmek istemiyorum (karmaşık hiyerarşilerde oldukça fazla görünüm ve düzenleme metni olabilir)

Bu yüzden bu çözümü biraz daha kolaylaştırdım:

@Override
public boolean dispatchTouchEvent(final MotionEvent ev) {
    // all touch events close the keyboard before they are processed except EditText instances.
    // if focus is an EditText we need to check, if the touchevent was inside the focus editTexts
    final View currentFocus = getCurrentFocus();
    if (!(currentFocus instanceof EditText) || !isTouchInsideView(ev, currentFocus)) {
        ((InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE))
            .hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    }
    return super.dispatchTouchEvent(ev);
}

/**
 * determine if the given motionevent is inside the given view.
 * 
 * @param ev
 *            the given view
 * @param currentFocus
 *            the motion event.
 * @return if the given motionevent is inside the given view
 */
private boolean isTouchInsideView(final MotionEvent ev, final View currentFocus) {
    final int[] loc = new int[2];
    currentFocus.getLocationOnScreen(loc);
    return ev.getRawX() > loc[0] && ev.getRawY() > loc[1] && ev.getRawX() < (loc[0] + currentFocus.getWidth())
        && ev.getRawY() < (loc[1] + currentFocus.getHeight());
}

Bir dezavantajı var:

Birinden EditTextdiğerine geçmek EditTextklavyeyi gizler ve yeniden gösterir - benim durumumda bu şekilde istenir, çünkü iki giriş bileşeni arasında geçiş yaptığınızı gösterir.


Bu yöntem, FragmentActivity ile sadece tak ve çalıştır özelliği açısından en iyi sonucu verdi.
BillyRayCyrus

Teşekkürler, en iyi yol! Ben de olay eylem için kontrol ekledi: int action = ev.getActionMasked (); if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {...}
sergey.n

Teşekkürler ! Zamanımı kurtardın.
I.d007

6

Plea: Hiç nüfuz sahibi olmadığımı biliyorum, ama lütfen cevabımı ciddiye al.

Sorun: Klavyeden uzağa tıkladığınızda yazılım klavyesini kapatın veya minimum kodla metin düzenleyin.

Çözüm: Butterknife olarak bilinen harici kütüphane .

Tek Hat Çözümü:

@OnClick(R.id.activity_signup_layout) public void closeKeyboard() { ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); }

Daha Okunabilir Çözüm:

@OnClick(R.id.activity_signup_layout) 
public void closeKeyboard() {
        InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}

Açıklama: OnClick Listener'ı etkinliğin XML Düzeni üst kimliğine bağlayın, böylece düzene yapılan herhangi bir tıklama (düzenleme metninde veya klavyede değil) klavyeyi gizleyecek olan kod snippet'ini çalıştırır.

Örnek: Düzen dosyanız R.layout.my_layout ve düzen kimliğiniz R.id.my_layout_id ise, Butterknife bağlama çağrınız şöyle görünmelidir:

(@OnClick(R.id.my_layout_id) 
public void yourMethod {
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}

Butterknife Dokümantasyon Linki: http://jakewharton.github.io/butterknife/

Plug: Butterknife android gelişiminde devrim yaratacak. Bir düşünün.

Not: Aynı sonuç harici kütüphane Butterknife kullanılmadan da elde edilebilir. Yukarıda açıklandığı gibi üst düzene bir OnClickListener ayarlayın.


Müthiş, mükemmel bir çözüm! Teşekkür ederim.
nullforlife

6

Kotlin'de aşağıdakileri yapabiliriz. Tüm görüşleri yinelemeye gerek yok. Parçalar için de çalışacaktır.

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
    currentFocus?.let {
        val imm: InputMethodManager = getSystemService(
            Context.INPUT_METHOD_SERVICE
        ) as (InputMethodManager)
        imm.hideSoftInputFromWindow(it.windowToken, 0)
    }
    return super.dispatchTouchEvent(ev)
}

Bu işe yarıyor ama bir hata var. Örneğin, metin görünümüne metin yapıştırmak istersem, klavye gizlenir ve sonra görünür. Biraz sinir bozucu.
George Shalvashvili

4

İşte fje'nin cevabında, sosite tarafından ortaya çıkan sorunları ele alan başka bir varyasyon.

Buradaki fikir, Faaliyetin dispatchTouchEventyöntemindeki aşağı ve yukarı hareketlerini ele almaktır. Aşağı eylemde, şu anda odaklanmış görünümü (varsa) ve dokunmanın içinde olup olmadığını not ederek her iki bilgi parçasını da daha sonra kullanmak üzere kaydediyoruz.

Yukarı eylemde, öncelikle başka bir görüşün potansiyel olarak odaklanmasına izin vermek için göndeririz. Bundan sonra, o sırada odaklanmış olan görünüm orijinal olarak odaklanmış görünümse ve aşağı dokunma bu görünümün içindeyse, klavyeyi açık bırakırız.

Halihazırda odaklanan görünüm orijinal olarak odaklanan görünümden farklıysa ve bir ise EditText, klavyeyi açık bırakıyoruz.

Aksi takdirde kapatırız.

Özetlemek gerekirse, bu şu şekilde çalışır:

  • o anda odaklanılanın içine dokunurken EditTextklavye açık kalır
  • odaktan EditTextdiğerine geçerken EditTextklavye açık kalır (kapanmaz / yeniden açılmaz)
  • o sırada odaklanmış EditTextdurumda olan ve başka bir yere dokunmadığınızda EditText, klavye kapanır
  • EditTextbağlamsal eylem çubuğunu (kesme / kopyalama / yapıştırma düğmeleriyle) getirmek için a düğmesine uzun süre bastığınızda , UP eylemi odaklanılanın dışında EditText(CAB'ye yer açmak için aşağı doğru hareket etse bile) açık kalır. . Ancak, CAB'deki bir düğmeye dokunduğunuzda klavyenin kapanacağını unutmayın. Bu arzu edilebilir veya edilmeyebilir; bir alandan kesmek / kopyalamak ve diğerine yapıştırmak istiyorsanız, olurdu. Eğer aynısını tekrar yapıştırmak isterseniz EditText, olmaz.
  • odaklama EditTextekranın altında olduğunda ve seçmek için bazı metne uzun tıkladığınızda, EditTextodağı tutar ve bu nedenle klavye istediğiniz gibi açılır, çünkü aşağıya "dokunma görünüm sınırları dahilinde" kontrolünü yapıyoruz , yukarı eylem değil.

    private View focusedViewOnActionDown;
    private boolean touchWasInsideFocusedView;
    
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                focusedViewOnActionDown = getCurrentFocus();
                if (focusedViewOnActionDown != null) {
                    final Rect rect = new Rect();
                    final int[] coordinates = new int[2];
    
                    focusedViewOnActionDown.getLocationOnScreen(coordinates);
    
                    rect.set(coordinates[0], coordinates[1],
                            coordinates[0] + focusedViewOnActionDown.getWidth(),
                            coordinates[1] + focusedViewOnActionDown.getHeight());
    
                    final int x = (int) ev.getX();
                    final int y = (int) ev.getY();
    
                    touchWasInsideFocusedView = rect.contains(x, y);
                }
                break;
    
            case MotionEvent.ACTION_UP:
    
                if (focusedViewOnActionDown != null) {
                    // dispatch to allow new view to (potentially) take focus
                    final boolean consumed = super.dispatchTouchEvent(ev);
    
                    final View currentFocus = getCurrentFocus();
    
                    // if the focus is still on the original view and the touch was inside that view,
                    // leave the keyboard open.  Otherwise, if the focus is now on another view and that view
                    // is an EditText, also leave the keyboard open.
                    if (currentFocus.equals(focusedViewOnActionDown)) {
                        if (touchWasInsideFocusedView) {
                            return consumed;
                        }
                    } else if (currentFocus instanceof EditText) {
                        return consumed;
                    }
    
                    // the touch was outside the originally focused view and not inside another EditText,
                    // so close the keyboard
                    InputMethodManager inputMethodManager =
                            (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    inputMethodManager.hideSoftInputFromWindow(
                        focusedViewOnActionDown.getWindowToken(), 0);
                    focusedViewOnActionDown.clearFocus();
    
                    return consumed;
                }
                break;
        }
    
        return super.dispatchTouchEvent(ev);
    }

4

çok basit, sadece son düzeninizi bu kodla odaklanabilir bir tıklanabilir yapın:

android:id="@+id/loginParentLayout"
android:clickable="true"
android:focusableInTouchMode="true"

ve daha sonra, en üst düzeye dokunduğunda, klavyeyi kapatmak için kod yazacağınız bir yöntemi çağıracağı bir düzen ve bir OnClickListner yazın. her ikisi için kod aşağıdadır; // bunu OnCreate () içine yazmalısınız

 yourLayout.setOnClickListener(new View.OnClickListener(){
                @Override
                public void onClick(View view) {
                    hideKeyboard(view);
                }
            });

Listeden çağrılan yöntem: -

 public void hideKeyboard(View view) {
     InputMethodManager imm =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }

4

Bu basit gereksinim için kabul edilen cevap biti karmaşık buluyorum. İşte aksaklık olmadan benim için işe yarayan.

findViewById(R.id.mainLayout).setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
            return false;
        }
    });

1
Eğer kullanıyorsanız NestedScrollViewveya karmaşık düzenleri, kabul cevabı bakın: stackoverflow.com/a/11656129/2914140 . Diğer kapların dokunma tüketebileceğini bilmelisiniz.
CoolMind

3

İPhone ile aynı konuya dayanan daha basit bir yaklaşım var. Düzenleme metninin bulunduğu dokunmatik etkinlikte arka planın düzenini geçersiz kılmanız yeterlidir. Sadece aktivitenin OnCreate'sinde bu kodu kullanın (login_fondo kök düzenidir):

    final LinearLayout llLogin = (LinearLayout)findViewById(R.id.login_fondo);
    llLogin.setOnTouchListener(
            new OnTouchListener()
            {
                @Override
                public boolean onTouch(View view, MotionEvent ev) {
                    InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(
                            android.content.Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(mActivity.getCurrentFocus().getWindowToken(), 0);
                    return false;
                }
            });

1
Söylediğim ve hatırladığım gibi, bu sadece form bir ScrollView içinde değilse çalışır.
htafoya

1
Arka plan düzeni başka alt düzenler içeriyorsa bu çok iyi sonuç vermez.
AxeEffect

OnClick'in tek seçenek olmadığını hatırlattığınız için teşekkür ederiz.
Harpreet

3

Yazılım klavyesini göster / gizle yöntemi

InputMethodManager inputMethodManager = (InputMethodManager) currentActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
    if (isShow) {
        if (currentActivity.getCurrentFocus() == null) {
            inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
        } else {
            inputMethodManager.showSoftInput(currentActivity.getCurrentFocus(), InputMethodManager.SHOW_FORCED);    
        }

    } else {
        if (currentActivity.getCurrentFocus() == null) {
            inputMethodManager.toggleSoftInput(InputMethodManager.HIDE_NOT_ALWAYS, 0);
        } else {
            inputMethodManager.hideSoftInputFromInputMethod(currentActivity.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);    
        }

    }

Umarım faydalı olmuştur


3

Bu benim için en kolay çözüm (ve benim tarafımdan çalıştı).

Bu, klavyeyi gizleme yöntemidir.

public void hideKeyboard(View view){
        if(!(view instanceof EditText)){
            InputMethodManager inputMethodManager=(InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
            inputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),0);
        }
    }

şimdi hideKeyboard, XML dosyanızın Tasarım görünümünden veya XML dosyanızın Metin görünümünde aşağıdaki kodu yazarak etkinliğin üst düzeninin onclick niteliğini yukarıdaki yönteme ayarlayın .

android:onClick="hideKeyboard"

2

Ben yöntemi rafine ettik, amacına hizmet etmek için tüm Aktivite veya Fragment sınıflarından erişilebilir böylece bazı UI yardımcı sınıf (tercihen, mutlaka) aşağıdaki kodu koymak var.

public static void serachAndHideSoftKeybordFromView(View view, final Activity act) {
    if(!(view instanceof EditText)) {
        view.setOnTouchListener(new View.OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                hideSoftKeyboard(act);
                return false;
            }
        });
    }
    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            View nextViewInHierarchy = ((ViewGroup) view).getChildAt(i);
            serachAndHideSoftKeybordFromView(nextViewInHierarchy, act);
        }
    }
}
public static void hideSoftKeyboard (Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    inputMethodManager.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0);
}

O zaman örneğin aktiviteden çağırmanız gerektiğini söyleyin, aşağıdaki gibi çağırın;

UIutils.serachAndHideSoftKeybordFromView(findViewById(android.R.id.content), YourActivityName.this);

Farkına varmak

findViewById (android.R.id.content)

Bu bize geçerli grubun kök görünümünü verir (kimliği kök görünümünde ayarlamamalısınız).

Şerefe :)



2

Aktivite

 @Override
 public boolean dispatchTouchEvent(MotionEvent ev) {
     ScreenUtils.hideKeyboard(this, findViewById(android.R.id.content).getWindowToken());
     return super.dispatchTouchEvent(ev);
 }

ScreenUtils

 public static void hideKeyboard(Context context, IBinder windowToken) {
     InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
     imm.hideSoftInputFromWindow(windowToken, InputMethodManager.HIDE_NOT_ALWAYS);
 }

3
Bu kod basittir, ancak bariz bir sorunu vardır: herhangi bir yere dokunulduğunda klavyeyi kapatır . Yani giriş imlecini hareket ettirmek için EditText'in farklı bir yerine dokunursanız, klavyeyi gizler ve klavye sistem tarafından tekrar açılır.
Lanet Sebzeler

2

Sadece bu kodu @Overide sınıfına ekleyin

public boolean dispatchTouchEvent(MotionEvent ev) {
    View view = getCurrentFocus();
    if (view != null && (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText && !view.getClass().getName().startsWith("android.webkit.")) {
        int scrcoords[] = new int[2];
        view.getLocationOnScreen(scrcoords);
        float x = ev.getRawX() + view.getLeft() - scrcoords[0];
        float y = ev.getRawY() + view.getTop() - scrcoords[1];
        if (x < view.getLeft() || x > view.getRight() || y < view.getTop() || y > view.getBottom())
            ((InputMethodManager)this.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow((this.getWindow().getDecorView().getApplicationWindowToken()), 0);
    }
    return super.dispatchTouchEvent(ev);
}

3
Bu soruya cevap verebilirken, cevabın temel kısımlarını ve muhtemelen OPs kodundaki sorunun ne olduğunu açıklamak daha iyidir.
pirho

yes @pirho Ben de seninle aynı fikirdeyim Haseeb doğru cevap vermeye konsantre olmalıydı.
Dilip

2
@Dilip Kabul ettiğiniz yorumları da oylayabileceğinizi biliyor muydunuz? Bu sadece yorum bölümünü temiz tutmaktır, bu yüzden aynı yoruma sahip birçok yorum olmamalıdır.
pirho

2

Tüm görünümlerde yineleme yapmak veya dispatchTouchEvent'i geçersiz kılmak yerine.

Neden yalnızca Aktivitenin onUserInteraction () yöntemini geçersiz kılmamakla kalmaz, bu, kullanıcı EditText'in dışına dokunduğunda klavyenin kapanmasını sağlar.

EditText scrollView içinde olsa bile çalışır.

@Override
public void onUserInteraction() {
    if (getCurrentFocus() != null) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
    }
}

bu daha iyi cevap
ovluca

Evet! Süper temiz bir cevap. Ve diğer tüm Etkinliklerinizin de kapsadığı bir AbstractActivity oluşturursanız, bunu uygulamanızdaki varsayılan davranış olarak pişirebilirsiniz.
wildcat12

1

Bu işi Fernando Camarago'nun çözümü üzerinde hafif bir varyantla aldım. Benim onCreate yönteminde kök görünümüne tek bir onTouchListener eklerim, ancak etkinliği bağımsız değişken olarak değil, görünümü gönderirim.

        findViewById(android.R.id.content).setOnTouchListener(new OnTouchListener() {           
        public boolean onTouch(View v, MotionEvent event) {
            Utils.hideSoftKeyboard(v);
            return false;
        }
    });

Ayrı bir Utils sınıfında ...

    public static void hideSoftKeyboard(View v) {
    InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 
    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}

1

Bu eski olabilir, ancak özel bir sınıf uygulayarak bu çalışmayı aldım

public class DismissKeyboardListener implements OnClickListener {

    Activity mAct;

    public DismissKeyboardListener(Activity act) {
        this.mAct = act;
    }

    @Override
    public void onClick(View v) {
        if ( v instanceof ViewGroup ) {
            hideSoftKeyboard( this.mAct );
        }
    }       
}

public void hideSoftKeyboard(Activity activity) {
        InputMethodManager imm = (InputMethodManager)
        getSystemService(Activity.INPUT_METHOD_SERVICE);
        imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
}

buradaki en iyi uygulama bir Yardımcı sınıf oluşturmaktır ve Göreli / Doğrusal Düzenlemeler her kapsayıcı bunu uygulamalıdır.

**** Bu sınıfı sadece ana Konteyner uygulamalıdır (optimizasyon için) ****

ve şu şekilde uygulayın:

Parent.setOnClickListener( new DismissKeyboardListener(this) ); 

bu Etkinlik için kullanılan anahtar kelimedir. eğer parçalıysanız getActivity () gibi kullanırsınız;

--- sana yardımcı olursa yaşasın ... --- Ralph'i şerefe ---


1

Bu, çoğunlukla mükemmel çalışan fje cevabının biraz değiştirilmiş bir versiyonudur.

Bu sürüm ACTION_DOWN kullanır, bu nedenle kaydırma eyleminin gerçekleştirilmesi klavyeyi de kapatır. Ayrıca başka bir EditText'i tıklamadığınız sürece olayı yaymaz. Bu, EditText'inizin dışında herhangi bir yere tıklamanın, başka bir tıklanabilir durumda bile, klavyeyi kapatması anlamına gelir.

@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
    if(ev.getAction() == MotionEvent.ACTION_DOWN)
    {
        final View view = getCurrentFocus();

        if(view != null)
        {
            final View viewTmp = getCurrentFocus();
            final View viewNew = viewTmp != null ? viewTmp : view;

            if(viewNew.equals(view))
            {
                final Rect rect = new Rect();
                final int[] coordinates = new int[2];

                view.getLocationOnScreen(coordinates);

                rect.set(coordinates[0], coordinates[1], coordinates[0] + view.getWidth(), coordinates[1] + view.getHeight());

                final int x = (int) ev.getX();
                final int y = (int) ev.getY();

                if(rect.contains(x, y))
                {
                    super.dispatchTouchEvent(ev);
                    return true;
                }
            }
            else if(viewNew instanceof EditText || viewNew instanceof CustomEditText)
            {
                super.dispatchTouchEvent(ev);
                return true;
            }

            final InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

            inputMethodManager.hideSoftInputFromWindow(viewNew.getWindowToken(), 0);

            viewNew.clearFocus();

            return true;
        }
    }
    return super.dispatchTouchEvent(ev);
}

Burada bir şey görünmüyor; Hem atıyorsanız viewve viewTmpkarşı getCurrentFocus()her zaman aynı değeri olacağız böylece.
Andy Dennie

1

Bu şekilde yaptım:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
   View view = getCurrentFocus();
   if (view != null && (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText && !view.getClass().getName().startsWith("android.webkit.")) {
            int scrcoords[] = new int[2];
            view.getLocationOnScreen(scrcoords);
            float x = ev.getRawX() + view.getLeft() - scrcoords[0];
            float y = ev.getRawY() + view.getTop() - scrcoords[1];
            if (x < view.getLeft() || x > view.getRight() || y < view.getTop() || y > view.getBottom())
                hideKeyboard(this);
        }
    return super.dispatchTouchEvent(ev);
}

Klavye kodunu gizle :

public static void hideKeyboard(Activity act) {
    if(act!=null)
      ((InputMethodManager)act.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow((act.getWindow().getDecorView().getApplicationWindowToken()), 0);
  }

Bitti

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.