สกัดกั้นปุ่มย้อนกลับจากแป้นพิมพ์อ่อน


84

ฉันมีกิจกรรมที่มีช่องป้อนข้อมูลหลายช่อง เมื่อกิจกรรมเริ่มต้นแป้นพิมพ์อ่อนจะปรากฏขึ้น เมื่อกดปุ่มย้อนกลับแป้นพิมพ์แบบนิ่มจะปิดลงและเพื่อปิดกิจกรรมฉันต้องกดปุ่มย้อนกลับอีกครั้ง

ดังนั้นคำถาม: มันเป็นไปได้ที่จะกดปุ่มตัดกลับไปที่แป้นพิมพ์อ่อนอย่างใกล้ชิดและกิจกรรมเสร็จสิ้นในการกดปุ่มย้อนกลับโดยไม่ต้องสร้างที่กำหนดเองInputMethodService?

ป.ล. ฉันรู้วิธีสกัดกั้นปุ่มย้อนกลับในกรณีอื่น ๆ : onKeyDown()หรือonBackPressed()แต่มันใช้ไม่ได้ในกรณีนี้: กดปุ่มย้อนกลับเพียงครั้งที่สองเท่านั้น

คำตอบ:


78

ใช่มันเป็นไปได้อย่างสมบูรณ์ที่จะแสดงและซ่อนแป้นพิมพ์และดักฟังการโทรไปที่ปุ่มย้อนกลับ มันเป็นความพยายามพิเศษเล็กน้อยเนื่องจากมีการกล่าวถึงไม่มีวิธีโดยตรงในการดำเนินการนี้ใน API กุญแจสำคัญคือการแทนที่boolean dispatchKeyEventPreIme(KeyEvent)ภายในเค้าโครง สิ่งที่เราทำคือสร้างเค้าโครงของเรา ฉันเลือก RelativeLayout เนื่องจากเป็นฐานของกิจกรรมของฉัน

<?xml version="1.0" encoding="utf-8"?>
<com.michaelhradek.superapp.utilities.SearchLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.michaelhradek.superapp"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/white">

ภายในกิจกรรมของเราเราตั้งค่าช่องป้อนข้อมูลและเรียกใช้setActivity(...)ฟังก์ชัน

private void initInputField() {
    mInputField = (EditText) findViewById(R.id.searchInput);        

    InputMethodManager imm = 
        (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 
    imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 
            InputMethodManager.HIDE_IMPLICIT_ONLY);

    mInputField.setOnEditorActionListener(new OnEditorActionListener() {

        @Override
        public boolean onEditorAction(TextView v, int actionId,
                KeyEvent event) {
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                performSearch();
                return true;
            }

            return false;
        }
    });

    // Let the layout know we are going to be overriding the back button
    SearchLayout.setSearchActivity(this);
}

เห็นได้ชัดว่าinitInputField()ฟังก์ชันตั้งค่าช่องป้อนข้อมูล นอกจากนี้ยังเปิดใช้งานปุ่ม Enter เพื่อเรียกใช้ฟังก์ชันการทำงาน (ในกรณีของฉันคือการค้นหา)

@Override
public void onBackPressed() {
    // It's expensive, if running turn it off.
    DataHelper.cancelSearch();
    hideKeyboard();
    super.onBackPressed();
}

ดังนั้นเมื่อonBackPressed()มีการเรียกใช้ภายในเลย์เอาต์ของเราเราสามารถทำอะไรก็ได้ที่ต้องการเช่นซ่อนแป้นพิมพ์:

private void hideKeyboard() {
    InputMethodManager imm = (InputMethodManager) 
        getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(mInputField.getWindowToken(), 0);
}

อย่างไรก็ตามนี่คือการแทนที่ RelativeLayout ของฉัน

package com.michaelhradek.superapp.utilities;

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.RelativeLayout;

/**
 * The root element in the search bar layout. This is a custom view just to 
 * override the handling of the back button.
 * 
 */
public class SearchLayout extends RelativeLayout {

    private static final String TAG = "SearchLayout";

    private static Activity mSearchActivity;;

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

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

    public static void setSearchActivity(Activity searchActivity) {
        mSearchActivity = searchActivity;
    }

    /**
     * Overrides the handling of the back key to move back to the 
     * previous sources or dismiss the search dialog, instead of 
     * dismissing the input method.
     */
    @Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        Log.d(TAG, "dispatchKeyEventPreIme(" + event + ")");
        if (mSearchActivity != null && 
                    event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            KeyEvent.DispatcherState state = getKeyDispatcherState();
            if (state != null) {
                if (event.getAction() == KeyEvent.ACTION_DOWN
                        && event.getRepeatCount() == 0) {
                    state.startTracking(event, this);
                    return true;
                } else if (event.getAction() == KeyEvent.ACTION_UP
                        && !event.isCanceled() && state.isTracking(event)) {
                    mSearchActivity.onBackPressed();
                    return true;
                }
            }
        }

        return super.dispatchKeyEventPreIme(event);
    }
}

น่าเสียดายที่ฉันไม่สามารถรับเครดิตทั้งหมดได้ หากคุณตรวจสอบแหล่งที่มาของ Android สำหรับช่อง SearchDialog อย่างรวดเร็วคุณจะเห็นว่าแนวคิดนี้มาจากที่ใด


2
วิธีนี้ใช้ได้ผลดีและไม่ยากเกินไปที่จะนำไปใช้ (แม้จะดูน่ากลัวเล็กน้อย)
Cesar

ขอบคุณมากสำหรับสิ่งนี้ฉันแค่หวังว่าฉันจะมีวิธีการบางอย่างเช่นนั้นเพื่อทำงานกับ Android เวอร์ชันเก่า
zidarsk8

3
รหัสได้ง่ายโดยเพียงแค่หล่อไปgetContext() Activityบริบทของเค้าโครงเป็นกิจกรรมที่เป็นปัญหาแน่นอน แต่ฉันไม่รู้ว่ามันเป็นไปไม่ได้
mxcl

3
mSearchActivity แบบคงที่ - มีกลิ่นผิดจากปัญหา จะเกิดอะไรขึ้นถ้าคุณมีสองสิ่งเหล่านี้ในเค้าโครง? ใช้ตัวแปรไม่คงที่แทน
Pointer Null

ใช้งานได้เหมือนมีเสน่ห์ แต่ฉันคิดว่ามันจะดีกว่าถ้าใช้อินเทอร์เฟซแบบคงที่ในเครื่องและตั้งค่าการใช้งานจากกิจกรรมภายในแทนที่จะใช้การกระทำแบบคงที่ ...
SpiralDev

81

onKeyDown ()และonBackPressed () ใช้ไม่ได้กับกรณีนี้ คุณต้องใช้onKeyPreIme

ในขั้นต้นคุณต้องสร้างข้อความแก้ไขแบบกำหนดเองที่ขยาย EditText และแล้วคุณจะต้องใช้วิธีการ onKeyPreIme ซึ่งควบคุมKeyEvent.KEYCODE_BACK หลังจากนี้ให้กดย้อนกลับเพียงครั้งเดียวเพื่อแก้ปัญหาของคุณ วิธีนี้ใช้ได้ผลกับฉันอย่างสมบูรณ์แบบ

CustomEditText.java

public class CustomEditText extends EditText {

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

    @Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            // User has pressed Back key. So hide the keyboard
            InputMethodManager mgr = (InputMethodManager)         

           getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            mgr.hideSoftInputFromWindow(this.getWindowToken(), 0);
            // TODO: Hide your view as you do it in your activity
        }
        return false;
}

ใน XML ของคุณ

<com.YOURAPP.CustomEditText
     android:id="@+id/CEditText"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"/> 

ในกิจกรรมของคุณ

public class MainActivity extends Activity {
   private CustomEditText editText;

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      editText = (CustomEditText) findViewById(R.id.CEditText);
   }
}

4
รักคำตอบนี้มาก การใช้สิ่งนี้กับแฟรกเมนต์เช่นกัน (ภายในแถบค้นหาซึ่งไม่ปรากฏขึ้นเสมอไป) และโดยการแนะนำให้แทนที่แก้ไขข้อความสิ่งนี้จะสรุปพฤติกรรมนี้ได้อย่างง่ายดายเฉพาะเมื่อจำเป็นเท่านั้น
Jawad

2
หมายเหตุสำหรับทุกคน: การเพิ่มอินเทอร์เฟซที่รวดเร็วทำให้ชีวิตดีขึ้นมากและโชคดีที่ทำได้ง่ายมาก
Jawad

วิธีเรียกเมธอดใน onKeyPreIme () ซึ่งอยู่ใน mainActivity.java
Abhigyan

คุณไม่จำเป็นต้องเรียกใช้ onKeyPreIme ในกิจกรรม หากคุณใช้ CustomEditText ในกิจกรรมของคุณเมธอดจะถูกเรียกใช้เมื่อคุณกดปุ่มใด ๆ บนแป้นพิมพ์
Alican Temel

1
ฉันคิดว่านี่เป็นคำตอบที่ดีที่สุดเพราะมันง่ายกว่ามากในการนำไปใช้ ในความเป็นจริงคุณต้องคัดลอกคลาส "CustomEditText" (และเปลี่ยนชื่อข้อความแก้ไขของคุณใน XML) และสร้างสิ่งที่คุณต้องการในพื้นที่ // TODO และเพิ่ม "return true" ในพื้นที่ TODO หากคุณไม่ต้องการปิดกิจกรรม ขอบคุณ!
Chrysotribax

10

ฉันพบว่าการแทนที่เมธอดdispatchKeyEventPreImeของ Layout Class ก็ใช้ได้ดีเช่นกัน เพียงตั้งค่ากิจกรรมหลักของคุณเป็นแอตทริบิวต์และเปิดใช้วิธีการที่กำหนดไว้ล่วงหน้า

public class LinearLayoutGradient extends LinearLayout {
    MainActivity a;

    public void setMainActivity(MainActivity a) {
        this.a = a;
    }

    @Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        if (a != null) {
            InputMethodManager imm = (InputMethodManager) a
                .getSystemService(Context.INPUT_METHOD_SERVICE);

            if (imm.isActive() && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                a.launchMethod;
            }
        }

        return super.dispatchKeyEventPreIme(event);
    }
}

คุณสามารถแสดง xml ของคุณสำหรับเลย์เอาต์นี้ได้ไหม ฉันกำลังพยายามตรวจสอบแนวทางนี้และฉันมีมุมมองพื้นผิวในรูปแบบการดูแลของฉัน แต่มันไม่ได้ขัดขวางอะไรเลย
ZZZ

โดยพื้นฐานแล้วมุมมองแบบกำหนดเองคือองค์ประกอบโดยรอบใน xml <?xml version="1.0" encoding="utf-8"?> <view class="package.LinearLayoutGradient"...
Kirill Rakhman

นอกจากนี้ยังใช้งานได้หากเราขยายมุมมองเดียวและแทนที่มัน dispatchKeyEventPreIme () ไม่จำเป็นต้องขยายเค้าโครงเอง
faizal

7

ฉันประสบความสำเร็จโดยการลบล้างdispatchKeyEvent :

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        finish();
        return true;
    }
    return super.dispatchKeyEvent(event);
}

มันซ่อนแป้นพิมพ์และเสร็จสิ้นกิจกรรม


ดูเหมือนว่าจะใช้ได้เฉพาะกับอุปกรณ์บางอย่างเช่น HTC Desire
Abhijit

5
ไม่ทำงานบน samsung note 2 dispatchKeyEvent () ไม่ถูกเรียกเมื่อซอฟต์คีย์บอร์ดขึ้น
faizal

ฉันลองใช้ Nexus 6 Emulator (Lollipop 5.0) -> ใช้งานได้และ Note 4 (CM12 Lollipop 5.0.2) -> ใช้งานไม่ได้ไม่ใช่วิธีแก้ปัญหาที่ดีที่สุด แต่อย่างไรก็ตามขอบคุณ :)
Martin Pfeffer

มีการคลิกปุ่มเมื่อปิดแป้นพิมพ์
slott

4

คุณแสดงแป้นพิมพ์อ่อนอย่างไร

หากคุณกำลังใช้InputMethodManager.showSoftInput()งานคุณสามารถลองส่งผ่านResultReceiverและนำonReceiveResult()ไปใช้เพื่อจัดการRESULT_HIDDEN

http://developer.android.com/reference/android/view/inputmethod/InputMethodManager.html


ฟังดูดี แต่showSoftInput()ไม่แสดงแป้นพิมพ์ที่โทรศัพท์และโปรแกรมจำลองดังนั้นฉันจึงใช้toggleSoftInput()
Sergey Glotov

1
ไม่ได้ผลสำหรับฉัน onReceiveResult () ถูกเรียกด้วย RESULT_SHOWN แต่ไม่ช้ากว่าด้วย RESULT_HIDDEN
Yulia Rogovaya

2

ฉันมีปัญหาเดียวกัน แต่แก้ไขได้โดยการสกัดกั้นการกดปุ่มย้อนกลับ ในกรณีของฉัน (HTC Desire, Android 2.2, API แอปพลิเคชันระดับ 4) มันจะปิดคีย์บอร์ดและเสร็จสิ้นกิจกรรมทันที ไม่รู้ว่าทำไมสิ่งนี้ถึงไม่เหมาะกับคุณด้วย:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        return true;
    }
    return super.onKeyDown(keyCode, event);
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        onBackPressed();
        return true;
    }
    return super.onKeyUp(keyCode, event);
}

/**
 * Called when the activity has detected the user's press of the back key
 */
private void onBackPressed() {
    Log.e(TAG, "back pressed");
    finish();
}

สิ่งนี้ใช้ไม่ได้กับ samsung galaxy note 2 ปุ่ม onkeydown () หรือ onbackpressed () จะไม่ทำงานเมื่อมองเห็นแป้นพิมพ์อ่อนและปุ่มย้อนกลับถูกกด
faizal

1

ใช้onKeyPreIme(int keyCode, KeyEvent event)วิธีการและตรวจสอบKeyEvent.KEYCODE_BACKเหตุการณ์ มันง่ายมากโดยไม่ต้องเข้ารหัสอะไรเลย


0

ลองใช้รหัสนี้ในการใช้งาน BackPressed ของคุณ ( Block Back Button ใน Android ):

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

ฉันขอแนะนำให้คุณดู @ ปิด / ซ่อน Android Soft Keyboard


1
ฉันได้ลองแล้ว แต่ยังต้องกดปุ่มย้อนกลับสองครั้ง ตามที่ฉันเข้าใจแล้วการคลิกครั้งแรกถูกขัดขวางโดยแป้นพิมพ์อ่อนจึงonBackPressed()ไม่ทำงาน และหลังจากโปรแกรมกดครั้งที่สองตกอยู่ในonBackPressed().
Sergey Glotov

@Sergey Glotov ฉันลองคำแนะนำมากมายสำหรับปัญหาและในที่สุดก็มาพร้อมกับวิธีแก้ปัญหาที่ไม่ค่อยดีนักซึ่งฉันต้องใช้ SoftKeyBoard ของตัวเอง แต่ฉันหวังว่าชุมชน Android จะออกมาพร้อมกับทางออกที่ดีกว่าในไม่ช้า
100rabh

0

โซลูชัน @mhradek เวอร์ชันของฉัน:

เค้าโครง

class BazingaLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
    ConstraintLayout(context, attrs, defStyleAttr) {

var activity: Activity? = null

override fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
    activity?.let {
        if (event.keyCode == KeyEvent.KEYCODE_BACK) {
            val state = keyDispatcherState
            if (state != null) {
                if (event.action == KeyEvent.ACTION_DOWN
                    && event.repeatCount == 0) {
                    state.startTracking(event, this)
                    return true
                } else if (event.action == KeyEvent.ACTION_UP && !event.isCanceled && state.isTracking(event)) {
                    it.onBackPressed()
                    return true
                }
            }
        }
    }
    return super.dispatchKeyEventPreIme(event)
}

}

xml ไฟล์

<com... BazingaLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/grey">
 </com... BazingaLayout>

ส่วนย่อย

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        (view as BazingaLayout).activity = activity
        super.onViewCreated(view, savedInstanceState)
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.