第二堂(3)应用程式与使用者的互动

Android API提供应用程式使用者互动的设计架构,你可以根据使用者在应用程式的操作,设计与提供应用程式与使用者的互动功能。例如使用者点击画面元件、按下实体按键,还有在触控萤幕上点击或滑动,这些操作行为通常会称为“事件”。应用程式可以依照需求加入事件的控制,当某一种事件发生的时候,也就是使用者执行某种操作,可以执行你为这些事件设计好的程式码。

Android系统的使用者操作事件控制,都是一些已经设计好的作法,根据使用者操作事件和画面元件的种类,通常是撰写实作一个接口(interface)的类别,根据这个接口的规定实作需要的方法,在方法里面设计需要执行的工作。

7-1 画面元件的onClick设定

想要让应用程式提供的画面元件,可以让使用者点击以后执行一个指定的工作,最简单的作法就是在画面元件加入“android:onClick”设定,例如最常用的按钮元件(Button)。如果需要的话,也可以为文字符件(TextView)执行onClick设定。开启“res/layout/activity_main.xml”档案,参考下面的内容加入需要的设定:

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">  
    ...    
    <!-- 加入“android:clickable="true"”的设定,TextView元件才可以点击 -->
    <!-- 加入“android:onClick="方法名称"”的设定 --> 
    <TextView 
        ...
        android:clickable="true"
        android:onClick="aboutApp" />
</LinearLayout>

TextView是用来显示文字的元件,所以要特别加入让它可以点击的设定,如果是按钮元件的话就不用特别设定。如果希望使用者点击TextView元件以后,在画面显示应用程式名称的讯息框,就要加入需要的程式码。开启专案的“MainActivity.java”,参考下面的内容加入需要的程式码:

package net.macdidi.myandroidtutorial;
...
// 加入讯息框的API
import android.widget.Toast;
 
public class MainActivity extends Activity {
 
    ...
 
    // 方法名称与onClick的设定一样,参数的型态是android.view.View
    public void aboutApp(View view) {
        // 显示讯息框,指定三个参数
        // Context:通常指定为“this”
        // String或int:设定显示在讯息框里面的讯息或文字资源
        // int:设定讯息框停留在画面的时间
        Toast.makeText(this, R.string.app_name, Toast.LENGTH_LONG).show();
    }
}

执行这个应用程式,在应用程式画面点击最下面的TextView元件,检查有没有显示讯息框。

7-2 选单事件控制

如果应用程式提供的功能比较多一些,为了让画面可以比较简洁,通常会把功能设计为选单,选单资源的部份已经在“第二堂(1)规划与建立应用程式需要的资源”建立好了。需要让使用者选择选单项目后执行一些特定的工作,最简单的作法是为选单项目加入“onClick”的设定。开启“res/menu/main_menu.xml”档案,参考下面的内容加入需要的设定:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
 
    <!-- 为选单项目加入“android:onClick”设定 -->
   <item
       android:id="@+id/search_item"
       android:showAsAction="always"
       android:icon="@android:drawable/ic_menu_search"
       android:onClick="clickMenuItem" />        
 
   <item
       android:id="@+id/add_item"
       android:showAsAction="always"
       android:icon="@android:drawable/ic_menu_add"
       android:onClick="clickMenuItem" />    
 
   <item
       android:id="@+id/revert_item"
       android:showAsAction="always"
       android:icon="@android:drawable/ic_menu_revert"
       android:onClick="clickMenuItem" />         
 
   <item
       android:id="@+id/delete_item"
       android:showAsAction="always"
       android:icon="@android:drawable/ic_menu_delete"
       android:onClick="clickMenuItem" />
 
    <!-- 这是外层的选单项目,所以不用设定 -->
    <item
        android:id="@+id/share_item"
        android:showAsAction="always"
        android:icon="@android:drawable/ic_menu_share"
        android:onClick="clickMenuItem" >
 
        <menu>
            <item 
                android:id="@+id/googleplus_item"
                android:title="Google+"
                android:onClick="clickMenuItem" />
            <item
                android:id="@+id/facebook_item"
                android:title="Facebook"
                android:onClick="clickMenuItem" />
        </menu>
    </item> 
 
</menu>

这里执行的设定跟之前的说明不太一样,所有选单项目设定的方法名称都是“clickMenuItem”。你也可以为每一个选单项目设定不同的方法名称,可是这样做的话,Activity元件里面就要宣告很多方法,所以使用这样的作法。开启“MainActivity.java”档案,参考下面的内容加入需要的程式码:

package net.macdidi.myandroidtutorial;
...
import android.app.AlertDialog;
import android.view.Menu;
import android.view.MenuItem;
 
public class MainActivity extends ActionBarActivity {
    ...
    // 加载选单资源
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.main_menu, menu);
        return true;
    }
 
    // 使用者选择所有的选单项目都会呼叫这个方法
    public void clickMenuItem(MenuItem item) {
        // 使用参数取得使用者选择的选单项目元件编号
        int itemId = item.getItemId();
 
        // 判断该执行什么工作,目前还没有加入需要执行的工作
        switch (itemId) {
        case R.id.search_item:
            break;
        case R.id.add_item:
            break;
        case R.id.revert_item:
            break;
        case R.id.delete_item:
            break;
        case R.id.googleplus_item:
            break;
        case R.id.facebook_item:
            break;
        }
 
        // 测试用的程式码,完成测试后记得移除
        AlertDialog.Builder dialog = 
                new AlertDialog.Builder(MainActivity.this);
        dialog.setTitle("MenuItem Test")
              .setMessage(item.getTitle())
              .setIcon(item.getIcon())
              .show();
    }
 
}

执行这个应用程式,选择画面上方的选单项目,检查有没有显示对话框。

7-3 监听与事件介绍

“android.view”和“android.widget”套件宣告了许多“Listener”接口,这些接口通常会叫作“监听接口”。每一个监听接口可以控制使用者在应用程式中执行的一种操作,这些接口的名称都很规则,都是使用“On种类Listener”的格式命名。例如下列宣告在“android.view.View”类别中的基本监听接口:

  • View.OnClickListener:执行点击事件。
  • View.OnLongClickListener:执行长按事件。
  • View.OnKeyListener:执行实体按键操作事件。
  • View.OnTouchListener:执行触控萤幕操作事件。

采用这种方式为某个画面元件加入事件控制,因为需要在程式码使用画面元件,所以一定要为元件取一个名称,设定元件名称使用“android:id="@+id/名称"”的格式:

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">  
    ...    
    <!-- 加入“android:id="@+id/名称"”的设定 --> 
    <TextView 
        android:id="@+id/show_app_name"
        ...  />
</LinearLayout>

为需要执行事件控制的元件设定好名称(id)以后,让使用者在点击这个元件以后,使用对话框显示比较详细的应用程式资讯,所以先在文字资源档(res/values/strings.xml)加入需要的资源:

<resources>
    ...
    <string name="about">这是Android Tutorial应用程式</string>
</resources>

开启专案的“MainActivity.java”,参考下面的内容加入需要的程式码:

package net.macdidi.myandroidtutorial;
 
...
 
public class MainActivity extends ActionBarActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // 读取在画面配置档已经设定好名称的元件
        TextView show_app_name = (TextView) findViewById(R.id.show_app_name);
 
        // 建立点击监听物件
        View.OnClickListener listener = new View.OnClickListener() {
 
            @Override
            public void onClick(View view) {
                AlertDialog.Builder d = 
                    new AlertDialog.Builder(MainActivity.this);
                d.setTitle(R.string.app_name)
                 .setMessage(R.string.about)
                 .show();
            }
 
        };
 
        // 注册点击监听物件
        show_app_name.setOnClickListener(listener);
    }
    ...
}

执行这个应用程式,在应用程式画面点击最下面的TextView元件,检查有没有显示对话框。因为你在这个TextView元件执行OnClickListener事件的注册,它的“android:onClick”设定就被覆蓋了,所以点击以后只会显示对话框。

一般的应用程式也很常使用长按事件,开启专案的“MainActivity.java”,参考下面的内容,把原来的点击事件改为长按事件:

package net.macdidi.myandroidtutorial;
 
...
 
public class MainActivity extends ActionBarActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // 读取在画面配置档已经设定好名称的元件
        TextView show_app_name = (TextView) findViewById(R.id.show_app_name);
 
        // 建立长按监听物件
        View.OnLongClickListener listener = new View.OnLongClickListener() {
 
            @Override
            public boolean onLongClick(View view) {
                AlertDialog.Builder dialog = 
                    new AlertDialog.Builder(MainActivity.this);
                dialog.setTitle(R.string.app_name)
                      .setMessage(R.string.about)
                      .show();
                return false;
            }
 
        };
 
        // 注册长按监听物件
        show_app_name.setOnLongClickListener(listener);
    }
    ...
}

执行这个应用程式,在应用程式画面点击最下面的TextView元件,会显示原来设定的讯息框,长按TextView元件会显示对话框。经由这两个练习,就可以了解Android事件的设计方式,在大部份的情况下,监听接口都会以“On”开头,宣告与建立好监听物件以后,呼叫元件的“set监听接口”方法执行注册的工作。

7-4 ListView元件的事件控制

ListView元件在应用程式中的应用非常多,从应用程式的功能表、浏览大量的资料或是让使用者执行资料的选择,应用程式需要类似列表资料的需求,都可以使用它来完成。它可以简单的列出一些文字的项目在画面上,让使用者浏览与选择。也可以自己设计需要的项目画面,加入图示、CheckBox或其它需要的画面元件,它呈现的画面与可以提供的操作功能都非常灵活。

这个记事本的主画面使用ListView元件显示所有的记事资料,选择一个项目以后可以显示详细的内容与执行后续的工作,所以需要为ListView设定选择项目的事件控制。开启专案的“MainActivity.java”,参考下面的内容加入需要的程式码:

package net.macdidi.myandroidtutorial;
...
import android.widget.ArrayAdapter;
import android.widget.ListView;
 
public class MainActivity extends ActionBarActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        // 增加“final”关键字,让巢状类别中的程式码使用
        final String[] data = {
                "关于Android Tutorial的事情",
                "一只非常可爱的小狗狗!",
                "一首非常好听的音乐!"};
        int layoutId = android.R.layout.simple_list_item_1;
        ArrayAdapter<String> adapter = 
                new ArrayAdapter<String>(this, layoutId, data);
        ListView item_list = (ListView)findViewById(R.id.item_list);
        item_list.setAdapter(adapter);
 
        // 建立选单项目点击监听物件
        AdapterView.OnItemClickListener itemListener = new AdapterView.OnItemClickListener() {
            // 第一个参数是使用者操作的ListView物件
            // 第二个参数是使用者选择的项目
            // 第三个参数是使用者选择的项目编号,第一个是0
            // 第四个参数在这里没有用途
            @Override
            public void onItemClick(AdapterView<?> parent, View view, 
                    int position, long id) {
                Toast.makeText(MainActivity.this, 
                        data[position], Toast.LENGTH_LONG).show();
            }
        };
 
        // 注册选单项目点击监听物件
        item_list.setOnItemClickListener(itemListener);
        ...
    }
    ...
}

执行这个应用程式,在应用程式画面点击ListView的选单项目,看看有没有显示选单项目的内容讯息框。ListView元件也提供项目长按事件,你可以依照应用程式的需求,使用点击与长按事件提供使用者的操作。开启专案的“MainActivity.java”,参考下面的内容加入需要的程式码:

package net.macdidi.myandroidtutorial;
...
import android.widget.ArrayAdapter;
import android.widget.ListView;
 
public class MainActivity extends ActionBarActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        // 增加“final”关键字,让巢状类别中的程式码使用
        final String[] data = {
                "关于Android Tutorial的事情",
                "一只非常可爱的小狗狗!",
                "一首非常好听的音乐!"};
        int layoutId = android.R.layout.simple_list_item_1;
        ArrayAdapter<String> adapter = 
                new ArrayAdapter<String>(this, layoutId, data);
        ListView item_list = (ListView)findViewById(R.id.item_list);
        item_list.setAdapter(adapter);
 
        ...
 
        // 建立选单项目长按监听物件
        AdapterView.OnItemLongClickListener itemLongListener = new AdapterView.OnItemLongClickListener() {
            // 第一个参数是使用者操作的ListView物件
            // 第二个参数是使用者选择的项目
            // 第三个参数是使用者选择的项目编号,第一个是0
            // 第四个参数在这里没有用途         
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, 
                    int position, long id) {
                Toast.makeText(MainActivity.this, 
                        "Long: " + data[position], Toast.LENGTH_LONG).show();
                return false;
            }
        };
 
        // 注册选单项目长按监听物件
        item_list.setOnItemLongClickListener(itemLongListener);
        ...
    }
    ...
}

执行这个应用程式,在应用程式画面长按ListView的选单项目,看看有没有显示选单项目的内容讯息框。

7-5 重新规划Activity元件的程式码

如果Activity元件需要的画面元件比较多一些,使用者操作的功能也比较复杂,你应该可以想像得到,这个Activity元件类别的onCreate方法,会有一大堆呼叫findViewById方法取得画面元件物件的叙述,还有宣告与建立监听物件与执行注册的叙述。这些需要的叙述通通写在onCreate方法中,以程式设计的概念来说,一个方法的宣告有上百行的程式叙述,应该不是一种很好的写法,对开发人员来说,以后的维护与修改都会是一件不容易的工作。

为了让所有Activity元件的程式码,都可以使用一种比较固定而且容易的设计方式来完成需要的工作,建议你可以在开发每一个Activity元件类别的时候,使用像这个样版的模式来开发Activity元件:

public class SampleActivity extends Activity {
 // 宣告所有需要的画面元件物件字段变量
    private ...;
 
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(...);
 
        // 呼叫自己额外宣告的方法,执行所有取得画面元件物件的工作
        processViews();
        // 呼叫自己额外宣告的方法,执行所有注册的工作
        processControllers();
    }
 
    private void processViews() {
        // 在这个方法中,取得画面元件物件后指定给字段变量
        ... = (...) findViewById(R.id.xxx);
    }
 
    private void processControllers() {
        // 在这个方法中,宣告或建立需要的监听物件
        //    并执行所有需要的注册工作
        ...
    }
    ...
}

熟悉这样的写法以后,原来需要执行的工作会在不同的方法中执行。你会先加入画面元件字段变量的宣告,然后在processViews方法中取得与设定画面元件物件,如果需要执行注册监听物件的工作,在processControllers方法中加入需要的程式码。这样把程式码依照工作简单的分开在不同方法中执行,对以后的维护与修改都会有一个比较固定的作法,而且比较不容易出错。所以就算是一个很简单的Activity元件,都建议你使用这样的写法。

开启专案的“MainActivity.java”,不改变原来撰写好的功能,把它改为下面的内容:

package net.macdidi.myandroidtutorial;
 
import android.app.AlertDialog;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
 
public class MainActivity extends ActionBarActivity {
    private ListView item_list;
    private TextView show_app_name;
 
    private static final String[] data = {
            "关于Android Tutorial的事情",
            "一只非常可爱的小狗狗!",
            "一首非常好听的音乐!"};
    private ArrayAdapter<String> adapter;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        processViews();
        processControllers();
 
        int layoutId = android.R.layout.simple_list_item_1;
        adapter = new ArrayAdapter<String>(this, layoutId, data);
        item_list.setAdapter(adapter);
    }
 
    private void processViews() {
        item_list = (ListView)findViewById(R.id.item_list);
        show_app_name = (TextView) findViewById(R.id.show_app_name);
    }
 
    private void processControllers() {
        // 建立选单项目点击监听物件
        AdapterView.OnItemClickListener itemListener = new AdapterView.OnItemClickListener() {
            // 第一个参数是使用者操作的ListView物件
            // 第二个参数是使用者选择的项目
            // 第三个参数是使用者选择的项目编号,第一个是0
            // 第四个参数在这里没有用途
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                                    int position, long id) {
                Toast.makeText(MainActivity.this,
                        data[position], Toast.LENGTH_LONG).show();
            }
        };
 
        // 注册选单项目点击监听物件
        item_list.setOnItemClickListener(itemListener);
 
        // 建立选单项目长按监听物件
        AdapterView.OnItemLongClickListener itemLongListener = new AdapterView.OnItemLongClickListener() {
            // 第一个参数是使用者操作的ListView物件
            // 第二个参数是使用者选择的项目
            // 第三个参数是使用者选择的项目编号,第一个是0
            // 第四个参数在这里没有用途
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view,
                                           int position, long id) {
                Toast.makeText(MainActivity.this,
                        "Long: " + data[position], Toast.LENGTH_LONG).show();
                return false;
            }
        };
 
        // 注册选单项目长按监听物件
        item_list.setOnItemLongClickListener(itemLongListener);
 
        // 建立长按监听物件
        View.OnLongClickListener listener = new View.OnLongClickListener() {
 
            @Override
            public boolean onLongClick(View view) {
                AlertDialog.Builder dialog =
                        new AlertDialog.Builder(MainActivity.this);
                dialog.setTitle(R.string.app_name)
                        .setMessage(R.string.about)
                        .show();
                return false;
            }
 
        };
 
        // 注册长按监听物件
        show_app_name.setOnLongClickListener(listener);
    }
 
    // 加载选单资源
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.menu_main, menu);
        return true;
    }
 
    // 使用者选择所有的选单项目都会呼叫这个方法
    public void clickMenuItem(MenuItem item) {
        // 使用参数取得使用者选择的选单项目元件编号
        int itemId = item.getItemId();
 
        // 判断该执行什么工作,目前还没有加入需要执行的工作
        switch (itemId) {
            case R.id.search_item:
                break;
            case R.id.add_item:
                break;
            case R.id.revert_item:
                break;
            case R.id.delete_item:
                break;
            case R.id.googleplus_item:
                break;
            case R.id.facebook_item:
                break;
        }
 
        // 测试用的程式码,完成测试后记得移除
        AlertDialog.Builder dialog =
                new AlertDialog.Builder(MainActivity.this);
        dialog.setTitle("MenuItem Test")
                .setMessage(item.getTitle())
                .setIcon(item.getIcon())
                .show();
    }
 
    // 方法名称与onClick的设定一样,参数的型态是android.view.View
    public void aboutApp(View view) {
        // 显示讯息框
        // Context:通常指定为“this”;如果在巢状类别中使用,要加上这个Activity元件类别的名称,例如“元件类别名称.this”
        // String或int:设定显示在讯息框里面的讯息或文字资源
        // int:设定讯息框停留在画面的时间,使用宣告在Toast类别中的变量,可以设定为“LENGTH_LONG”和“LENGTH_SHORT”
        Toast.makeText(this, R.string.app_name, Toast.LENGTH_LONG).show();
    }
 
}

执行这个应用程式,确认所有功能都还是可以正确的运作。

文章导航