第六章 事件

      在〈第六章 事件〉中尚無留言

在說明畫面元件時, 可以在xml裏註明android:onClick=”bt_click” 然後再於Java程式碼中撰寫public void bt_click(View v){}的方法. 此時使用者就可以經由按一下畫面元件, 執行所需的工作

此種作法, 可以應付簡易的互動工作, 也很常用. 但當按下實体按鍵時, 或想要使用其他的事件時, 就需使用Listener的面介

Listener介面

在android.view 及android.widget中, 宣告了基本的監聽介面(Listener), 常見的有如下
View.OnClickListener : 點一下事件
View.OnLongClickListener : 長按事件
View.OnKeyListener : 實体按鍵事件
View.OnTouchListener : 螢幕觸控事件

使用Listener介面的畫面元件, 都需有自己的名稱 : android:id=”@+id/名稱”

<Button
    android:id="@+id/btOk"
    android:text="確定"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_centerHorizontal="true" />

再來就是實作監聽介面, 將要執行的任務寫在裏面, 然後在畫面元件中註冊要監聽的介面
為了讓剛接觸Android的人有著全盤的了解, 也複習Java的類別機制, 所以分三部份說明.
第一種為內部類別, 第二種為匿名類別, 第三種為簡化匿名類別

內部類別

監聽介面, 通常是針對畫面元件設定的, 所以大都把監聽介面設計成內部類別, 並加上private, 讓外界無法存取

public class MainActivity extends AppCompatActivity {
    private class MyListener implements View.OnClickListener{
        @Override
        public void onClick(View view) {
            Toast.makeText(MainActivity.this, "OK button was clicked", 
               Toast.LENGTH_SHORT).show();
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn=(Button)findViewById(R.id.btOk);
        MyListener listener=new MyListener();
        btn.setOnClickListener(listener);
    }
}

上述紅色標示的, 即是內部類別的宣告.
棕色處使用findViewById(R.id.btOk)取得按鈕實体‧
藍色處產生監聽物件(listener), 再使用按鈕的setOnClickListener()註冊listener.


匿名類別

內部類別還要宣告class, 實在是麻煩, 因此可以使用匿名類別來簡化

public class MainActivity extends AppCompatActivity {
    View.OnClickListener listener= new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            switch(view.getId()){
                case R.id.btOk:
                    Toast.makeText(MainActivity.this, "OK button was clicked", 
                       Toast.LENGTH_SHORT).show();
                    break;
                case R.id.btCancel:
                    Toast.makeText(MainActivity.this, "Cancel button was clicked", 
                       Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btnOk=(Button)findViewById(R.id.btOk);
        Button btnCancel=(Button)findViewById(R.id.btCancel);
        btnOk.setOnClickListener(listener);
        btnCancel.setOnClickListener(listener);
    }
}

上述紅色處使用匿名類別直接產生listener物件, 然後藍色處直接註冊


 簡化匿名類別

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btnOk=(Button)findViewById(R.id.btOk);
        btnOk.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "OK button was clicked", 
                   Toast.LENGTH_SHORT).show();
            }
        });
    }
}

簡化程式架構

onCreate()常需要大量初始化元件, 實作及註冊監控介面, 造成方法內的程式碼雜亂不易除錯, 所以可以使用如下架構簡化

public class MainActivity extends AppCompatActivity {
    private Button btnOk, btnCancel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        processViews();
        processControllers();
    }
    private void processViews(){
        btnOk=(Button)findViewById(R.id.btOk);
        btnCancel=(Button)findViewById(R.id.btCancel);
    }
    private void processControllers(){
        btnOk.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "OK button was clicked", Toast.LENGTH_SHORT).show();
            }
        });        
    }
}

在processViews方法中取得畫面元件物件
在processControllers方法中實作監控介面及註冊

OnFocusChangeListener

當畫面元件焦點改變後所引發的事件. 請注意, 得到焦點時會發生此事件, 而離開時也會, 可用第二個參數boolean值判斷是進入或者是離開

public class MainActivity extends AppCompatActivity {
    private EditText edit1, edit2;
    private TextView txt;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        processViews();
        processControllers();
    }
    private void processViews(){
        edit1=(EditText)findViewById(R.id.edit1);
        edit2=(EditText)findViewById(R.id.edit2);
        txt=(TextView)findViewById(R.id.txt);
    }
    private void processControllers(){
        View.OnFocusChangeListener focus=new View.OnFocusChangeListener(){
            @Override
            public void onFocusChange(View view, boolean b) {
                txt.setText(b?"Edit1":"Edit2");
            }
        };
        edit1.setOnFocusChangeListener(focus);
    }
}

OnTouchListener

畫面元件的OnTouchListener, 需覆蓋onTouch(View, MotionEvent)方法. 觸發方式為在元件上點一下不放, 然後移動, 最後再放開, 就像在模擬滑鼠按下不放然後移動的過程

onTouch的第二參數為MotionEvent參考型態, 此型態可用getAction()方法取得事件, 有Down, Up, Move等較為常用, 也可用getX(), getY()取得目前位置

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn=(Button)findViewById(R.id.btn);
        btn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent event) {
                switch(event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        Log.d("Thomas", "Down");
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.d("Thomas", "Up");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        Log.d("Thomas", event.getX()+":"+event.getY());
                }
                return false;
            }
        });
    }
}

OnCheckedChangeListener

CheckBox, ToggleButton, Switch, RadioButton可以使用
CompoundButton.OnCheckedChangeListener

而RadioGroup則使用RadioGroup.OnCheckedChangeListener

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RadioGroup group=(RadioGroup)findViewById(R.id.group);
        group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup radioGroup, int id) {
                RadioButton rb=(RadioButton)findViewById(id);
                Toast.makeText(MainActivity.this, rb.getText(), Toast.LENGTH_SHORT).show();
            }
        });
    }
}

在RadioGroup 的onCheckedChanged中, 第二參數為被按下的RadioButton id, 所以可由此id找到是那一個RadioButton被按下.

Activity元件事件

上述說明的都是畫面元件的事件. 而Activity活動元件也有自己的事件

onBackPressed

當返回鍵被按下時, 應用程式會被結束. 若要在按下返回鍵時提醒使用者確定要離開嗎, 則可以在此彈出對話方塊詢問

public class MainActivity extends AppCompatActivity {
    @Override
    public void onBackPressed() {
        AlertDialog.Builder dialog = new AlertDialog.Builder(this);
        dialog.setMessage("確定要離開嗎");
        dialog.setPositiveButton("Yes", new DialogInterface.OnClickListener(){
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                MainActivity.super.onBackPressed();
            }
        });
        dialog.setNegativeButton("No",new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface arg0, int arg1) {}
        });
        dialog.show();
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

onKeyDown

只有返回鍵被按下時才會觸發, 所以是否結束的對話方塊也可以寫在這裏

onTouchEvent

當Activity視窗範圍內有發生觸控螢幕被按, 就會執行, 同樣包含了Down, Up, Movie等事件.

請特別注意一般網站及書本錯誤的資訊

在Activity內若有畫面元件, 比如button1, 註冊OnTouchListener, 而且在onTouch()的返回值果為若為true, 則表示onTouch()被消耗掉, 就不會再傳給button1的其他事件. 若返回結果是false, 則表示沒被消耗掉, 會被button1的其他事件再度處理.
而一般網站及書本總是誤寫成 :
返回為false, 則會繼續報給其他的物件, 比如Activity也能接收此事件, 這是大錯特錯的. 

如下的程式, 在onTouch()中返回true, 所以Button的onClick()就永遠接收不到訊息. 如果onTouch()返回false, 則Button的onClick()就會被觸發. 而且, 不論onTouch()是true or false, Activity onTouch永遠都收不到的, 因為觸發事件是Button的事, 跟Activity一點關係都沒有

public class MainActivity extends AppCompatActivity {
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("Thomas", "Activity onTouch");
        return super.onTouchEvent(event);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn=(Button)findViewById(R.id.button);
        btn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                Log.d("Thomas", "onTouch");
                return true; //消耗事件
            }
        });
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("Thomas","onClick");
            }
        });
    }
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *