21 February 2015

ลองหัดสร้าง Class และ Listener กันเถอะ - ตอนที่ 2

Updated on


        กลับมาต่อกับบทความตอนที่ 2 ที่จะสอนการสร้าง Class กับ Listener ไว้ใช้งานเอง แต่ทว่าบทความชุดนี้จะพิเศษหน่อยก็ตรงที่ยกตัวอย่างจากการใช้งานจริงในการพัฒนาแอพแอนดรอยด์ ดังนั้นบทความก็จะยาวกว่าปกติหน่อยนะครับ XD

บทความทั้งหมดในชุดนี้

        • ลองหัดสร้าง Class และ Listener กันเถอะ - ตอนที่ 1
        • ลองหัดสร้าง Class และ Listener กันเถอะ - ตอนที่ 2
        • ลองหัดสร้าง Class และ Listener กันเถอะ - ตอนที่ 3

มาเริ่มกันเถอะ

        จากคราวที่แล้วเจ้าของบล็อกได้พูดในส่วนของ Android Design Style กันไปแล้ว ทีนี้มาดูส่วนของโค๊ดกันบ้าง

Android Code Style

        เริ่มจากการทำงานแบบง่ายๆก่อน ก็คือ กดปุ่มแล้วแสดง Dialog ที่เตรียมไว้ ดังนั้นโค๊ดโดยทั่วไปก็จะมีลักษณะแบบนี้

package com.example.akexorcist.customdialogclass;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;

public class MainActivity extends Activity implements View.OnClickListener {
    Button buttonAlert = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        buttonAlert = (Button) findViewById(R.id.button_alert);
        buttonAlert.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()) {
        case R.id.button_alert:
            final Dialog dialog = new Dialog(this);
            dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
            dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
            dialog.setContentView(R.layout.layout_dialog);
            dialog.setCancelable(false);

            Button buttonOk = (Button)dialog.findViewById(R.id.button_ok);
            buttonOk.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    dialog.dismiss();
                }
             });

            dialog.show();
            break;
        }
    }
}


       แต่ถ้าอยากจะทำซัก 4 ปุ่ม ให้กดแล้วแสดงข้อความใน Dialog ไม่เหมือนกันล่ะ?
   
       ผู้ที่หลงเข้ามาอ่านหลายๆคนก็อาจจะบอกว่าทำแบบนี้สิ

public void showDialog(String message) {
    final Dialog dialog = new Dialog(this);
    dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
    dialog.setContentView(R.layout.layout_dialog);
    dialog.setCancelable(false);

    Button buttonOk = (Button) dialog.findViewById(R.id.button_ok);
    buttonOk.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
            dialog.dismiss();
        }
    });

    TextView tvMessage = (TextView) dialog.findViewById(R.id.tv_message);'
    tvMessage.setText(message);

    dialog.show();
}

        นั่นก็คือการรวบคำสั่งของ Dialog ให้เป็นฟังก์ชัน โดยมี String เป็น Parameter เพื่อกำหนดข้อความที่แสดงใน Dialog


        แต่ถ้า...

        อยากให้ Activity อื่นๆสามารถเรียกใช้งานได้เหมือนกันล่ะ?

        อยากให้ Dialog แสดงไม่ทับกันล่ะ?

        อยากให้มี Event Callback (หรือ Listener) เมื่อผู้ใช้กดปุ่ม OK ใน Dialog ล่ะ?

        ถึงแม้ว่าจะสามารถแก้ด้วยการทำ Function ได้ก็ตาม แต่ถ้าอยากจะเรียกใช้งานใน Activity อื่นๆก็ต้องเขียนโค๊ดเดิมใน Activity นั้นๆ แค่คิดก็ปวดหัวแล้ว เพราะมันเสียเวลามาก ถึงแม้ว่าจะเป็นการ Copy & Paste ตัวโค๊ดก็เถอะ และยังไม่รวมไปถึงการรกที่จะเกิดขึ้นจากโค๊ดที่จะเขียนเพิ่มเข้าไปด้วย

        ดังนั้น Class จึงเป็นคำตอบของคำถามเหล่านี้ เพราะว่า Class จะช่วยแบ่งความยุ่งยากซับซ้อนของโค๊ดแต่ละส่วนให้แยกออกจากกันเพื่อให้จัดการได้ง่าย

        ถ้างั้นลองสร้าง Class ขึ้นมาเลยดีกว่า เจ้าของบล็อกขอตั้งชื่อว่า MyAlertDialog


        โดยให้สร้าง Class เปล่าๆขึ้นมาก่อน

MyAlertDialog.java
package com.example.akexorcist.customdialogclass;

public class MyAlertDialog {

}

        ตามด้วย Constructor โดยสิ่งที่ต้องการคือ Context เพราะว่าต้องใช้ตอนสร้าง Dialog

MyAlertDialog.java
package com.example.akexorcist.customdialogclass;

import android.content.Context;

public class MyAlertDialog {
    private Context context = null;

    public MyAlertDialog(Context context) {
        this.context = context;
    }
}

        ทีนี้ก็เพิ่ม Method สำหรับแสดง Dialog ที่ได้สร้างไว้ โดยใช้เป็นชื่อว่า show

MyAlertDialog.java
package com.example.akexorcist.customdialogclass;

import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.view.Window;
import android.widget.Button;

public class MyAlertDialog {
    private Context context = null;

    public MyAlertDialog(Context context) {
        this.context = context;
    }

    public void show() {
        final Dialog dialog = new Dialog(context);
        dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.layout_dialog);
        dialog.setCancelable(false);

        Button buttonOk = (Button) dialog.findViewById(R.id.button_ok);
        buttonOk.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                dialog.dismiss();
            }
        });
        dialog.show();
    }
}

        ดังนั้นเวลาเรียกใช้งานก็จะเป็นแบบนี้แทน

MainActivity.java
package com.example.akexorcist.customdialogclass;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements View.OnClickListener {
    Button buttonAlert = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        buttonAlert = (Button) findViewById(R.id.button_alert);
        buttonAlert.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button_alert:
                MyAlertDialog dialog = new MyAlertDialog(this);
                dialog.show();
                break;
        }
    }
}

        ในคลาส MyAlertDialog ดูเหมือนว่าจะไม่ซับซ้อนมากนัก แต่การเอาคำสั่งทั้งหมดไปดองไว้ที่ Method ที่ชื่อว่า show ก็คงไม่ค่อยเหมาะซักเท่าไร ดังนั้นเจ้าของบล็อกขอจัดใหม่เป็นแบบนี้

MyAlertDialog.java
package com.example.akexorcist.customdialogclass;

import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.view.Window;
import android.widget.Button;

public class MyAlertDialog implements View.OnClickListener {
    private Dialog dialog = null;
    private Button buttonOk = null;

    public MyAlertDialog(Context context) {
        dialog = new Dialog(context);
        dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.layout_dialog);
        dialog.setCancelable(false);

        buttonOk = (Button) dialog.findViewById(R.id.button_ok);
        buttonOk.setOnClickListener(this);
    }

    public void show() {
        dialog.show();
    }

    @Override
    public void onClick(View v) {
        switch(v.getId) {
        case R.id.button_ok:
            dialog.dismiss();
            break;
        }
    }
}

        ดู Make Sense มากขึ้นละ โดยให้สร้าง Dialog ขึ้นมาตอนที่ประกาศ Constructor ทีเดียวไปเลย เพราะถ้าให้มันประกาศทุกครั้งตอนเรียกคำสั่ง show ก็จะดูเกินจำเป็นไปหน่อย แล้วที่คำสั่ง show ก็จะมีแค่คำสั่งแสดง Dialog และ OnClickListener ก็ย้ายไป Implement ที่คลาส MyAlertDialog แทน เพื่อให้ดูง่ายขึ้น


        ถ้าอยากให้ Dialog กำหนดข้อความที่จะแสดงได้ล่ะ?

        อาจจะต้องทำ Method อีกซักตัวเพื่อใช้กำหนดข้อความ ดังนั้นเจ้าของบล็อกจึงสร้าง Method ชื่อว่า setMessage เพื่อใช้กำหนดข้อความให้กับ Text View ใน Dialog ดังนี้

MyAlertDialog.java
package com.example.akexorcist.customdialogclass;

import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;

public class MyAlertDialog implements View.OnClickListener {
    private Context context = null;
    private Dialog dialog = null;
    private Button buttonOk = null;
    private TextView tvMessage = null;

    public MyAlertDialog(Context context) {
        this.context = context;

        dialog = new Dialog(context);
        dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.layout_dialog);
        dialog.setCancelable(false);

        buttonOk = (Button) dialog.findViewById(R.id.button_ok);
        buttonOk.setOnClickListener(this);

        tvMessage = (TextView) dialog.findViewById(R.id.tv_message);
    }

    public void setMessage(String message) {
        tvMessage.setText(message);
    }

    public void show() {
        dialog.show();
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()) {
        case R.id.button_ok:
            dialog.dismiss();
            break;
        }
    }
}

        โดยเจ้าของบล็อกกำหนด Text View ใน Consturctor เลย  และเมื่อเรียกใช้คำสั่ง setMessage ก็จะทำการกำหนดข้อความที่จะแสดงใน Dialog


        ดังนั้นเวลาเจ้าของบล็อกเรียกใช้ Dialog โดยกำหนดข้อความด้วย ก็จะได้ออกมาแบบนี้

MainActivity.java
package com.example.akexorcist.customdialogclass;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements View.OnClickListener {
    Button buttonAlert = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        buttonAlert = (Button) findViewById(R.id.button_alert);
        buttonAlert.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button_alert:
                MyAlertDialog dialog = new MyAlertDialog(this);
                dialog.setMessage("Android is Great!");
                dialog.show();
                break;
        }
    }
}

       พอลองทดสอบดูก็จะเห็นว่าข้อความเปลี่ยนไปตามที่กำหนดแล้ว



        ถ้าอยากจะกำหนดข้อความจาก String Resource ล่ะ?

        เพราะว่าการกำหนดผ่านโค๊ดโดยตรงจะมีข้อเสียคือไม่สามารถทำ Multiple Language ได้ ดังนั้นจึงควรทำให้ Dialog สามารถกำหนดข้อความจาก String Resource ได้


MyAlertDialog.java
package com.example.akexorcist.customdialogclass;

import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;

public class MyAlertDialog implements View.OnClickListener {
    private Context context = null;
    private Dialog dialog = null;
    private Button buttonOk = null;
    private TextView tvMessage = null;

    public MyAlertDialog(Context context) {
        this.context = context;

        dialog = new Dialog(context);
        dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.layout_dialog);
        dialog.setCancelable(false);

        buttonOk = (Button) dialog.findViewById(R.id.button_ok);
        buttonOk.setOnClickListener(this);

        tvMessage = (TextView) dialog.findViewById(R.id.tv_message);
    }

    public void setMessage(int resourceId) {
        setMessage(context.getString(resourceId));
    }
    
    public void setMessage(String message) {
        tvMessage.setText(message);
    }

    public void show() {
        dialog.show();
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()) {
        case R.id.button_ok:
            dialog.dismiss();
            break;
        }
    }
}

        ที่เจ้าของบล็อกทำก็คือใช้ประโยชน์จาก Context เพื่อดึง String Resource

        ทีนี้กลับมาเปิด strings.xml ก่อน แล้วใส่ข้อความเก็บไว้ในนี้ซะ

strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">CustomDialogClass</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>

    <string name="android_is_great">Android is Great!</string>
    <string name="do_not_press">Hey!\nDon\'t press this button</string>
    <string name="ok">OK</string>

</resources>

        ส่วน Method ใน MyAlertDialog ก็จะ Overload จากของเดิม มาเป็นคำสั่ง setMessage ที่กำหนดด้วย String Resource ได้

MyAlertDialog.java
package com.example.akexorcist.customdialogclass;

import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;

public class MyAlertDialog implements View.OnClickListener {
    private Context context = null;
    private Dialog dialog = null;
    private Button buttonOk = null;
    private TextView tvMessage = null;

    public MyAlertDialog(Context context) {
        this.context = context;

        dialog = new Dialog(context);
        dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.layout_dialog);
        dialog.setCancelable(false);

        buttonOk = (Button) dialog.findViewById(R.id.button_ok);
        buttonOk.setOnClickListener(this);

        tvMessage = (TextView) dialog.findViewById(R.id.tv_message);
    }

    public void setMessage(int resourceId) {
        setMessage(context.getString(resourceId));
    }

    public void setMessage(String message) {
        tvMessage.setText(message);
    }

    public void show() {
        dialog.show();
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()) {
        case R.id.button_ok:
            dialog.dismiss();
            break;
        }
    }
}

        ดังนั้นจากของเดิมที่กำหนดเป็น String ก็สามารถใช้เป็น String Resource ได้แล้ว (กำหนดได้สองแบบ)

MainActivity.java
package com.example.akexorcist.customdialogclass;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements View.OnClickListener {
    Button buttonAlert = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        buttonAlert = (Button) findViewById(R.id.button_alert);
        buttonAlert.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button_alert:
                MyAlertDialog dialog = new MyAlertDialog(this);
                dialog.setMessage("Android is Great!");
                dialog.show();
                break;
        }
    }
}

       และถ้าจะให้ดี setMessage ที่กำหนดเป็น String ควรใช้เป็น CharSequence แทน เพื่อให้รองรับกับข้อความรูปแบบอื่นๆได้ เพราะ String ก็เป็นคลาสที่สืบทอดมาจาก CharSequence อยู่แล้ว และคำสั่ง setText ของ Text View ก็รองรับการกำหนดด้วย CharSequence อยู่แล้ว

public void setMessage(CharSequence message) {
    tvMessage.setText(message);
}

        ดังนั้น MyAlertDialog ก็จะกลายเป็นแบบนี้

MyAlertDialog.java
public void setMessage(CharSepackage com.example.akexorcist.customdialogclass;

import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;

public class MyAlertDialog implements View.OnClickListener {
    private Context context = null;
    private Dialog dialog = null;
    private Button buttonOk = null;
    private TextView tvMessage = null;

    public MyAlertDialog(Context context) {
        this.context = context;

        dialog = new Dialog(context);
        dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.layout_dialog);
        dialog.setCancelable(false);

        buttonOk = (Button) dialog.findViewById(R.id.button_ok);
        buttonOk.setOnClickListener(this);

        tvMessage = (TextView) dialog.findViewById(R.id.tv_message);
    }

    public void setMessage(int resourceId) {
        setMessage(context.getString(resourceId));
    }

    public void setMessage(CharSequence message) {
        tvMessage.setText(message);
    }

    public void show() {
        dialog.show();
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()) {
        case R.id.button_ok:
            dialog.dismiss();
            break;
        }
    }
}


        Dialog เบื้องต้นพร้อมใช้งานแล้ว!! ต่อไปมาลองสร้าง Listener กันต่อเลยดีกว่า

        จะสร้าง Listener สำหรับอะไรดีล่ะ?

        เนื่องจากเจ้าของบล็อกเห็นว่าใน Dialog ตัวนี้สามารถกดปุ่มเพื่อปิด Dialog ได้ ดังนั้นก็เอามาทำเป็น Listener ให้กับ MyAlertDialog ซะเลย ว่าเมื่อกดปิด Dialog ก็จะมี Callback กลับมาบอกได้ว่า Dialog ถูกปิดแล้ว

        เอาล่ะ! ลองสร้างกันเลย!

        สำหรับการสร้าง Listener จะมีรูปแบบดังนี้

public interface MyListener {
    public void myCallbackMethod1(int arg1);
    public void myCallbackMethod2(Object arg1, float arg2);
    public boolean myCallbackMethod3();
}

        MyListener คือชื่อ Listener ที่ต้องการสร้าง สามารถกำหนดได้ตามใจชอบ
        myCallbackMethod คือ Callback ที่จะให้ Listener เรียกเมื่อเกิด Event ใดๆ จะมีกี่ Method ก็ได้ จะส่ง Parameter ด้วยก็ได้ หรือจะ Return ค่าบางอย่างก็ได้

        อาจจะดูเข้าใจยากสำหรับผู้ที่หลงเข้ามาอ่านบางคน ดังนั้นมาดูวิธีการใช้งานดีกว่า จะได้เข้าใจการทำงานได้มากขึ้น

        เจ้าของบล็อกขอตั้งชื่อ Listener ว่า OnDialogDismissListener และให้ Callback Method มีแค่ onDismiss ที่จะให้ทำงานเมื่อผู้ใช้ปิด Dialog

        ดังนั้นก็จะออกมาเป็นแบบนี้

public interface OnDialogDismissListener {
    public void onDismiss();
}

        แต่ยังไม่เสร็จนะ เพราะว่าเป็นการสร้าง Interface ขึ้นมาเท่านั้น ในการเรียกใช้งานจะต้องประกาศ Instance ถึงจะเรียกใช้งานได้ ถ้านึกไม่ออก ลองนึกถึงคำสั่ง setOnClickListener ก็ได้นะ

OnDialogDismissListener dislogDismissListener = null;

public void setOnDialogDismissListener(OnDialogDismissListener listener) {
    dialogDismissListener = listener;
}

public interface OnDialogDismissListener {
    public void onDismiss();
}

        สาเหตุที่เจ้าของบล็อกตั้งชื่อซะยาวเหยียด เพราะเผื่อกรณีที่มีการสร้าง Listener หลายๆตัวในคลาสนั้นๆ จึงทำให้การใช้ชื่อว่า listener ดูไม่เหมาะซักเท่าไร ทำให้สับสนได้ง่าย เลยใช้ชื่อยาวๆเพื่อให้เข้าใจได้ทันทีว่าเป็น Instance ของ Listener ตัวไหน

        แล้วก็เอาไปใส่ไว้ใน MyAlertDialog ได้เลย

package com.example.akexorcist.customdialogclass;

import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;

public class MyAlertDialog implements View.OnClickListener {
    private Context context = null;
    private Dialog dialog = null;
    private Button buttonOk = null;
    private TextView tvMessage = null;
    private OnDialogDismissListener dialogDismissListener = null;

    public MyAlertDialog(Context context) {
        this.context = context;

        dialog = new Dialog(context);
        dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.layout_dialog);
        dialog.setCancelable(false);

        buttonOk = (Button) dialog.findViewById(R.id.button_ok);
        buttonOk.setOnClickListener(this);

        tvMessage = (TextView) dialog.findViewById(R.id.tv_message);
    }

    public void setMessage(int resourceId) {
        setMessage(context.getString(resourceId));
    }

    public void setMessage(CharSequence message) {
        tvMessage.setText(message);
    }

    public void show() {
        dialog.show();
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()) {
        case R.id.button_ok:
            dialog.dismiss();
            break;
        }
    }

    public setOnDialogDismissListener(OnDialogDismissListener listener) {
        dialogDismissListener = listener;
    }

    public interface OnDialogDismissListener {
        public void onDismiss();
    }
}

        เมื่อไรที่อยากให้เรียก Callback Method ตัวนั้นๆก็จะเรียกแบบนี้

mListener.myCallbackMethod();

        สมมติว่าตอนประกาศ Callback Method มีการระบุ Parameter ด้วย ก็กำหนดให้ครบด้วยนะ

Object object = ...
int val = ...

mListener.myCallbackMethod(object, val);

        หรือถ้ามีการ Return ค่าบางอย่างก็สามารถเก็บค่าที่ Return มาเพื่อเอาไปใช้งานต่อได้เลยเช่นกัน

int val = ...
boolean state = mListener.myCallbackMethod(val);

        จากกรณีของเจ้าของบล็อกควรจะเป็นแบบนี้

dialogDismissListener.onDismiss();

        แต่รู้หรือไม่ว่าอาจจะเกิด NullPointerException ขึ้นได้ด้วยนะเออ

        เพราะว่าตอนที่กำหนดค่าให้กับ dialogDismissListener จะต้องใช้คำสั่ง setOnDialogDismissListener ที่ไม่ได้บังคับว่าต้องเรียกใช้ Method นี้ทุกครั้ง นั่นหมายความว่ามันอาจจะเกิด NullPointerException ได้เมื่อไม่ได้เรียก setOnDialogDismissListener นั่นเอง

       ดังนั้นเวลาที่เรียก Callback Method จะต้องเช็คด้วยว่า Instance ของ Listener มีค่าเป็น Null หรือไม่

if(dialogDismissListener != null) {
    dialogDismissListener.onDismiss();
}

       เมื่อใช้งานจริงๆก็จะออกมาเป็นแบบนี้

 package com.example.akexorcist.customdialogclass;

import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;

public class MyAlertDialog implements View.OnClickListener {
    private Context context = null  ;
    private Dialog dialog = null;
    private Button buttonOk = null;
    private TextView tvMessage = null;
    private OnDialogDismissListener dialogDismissListener = null;

    public MyAlertDialog(Context context) {
        this.context = context;

        dialog = new Dialog(context);
        dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.layout_dialog);
        dialog.setCancelable(false);

        buttonOk = (Button) dialog.findViewById(R.id.button_ok);
        buttonOk.setOnClickListener(this);

        tvMessage = (TextView) dialog.findViewById(R.id.tv_message);
    }

    public void setMessage(int resourceId) {
        setMessage(context.getString(resourceId));
    }

    public void setMessage(CharSequence message) {
        tvMessage.setText(message);
    }

    public void show() {
        dialog.show();
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()) {
        case R.id.button_ok:
            dialog.dismiss();
            if(dialogDismissListener != null)
                dialogDismissListener.onDismiss();
            break;
        }
    }
    
    public void setOnDialogDismissListener(OnDialogDismissListener listener) {
        dialogDismissListener = listener;
    }

    public interface OnDialogDismissListener {
        public void onDismiss();
    }
}

        เห็นกันมั้ยเอ่ยว่าเจ้าของบล็อกเรียก onDismiss ที่ไหน?

        เมื่อกด button_ok นั่นเอง เพื่อที่ว่าจะได้ส่ง Callback บอกว่า Dialog ถูกปิดลงแล้ว

        เวลาเรียกใช้งานก็จะออกมาในรูปแบบที่คุ้นเคยกันดี

MyAlertDialog dialog = new MyAlertDialog(this);
dialog.setMessage(R.string.android_is_great);
dialog.setOnDialogDismissListener(new MyAlertDialog.OnDialogDismissListener() {
    @Override
    public void onDismiss() {

    }
});
dialog.show();

        คล้ายๆกับ OnClickListener เลยใช่มั้ยล่ะ! ซึ่ง onDismiss ในนี้ก็จะถูกเรียกเมื่อ Dialog ถูกปิดนั่นเอง ดังนั้นโค๊ดใน MainActivity.java ก็จะเป็นแบบนี้แทน

MainActivity.java
package com.example.akexorcist.customdialogclass;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements View.OnClickListener, MyAlertDialog.OnDialogDismissListener {
    Button buttonAlert = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        buttonAlert = (Button) findViewById(R.id.button_alert);
        buttonAlert.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button_alert:
                MyAlertDialog dialog = new MyAlertDialog(this);
                dialog.setMessage(R.string.android_is_great);
                dialog.setOnDialogDismissListener(MainActivity.this);
                dialog.show();
                break;
        }
    }

    @Override
    public void onDismiss() {
        Toast.makeText(this, "Dialog Closed", Toast.LENGTH_SHORT).show();
    }
}

        เมื่อ Dialog ถูกปิดลง ก็จะแสดง Toast ว่า "Dialog Closed"

        แต่เพื่อความเหมาะสม ขอย้ายข้อความไปเก็บไว้ใน String Resource นะครับ

strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">CustomDialogClass</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>

    <string name="dialog_closed">Dialog Closed</string>
    <string name="android_is_great">Android is Great!</string>
    <string name="do_not_press">Hey!\nDon\'t press this button</string>
    <string name="ok">OK</string>

</resources>

        แล้วในคำสั่ง Toast ใช้แบบนี้แทน

Toast.makeText(this, R.string.dialog_closed, Toast.LENGTH_SHORT).show();


        ลองคิดต่อให้ไกลขึ้น

        เนื่องจาก Dialog ของเจ้าของบล็อกมีการกำหนด Cancelable เป็น True ไว้ จึงทำให้กดปิด Dialog ด้วยการกดที่ข้างนอก Dialog ได้ แต่เจ้าของบล็อกอยากจะทำให้มันยืดหยุ่นกว่านี้ สามารถกำหนดได้ว่าจะให้กดนอกกรอบ Dialog แล้วปิด Dialog หรือไม่ โดยที่ onDismiss ก็ยังคงทำงานอยู่

        กลับมาที่ MyAlertDialog.java เพื่อทำให้มันยืดหยุ่นกว่านี้อีกหน่อย โดยเจ้าของบล็อกจะเพิ่ม Method เข้าไปอีก 3 Method ดังนี้

public void setCancelable(boolean cancelable) {
    dialog.setCancelable(cancelable);
}

public boolean isShowing() {
    return dialog.isShowing();
}

public void dismiss() {
    if(isShowing())
        dialog.dismiss();
}

        ในกรณีที่ไม่ได้ใช้คำสั่ง setCancelable ก็จะมี Defaule เป็น True (กดพื้นที่นอก Dialog เพื่อปิดได้) และถ้าสังเกตดีๆจะเห็นว่าเจ้าของบล็อกเพิ่ม Method ที่ชื่อว่า dismiss เข้าไป แต่กลับไม่เรียก onDismiss ของ dialogDismissListener ซะงั้น ตรงนี้จึงกลายเป็นช่องโหว่ที่ทำให้ onDismiss ไม่ทำงานได้

        แต่ที่เจ้าของบล็อกไม่ใส่ onDismiss เพราะว่าเจ้าของบล็อกจะปรับเปลี่ยนใหม่ซะหน่อย โดยใช้ประโยชน์จาก OnDismissListener ที่มีอยู่แล้วของ Dialog นั่นแหละ เอามาทำเป็น Listener ของคลาสนี้อีกต่อ

dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
    @Override
    public void onDismiss(DialogInterface dialog) {
        if(dialogDismissListener != null)
            dialogDismissListener.onDismiss();
    }
});

        ดังนั้นเจ้าของบล็อกไม่จำเป็นต้องใช้คำสั่ง onDismiss ทุกๆที่ให้เปลืองบรรทัด เพราะจะใช้ประโยชน์จาก OnDismissListener ของคลาส Dialog มาช่วยให้ง่ายขึ้น

        จึงกลายเป็นแบบนี้แทน

MyAlertDialog.java
package com.example.akexorcist.customdialogclass;

import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;

public class MyAlertDialog implements View.OnClickListener, DialogInterface.OnDismissListener {
    private Context context = null  ;
    private Dialog dialog = null;
    private Button buttonOk = null;
    private TextView tvMessage = null;
    private OnDialogDismissListener dialogDismissListener = null;

    public MyAlertDialog(Context context) {
        this.context = context;

        dialog = new Dialog(context);
        dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.layout_dialog);
        dialog.setOnDismissListener(this);

        buttonOk = (Button) dialog.findViewById(R.id.button_ok);
        buttonOk.setOnClickListener(this);

        tvMessage = (TextView) dialog.findViewById(R.id.tv_message);
    }

    public void setMessage(int resourceId) {
        setMessage(context.getString(resourceId));
    }

    public void setMessage(CharSequence message) {
        tvMessage.setText(message);
    }

    public void setCancelable(boolean cancelable) {
        dialog.setCancelable(cancelable);
    }

    public boolean isShowing() {
        return dialog.isShowing();
    }

    public void dismiss() {
        if(isShowing())
            dialog.dismiss();
    }

    public void show() {
        dialog.show();
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()) {
        case R.id.button_ok:
            dialog.dismiss();
            break;
        }
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        if(dialogDismissListener != null)
            dialogDismissListener.onDismiss();
    }

    public void setOnDialogDismissListener(OnDialogDismissListener listener) {
        dialogDismissListener = listener;
    }

    public interface OnDialogDismissListener {
        public void onDismiss();
    }
}

        อ๊ะๆระวังสับสนนะจ๊ะ เพราะตอนนี้มี onDismiss จาก OnDialogDismissListener ที่สร้างเอง กับ onDismiss ที่มาจากการ Implements DialogInterface.OnDismissListener นะครับ (สังเกตได้จาก @Override)

        ส่วนโค๊ดใน MainActivity ไม่ต้องเปลี่ยนแปลงอะไรเลย เพราะเจ้าของบล็อกแก้ไขเพิ่มเติมโดยไม่ส่งผลกับตอนเรียกใช้งาน

สรุป

        การสร้าง Class ขึ้นมาใช้งานแบบนี้ก็เพื่อลดความซับซ้อนของโค๊ดในแต่ละไฟล์ออกจากกัน แบ่งไปตามหน้าที่ของตนเอง และ MyAlertDialog ก็เป็นตัวอย่างของการแยกโค๊ดการทำงานสำหรับ Custom Dialog ออกมาจากโค๊ดหลัก เพื่อให้สามารถจัดการได้ง่ายขึ้น อยากแก้ไขหรือเพิ่มเติมก็ทำที่คลาสตัวนั้นๆได้เลย ไม่ต้องมานั่งไล่หาว่าใส่คำสั่งไว้ที่ไหนบ้าง

        แล้ว Class กับ Listener ควรจะสร้างเมื่อไร และควรจะมีอะไรใน Class บ้าง?

        สำหรับเจ้าของบล็อก การสร้าง Class จะทำก็ต่อเมื่อรู้สึกว่ามีโค๊ดที่สามารถแยกการทำงานได้ เพื่อไม่ให้ไปปนกับโค๊ดหลักจนสับสน (เพราะตอนมานั่งไล่โค๊ดทีหลังมันจะมึนง่าย) และจะสร้างขึ้นมาเมื่อเห็นว่ามีโค๊ดบางอย่างมีการเรียกใช้งานซ้ำๆ

        ส่วนพวก Method นั้นจะขึ้นอยู่กับว่าต้องการใช้งานอะไรบ้าง ก็เขียนมันขึ้นมา บางอย่างก็เขียนเผื่อเรียกใช้ (แต่ก็ควรเผื่อเท่าที่จำเป็น)

        และ Listener ก็ขึ้นอยู่กับว่าอยากให้มี Event Callback เมื่อไร ก็สร้างมันขึ้นมา อยากให้มัน Callback เมื่อไรก็เรียกเมื่อนั้น เท่านั้นแหละ


        อ๊ะๆ ยังไม่จบนะครับ เพราะเจ้าของบล็อกยังรู้สึกว่า MyAlertDialog ตัวนี้ยังซับซ้อนเกินไป เดี๋ยวบทความหน้าจะมารวบรัดให้มันสั้นและเรียกใช้งานง่ายกว่านี้อีก!!


        เป็นบทความที่ยาวชะมัด