IT이야기

Android : 모달 대화 상자 또는 유사한 모달 동작을 얻는 방법

cyworld 2021. 3. 23. 21:12
반응형

Android : 모달 대화 상자 또는 유사한 모달 동작을 얻는 방법은 무엇입니까?


요즘 저는 Android에서 모달 대화 상자를 시뮬레이션하는 작업을하고 있습니다. 나는 많이 봤고 많은 토론이 있지만 슬프게도 모달을 얻을 수있는 옵션이 많지 않습니다. 다음은 몇 가지 배경,
대화 상자, 모달 대화 상자 및 차단
대화 상자 / AlertDialogs : 대화 상자가 켜져있는 동안 "실행을 차단"하는 방법 (.NET 스타일)

모달 동작을 얻을 수있는 직접적인 방법은 없습니다. 그런 다음 가능한 3 가지 해결책을 찾았습니다
. 1.이 스레드가 말한 것처럼 대화 테마 활동을 사용합니다 . 그러나 여전히 주 활동이 대화 활동 반환을 진정으로 기다리도록 만들 수는 없습니다. 주요 활동은 정지 상태로 바뀌고 다시 시작되었습니다.
2. 하나의 작업자 스레드를 빌드하고 스레드 동기화를 사용합니다. 그러나 그것은 내 앱에 대한 거대한 리팩토링 작업이므로 이제 기본 UI 스레드에 단일 기본 활동과 서비스가 있습니다.
3. 모달 대화 상자가있을 때 루프 내에서 이벤트 처리를 인계하고 대화 상자가 닫히면 루프를 종료합니다. 실제로 Windows에서 정확히 수행하는 것과 같은 실제 모달 대화 상자를 만드는 방법입니다. 나는 아직도 이런 식으로 프로토 타입을 만들지 않았다.

대화 테마 활동으로 시뮬레이션하고 싶습니다
. 1. startActivityForResult ()로 대화 활동 시작
2. onActivityResult ()에서 결과 가져 오기
다음은 몇 가지 소스입니다.

public class MainActivity extends Activity {

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

    MyView v = new MyView(this);
    setContentView(v);
}

private final int RESULT_CODE_ALERT = 1;
private boolean mAlertResult = false;
public boolean startAlertDialog() {
    Intent it = new Intent(this, DialogActivity.class);
    it.putExtra("AlertInfo", "This is an alert");
    startActivityForResult(it, RESULT_CODE_ALERT);

    // I want to wait right here
    return mAlertResult;
}

@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case RESULT_CODE_ALERT:
        Bundle ret = data.getExtras();
        mAlertResult = ret.getBoolean("AlertResult");
        break;
    }
}
}

startAlertDialog의 호출자는 실행을 차단하고 반환 된 결과를 기대합니다. 그러나 startAlertDialog는 당연히 즉시 반환되었으며 DialogActivity가 작동하는 동안 주요 활동은 STOP 상태가되었습니다.

그래서 문제는 어떻게 주요 활동이 결과를 정말로 기다리게 하는가입니다.
감사.


사용하는 동안 모달 대화 상자가 있습니다.

setCancelable(false);

DialogFragment (DialogBuilder가 아님)에서.


당신이 계획 한 방식은 불가능합니다. 첫째, UI 스레드를 차단할 수 없습니다. 귀하의 신청이 종료됩니다. 둘째, 다른 활동이 시작될 때 호출되는 수명주기 메서드를 처리해야합니다 startActivity(다른 활동이 실행되는 동안 원래 활동은 일시 중지됨). 셋째, startAlertDialog()UI 스레드가 아닌 스레드 동기화 (예 Object.wait():) 및 일부 AlertDialog. 그러나, 나는 강력하게 이 작업을 수행하지 않는 것이 좋습니다. 추악하고 확실히 부서 질 것이며 일이 작동하도록 의도 된 방식이 아닙니다.

이러한 이벤트의 비동기 특성을 포착하도록 접근 방식을 재 설계하십시오. 예를 들어 사용자에게 결정을 요청하는 대화 상자 (예 : ToS 수락 여부)를 원하고 해당 결정에 따라 특별한 작업을 수행하려면 다음과 같은 대화 상자를 만듭니다.

AlertDialog dialog = new AlertDialog.Builder(context).setMessage(R.string.someText)
                .setPositiveButton(android.R.string.ok, new OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        // Do stuff if user accepts
                    }
                }).setNegativeButton(android.R.string.cancel, new OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        // Do stuff when user neglects.
                    }
                }).setOnCancelListener(new OnCancelListener() {

                    @Override
                    public void onCancel(DialogInterface dialog) {
                        dialog.dismiss();
                        // Do stuff when cancelled
                    }
                }).create();
dialog.show();

그런 다음 그에 따라 긍정적이거나 부정적인 피드백을 처리하는 두 가지 방법이 있습니다 (즉, 작업을 진행하거나 활동을 완료하거나 의미가있는 것).


안타깝게도 Android 및 iOS 개발자는 Modal Dialog 개념을 거부 할 수있을만큼 강력하고 똑똑하다고 결정했습니다 (이미 수년 동안 시장에 출시되었으며 이전에는 누구에게도 신경 쓰지 않았습니다).

내 해결책은 다음과 같습니다.

    int pressedButtonID;
    private final Semaphore dialogSemaphore = new Semaphore(0, true);
    final Runnable mMyDialog = new Runnable()
    {
        public void run()
        {
            AlertDialog errorDialog = new AlertDialog.Builder( [your activity object here] ).create();
            errorDialog.setMessage("My dialog!");
            errorDialog.setButton("My Button1", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    pressedButtonID = MY_BUTTON_ID1;
                    dialogSemaphore.release();
                    }
                });
            errorDialog.setButton2("My Button2", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    pressedButtonID = MY_BUTTON_ID2;
                    dialogSemaphore.release();
                    }
                });
            errorDialog.setCancelable(false);
            errorDialog.show();
        }
    };

    public int ShowMyModalDialog()  //should be called from non-UI thread
    {
        pressedButtonID = MY_BUTTON_INVALID_ID;
        runOnUiThread(mMyDialog);
        try
        {
            dialogSemaphore.acquire();
        }
        catch (InterruptedException e)
        {
        }
        return pressedButtonID;
    }

마침내 저는 정말 간단하고 간단한 해결책을 찾았습니다.

Win32 프로그래밍에 익숙한 사람들은 모달 대화 상자를 구현하는 방법을 알고있을 것입니다. 일반적으로 모달 대화 상자가있을 때 중첩 된 메시지 루프 (GetMessage / PostMessage에 의해)를 실행합니다. 그래서 저는이 전통적인 방식으로 저만의 모달 대화를 구현하려고했습니다.

처음에 안드로이드는 ui 스레드 메시지 루프에 삽입 할 인터페이스를 제공하지 않았거나 찾지 못했습니다. 소스 인 Looper.loop ()를 살펴 보았을 때 정확히 내가 원했던 것임을 알았습니다. 그러나 여전히 MessageQueue / Message는 공용 인터페이스를 제공하지 않았습니다. 다행히 우리는 자바에 반영되어 있습니다. 기본적으로 Looper.loop ()가 수행 한 작업을 정확히 복사하여 워크 플로를 차단하고 이벤트를 제대로 처리했습니다. 중첩 된 모달 대화 상자를 테스트하지는 않았지만 이론적으로는 작동합니다.

다음은 내 소스 코드입니다.

public class ModalDialog {

private boolean mChoice = false;        
private boolean mQuitModal = false;     

private Method mMsgQueueNextMethod = null;
private Field mMsgTargetFiled = null;

public ModalDialog() {
}

public void showAlertDialog(Context context, String info) {
    if (!prepareModal()) {
        return;
    }

    // build alert dialog
    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    builder.setMessage(info);
    builder.setCancelable(false);
    builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            ModalDialog.this.mQuitModal = true;
            dialog.dismiss();
        }
    });

    AlertDialog alert = builder.create();
    alert.show();

    // run in modal mode
    doModal();
}

public boolean showConfirmDialog(Context context, String info) {
    if (!prepareModal()) {
        return false;
    }

    // reset choice
    mChoice = false;

    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    builder.setMessage(info);
    builder.setCancelable(false);
    builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            ModalDialog.this.mQuitModal = true;
            ModalDialog.this.mChoice = true;
            dialog.dismiss();
        }
    });

    builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            ModalDialog.this.mQuitModal = true;
            ModalDialog.this.mChoice = false;
            dialog.cancel();
        }
    });

    AlertDialog alert = builder.create();
    alert.show();

    doModal();
    return mChoice;
}

private boolean prepareModal() {
    Class<?> clsMsgQueue = null;
    Class<?> clsMessage = null;

    try {
        clsMsgQueue = Class.forName("android.os.MessageQueue");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        return false;
    }

    try {
        clsMessage = Class.forName("android.os.Message");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        return false;
    }

    try {
        mMsgQueueNextMethod = clsMsgQueue.getDeclaredMethod("next", new Class[]{});
    } catch (SecurityException e) {
        e.printStackTrace();
        return false;
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
        return false;
    }

    mMsgQueueNextMethod.setAccessible(true);

    try {
        mMsgTargetFiled = clsMessage.getDeclaredField("target");
    } catch (SecurityException e) {
        e.printStackTrace();
        return false;
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
        return false;
    }

    mMsgTargetFiled.setAccessible(true);
    return true;
}

private void doModal() {
    mQuitModal = false;

    // get message queue associated with main UI thread
    MessageQueue queue = Looper.myQueue();
    while (!mQuitModal) {
        // call queue.next(), might block
        Message msg = null;
        try {
            msg = (Message)mMsgQueueNextMethod.invoke(queue, new Object[]{});
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        if (null != msg) {
            Handler target = null;
            try {
                target = (Handler)mMsgTargetFiled.get(msg);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            if (target == null) {
                // No target is a magic identifier for the quit message.
                mQuitModal = true;
            }

            target.dispatchMessage(msg);
            msg.recycle();
        }
    }
}
}

도움이 되었기를 바랍니다.


이것은 나를 위해 작동합니다 : 활동을 대화 상자로 만듭니다. 그때,

  1. 활동에 대한 매니페스트에 다음을 추가하십시오.

    android : theme = "@ android : style / Theme.Dialog"

  2. 이것을 onCreate 활동에 추가하십시오.

    setFinishOnTouchOutside (false);

  3. 활동에서 onBackPressed를 재정의합니다.

    @Override public void onBackPressed () {// "back"이이 활동을 떠나는 것을 방지}

첫 번째는 활동에 대화 모양을 제공합니다. 후자의 두 개는 모달 대화 상자처럼 작동합니다.


hackbod 및 다른 사람들이 지적했듯이 Android는 의도적으로 중첩 된 이벤트 루프를 수행하는 방법을 제공하지 않습니다. 나는 그 이유를 이해하지만이를 필요로하는 특정 상황이 있습니다. 우리의 경우에는 다양한 플랫폼에서 실행되는 자체 가상 머신이 있으며이를 Android로 이식하고 싶었습니다. 내부적으로는 중첩 된 이벤트 루프가 필요한 곳이 많으며 Android 용으로 전체를 다시 작성하는 것은 실제로 불가능합니다. 어쨌든 여기에 해결책이 있습니다 (기본적으로 Android에서 비 차단 이벤트 처리어떻게 할 수 있습니까? 에서 가져 왔지만 시간 제한을 추가했습니다) :

private class IdleHandler implements MessageQueue.IdleHandler
{
    private Looper _looper;
    private int _timeout;
    protected IdleHandler(Looper looper, int timeout)
    {
        _looper = looper;
        _timeout = timeout;
    }

    public boolean queueIdle()
    {
        _uiEventsHandler = new Handler(_looper);
        if (_timeout > 0)
        {
            _uiEventsHandler.postDelayed(_uiEventsTask, _timeout);
        }
        else
        {
            _uiEventsHandler.post(_uiEventsTask);
        }
        return(false);
    }
};

private boolean _processingEventsf = false;
private Handler _uiEventsHandler = null;

private Runnable _uiEventsTask = new Runnable()
{
    public void run() {
    Looper looper = Looper.myLooper();
    looper.quit();
    _uiEventsHandler.removeCallbacks(this);
    _uiEventsHandler = null;
    }
};

public void processEvents(int timeout)
{
    if (!_processingEventsf)
    {
        Looper looper = Looper.myLooper();
        looper.myQueue().addIdleHandler(new IdleHandler(looper, timeout));
        _processingEventsf = true;
        try
        {
            looper.loop();
        } catch (RuntimeException re)
        {
            // We get an exception when we try to quit the loop.
        }
        _processingEventsf = false;
     }
}

내가 좋아하는 비슷한 솔루션이 다섯 번째 ,하지만 조금 더 간단한 비트와 반성이 필요하지 않습니다. 내 생각은 루퍼를 종료하기 위해 예외를 사용하지 않는 이유입니다. 그래서 내 커스텀 루퍼는 다음과 같이 읽습니다.

1) throw되는 예외 :

final class KillException extends RuntimeException {
}

2) 맞춤 루퍼 :

public final class KillLooper implements Runnable {
    private final static KillLooper DEFAULT = new KillLooper();

    private KillLooper() {
    }

    public static void loop() {
        try {
            Looper.loop();
        } catch (KillException x) {
            /* */
        }
    }

    public static void quit(View v) {
        v.post(KillLooper.DEFAULT);
    }

    public void run() {
        throw new KillException();
    }

}

커스텀 루퍼의 사용은 아주 간단합니다. foo 대화 상자가 있다고 가정하고 foo 대화 상자를 모달로 호출하려는 위치에서 다음을 수행하십시오.

a) foo를 호출 할 때 :

foo.show();
KillLooper.loop();

foo 대화 상자에서 종료하려면 사용자 지정 루퍼의 quit 메서드를 호출하기 만하면됩니다. 이것은 다음과 같습니다.

b) foo에서 나갈 때 :

dismiss();
KillLooper.quit(getContentView());

최근에 5.1.1 Android에서 몇 가지 문제를 보았습니다. 주 메뉴에서 모달 대화 상자를 호출하지 말고 대신 모달 대화 상자를 호출하는 이벤트를 게시하십시오. 게시하지 않으면 주 메뉴가 중단되고 내 앱에서 Looper :: pollInner () SIGSEGVs를 보았습니다.


어렵지 않습니다.

waiting_for_result활동이 재개 될 때마다 소유자 활동 (이름 ) 에 플래그가 있다고 가정합니다 .

public void onResume(){
    if (waiting_for_result) {
        // Start the dialog Activity
    }
}

이것은 모달 대화 상자가 닫히지 않는 한 소유자 활동을 보장하며 포커스를 얻으려고 할 때마다 모달 대화 상자 활동으로 전달됩니다.


한 가지 해결책은 다음과 같습니다.

  1. 선택한 각 버튼의 모든 코드를 각 버튼의 리스너에 넣습니다.
  2. alert.show();Alert를 호출하는 함수의 마지막 코드 줄이어야합니다. 이 줄 이후의 모든 코드는 경고를 닫을 때까지 기다리지 않고 즉시 실행됩니다.

희망 도움!


다른 구성 요소를 클릭하여 대화 상자를 닫을 수 있으므로 이것이 100 % 모달인지 확실하지 않지만 루프 구성과 혼동되어 다른 가능성으로 제공합니다. 그것은 저에게 잘 작동했기 때문에 아이디어를 공유하고 싶습니다. 하나의 메서드에서 대화 상자를 생성하고 연 다음 콜백 메서드에서 닫을 수 있으며 프로그램은 콜백 메서드를 실행하기 전에 대화 상자 응답을 기다립니다. 그런 다음 새 스레드에서 나머지 콜백 메서드를 실행하면 나머지 코드가 실행되기 전에 대화 상자도 먼저 닫힙니다. 당신이해야 할 유일한 일은 글로벌 대화 상자 변수를 갖는 것입니다. 그래야 다른 방법으로 접근 할 수 있습니다. 따라서 다음과 같은 것이 작동 할 수 있습니다.

public class MyActivity extends ...
{
    /** Global dialog reference */
    private AlertDialog okDialog;

    /** Show the dialog box */
    public void showDialog(View view) 
    {
        // prepare the alert box
        AlertDialog.Builder alertBox = new AlertDialog.Builder(...);

        ...

        // set a negative/no button and create a listener
        alertBox.setNegativeButton("No", new DialogInterface.OnClickListener() {
            // do something when the button is clicked
            public void onClick(DialogInterface arg0, int arg1) {
                //no reply or do nothing;
            }
        });

        // set a positive/yes button and create a listener
        alertBox.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
            // do something when the button is clicked
            public void onClick(DialogInterface arg0, int arg1) {
                callbackMethod(params);
            }
        });

        //show the dialog
        okDialog = alertBox.create();
        okDialog.show();
    }


    /** The yes reply method */
    private void callbackMethod(params)
    {
        //first statement closes the dialog box
        okDialog.dismiss();

        //the other statements run in a new thread
        new Thread() {
            public void run() {
                try {
                    //statements or even a runOnUiThread
                }
                catch (Exception ex) {
                    ...
                }
            }
        }.start();
    }
}

활동에 필요한 다음 메서드를 호출하는 BroadcastReceiver를 사용합니다.

dialogFragment.show (fragmentTransaction, TAG); 그리고 onReceive ()에서 계속하십시오-나는 100 % 긍정적 인 것은 아니지만 startActivityForResult (); 정확히이 개념을 기반으로합니다.

해당 메소드가 수신자에서 호출 될 때까지 코드는 ANR없이 사용자 상호 작용을 기다립니다.

DialogFragment의 onCreateView 메서드

private static final String ACTION_CONTINUE = "com.package.name.action_continue";

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.fragment_dialog, container, false);
        Button ok_button = v.findViewById(R.id.dialog_ok_button);
        ok_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent();
                i.setAction(ACTION_CONTINUE);
                getActivity().getApplicationContext().sendBroadcast(i);
                dismiss();
            }
        });


    return v;
}

이 메서드는 DialogFrament 확장 클래스를 빌드하고 활동을 통해 해당 클래스의 인스턴스를 호출하는 데 의존합니다.

하나...

간단하고 명확하며 쉽고 진정한 모달입니다.

참조 URL : https://stackoverflow.com/questions/6120567/android-how-to-get-a-modal-dialog-or-similar-modal-behavior

반응형