封面画师:画师JW     封面ID:78042954

基本程序单元Activity

Activity概述

Activity中文含义:活动,但是在Android中Activity的含义是手机屏幕的一屏,它是Android程序中重要的基本组件之一。

Activity的4种状态

运行状态:当安卓应用在运行时,此时就是处于运行状态。

暂停状态:当我们在安卓应用中执行退出时,此时应用往往会弹出“确认退出”的对话框,此时就是处于暂停状态。

停止状态:在 暂停状态 时,我们点击了“确认退出”的对话框中的“确认”按钮后,此时就是处于停止状态。

销毁状态:当我们在系统后台将某个安卓应用进程杀掉后,此时就是处于销毁状态。

运行状态和暂停状态是可见的,其他两种是不可见的。

Activity的生命周期

image-20200423135231955

矩形框内表示内容可已被回调的方法,非矩形框内表示Activity的重要状态。

创建、启动和关闭Activity

创建Activity

  1. 创建继承自Activity的Activity;
1
public class DetailActivity extends Activity {...}
  1. 重写需要的回调方法;
  2. 设置要显示的视图;
1
2
3
4
5
@Override    //通常重写onCreate()方法
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);        //设置布局
}

到这一步,Activity的基本骨架已经完成,但是这样的Activity并不能成功运行。

我们还需要配置我们定义的Activity

进入AndroidManifest.xml进行配置:

注意:我们在配置Activity时,需要注意我们自定义的Activity是否在该配置文件制定的包下,如果不在,需要我们书写包名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yang.activity">    <!--注意此处的package属性值-->
    <application
        ...>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!--注册的Activity-->
        <activity android:name=".DetailActivity" android:label = "详细"></activity>
    </application>
</manifest>

当然我们也可以使用Android Studio直接创建Activity。

启动Activity

当我们创建并配置好一个Activity后,Activity并不会直接显示在屏幕上,还需要我们启动它。

  • 对于入口Activity,我们可以在AndroidManifest.xml进行配置:(将一个Activity配置成程序入口)
1
2
3
4
5
6
7
<!--配置一个intent过滤器-->
<intent-filter>
    <!--把一个Activity设置为程序的主启动项-->
    <action android:name="android.intent.action.MAIN" />
    <!--指定在什么环境下这个动作才会被响应-->
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

Intent理解: 我们假设把Activity比作成一个人,那么Intent就相当于这个人的想法。可见,Activity需要通过Intent来表达自己的“意图”。我们可以在后面内容的Intent处看到其详细理解。

  • 对于其他的Activity我们可以使用startActivity() 来启动一个Activity。

在MainActivity中启动DetailActivity:

activity_main.xml:

1
2
3
4
5
<Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="查看详细"/>

detail_main.xml:

1
2
3
4
5
6
7
8
9
<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="详细内容"/>
<Button
        android:id="@+id/Button_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="关闭"/>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button1);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //创建Intent对象,参数为上下文对象和跳转的界面的Activity的类
                Intent intent = new Intent(MainActivity.this, DetailActivity.class);
                //启动Activity
                startActivity(intent);
            }
        });
    }
}

关闭Activity

直接使用finish()即可。

如果关闭的Activity不是主入口,那么关闭这个Activity就会返回到调用它的Activity中。如果是主入口的Activity,就会返回到主屏当中。

我们在DetailActivity中进行编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DetailActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);
        Button button = findViewById(R.id.Button_close);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
}

实现刷新当前Activity:直接调用onCreate(null);即可

多个Activity的使用

Bundle

Bundle:一个键值对的组合,要读取时,我们只需要使用key就可以找到对应的value。

可以使用Bundle在Activity之间交换数据。

使用Bundle数据交互示意图:

image-20200423211114313

模拟淘宝填写并显示收货地址的功能(数据交互)

activity_main.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    tools:context=".MainActivity">
    <EditText
        android:id="@+id/myAddress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入你的地址"/>
    ...
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginEnd="10dp"
        android:layout_marginTop="10dp"
        android:background="#FF5000"
        android:text="保存"/>
</LinearLayout>

activity_address.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    tools:context=".addressActivity">
    <TextView
        android:id="@+id/address"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    ...
</LinearLayout>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String address = ((EditText)findViewById(R.id.myAddress)).getText().toString();
                if (!"".equals(address)){
                    Intent intent = new Intent(MainActivity.this, addressActivity.class);
                    Bundle bundle = new Bundle();
                    bundle.putCharSequence("address",address);
                    intent.putExtras(bundle);
                    startActivity(intent);
                }else {
                    Toast.makeText(MainActivity.this, "请填写完整的信息!", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
}

AddressActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
public class addressActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_address);
        Intent intent = getIntent();
        Bundle bundle = intent.getExtras();
        String address = bundle.getString("address");
        TextView textView_address = findViewById(R.id.address);
        textView_address.setText(address);
    }
}

调用另一个Activity并返回结果

startActivityForResult();在此需要使用这个方法!

  • startActivityForResult()方法的基本格式如下:
1
public void startActivityForResult(Intent intent, int requestCode)

requestCode 顾名思义是请求码的含义,需要程序员自定义

示例:

1
2
3
4
//创建Intent对象
Intent intent = new Intent(MainActivity.this, DetailActivity.class);
//启动新的Activity
startActivityForResult(intent,0x007);

实现头像的切换: 需要准备6张图片,1张默认头像,5张可供选择的头像。

activity_main.xml:

1
2
3
4
5
6
7
8
9
10
11
12
<ImageView
        android:id="@+id/imageView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center_horizontal"
        android:src="@drawable/avatar"/>
<Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:layout_gravity="center_horizontal"
        android:text="选择头像"/>

activuty_head.xml:

1
2
3
4
5
6
7
8
<GridView
        android:id="@+id/gridview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="10dp"
        android:horizontalSpacing="3dp"
        android:verticalSpacing="3dp"
        android:numColumns="4"/>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, HeadActivity.class);
                startActivityForResult(intent,0x11);
            }
        });

    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == 0x11 && resultCode == 0x11){
            Bundle bundle = data.getExtras();
            int image = bundle.getInt("image");
            ImageView imageView = findViewById(R.id.imageView);
            imageView.setImageResource(image);
        }
    }
}

HeadActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class HeadActivity extends AppCompatActivity {
    private int[] pics = new int[]{R.drawable.boy, R.drawable.boy2, R.drawable.girl,
            R.drawable.longhairgirl, R.drawable.shorthairgirl};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_head);
        GridView gridView = findViewById(R.id.gridview);
        BaseAdapter adapter = new BaseAdapter() {
            @Override
            public int getCount() {        //注意此处记得修改
                return pics.length;
            }
            @Override
            public Object getItem(int position) {        //注意此处记得修改
                return position;
            }
            @Override
            public long getItemId(int position) {        //注意此处记得修改
                return position;
            }
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                ImageView imageView;
                if (convertView == null) {
                    imageView = new ImageView(HeadActivity.this);
                    imageView.setAdjustViewBounds(true);
                    imageView.setMaxWidth(158);
                    imageView.setMaxHeight(150);
                    imageView.setPadding(5, 5, 5, 5);
                } else {
                    imageView = (ImageView) convertView;
                }
                imageView.setImageResource(pics[position]);
                return imageView;
            }
        };
        gridView.setAdapter(adapter);
        gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Intent intent = getIntent();
                Bundle bundle = new Bundle();
                bundle.putInt("image",pics[position]);
                intent.putExtras(bundle);
                setResult(0x11,intent);
                finish();
            }
        });
    }
}

使用Fragment

Fragment的生命周期

通俗理解:

如果我们把Activity比作成鱼塘的水,那么Fragment就相当于鱼塘中的鱼。当鱼塘干涸时,鱼也就死掉了;当Activity暂停时,里面的所有Fragment也会被暂停;当Activity被销毁时,它里面的所有Fragment也会被销毁。只有当Activity正在运行时,我们才能对里面的Fragment进行操作。

image-20200424151155508

返回栈:一组Activity的集合,在这里面按照先进后出的原则(栈)放置了一系列Activity。当我们进行Fragment的转换的时候,我们可以把Fragment放入到Activity的返回栈中。

创建Fragment

extends Fragment

通过继承Fragment即可,也可以通过继承一个已经存在的Fragment子类。

新建一个布局文件:fragment_list.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text=" Fragment content"/>
</LinearLayout>

在包下新建一个Java类:FragmentList.java

继承Fragment,并重写onCreateView。

1
2
3
4
5
6
7
8
public class ListFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_list, container, false);
        return view;
    }
}

在Activity中添加Fragment

  1. 直接在布局文件中添加Fragment。

创建两个布局文件:fragment_list.xml,fragment_detail.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20dp"
        android:text=" List Fragment" />
</LinearLayout>
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:padding="10dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Detail Fragment"
        android:textSize="20dp"/>
</LinearLayout>

两个布局文件对应的Activity:ListFragment.java,DetailFragment.xml 继承Fragment,并重写onCreateView。

1
2
3
4
5
6
7
8
public class ListFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_list, container, false);
        return view;
    }
}
1
2
3
4
5
6
7
8
9
public class DetailFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_detail,container,false);

        return view;
    }
}

activity_main.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:paddingStart="16dp"
    android:paddingEnd="16dp"
    android:paddingTop="16dp"
    android:paddingBottom="16dp"
    android:orientation="horizontal"
    tools:context=".MainActivity">
    <fragment
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:name="com.yang.fragment.ListFragment"
        android:id="@+id/list"
        android:layout_weight="1"/>
    <fragment
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:name="com.yang.fragment.DetailFragment"
        android:id="@+id/detail"
        android:layout_weight="2"/>
</LinearLayout>
  1. 当Activity运行时添加Fragment

利用 上述代码,不修改两个布局文件(fragment_list.xml,fragment_detail.xml)。

修改activity_main.xml:

1
2
3
4
5
6
7
8
9
10
<LinearLayout>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Activity"/>
    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/fl"></FrameLayout>
</LinearLayout>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DetailFragment detailFragment = new DetailFragment();
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.add(R.id.fl,detailFragment);
        fragmentTransaction.commit();
    }
}

实现Tab标签切换功能

在本次案例中,使用Fragment实现Tab标签的切换功能。

我们需要准备三个图标资源:主页、地图、我

我们先设置三个标签对应的页面,我们使用文本框来代替:

app_fragment.xml、map_fragment.xml、mine_fragment.xml:

1
2
3
4
5
6
7
8
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="App Fragment"/>
</RelativeLayout>
1
2
3
4
5
6
7
8
9
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:padding="30dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Map Fragment" />
</RelativeLayout>
1
2
3
4
5
6
7
8
9
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:padding="30dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Mine Fragment" />
</RelativeLayout>

然后我们需要创建页面对应的Fragment:

我们在此仅贴出一个,其他两个修改inflater.inflate()的参数即可。

1
2
3
4
5
6
7
8
public class MapFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.map_fragment, null);
        return view;
    }
}

在activity_app.xml中添加Fragment标记和标签栏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<RelativeLayout >
    <fragment
        android:id="@+id/fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.yang.tabfragment.AppFragment"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">
        <ImageView
            android:id="@+id/home"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"
            android:src="@drawable/home"/>
        <ImageView
            android:id="@+id/map"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"
            android:src="@drawable/map"/>
        <ImageView
            android:id="@+id/mine"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"
            android:src="@drawable/mine"/>
    </LinearLayout>
</RelativeLayout>

最后编写MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView home = findViewById(R.id.home);
        ImageView map = findViewById(R.id.map);
        ImageView mine = findViewById(R.id.mine);
        //设置监听器
        home.setOnClickListener(l);
        map.setOnClickListener(l);
        mine.setOnClickListener(l);
    }
    View.OnClickListener l = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            Fragment fragment = null;
            switch (v.getId()){
                case R.id.home:
                    fragment = new AppFragment();
                    break;
                case R.id.map:
                    fragment = new MapFragment();
                    break;
                case R.id.mine:
                    fragment = new MineFragment();
                    break;
                default:break;
            }
            fragmentTransaction.replace(R.id.fragment,fragment);
            fragmentTransaction.commit();
        }
    };
}

应用核心Intent

初始Intent

中文翻译:n. 意图、目的

概括Intent

我们在Android应用程序中需要用到三种组件:ActivityServiceBroadcastReceiver。这三个组件是独立的,但他们可以相互调用、协调工作,最终组成一个完整的安卓应用。

Intent是 Android 中的一个用于组件间互相通信的信息对象,常用于启动组件和传递数据。Intent 最重要的就是其包含的信息。Intent 发出的时候,系统对应的行为正是由 Intent 所包含信息的组合决定。一个 Intent 所包含的信息如下图:

Intent包含的信息

通俗理解: 假设我们是安卓三种组件中的一种,商家是安卓三种组件中的另外一种,我们需要在商家处购买货物,货物就相当于是Bundle,而运送获取的快递员就相当于是Intent。Intent起到了信使的作用。

Intent工作过程

假设有两个Activity,分别取名为ListActivity和DetailActivity,要实现ListActivity跳转到DetailActivity:

image-20200424214224777

Intent基本应用

  1. 通过Intent可以开启另一个Activity(比如用户登录成功后跳转至主页);
  2. 通过Intent可以开启一个Service(比如点击下载按钮后下载某种文件、应用);
  3. 用来传递广播。

Intent对象的属性

Intent对象的属性包括:Component nameActionDataCategoryExtrasFlags

可以参考上文中 概述Intent 中的图。

Component name

用于设置Intent对象的组件名称。通过设置Component name可以启动其他的Activity,或者其他应用中的Activity。我们可以通过指定包名和类名来确定唯一一个Activity。

在此我们可以使用setComponent()

activity_main.xml

1
2
3
4
5
<Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="查看详细"/>

activity_detail.xml

1
2
3
4
<TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="详细内容"/>

DetailActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                ComponentName componentName = new ComponentName("com.yang.component_name","com.yang.component_name.DetailActivity");
                intent.setComponent(componentName);
                startActivity(intent);
            }
        });
    }
}

Action和Data

Action用来指定将要执行的动作,Data用来指定具体的数据。通常情况下,两者需要一起使用。

Action属性可以通过Intent定义的Action常量来设置,具体我们可以查看SDK API文档。

常量参考链接: 点击查看1 点击查看2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
★intent action大全★
ACTION_MAIN                         作为一个主要的进入口,而并不期望去接受数据 
ACTION_VIEW                         向用户去显示数据 
ACTION_ATTACH_DATA                  用于指定一些数据应该附属于一些其他的地方,例如,图片数据应该附属于联系人 
ACTION_EDIT                         访问已给的数据,提供明确的可编辑 
ACTION_PICK                         从数据中选择一个子项目,并返回你所选中的项目 
ACTION_CHOOSER                      显示一个activity选择器,允许用户在进程之前选择他们想要的 
ACTION_GET_CONTENT                  允许用户选择特殊种类的数据,并返回(特殊种类的数据:照一张相片或录一段音) 
ACTION_DIAL                         拨打一个指定的号码,显示一个带有号码的用户界面,允许用户去启动呼叫 
ACTION_CALL                         根据指定的数据执行一次呼叫(ACTION_CALL在应用中启动一次呼叫有缺陷,多数应用ACTION_DIAL,ACTION_CALL不能用在紧急呼叫上,紧急呼叫可以用ACTION_DIAL来实现) 
ACTION_SEND                         传递数据,被传送的数据没有指定,接收的action请求用户发数据 
ACTION_SENDTO                       发送一跳信息到指定的某人 
ACTION_ANSWER                       处理一个打进电话呼叫 
ACTION_INSERT                       插入一条空项目到已给的容器 
ACTION_DELETE                       从容器中删除已给的数据 
ACTION_RUN                          运行数据,运行维护 
ACTION_SYNC                         同步执行一个数据 
ACTION_PICK_ACTIVITY                为已知的Intent选择一个Activity,返回选中的类 
ACTION_SEARCH                       执行一次搜索 
ACTION_WEB_SEARCH                   执行一次web搜索 
ACTION_FACTORY_TEST                 工场测试的主要进入点

Data属性是一个URI对象,通常情况下,它包括数据的URI和MIME类型,不同的Action有不同的数据规格。比如:Action字段 > Data字段

image-20200424233840593


案例:使用Intent实现拨打电话和发送短信功能

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<RelativeLayout >
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="请选择你需要的功能:"/>
    <ImageButton
        android:id="@+id/imageButton_phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/phone"
        android:layout_below="@+id/text"
        android:layout_marginTop="30dp"
        android:background="#0000"
        android:scaleType="fitXY"/>
    <ImageButton
        android:id="@+id/imageButton_sms"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/sms"
        android:layout_toRightOf="@+id/imageButton_phone"
        android:layout_below="@+id/text"
        android:layout_marginTop="30dp"
        android:layout_marginLeft="30dp"
        android:background="#0000"
        android:scaleType="fitXY"/>
</RelativeLayout>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageButton imageButton = findViewById(R.id.imageButton_phone);
        ImageButton imageButton1 = findViewById(R.id.imageButton_sms);
        imageButton.setOnClickListener(l);
        imageButton1.setOnClickListener(l);
    }

    View.OnClickListener l = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent();
            ImageButton imageButton = (ImageButton)v;
            switch (imageButton.getId()){
                case R.id.imageButton_phone:
                    intent.setAction(intent.ACTION_DIAL);
                    intent.setData(Uri.parse("tel:10086"));
                    startActivity(intent);
                    break;
                case R.id.imageButton_sms:
                    intent.setAction(intent.ACTION_SENDTO);
                    intent.setData(Uri.parse("smsto:10086"));
                    intent.putExtra("sms_body","CXLL");
                    startActivity(intent);
                    break;
            }
        }
    };
}

因为要打电话和发短信,需要在AndroidManifest.xml中开启权限。

AndroidManifest.xml:

1
2
3
4
5
<manifest >
    <uses-permission android:name="android.permission.CALL_PHONE"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
    ...
</manifest>

Action和Category

Action属性的使用方法与上一条一样。

Category属性:对执行动作的类别进行描述,在使用这个属性时,可以使用Intent提供的Category常量来设置,具体我们可以查看SDK API文档。

参考链接:参考链接1

Category常量 常量对应字符串 简单说明
CATEGORY_DEFAULT android.intent.category.DEFAULT 默认的Category
CATEGORY_BROWSABLE android.intent.category.BROWSABLE 指定该Activity能被浏览器安全调用
CATEGORY_TAB android.intent.category.TAB 指定Activity作为TabActivity的Tab页
CATEGORY_LAUNCHER android.intent.category.LAUNCHER Activity显示顶级程序列表中(设置程序主入口)
CATEGORY_INFO android.intent.category.INFO 用于提供包信息
CATEGORY_HOME android.intent.category.HOME 设置该Activity随系统启动而运行(返回系统桌面)
CATEGORY_PREFERENCE android.intent.category.PREFERENCE 该Activity是参数面板
CATEGORY_TEST android.intent.category.TEST 该Activity是一个测试
CATEGORY_CAR_DOCK android.intent.category.CAR_DOCK 指定手机被插入汽车底座(硬件)时运行该Activity
CATEGORY_DESK_DOCK android.intent.category.DESK_DOCK 指定手机被插入桌面底座(硬件)时运行该Activity
CATEGORY_CAR_MODE android.intent.category.CAR_MODE 设置该Activity可在车载环境下使用

案例:实现单击按钮关闭界面回到系统桌面

activity_main.xml:

1
2
3
4
5
6
7
8
9
<LinearLayout >
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="70dp"
        android:layout_marginStart="60dp"
        android:text="关闭界面"/>
</LinearLayout>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button1);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction(intent.ACTION_MAIN);
                intent.addCategory(intent.CATEGORY_HOME);
                startActivity(intent);
            }
        });
    }
}

Extras属性

主要用于向Intent组件中添加附加信息。通常情况下,这些附加信息由键值对的形式来保存。

在Intent对象中提供了putExtras()方法,用于把Bundle对象作为附加数据来进行添加。如果我们要获取保存在Bundle对象中的信息,可以使用getExtras()方法来获取。

Bundle对象传递数据需要数据小于0.5MB,否则会报OOM(Out Of Memory)错误。

这个属性常用于在多个Activity进行数据交换时。

Flags属性

作用:指示安卓程序应该如何启动另一个Activity,指示程序启动以后如何处理。

在使用这个属性时,可以使用Intent提供的Flags常量来设置,具体我们可以查看SDK API文档。

在此附上三个参考链接以供查看: 参考链接1 参考链接2 参考链接3


案例:Intent.FLAG_ACTIVITY_NO_HISTORY的使用

activity_main.xml:

1
2
3
4
5
<Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="查看详情"/>

activity_detail.xml:

1
2
3
4
<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="详情内容"/>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,DetailActivity.class);
                //让我们当前的Activity不在历史栈中保留
                intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
                startActivity(intent);
            }
        });
    }
}

运行结果:

如果不使用FLAG_ACTIVITY_NO_HISTORY,当我们点击“查看详情”按钮后,界面会跳转到activity_detail界面,并显示内容:“详情内容”。此时我们不停止当前程序,然后返回至系统桌面,再在后台找到该项目并进入该项目,进去后我们会发现当前界面依旧在activity_detail界面

如果使用FLAG_ACTIVITY_NO_HISTORY,当我们点击“查看详情”按钮后,界面会跳转到activity_detail界面,并显示内容:“详情内容”。此时我们不停止当前程序,然后返回至系统桌面,再在后台找到该项目并进入该项目,进去后我们会发现当前界面已经返回至程序入口界面

Intent种类

Intent种类分为两种:显式Intent隐式Intent

显式Intent

含义:我们在创建Intent对象时,直接指定目标组件(Activity、Service、BroadcastReceiver)名称就可以启动目标组件。我们明确知道要启动的Activity或Service类的名称时就使用显式Intent。

创建显式Intent对象的语法格式如下:

1
Intent intent = new Intent(Context packageContext, Class<?> cls);

其中:

packageContext为上下文对象,如:MainActivity.this

cls为要启动的Activity的类,如:DetailActivity.class

隐式Intent

含义:我们在创建Intent对象时,不指定目标组件,而是定义action、category或data,让Android系统根据设置好的匹配规则找到目标组件。使用隐式Intent时,我们通常需要给Intent对象定义action、category或data属性。


案例:使用隐式Intent打开哔哩哔哩

activity_main.xml:

1
2
3
4
5
<Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="打开哔哩哔哩"/>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction(intent.ACTION_VIEW);
                intent.setData(Uri.parse("http://www.bilibili.com"));
                startActivity(intent);
            }
        });
    }
}

显式Intent和隐式Intent的区别:

显式Intent 隐式Intent
直接指定目标组件的名称 不会用组件名称定义要激活的目标组件
多用于在应用程序内部传递信息 多用于在不同应用之间传递信息

Intent过滤器

主要应用在使用隐式Intent来启动Activity时。

隐式启动Activity时,并没有在Intent中指明Acitivity所在的类,因此,Android系统一定存在某种匹配机制,使Android系统能够根据Intent中的数据信息,找到需要启动的Activity。这种匹配机制是依靠Android系统中的Intent过滤器(Intent Filter)来实现的。

Intent过滤器是一种根据Intent中的动作(action)、类别(category)和数据(data)等内容,对适合接收该 Intent 的组件进行匹配和筛选的机制。Intent过滤器可以匹配数据类型、路径和协议,还可以确定多个匹配项顺序的优先级(priority)。应用程序的Activity、Service和 BroadcastReceiver 组件都可以注册Intent过滤器。这样,这些组件在特定的数据格式上则可以产生相应的动作。

为了使组件能够注册Intent过滤器,通常在AndroidManifest.xml 的各个组件下定义 < intent-filter > 节点,然后再< intent-filter >节点中声明该组件所支持的动作、执行的环境和数据格式等信息。当然,也可以在程序代码中动态的为组件设置Intent过滤器。 < intent-filter >节点支持< action >标签 、 < category >标签 和 < data >标签,分别用来定义Intent过滤器的动作、类型和数据。

参考链接:参考链接

常用的标签:

image-20200426000949802

在Activity中使用包含定义动作的隐式Intent启动另外一个Activity

案例概述:主入口有一张尺寸较小的图片,点击下方的“查看大图”后,弹出选择框,选择后进入页面查看大图。因此本案例需要首先准备一张图片。

image-20200426155241982

activity_main.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<RelativeLayout>
    <ImageView
        android:id="@+id/image"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerInParent="true"
        android:src="@drawable/image"/>
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/image"
        android:layout_centerInParent="true"
        android:text="查看大图"/>
</RelativeLayout>

activity_show.xml:

1
2
3
4
<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image"/>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction(intent.ACTION_VIEW);
                startActivity(intent);
            }
        });
    }
}

最后需要进入AndroidManifest.xml文件进行配置:

1
2
3
4
5
6
<activity android:name=".ShowActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

Android程序调试

DDMS工具使用

Android开发管径提供的调试工具,可以查看到指定进程运行时的线程信息、内存信息、内存分配、

为测试设备截屏、查看Log Cat日志等等。

谷歌正慢慢将DDMS抛弃!!

打开DDMS

  1. 在Android Studio 中打开;
  2. 独立打开(在SDK目录下的可执行程序,在Android 10 SDK目录下无该可执行程序)。

打开DDMS之前,我们需要在Android Studio中打开模拟器。

Android Studio升级到 3.0 以后,使用Android Profiler替代了DDMS。

image-20200426162158810

输出日志信息

Log类

结构:

继承 java.lang.Object
在包 android.util.Log 中

Log类提供的方法

方法 作用 日志颜色
Log.v(String tag,String msg) 输出冗余信息 黑色 - VERBOSE
Log.d(String tag,String msg) 输出调试信息 蓝色 - DEBUG
Log.i(String tag,String msg) 输出普通信息 绿色 - INFO
Log.w(String tag,String msg) 输出警告信息 橙色 - WARN
Log.e(String tag,String msg) 输出错误信息 红色 - ERROR

日志级别从低到高。


案例:测试输出日志信息

activity_main.xml:

1
2
3
4
5
6
7
8
<RelativeLayout >
    <ImageView
        android:id="@+id/image"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerInParent="true"
        android:src="@drawable/image"/>
</RelativeLayout>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity:";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.e(TAG,"MR输出的【错误信息】");
        Log.w(TAG,"MR输出的【警告信息】");
        Log.i(TAG,"MR输出的【普通信息】");
        Log.d(TAG,"MR输出的【调试信息】");
        Log.v(TAG,"MR输出的【冗余信息】");
    }
}

输出结果:

image-20200426165421978

日志级别过滤器可以输出比选中级别高的日志信息。

程序调试

使用Android Studio 编译器调试

使用Android Studio 编写代码时,使用ALT + ENTER快捷键可以快速修正代码。

其他AS使用操作与IDEA一样,毕竟AS是基于 Intellij IDEA的,在此不再累述。

使用Android Studio 调试器调试

使用断点的方式进行调试,设置好断点后,使用Debug运行。

Debug调试按钮:

Step Over: 单步跳过,运行单独的一行程序代码,但是不进入调用这个方法的内部。如果方法中有断点,则会在断点处停止。

Step Into:单步跳入,跳入到调用方法或者对象的内部单步执行程序。

Force Step Into:强制单步跳入,跳入所有被调入的方法。

Step Out:单步跳出,跳出当前执行或跳入的方法。

Run to Cursor:运行到下一个断点,如果程序中没有断点或出现异常,程序直接运行到结束。

Evaluate Expression:计算表达式。可以输入某些变量名,然后进行计算。

Variables(变量面板):

image-20200426173809958

在变量面板修改某个变量的值:右击选中变量 > Set Value > 输入修改的值并回车以完成修改。

Android事件处理和手势

事件处理概述

基于监听的事件处理

做法:

为安卓的UI组件绑定特定的事件监听器。比如:为按钮绑定单击事件监听器。

三类对象:

事件监听器(Event Listener):监听事件源发生的事件,并对不用的事件作出相应的响应。

事件源(Event Source):事件产生的来源。一般情况下指各种组件,比如说窗口、按钮、菜单等。

事件(Event):封装了UI组件上发生的特定事件的具体信息。

处理流程:

image-20200426180158279

基于回调的事件处理

做法:

重写Android组件特定的回调方法或重写Activity的回调方法。

回调方法:当某个事件发生时所调用的方法。

重写的方法:

通常重写 onTouchEvent()onKeyDown()onKeyUp()这三个方法。


重写的三种方法演示:

activity_main.xml 使用默认的即可。

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MainActivity extends AppCompatActivity {

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Toast.makeText(MainActivity.this,"触摸",Toast.LENGTH_SHORT).show();
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Toast.makeText(MainActivity.this,"按下",Toast.LENGTH_SHORT).show();
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        Toast.makeText(MainActivity.this,"抬起",Toast.LENGTH_SHORT).show();
        return super.onKeyUp(keyCode, event);
    }
}

测试方法: 运行后点击屏幕任意空白处可以测试“触摸”,调节音量可以测试“按下”和“抬起”。

区别

对于通用性的事件采用基于回调的事件处理方式。

对于某些特定的事件采用基于监听的事件处理方式。

物理按键事件处理

物理按键:手机上真实存在的按键。

按键状态:

安卓为每一个物理按键都提供了三个方法,分别是:

按下,但没有松开:onKeyDown()

抬起,松开某个按键:onKeyUp()

长按,长按某个按键:onKeyLongPress()

物理按键常量:

音量键:KEYCODE_VOLUME_UP KEYCODE_VOLUME_DOWM

电源键:KEYCODE_POWER

返回键:KEYCODE_BACK

主屏键:KEYCODE_HOME

菜单键:KEYCODE_MENU

双击返回键退出应用

本质是两秒内点击两次返回键退出应用。

activity_main.xml 使用默认文件。

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//继承Activity不让界面显示ActionBar
public class MainActivity extends Activity {
    private long exitTime = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    //1. 重写onKeyDown()方法来拦截用户单击后退按钮事件
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK){
            exit();
            return false;
        }
        return super.onKeyDown(keyCode, event);
    }
    //2. 创建退出方法exit()
    public void exit(){
        //判断两次按下返回键的时间差
        if ((System.currentTimeMillis()-exitTime >2000)){
            Toast.makeText(MainActivity.this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
            exitTime = System.currentTimeMillis();
        }else{
            finish();
            System.exit(0);
        }
    }
}

触摸屏事件处理

单击事件

为主键设置单击事件监听器:setOnClickListener(View.OnClickListener)

setOnClickListener()

1
2
3
public static interface View.OnClickListener{
    public void onClick(View v);
}

正因如此,我们在以前为按钮设置单击事件监听器时需要重写onClick()方法。

长按事件

长按事件需要长按2秒以上才会触发

setOnLongClickListener(View.OnLongClickListener)为组件设置长按事件监听器。

1
2
3
public static interface View.OnLongClickListener{
    public boolean onLongClick(View v);
}

我们创建长按事件监听器时需要重写onLongClick()方法。

长按图片,弹出 收藏/举报 菜单

activity_main.xml:

1
2
3
4
5
6
7
<LinearLayout android:gravity="center">
   <ImageView
       android:id="@+id/image"
       android:layout_width="250dp"
       android:layout_height="250dp"
       android:src="@drawable/image"/>
</LinearLayout>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //2. 将长按事件注册到菜单中,并打开菜单
        ImageView imageView = findViewById(R.id.image);
        imageView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                registerForContextMenu(v);
                openContextMenu(v);
                return true;
            }
        });
    }
    // 1. 在MainActivity中重写onCreateContextMenu菜单,为菜单添加选项值
    @Override
    public void onCreateContextMenu(ContextMenu menu, 
                                    View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        menu.add("收藏");
        menu.add("举报");
    }
}

触摸事件

setOntouchListener(View.OnTouchListener)为组件设置触摸事件监听器

1
2
3
public interface View.OnTouchListener{
    public abstract boolean onTouch(View v, MotionEvent event);
}

我们创建触摸事件监听器时需要重写onTouch()方法。

MotionEvent:保存发生触摸的位置、事件等细节信息。

实现帽子的拖动

我们需要先准备一个帽子的图片资源。

activity_main.xml:

1
2
3
4
5
6
7
8
<RelativeLayout 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:id="@+id/relative"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
</RelativeLayout>

新建一个Java类,取名为HatView.java用于指定帽子的位置和绘制帽子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 创建自定义View,绘制帽子
public class HatView extends View {
    public float bitmapX;
    public float bitmapY;
    
    public HatView(Context context) {
        super(context);
        bitmapX = 65;
        bitmapY = 0;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(),
                                                     R.drawable.hat);
        canvas.drawBitmap(bitmap,bitmapX,bitmapY,paint);
        if (!bitmap.isRecycled()){
            bitmap.recycle();
        }
    }
}

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 2. 创建并实例化帽子类对象,并未帽子添加触摸事件监听器,在重写的触摸方法中根据触摸的位置重绘帽子
        final HatView hat = new HatView(MainActivity.this);
        hat.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                hat.bitmapX = event.getX() - 80;
                hat.bitmapY = event.getY() - 80;
                hat.invalidate();
                return true;
            }
        });
        // 3. 把帽子添加到布局管理器中
        RelativeLayout rl = findViewById(R.id.relative);
        rl.addView(hat);
    }
}

单击事件和触摸事件的区别

安卓应用在执行的时候会先触发触摸事件,如果触摸事件没有完全消费掉事件,再触发单击事件

消费事件:一次UI操作是否完成响应了,如果在重写的方法当中返回了true,那么就表示完全消费了这个事件;如果返回的是false,那么它会再交给后面的事件进行处理。

单击事件与触摸事件的区别案例

activity_main.xml:

1
2
3
4
5
<Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="单击与触摸的区别"/>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.btn);
        //1. 为按钮添加单击事件监听器
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("onClick","单击事件");
            }
        });
        //2. 为按钮添加触摸事件监听器
        button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN){
                    Log.i("onTouch","按下");
                } else if(event.getAction() == MotionEvent.ACTION_UP){
                    Log.i("onTouch","抬起");
                }
                return true;
            }
        });
    }
}

编写好后,我们启动模拟器运行程序,打开Logcat观察日志输出结果:

此时重写的触摸事件方法返回值为false,点击按钮,日志输出结果为:

image-20200426231348614

如果将重写的触摸事件方法返回值改为true,那么日志输出结果为:

image-20200426231534512

结论:

单击事件触发一个动作,触摸事件触发两个动作。

安卓应用在执行的时候会先触发触摸事件,如果触摸事件没有完全消费掉事件,再触发单击事件。

手势检测

安卓为手势检测提供了GestureDetector类。

我们在创建这个类的对象的时候需要传入GestureDetector.OnGestureListener接口的实例,这个接口是个监听器,用来对用户的手势进行响应。

我们在实现OnGestureListener实例的时候,必须重写六个方法:

方法名 含义
onDown() 触摸事件按下时触发
onFling() 当用户的手指在屏幕上滑过一段距离时触发
onLongPress() 当用户的手指在屏幕上长按时触发
onScroll() 当用户的手指在屏幕上滑动时触发
onShowPress() 当用户的手指在屏幕上还没有滑动时触发
onSingleTapUp() 当用户的手指在屏幕上轻击时触发

实现滑动图片以切换图片

**准备:**我们需要准备四张图片,取名为image1 ~ image4,并将它们放置于drawable目录下。然后编写动画文件,并放置于res/anim下(anim需要自行创建)。

动画文件:

向左推进:push_left_in.xml

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="100%p" android:toXDelta="0"
        android:duration="500" />
    <alpha android:fromAlpha="0.1" android:toAlpha="1.0"
        android:duration="500" />
</set>

向左推出:push_left_out.xml

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="0" android:toXDelta="-100%p"
        android:duration="500" />
    <alpha android:fromAlpha="1.0" android:toAlpha="0.1"
        android:duration="500" />
</set>

向右推进:push_right_in.xml

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="-100%p" android:toXDelta="0"
        android:duration="500" />
    <alpha android:fromAlpha="0.1" android:toAlpha="1.0"
        android:duration="500" />
</set>

向右推出:push_right_out.xml

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="0" android:toXDelta="100%p"
        android:duration="500" />
    <alpha android:fromAlpha="1.0" android:toAlpha="0.1"
        android:duration="500" />
</set>

activity_main.xml:

1
2
3
4
<ViewFlipper
        android:id="@+id/flipper"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ViewFlipper>

ViewFlipper 使用动画来控制多个组件之间的切换效果的。

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//1. 让MainActivity实现GestureDetector.OnGestureListener接口,并实现其所有方法
public class MainActivity extends AppCompatActivity implements GestureDetector.OnGestureListener {

    Animation[] animations = new Animation[4];
    final int distance = 50;
    private int[] images = new int[]{R.drawable.image1, R.drawable.image2,
            R.drawable.image3, R.drawable.image4};
    ViewFlipper flipper;
    // 2. 定义一个全局的手势检测器
    GestureDetector detector;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        detector = new GestureDetector(MainActivity.this, this);
        //3. 将要显示的图片加载到ViewFlipper中,并初始化动画数组
        flipper = findViewById(R.id.flipper);
        for (int i = 0; i < images.length; i++) {
            ImageView imageView = new ImageView(this);
            imageView.setImageResource(images[i]);
            flipper.addView(imageView);
        }
        animations[0] = AnimationUtils.loadAnimation(this,R.anim.push_right_in);
        animations[1] = AnimationUtils.loadAnimation(this,R.anim.push_right_out);
        animations[2] = AnimationUtils.loadAnimation(this,R.anim.push_left_in);
        animations[3] = AnimationUtils.loadAnimation(this,R.anim.push_left_out);
    }

    @Override
    public boolean onDown(MotionEvent e) {return false;}

    @Override
    public void onShowPress(MotionEvent e) { }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {return false;}

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2,
                            float distanceX, float distanceY) {return false;}

    @Override
    public void onLongPress(MotionEvent e) { }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvet e2, float velocityX, float velocityY) {
        //4. 在onFling()中通过触摸事件的X坐标判断是向左滑动还是向右滑动,并为其设置动画
        //从右向左滑
        if (e1.getX() - e2.getX() > distance){
            flipper.setInAnimation(animations[2]);
            flipper.setOutAnimation(animations[3]);
            flipper.showPrevious();
            return true;
        }else if (e2.getX() - e1.getX() > distance){
            flipper.setInAnimation(animations[0]);
            flipper.setOutAnimation(animations[1]);
            flipper.showNext();
            return true;
        }
        return false;
    }
    //5. 将Activity上的触摸事件交给GestureDetector处理
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return detector.onTouchEvent(event);
    }
}

手势的添加

我们可以自定义我们的手势,就像手写输入法可以识别我们手写的文字一样。

我们需要先创建手势文件,手势文件的创建需要用到一个APP:GestureBuilder。该软件可以在谷歌应用商店安装。(当然,进谷歌应用商店需要绿色上网,或者你也可以联系本文作者获取软件)

该软件在低版本的Android Studio中是内置的,但高版本似乎取消了这个软件。

注意:GestureBuilder需要安装在我们自己的手机上,不是Android Studio的虚拟机上。

GestureBuilder的使用很简单,在此就不再描述其使用方法。

手势文件的位置

我们用GestureBuilder创建好我们的手势文件后,我们需要获取我们的手势文件,该手势文件的位置在:

1
/storage/emulated/0/Android/data/pack.GestureApp/files/gesture.txt

其中,gesture.txt为手势文件的默认名称,你也可以将其修改,在这里我选择使用默认名称。

如果你成功找到手势文件了,需要将这个手势文件发送至你的电脑。

手势文件的位置

我们将我们的手势文件放置res/raw目录下。

Android Studio的res目录下没有默认创建raw目录,因此需要我们手动创建。

到此,我们已经成功创建好了手势文件,并将其放置于指定的目录当中了。

手势的识别

通过手势的添加后,我们已经拥有了我们的手势文件,并放置在了指定的目录。

接下来我们可以通过一个示例来完成手势的识别。

activity_main.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<RelativeLayout 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"
    tools:context=".MainActivity">
    <!--1.在布局文件中添加一个编辑框和手势组件-->
    <EditText
        android:id="@+id/editText"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="40dp"
        android:layout_marginTop="190dp"/>
    <android.gesture.GestureOverlayView
        android:id="@+id/gesture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentBottom="true"
        android:gestureStrokeType="multiple">
    </android.gesture.GestureOverlayView>
</RelativeLayout>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//2. 实现GestureOverlayView.OnGesturePerformedListener接口,并重写onGesturePerformed()方法
public class MainActivity extends Activity implements GestureOverlayView.OnGesturePerformedListener {

    private GestureLibrary library;
    private EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //3.加载raw文件夹中的手势文件,如果失败退出应用
        library = GestureLibraries.fromRawResource(MainActivity.this, R.raw.gesture);
        editText = findViewById(R.id.editText);
        if (!library.load()) {
            finish();
        }
        //4.获得GestureOverlayView组件,并且为其设置属性值和事件监听器
        GestureOverlayView gestureOverlayView = findViewById(R.id.gesture);
        //设置手势颜色
        gestureOverlayView.setGestureColor(Color.BLACK);
        //设置淡出屏幕的间隔事件 1000ms
        gestureOverlayView.setFadeOffset(1000);
        //添加事件监听器
        gestureOverlayView.addOnGesturePerformedListener(this);
    }

    @Override
    public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
        //5. 获取最佳匹配进行显示,并更新编辑框
        //获取全部预测结果
        ArrayList<Prediction> gestures = library.recognize(gesture);
        //保存当前预测的索引号
        int index = 0;
        //保存当前预测的得分
        double score = 0.0;
        //获得最佳匹配结果
        for (int i = 0; i < gestures.size(); i++) {
            //获取预测结果
            Prediction result = gestures.get(i);
            if (result.score > score){
                index = i;
                score = result.score;
            }
        }
        //更新编辑框内容
        //获取编辑框原有内容
        String text = editText.getText().toString();
        //在原有内容上追加最佳匹配结果
        text += gestures.get(index).name;
        //更新编辑框
        editText.setText(text);
        //设置光标置于文字末尾
        editText.setSelection(text.length());
    }
}

Android核心与事件处理完