Android入门基础教程1

前序:Android入门基础教程_5239ZM的博客-CSDN博客

第6章 ViewPager(视图滑动切换)

6.1 ViewPager的简单介绍

ViewPager就是一个简单的页面切换组件,我们可以往里面填充多个View,然后我们可以左 右滑动,从而切换不同的View,我们可以通过setPageTransformer()方法为我们的ViewPager 设置切换时的动画效果,和前面学的ListView,GridView一样,我们也需要一个Adapter (适配器)将我们的View和ViewPager进行绑定,而ViewPager则有一个特定的Adapter—— PagerAdapter!另外,Google官方是建议我们使用Fragment来填充ViewPager的,这样 可以更加方便的生成每个Page,以及管理每个Page的生命周期!给我们提供了两个Fragment 专用的Adapter:FragmentPageAdapterFragmentStatePagerAdapter 我们简要的来分析下这两个Adapter的区别:

  • FragmentPageAdapter:和PagerAdapter一样,只会缓存当前的Fragment以及左边一个,右边 一个,即总共会缓存3个Fragment而已,假如有1,2,3,4四个页面:
    处于1页面:缓存1,2
    处于2页面:缓存1,2,3
    处于3页面:销毁1页面,缓存2,3,4
    处于4页面:销毁2页面,缓存3,4
    更多页面的情况,依次类推~
  • FragmentStatePagerAdapter:当Fragment对用户不 见得时,整个Fragment会被销毁, 只会保存Fragment的状态!而在页面需要重新显示的时候,会生成新的页面!

综上,FragmentPageAdapter适合固定的页面较少的场合;而FragmentStatePagerAdapter则适合 于页面较多或者页面内容非常复杂(需占用大量内存)的情况!

使用步骤:

Step 1:在activity_main.xml中编写ViewPage控件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 2:在layout/创建多个布局layouti.xm。

Step 3:编写MyPagerAdapter.java继承PagerAdapter并实现其方法。使用这个PagerAdapter需要重写下面的四个方法: 当然,这只是官方建议,实际上我们只需重写getCount()和isViewFromObject()就可以了

  • public int getCount();
    获得viewpager中有多少个view
  • public boolean isViewFromObject(@NonNull View view, @NonNull Object object);
    移除一个给定位置的页面。适配器有责任从容器中删除这个视图,这是为了确保在finishUpdate(viewGroup)返回时视图能够被移除。

 而另外两个方法则是涉及到一个key的东西:

  • public Object instantiateItem(@NonNull ViewGroup container, int position);
    ①将给定位置的view添加到ViewGroup(容器)中,创建并显示出来
    ②返回一个代表新增页面的Object(key),通常都是直接返回view本身就可以了,当然你也可以 自定义自己的key,但是key和每个view要一一对应的关系
  • public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object);
    判断instantiateItem(ViewGroup, int)函数所返回来的Key与一个页面视图是否是 代表的同一个视图(即它俩是否是对应的,对应的表示同一个View),通常我们直接写 return view == object

 MyViewPagerAdapter.java

public class MyPagerAdapter extends PagerAdapter {

    private ArrayList<View> listView;

    public MyPagerAdapter(ArrayList<View> listView) {
        this.listView = listView;
    }

    @Override
    public int getCount() {
        return listView.size();
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return view == object;
    }

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        container.addView(listView.get(position), 0);
        return listView.get(position);
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        container.removeView(listView.get(position));
    }
}

Step 4:获取加载布局的系统服务。 

Step 5:准备数据。利用LayoutInflater对象的inflate()方法获取切换的布局并且添加到列表集合中。

  • LayoutInflater. inflate() 这个方法的作用类似于 findViewById() 。不同点是 inflate() 是用来找 res/layout/ 下的 xml 布局文件,并且实例化,而 findViewById() 是找 xml 布局文件下的具体 widget 控件

Step 6:实例化 PagerAdapter适配器。

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

        //获取加载布局的系统服务
        LayoutInflater layoutInflater = LayoutInflater.from(this);

        View view1 = layoutInflater.inflate(R.layout.layout1, null);
        View view2 = layoutInflater.inflate(R.layout.layout2, null);
        View view3 = layoutInflater.inflate(R.layout.layout3, null);
        ArrayList<View> listView = new ArrayList<>();
        listView.add(view1);
        listView.add(view2);
        listView.add(view3);

        ViewPager viewPager = findViewById(R.id.vp);
        MyPagerAdapter myPagerAdapter = new MyPagerAdapter(listView);
        viewPager.setAdapter(myPagerAdapter);
    }
}

运行效果图:

更多详见:2.6.3 ViewPager的简单使用 | 菜鸟教程 (runoob.com)

第7章 Activity(活动)

7.1 Activity的介绍

7.1.1 什么是Activity

定义:Activity是Android的四大组件之一。是用户操作的可视化界面;它为用户提供了一个完成操作指令的窗口。当我们创建完毕Activity之后,需要调用setContentView()方法来完成界面的显示;以此来为用户提供交互的入口。在Android App 中只要能看见的几乎都要依托于Activity,所以Activity是在开发中使用最频繁的一种组件。

7.1.2 Activity的声明

要使应用能使用Activity就需要在AndroidManifest.xml文件中进行声明及其设置特定属性,步骤为:

  1. 打开AndroidManifest.xml配置文件
  2. 在<manifest>标签下的<application>标签中添加<activity>元素并指明android:name=".类名"

注意:其中'.'代表<manifest>标签下的package的属性值(包名),或者name属性值也可写Activity的全类名

7.2 Activity的生命周期

7.2.1 Activity的生命周期介绍

在Android中会维持一个Activity Stack(Activity栈),当一个新的Activity创建时,它就会放到栈顶,这个Activity就处于运行状态。当再有一个新的Activity被创建后,会重新压人栈顶,而之前的Activity则会在这个新的Activity底下,就像枪梭压入子弹一样。而且之前的Activity就会进入后台。
一个Activity实质上有四种状态:

  1. 运行中(Running/Active):这时Activity位于栈顶,是可见的,并且可以用户交互。
  2. 暂停(Paused):当Activity失去焦点,不能跟用户交互了,但依然可见,就处于暂停状态。当一个新的非全屏的Activity或者一个透明的Activity放置在栈顶,Activity就处于暂停状态;这个时候Activity的各种数据还被保持着;只有在系统内存在极低的状态下,系统才会自动的去销毁Activity。
  3. 停止(Stoped):当一个Activity被另一个Activity完全覆盖,或者点击HOME键退入了后台,这时候Activity处于停止状态。这里有些是跟暂停状态相似的:这个时候Activity的各种数据还被保持着;当系统的别的地方需要用到内容时,系统会自动的去销毁Activity。
  4. 销毁(Detroyed):当我们点击返回键或者系统在内存不够用的情况下就会把Activity从栈里移除销毁,被系统回收,这时候,Activity处于销毁状态。

7.2.2 Activity的生命周期状态

  • onCreate:表示窗口正在被创建,比如加载layout布局文件啊(setContentView)。 所以我们可以在这个方法中,做一些初始化的操作。
  • onStart:表示Activity正在被启动,即将开始,此时的窗口已经可见了,但是还没有出现在前台,所以无法和用户进行交互。也就是说此时的窗口正处在 不可见—>可见 的过程中。
  • onRestart:表示窗口正在重新启动。在什么场景下会调用这个呢?比如:从A页面进入B页面,然后点击BACK键(或者自己的返回上一页的按钮)回到A页面,那么就会调用A页面的onRestart方法了。(当前也牵扯到A和B页面的其他生命周期方法的调用,这个我们后面再详细说明)。再比如:点击HOME键回到桌面,然后通过点击任务栏或者点击应用图标再次进入A页面,都可以触发调用这个方法
  • onResume:表示此时的窗口已经可见了,显示在前台并且进行活动了,我们也可以与窗口进行交互了。
  • onPause:表示窗口正在停止,这是我们可以做一些存储数据、或者停止动画等一些不太耗时的操作,因为会影响到下一个Activity的显示。onPause执行完成之后,新的Activity的onResume才会执行。
  • onStop:表示窗口即将停止,此时,可以做一些稍微重量级的回收工作,但是也不能太耗时哈。
  • onDestroy:表示窗口即将被销毁。这是Activity生命周期中的最后一步了。这里,我们可以做一些回收工作和最终的资源释放工作。

7.2.3 Activity的生命周期调用

  • 启动一个Activity
    onCreate()–>onStart()–>onResume()
  • 在Activity创建完成后,点击Home回调主界面时
    onPause()–>onStop()
  • 当点击Home键后,点击App回到Activity时
    onRestart()–>onStart()–>onResume()
  • 在原有的Activity的基础上打新的Activity时
    开启新的Activity,原Activity生命周期执行方法顺序为:onPause()–>onStop(),事实上跟点击home键是一样的。
    但是这里有点要注意的是如果新的Activity使用了透明主题,那么当前Activity不会回调onStop方法。同时我们发现新Activity(SecondActivity)生命周期方法是在原Activity的onPause方法执行完成后才可以被回调,这也就是前面我们为什么说在onPause方法不能操作耗时任务的原因了。
  • 回退到原来的Activity时不会调用不会调用onRestart()
    onCreate()–>onStart()–>onResume()
  • 点击Back键回退时,相当于退出了当前Activity,Activity将被销毁
    onPause()–>onStop()–>onDestroy()

7.3 Intent和IntentFilter

7.3.1 Intent的基本概念

Intent中文意思指”意图”,按照Android的设计理念,Android使用Intent来封装程序的”调用意图”,不管启动Activity(活动)、Service(服务)、BroadcastReceiver(广播接收器),Android都使用统一的Intent对象来封装这一”启动意图”。此外,Intent也是应用程序组件之间通信的重要媒介。在Android中指定的了具体是某个组件,那么就是显性意图;如果只是提出要求没有指定具体的某个人,在Android中即没有指定某个具体的组件,那么就是隐式意图;所有Intent页面跳转的方式又分为显示跳转和隐式跳转。

7.3.2 Intent和三大组件

Android应用程序包含三种重要组件:Activity(活动)、Service(服务)、BroadcastReceiver(广播接收器),应用程序采用一致的方式启动它们,都是依靠Intent来进行启动的,Intent中封装了程序要启动的意图。

下面是Intent启动不同组件的部分方法:

  • Activity组件:
    startActivity(Intent intent);
    startActivityForResult(Intent intent,int requestCode);
  • Service组件:
    startService(Intent intent);
    bindService(Intent intent,ServiceConnection conn,int flags);
  • BroadcastReceiver组件:
    sendBroadcast(Intent intent);
    sendOrderedBroadcast(Intent intent,String receiverPermission);

7.3.3 Intent的跳转方式

  1. Intent显式跳转页面:
    显示意图的跳转是Intent对象中包含了目标的class文件
    Intent intent = new Intent(this, SecondActivity.class);
    startActivity(intent);
    如果要传递数据也可以通过Intent对象使用putExtra方法来传递数据。
    这里的目标文件必须是要在AndroidManifest.xml里面注册。
  2. Intent隐式跳转页面:
    隐式Intent不会明确指出需要激活的目标组件,它被广泛地应用在不同应用程序之间传递消息。Android系统会使用IntentFilter匹配相应的组件,匹配的属性主要包括以下三个:
    action:表示Intent对象要完成的意图动作。
    data:指定数据的URL或者数据MIME类型他的值通常与Intent的action属性有关联。
    category:表示activity的动作的类别。

注意:在隐式跳转中,匹配的每一个属性可以罗列多个

7.3.3 IntentFilter的基本概念

IntentFilter的意思是“意图过滤器”当我们隐式的启动系统组件的时候,就会根据IntentFilter来匹配相应的组件进行启动。

7.3.4 IntentFilter的属性

Intent通过下面的属性来描述的某个意图:

  • action(动作): 用来表示意图的动作,如:查看,发邮件,打电话
  • category(类别): 用来表示动作的类别。
  • data(数据): 表示与动作要操作的数据。如:查看指定的联系人
  • type(数据类型): 对data类型的描述。
  • extras(附加信息): 附加信息。如:详细资料,一个文件,某事。
  • component(目标组件): 目标组件。

7.3.5 action(动作)属性

动作很大程度上决定了Intent如何构建,特别是数据和附加信息,就像一个方法名决定了参数和返回值一样,所以应该尽可能明确地指定动作,并紧密关联到其他的Intent字段,如Category和Data。

常用动作 
最常用的是Action_MAIN(作为初始的Activity启动,没有数据的输入输出) 

  1. ACTION_MAIN 作为一个主要的进入口,而并不期望去接受数据
  2. ACTION_VIEW 向用户去显示数据
  3. ACTION_ATTACH_DATA 别用于指定一些数据应该附属于一些其他的地方,例如,图片数据应该附属于联系人 
  4. ACTION_EDIT 访问已给的数据,提供明确的可编辑
  5. ACTION_GET_CONTENT 允许用户选择特殊种类的数据,并返回(特殊种类的数据:照一张相片或录一段音)
  6. ACTION_DIAL 拨打一个指定的号码,显示一个带有号码的用户界面,允许用户去启动呼叫
  7. ACTION_CALL 根据指定的数据执行一次呼叫(有缺陷,使用ACTION_DIAL) 
  8. ACTION_SEND 传递数据,被传送的数据没有指定,接收的action请求用户发数据
  9. ACTION_SENDTO 发送一条信息到指定的某人
  10. ACTION_ANSWER 处理一个打进电话呼叫
  11. ACTION_INSERT 插入一条空项目到已给的容器
  12. ACTION_DELETE 从容器中删除已给的数据
  13. ACTION_SEARCH 执行一次搜索
  14. ACTION_WEB_SEARCH 执行一次web搜索

上面的动作都是Intent对象引用才有实际意义的。 
setAction(String action) 用来设置Intent的动作,参数可以为常量 
getAction() 方法用来获取Intent动作名称 
上面的Action都是系统定义好的,具有一定意义的动作指向的动作。 
Intent的Action对象其实就是一个字符串常量,系统的Action对象是系统定义好的字符串常量,我们也可以自己定义自己的Action作为字符串常量。就像上面的例子使用到了自定义的Action字符串对象。

7.3.6 category(类别)属性

Intent的action、category属性都是普通的字符串,其中action表示Intent需要完成的一个抽象”动作”,而category则为action添加额外的类别信息,通常action和category一块使用。 
需要指出的是,一个Intent中只能包含一个action属性,但可以包含多个category属性。当程序创建Intent时,该Intent默认启动常量值为andorid.intent.category.DEFAULT的组件。这里的一个Intent中只能包含一个action属性,并不是Activity中xml的设置规范,而是你要跳转到的页面去,你只能设置一个Action的值。 
常用的Category: 

  1. CATEGORY_DEFAULT:Android系统中默认的执行方式,按照普通Activity的执行方式执行。  
  2. CATEGORY_HOME:设置该组件为Home Activity。 
  3. CATEGORY_PREFERENCE:设置该组件为Preference。  
  4. CATEGORY_LAUNCHER:设置为当前应用程序优先级最高的Activity,通常与ACTION_MAIN配合使用。  
  5. CATEGORY_BROWSABLE:设置该组件可以使用浏览器启动。  
  6. CATEGORY_GADGET:设置该组件可以内嵌到另外的Activity中。 

上面的类别都是Intent对象引用才有实际意义的。

7.3.7 data(数据)属性

Data数据用来向Action属性提供动作的数据。这里的Data不是Intent里面的数据,而是指明动作的具体数据,比如说动作是打电话,那么打给具体的某一个人,就用到了date里面的数据来指定。同样发邮件、或打开具体的某一个网址也是通过Data数据。
Data属性只接受Uri对象,Uri对象是统一资源标识符。对应概念不用太多理解,只需知道里面某些具体值的表现形式就可以了。
Uri其实就是相当于一个网址,如图所示:

网址只是Uri其中一种格式的字符串,要使用它还要把它解析后转化为Uri类型。
为Intent对象添加Data数据,代码:
intent.setData(Uri.parse(“http://www.baidu.com“));
这里的Uri的有两个没显示的属性:port的值是8080,path的值是/index
通过下面三句代码就可以跳转到百度主页面:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);

7.3.8 type(数据类型)属性

与Data有关的,这个不是Intent的数据类型,是Intent的Action的Data数据的类型。 
比如: 
{“.mp3”, “audio/x-mpeg”}, 
{“.mp4”, “video/mp4”}, 
{“.gif”, “image/gif”}, 
{“.rmvb”, “audio/x-pn-realaudio”}, 
这里只是做几个简单的示例介绍,如果是打开gif格式的数据文件,设置type=“image/gif”

7.3.9 extras(附加信息)属性

Extras属性主要用于传递目标组件所需要的额外的数据。这个数据是可以通过Intent来保存的数据和Intent对象来获取数据。

通过putExtras()方法设置。保存数据
通过putExtras()方法设置。获取数据
通常我们使用Intent来直接传递Bundle对象,但也可以传递其他系统内置的一些参数。 
如果要传递是是对象,那么对象必须实现序列化。

7.3.10 component(目标组件)属性

这个属性用得比较少,最好不用。如果是显示调用直接指定目标类的class文件名就可以使用了。

7.3.11 Intent的Action属性的部分常量值

  1. ACTION_MAIN:Android Application的入口,每个Android应用必须且只能包含一个此类型的Action声明。
  2. ACTION_VIEW:系统根据不同的Data类型,通过已注册的对应Application显示数据。
  3. ACTION_EDIT:系统根据不同的Data类型,通过已注册的对应Application编辑示数据。
  4. ACTION_DIAL:系统默打开拨号程序,如果Data中设置电话号码,则拨号框中会显示此号码。
  5. ACTION_CALL:直接呼叫Data中所带的号码。
  6. ACTION_ANSWER:接听来电。
  7. ACTION_SEND:由用户指定发送方式进行数据发送操作。
  8. ACTION_SENDTO:系统根据不同的Data类型,通过已注册的对应Application进行数据发送操作。
  9. ACTION_BOOT_COMPLETED:Android系统在启动完毕后发出带有此Action的广播(Broadcast)。
  10. ACTION_TIME_CHANGED:Android系统的时间发生改变后发出带有此Action的广播(Broadcast)。
  11. ACTION_PACKAGE_ADDED:Android系统安装了新的App之后发出带有此Action的广播(Broadcast)。
  12. ACTION_PACKAGE_CHANGED:Android系统中已存在的App发生改变之后(如更新)发出带有此Action的广播(Broadcast)。
  13. ACTION_PACKAGE_REMOVED:Android系统卸载App之后发出带有此Action的广播(Broadcast)。

7.3.12 Intent的Category属性的部分常量值

  1. CATEGORY_DEFAULT:Android系统中默认的执行方式,按照普通Activity的执行方式执行。
  2. CATEGORY_PREFERENCE:设置该组件为Preference。
  3. CATEGORY_LAUNCHER:设置为当前应用程序优先级最高的Activity,通常与ACTION_MAIN配合使用。
  4. CATEGORY_BROWSABLE:设置该组件可以使用浏览器启动。
  5. CATEGORY_GADGET:设置该组件可以内嵌到另外的Activity中。

7.3.13 Intent的Data属性的部分常量值

  1. tel:号码数据格式,后跟电话号码。 
  2. mailto:邮件数据格式,后跟邮件收件人地址。
  3. smsto:短息数据格式,后跟短信接收号码。
  4. content:内容数据格式,后跟需要读取的内容。 
  5. file:文件数据格式,后跟文件路径。
  6. market:search?q=pname:pkgname:市场数据格式,在Google Market里搜索包名为pkgname的应用。
  7. geo:latitude, longitude:经纬数据格式,在地图上显示经纬度所指定的位置。

7.3.14 Intent的Extras属性的部分常量值

  1. EXTRA_BCC:存放邮件密送人地址的字符串数组。
  2. EXTRA_CC:存放邮件抄送人地址的字符串数组。
  3. EXTRA_EMAIL:存放邮件地址的字符串数组。
  4. EXTRA_SUBJECT:存放邮件主题字符串。
  5. EXTRA_TEXT:存放邮件内容。
  6. EXTRA_KEY_EVENT:以KeyEvent对象方式存放触发Intent的按键。
  7. EXTRA_PHONE_NUMBER:存放调用ACTION_CALL时的电话号码。

7.3.15 Intent的MimeType属性的部分常量值

{“.3gp”, “video/3gpp”},
{“.apk”, “application/vnd.android.package-archive”},
{“.asf”, “video/x-ms-asf”},
{“.avi”, “video/x-msvideo”},
{“.bin”, “application/octet-stream”},
{“.bmp”, “image/bmp”},
{“.c”, “text/plain”},
{“.class”, “application/octet-stream”},
{“.conf”, “text/plain”},
{“.cpp”, “text/plain”},
{“.doc”, “application/msword”},
{“.docx”, “application/vnd.openxmlformats-officedocument.wordprocessingml.document”},
{“.xls”, “application/vnd.ms-excel”},
{“.xlsx”, “application/vnd.openxmlformats-officedocument.spreadsheetml.sheet”},
{“.exe”, “application/octet-stream”},
{“.gif”, “image/gif”},
{“.gtar”, “application/x-gtar”},
{“.gz”, “application/x-gzip”},
{“.h”, “text/plain”},
{“.htm”, “text/html”},
{“.html”, “text/html”},
{“.jar”, “application/Java-archive”},
{“.java”, “text/plain”},
{“.jpeg”, “image/jpeg”},
{“.jpg”, “image/jpeg”},
{“.js”, “application/x-JavaScript”},
{“.log”, “text/plain”},
{“.m3u”, “audio/x-mpegurl”},
{“.m4a”, “audio/mp4a-latm”},
{“.m4b”, “audio/mp4a-latm”},
{“.m4p”, “audio/mp4a-latm”},
{“.m4u”, “video/vnd.mpegurl”},
{“.m4v”, “video/x-m4v”},
{“.mov”, “video/quicktime”},
{“.mp2”, “audio/x-mpeg”},
{“.mp3”, “audio/x-mpeg”},
{“.mp4”, “video/mp4”},
{“.mpc”, “application/vnd.mpohun.certificate”},
{“.mpe”, “video/mpeg”},
{“.mpeg”, “video/mpeg”},
{“.mpg”, “video/mpeg”},
{“.mpg4”, “video/mp4”},
{“.mpga”, “audio/mpeg”},
{“.msg”, “application/vnd.ms-outlook”},
{“.ogg”, “audio/ogg”},
{“.pdf”, “application/pdf”},
{“.png”, “image/png”},
{“.pps”, “application/vnd.ms-powerpoint”},
{“.ppt”, “application/vnd.ms-powerpoint”},
{“.pptx”, “application/vnd.openxmlformats-officedocument.presentationml.presentation”},
{“.prop”, “text/plain”},
{“.rc”, “text/plain”},
{“.rmvb”, “audio/x-pn-realaudio”},
{“.rtf”, “application/rtf”},
{“.sh”, “text/plain”},
{“.tar”, “application/x-tar”},
{“.tgz”, “application/x-compressed”},
{“.txt”, “text/plain”},
{“.wav”, “audio/x-wav”},
{“.wma”, “audio/x-ms-wma”},
{“.wmv”, “audio/x-ms-wmv”},
{“.wps”, “application/vnd.ms-works”},
{“.xml”, “text/plain”},
{“.z”, “application/x-compress”},
{“.zip”, “application/x-zip-compressed”},

7.3部分的内容为转载:CSDN博主「峥嵘life」的原创文章,原文链接:https://blog.csdn.net/wenzhi20102321/article/details/52876648

7.4 Activity之间的跳转

7.4.1 Activity之间的数据传递

Android提供的Intent可以在界面跳转时传递参数。使用Intent传递参数有两种方式,具体如下:

  1. 使用Intent的putExtra()方法传递数据
    putExtra()方法的参数为<key,value>的形式,并且Android系统提供了多个重载的putExtra()方法供不同类型的参数传递
    获取参数:首先通过getIntent()方法获取到Intent对象,然后通过Intent对象的getXxxExtra()方法获取到参数
  2. 使用Bundle类传递数据
    此方法是先将参数通过Bundle对象的putXxx()方法保存在Bundle对象中,同样是<key,value>的形式,然后再通过Intent的putExtras()方法保存到Intent对象中
    获取参数:首先通过getIntent()方法获取到Intent对象,然后通过Intent对象的getExtras()方法获取到Bundle对象,然后通过Intent对象的getXxx()方法获取到参数
  3. 如何传递对象(让对象具有被传递的功能)
    第一种方法:让对象类实现 Serializable 接口,同样通过以上两种方式传递,在另一个Activity中使用 getSerializableExtra("key")获取到对象,须强转为目标对象,此方法基于JVM虚拟机,不兼容Android虚拟机
    第二种方法:让对象类实现 Parcelable 接口,同样通过以上两种方式传递,Android推荐使用,在另一个Activity中使用 getParcelableExtra("key")获取到对象,不用强转为目标对象(使用了泛型),此方法兼容Android虚拟机

7.4.2 Activity之间的数据回传

当我们从MainActivity界面跳转到SecondActivity界面时,在SecondActivity界面进行操作后,当关闭SecondActivity界面时,想要从该界面返回一些数据到MainActivity界面。此时Android系统为我们提供了一些方法用于Activity之间数据的回传。具体如下:

  1. startActivityForResult(intent, REQUEST_CODE)方法:
    该方法跟startActivity()方法一样,也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动。
    REQUEST_CODE为请求码,用于在之后的回调中判断数据的来源。
  2. setResult(RESULT_CODE, intent)方法:
    该方法是专门用于向上一个活动返回数据的。
    RESULT_CODE为返回码,用于向上一个活动返回处理结果,该结果可用于在之后的回调中判断数据的来源,一般只使用RESULT_OK或RESULT_CANCELED这两个值;
  3. onActivityResult()方法: 
    由于我们是使用startActivityForResult()方法来启动的Activity,在Activity被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在MainActivity中重写这个方法来得到返回数据。

注意1:若使用此方法进行Activity之间数据的传递与回传,则必须同时使用以上3个方法,其中第3个方法是一个用于重写的方法

注意2:新手在使用Intent启动其他的Activity时,需要在AndroidManifest.xml清单文件中进行声明,其中android:exported 属性表示是否支持其它应用调用/启动该组件。

activity_main.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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/et_name"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:hint="姓名"/>

    <EditText
        android:id="@+id/et_score"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:hint="分数"/>

    <Button
        android:id="@+id/btn_submit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="提交数据"/>

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:hint="返回的结果"/>

</LinearLayout>

MainActivity.java代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_name,et_score;
    private Button btn_submit;
    private TextView tv_result;
    public static final int REQUEST_CODE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_name = findViewById(R.id.et_name);
        et_score = findViewById(R.id.et_score);
        btn_submit  = findViewById(R.id.btn_submit);
        tv_result = findViewById(R.id.tv_result);

        btn_submit.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        String name = et_name.getText().toString();
        Double score = Double.valueOf(et_score.getText().toString());
        Intent intent = new Intent(this, SecondActivity.class);
        intent.putExtra("name",name);
        intent.putExtra("score", score);
        startActivityForResult(intent, REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE) {
            if (resultCode == SecondActivity.RESULT_CODE) {
                tv_result.setText("返回的结果为:"+data.getStringExtra("result"));
            }
        }
    }
}

activity_second.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".SecondActivity">

    <TextView
        android:id="@+id/tv_data"
        android:layout_width="match_parent"
        android:layout_height="150dp"/>

    <EditText
        android:id="@+id/et_result"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:hint="返回的数据"/>

    <Button
        android:id="@+id/btn_return"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="返回数据"/>

</LinearLayout>

SecondActivity.java代码:

public class SecondActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView tv_data;
    private EditText et_result;
    private Button btn_return;
    public static final int RESULT_CODE = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        tv_data = findViewById(R.id.tv_data);
        et_result = findViewById(R.id.et_result);
        btn_return = findViewById(R.id.btn_return);

        Intent intent = getIntent();
        StringBuffer data = new StringBuffer();
        data.append("姓名:" + intent.getStringExtra("name"));
        data.append("\n分数:" + intent.getDoubleExtra("score", 0));
        tv_data.setText(data);

        btn_return.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Intent intent = new Intent(this, SecondActivity.class);
        intent.putExtra("result", et_result.getText().toString());
        setResult(RESULT_CODE, intent);
        finish(); //用于结束一个Activity的生命周期,finish会调用到onDestory()方法。
    }
}

运行效果:

第8章 Service(服务)

8.1 线程的相关概念

8.1.1 相关概念

  • 程序:为了完成特定任务,用某种语言编写的一组指令集合(一组静态代码)
  • 进程:进程是系统调度与资源分配(CPU、内存)的基本单位,它是程序执行时的一个实例。操作系统会为每个进程分配一段内存空间!程序的依次动态执行,经历代码的加载,执行, 执行完毕的完整过程!
  • 线程:线程是程序执行时的最小单位,是进程的一个执行流,CPU调度和分派的基本单位。每个进程可能有多个线程,线程需要放在一个进程中才能执行,线程由程序负责管理,而进程则由系统进行调度!
  • 多线程的理解:并行执行多条指令,将CPU时间片按照调度算法分配给各个 线程,实际上是分时执行的,只是这个切换的时间很短,用户感觉到"同时"而已!

8.1.2 线程的生命周期

8.1.3  创建线程的三种方式

  • 继承Thread类,并重写run方法
  • 实现Callable接口,并重写call()方法
  • 实现Callable接口,并重写call()方法

详见1:https://www.jianshu.com/p/0977349d20db

详见2:https://blog.csdn.net/chaochao2113/article/details/118861041

8.2 Service与Thread线程的区别

其实他们两者并没有太大的关系,不过有很多朋友经常把这两个混淆了! Thread是线程,程序执行的最小单元,分配CPU的基本单位! 而Service则是Android提供一个允许长时间留驻后台的一个组件,最常见的用法就是做轮询操作!或者想在后台做一些事情,比如后台下载更新、音乐播放等! 记得别把这两个概念混淆! 

8.3 Service的生命周期

8.3.1 Service的生命周期图

8.3.2 Service的相关方法详解

  • onCreate():当Service第一次被创建后立即回调该方法,该方法在整个生命周期 中只会调用一次!
  • onDestory():当Service被关闭时会回调该方法,该方法只会回调一次!
  • onStartCommand(intent,flag,startId):早期版本是onStart(intent,startId), 当客户端调用startService(Intent)方法时会回调,可多次调用StartService方法, 但不会再创建新的Service对象,而是继续复用前面产生的Service对象,但会继续回调 onStartCommand()方法!
  • IBinder onOnbind(intent):该方法是Service都必须实现的方法,该方法会返回一个 IBinder对象,app通过该对象与Service组件进行通信!
  • onUnbind(intent):当该Service上绑定的所有客户端都断开时会回调该方法!

8.3.3 Service的两种使用方式

  • StartService启动Service
    首次启动会创建一个Service实例,依次调用onCreate()和onStartCommand()方法,此时Service 进入运行状态,如果再次调用StartService启动Service,将不会再创建新的Service对象, 系统会直接复用前面创建的Service对象,调用它的onStartCommand()方法!
    但这样的Service与它的调用者无必然的联系,就是说当调用者结束了自己的生命周期, 但是只要不调用stopService,那么Service还是会继续运行的!
    无论启动了多少次Service,只需调用一次StopService即可停掉Service

  • BindService启动Service
    当首次使用bindService绑定一个Service时,系统会实例化一个Service实例,并调用其onCreate()和onBind()方法,然后调用者就可以通过IBinder和Service进行交互了,此后如果再次使用bindService绑定Service,系统不会创建新的Sevice实例,也不会再调用onBind()方法,只会直接把IBinder对象传递给其他后来增加的客户端!
    如果我们解除与服务的绑定,只需调用unbindService(),此时onUnbind和onDestory方法将会被调用!这是一个客户端的情况,假如是多个客户端绑定同一个Service的话,情况如下 当一个客户完成和service之间的互动后,它调用 unbindService() 方法来解除绑定。当所有的客户端都和service解除绑定后,系统会销毁service。(除非service也被startService()方法开启)
    另外,和上面那张情况不同,bindService模式下的Service是与调用者相互关联的,可以理解为 "一条绳子上的蚂蚱",要死一起死,在bindService后,一旦调用者销毁,那么Service也立即终止!
    通过BindService调用Service时调用的Context的bindService的解析 bindService(Intent Service,ServiceConnection conn,int flags)
    service:通过该intent指定要启动的Service
    conn:ServiceConnection对象,用户监听访问者与Service间的连接情况, 连接成功回调该对象中的onServiceConnected(ComponentName,IBinder)方法; 如果Service所在的宿主由于异常终止或者其他原因终止,导致Service与访问者间断开 连接时调用onServiceDisconnected(CompanentName)方法,主动通过unBindService() 方法断开并不会调用上述方法!
    flags:指定绑定时是否自动创建Service(如果Service还未创建), 参数可以是0(不自动创建),BIND_AUTO_CREATE(自动创建)

  • StartService启动Service后bindService绑定
    如果Service已经由某个客户端通过StartService()启动,接下来由其他客户端 再调用bindService()绑定到该Service后调用unbindService()解除绑定最后在 调用bindService()绑定到Service的话,此时所触发的生命周期方法如下:
    onCreate( )->onStartCommand( )->onBind( )->onUnbind( )->onRebind( )
    PS:前提是:onUnbind()方法返回true!!! 这里或许部分读者有疑惑了,调用了unbindService后Service不是应该调用 onDistory()方法么!其实这是因为这个Service是由我们的StartService来启动的 ,所以你调用onUnbind()方法取消绑定,Service也是不会终止的!
    得出的结论: 假如我们使用bindService来绑定一个启动的Service,注意是已经启动的Service!!! 系统只是将Service的内部IBinder对象传递给Activity,并不会将Service的生命周期 与Activity绑定,因此调用unBindService( )方法取消绑定时,Service也不会被销毁!

StartService启动与BindService启动的区别:
①启动方式:前者startService、后者bindService;
②和Activity联系:前者Acitivty销毁,service仍然继续运行,后者跟随Acitivity一起销毁;
③方法调用和数据交换:前者不可以,后者可以;
④回调的周期函数:前者是onStartCommand,后者是onBind
⑤结束方式:前者是stopService或者stopSelf,后者是unbindService。

生命周期的不同:
startService方式:调用startService方法->onCreate->onStartCommand->Servcie运行->调用stopService->Service停止->onDestroy->Service销毁;
bindService方式:调用bindServcie方法->onCreate->onBind->绑定Service->调用unbindService方法->解绑Service->onUnBind->onDestroy->Service销毁。

第9章 BroadcastReceiver(广播接收器) 

9.1 广播与广播接收器

9.1.1 什么是广播

广播(Broadcast)是 Android 系统中应用程序间通信的手段。
当有特定事件发生时,例如有来电、有短信、电池电量变化等事件发生时,Android 系统都会产生特定的 Intent 对象并且自动进行广播,而针对特定事件注册的 BroadcastReceiver 会接收到这些广播,并获取 Intent 对象中的数据进行处理。
在广播 Intent 对象时可以指定用户权限,以此限制仅有获得了相应权限的 BroadcastReceiver 才能接收并处理对应的广播。

9.1.2 什么是广播接收器 

        BroadcastReceiver翻译为广播接收者,它是一个系统全局的监听器,用于监听系统全局的Broadcast消息,所以它可以很方便的进行系统组件之间的通信。

  BroadcastReceiver虽然是一个监听器,但是它和之前用到的OnXxxListener不同,那些只是程序级别的监听器,运行在指定程序的所在进程中,当程序退出的时候,OnXxxListener监听器也就随之关闭了,但是BroadcastReceiver属于系统级的监听器,它拥有自己的进程,只要存在与之匹配的Broadcast被以Intent的形式发送出来,BroadcastReceiver就会被激活。

  虽然同属Android的四大组件,BroadcastReceiver也有自己独立的声明周期,但是和Activity、Service又不同。当在系统注册一个BroadcastReceiver之后,每次系统以一个Intent的形式发布Broadcast的时候,系统都会创建与之对应的BroadcastReceiver广播接收者实例,并自动触发它的onReceive()方法,当onReceive()方法被执行完成之后,BroadcastReceiver的实例就会被销毁。虽然它独自享用一个单独的进程,但也不是没有限制的,如果BroadcastReceiver.onReceive()方法不能在10秒内执行完成,Android系统就会认为该BroadcastReceiver对象无响应,然后弹出ANR(Application No Response)对话框,所以不要在BroadcastReceiver.onReceive()方法内执行一些耗时的操作。

  如果需要根据广播内容完成一些耗时的操作,一般考虑通过Intent启动一个Service来完成该操作,而不应该在BroadcastReceiver中开启一个新线程完成耗时的操作,因为BroadcastReceiver本身的生命周期很短,可能出现的情况是子线程还没有结束,BroadcastReceiver就已经退出的情况,而如果BroadcastReceiver所在的进程结束了,该线程就会被标记为一个空线程,根据Android的内存管理策略,在系统内存紧张的时候,会按照优先级,结束优先级低的线程,而空线程无异是优先级最低的,这样就可能导致BroadcastReceiver启动的子线程不能执行完成。

9.2 发送广播的类型

9.2.1 标准广播

它是完全异步的,也就是说,在逻辑上,当一个Broadcast被发出之后,所有的与之匹配的BroadcastReceiver都同时接收到Broadcast。优点是传递效率比较高,但是也有缺点,就是一个BroadcastReceiver不能影响其他响应这条Broadcast的BroadcastReceiver。

  • sendBroadcast():发送普通广播。

9.2.2 有序广播

它是同步执行的,就是说有序广播的接收器将会按照预先声明的优先级(-1000~1000)依次接受Broadcast,优先级越高,越先被执行。因为是顺序执行,所有优先级高的接收器,可以把执行结果传入下一个接收器中,也可以终止Broadcast的传播(通过abortBroadcast()方法),一旦Broadcast的传播被终止,优先级低于它的接收器就不会再接收到这条Broadcast了。

  • sendOrderedBroadcast():发送有序广播。

注:有序广播能被设置了高优先级的广播接收者的 abortBroadcast() 截断。

9.3 如何使用BroadcastReceiver

        BroadcastReceiver本质上还是一个监听器,所以使用BroadcastReceiver的方法也是非常简单,只需要继承BroadcastReceiver,在其中重写onReceive(Context context,Intent intent)即可。一旦实现了BroadcastReceiver,并部署到系统中后,就可以在系统的任何位置,通过sendBroadcast、sendOrderedBroadcast方法发送Broadcast给这个BroadcastReceiver。

        但是仅仅继承BroadcastReceiver和实现onReceive()方法是不够的,同为Android系统组件,它也必须在Android系统中注册,注册一个BroadcastReceiver有两种方式:

9.3.1 静态注册(不建议使用)

使用清单文件AndroidManifest.xml注册,在<application/>节点中,使用<receiver/>节点注册,并用android:name属性中指定注册的BroadcastReceiver对象,一般还会通过<Intent-filter/>指定<action/>和<category/>,并在<Intent-filter/>节点中通过android:priority属性设置BroadcastReceiver的优先级,在-1000~1000范围内,数值越到优先级越高。

        <receiver android:name=".MyBroadcastReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="com.example.mybroadcastreceiver.staticReceiver"/>
            </intent-filter>
        </receiver>

广播接收者接收Android 8.0及以上的静态注册的广播:

intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);

intent.setComponent(new ComponentName("com.example.mybroadcastreceiver","com.example.mybroadcastreceiver.MyBroadcastReceiver"));
//第一个参数为广播接收者的包名,第二个参数为广播接收者的类名

intent.setPackage("com.example.mybroadcastreceiver");
//参数为广播接收者的报名

9.3.2 动态注册

就是在Java代码中指定 IntentFilter,然后添加不同的 Action 即可,想监听什么广播就写什么 Action,最后通过registerReceiver(BroadcastReceiver receiver, IntentFilter filter) 方法进行注册,另外动态注册的广播,一定要调用unregisterReceiver() 方法撤销广 播的注册。

        MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.mybroadcastreceiver.dynamicReceiver");
//参数为标识名,用于发送广播时识的别时
        registerReceiver(myBroadcastReceiver, intentFilter);

9.3.3 广播接收总结

  • 广播接收的特点:
    ①广播接收器接收到相应广播后,会自动回调 onReceive() 方法
    ②一般情况下,onReceive方法会涉及 与 其他组件之间的交互,如发送Notification、启动Service
    ③默认情况下,广播接收器运行在 UI 线程,因此,onReceive()方法不能执行耗时操作(10s的时间),否则将导致ANR
  • 实现自定义的广播接收:
    ①继承BroadcastReceiver类,实现自定义的广播接收者,实现onReceive(Context context, Intent intent)方法用于接受广播
    ②注册BroadcastReceiver,静态注册 / 动态注册
    sendBroadcast(Intent intent); 发送广播
    ④在onReceive(Context context, Intent intent)中处理逻辑业务; 接收广播

activity_main.xml代码: 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <Button
        android:id="@+id/btn_static"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="静态注册广播"
        android:textSize="30sp"
        app:layout_constraintBottom_toTopOf="@+id/btn_dynamic"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_dynamic"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="动态注册广播"
        android:textSize="30sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_static" />

</androidx.constraintlayout.widget.ConstraintLayout>

MyBroadcastReceiver.java代码:

public class MyBroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "zhumeng";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e(TAG, "接收到广播消息‘"+intent.getStringExtra("msg")+"’");
    }
}

MainActivity.java代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private MyBroadcastReceiver myBroadcastReceiver;

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

        Button btn_static = findViewById(R.id.btn_static);
        Button btn_dynamic = findViewById(R.id.btn_dynamic);

        btn_static.setOnClickListener(this);
        btn_dynamic.setOnClickListener(this);
        
        initReceiver();
    }

    private void initReceiver() { //动态注册广播接收者
        myBroadcastReceiver = new MyBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.mybroadcastreceiver.dynamicReceiver");
        registerReceiver(myBroadcastReceiver, intentFilter);
    }

    @Override
    public void onClick(View view) {
        Log.e("zhumeng", "单击事件发生");
        Intent intent;
        switch (view.getId()){
            case R.id.btn_static:
                intent = new Intent();
                intent.setAction("com.example.mybroadcastreceiver.staticReceiver");
//                intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
//                intent.setComponent(new ComponentName("com.example.mybroadcastreceiver","com.example.mybroadcastreceiver.MyBroadcastReceiver"));
                intent.setPackage("com.example.mybroadcastreceiver");
                intent.putExtra("msg","静态注册的广播");
                sendBroadcast(intent);
                break;
            case R.id.btn_dynamic:
                intent = new Intent();
                intent.setAction("com.example.mybroadcastreceiver.dynamicReceiver");
                intent.putExtra("msg","动态注册的广播");
                sendBroadcast(intent);
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(myBroadcastReceiver);
    }
}

运行效果图:

第8章 Fragment(碎片)

8.1 Fragment是什么

8.1.1 Fragment的历史

 Fragment是Android3.0后引入的一个新的API,他出现的初衷是为了适应大屏幕的平板电脑, 当然现在他仍然是平板APP UI设计的宠儿。

8.1.2 Fragment是什么

Fragment(碎片)是一种嵌入在Activity中的UI片段,它可以用来描述Activity中的一部分布局。如果Activity界面布局中的控件比较多比较复杂,那么Activity管理起来就很麻烦,我们可以使用Fragment把屏幕划分成几个片段,进行模块化的管理,从而使程序更加合理和充分地利用屏幕的空间。
一个Activity中可以包含多个Fragment,一个Fragment也可以在多个Activity中使用,如果在Activity中有多个相同的业务模块,则可以复用Fragment。
想想,如果一个很大的界面,我们就一个布局,写起界面来会有多麻烦,而且如果组件多的话是管理起来也很麻烦!而使用Fragment我们可以把屏幕划分成几块,然后进行分组,进行一个模块化的管理!从而可以更加方便的在运行过程中动态地更新Activity的用户界面!另外Fragment并不能单独使用,他需要嵌套在Activity中使用,尽管他拥有自己的生命周期,但是还是会受到宿主Activity的生命周期的影响。比如,当Activity暂停时,其中的所有片段也会暂停;当Activity被销毁时,所有片段也会被销毁。 不过,当Activity 正在运行(处于已恢复生命周期状态)时,您可以独立操纵每个片段,如添加或移除它们。 当您执行此类片段事务时,您也可以将其添加到由Activity管理的返回栈,Activity中的每个返回栈条目都是一条已发生片段事务的记录。返回栈让用户可以通过按返回按钮撤消片段事务(后退)。
下面是一个典型的示例演示如何让两个由碎片定义的UI模块,在为平板设计的活动中组合,在为手持设备设计的活动中分离。

当运行在在平板尺寸的设备上,这个应用程序可以在活动A中嵌入两个碎片。在手机设备屏幕上,由于没有足够的空间,活动A仅包含有文章列表的碎片,当用户点击文章时,启动包含第二个碎片的活动B来阅读文章。 

8.2 Fragment的生命周期

8.2.1 Fragment的生命周期简介

Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。Fragment状态与Activity类似,也存在如下4种状态:

  • 运行:当前Fmgment位于前台,用户可见,可以获得焦点。
  • 暂停:其他Activity位于前台,该Fragment依然可见,只是不能获得焦点。
  • 停止:该Fragment不可见,失去焦点。
  • 销毁:该Fragment被完全删除,或该Fragment所在的Activity被结束。

8.2.2 Fragment的生命周期状态 

Fragment的生命周期看起来和Activity的生命周期类似,但是多了一些,因为Fragment的生命周期是和其关联的Activity有关。

(1)onAttach(Context context):在Fragment和Activity关联上的时候调用,且仅调用一次。在该回调中我们可以将context转化为Activity保存下来,从而避免后期频繁调用getAtivity()获取Activity的局面,避免了在某些情况下getAtivity()为空的异常(Activity和Fragment分离的情况下)。同时也可以在该回调中将传入的Arguments提取并解析,在这里强烈推荐通过setArguments给Fragment传参数,因为在应用被系统回收时Fragment不会保存相关属性,具体之后会讲解。

(2)onCreate(Bundle savedInstanceState):在最初创建Fragment的时候会调用,和Activity的onCreate类似。

(3)View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState):在准备绘制Fragment界面时调用,返回值为Fragment要绘制布局的根视图,当然也可以返回null。注意使用inflater构建View时一定要将attachToRoot指明false,因为Fragment会自动将视图添加到container中,attachToRoot为true会重复添加报错。onCreateView并不是一定会被调用,当添加的是没有界面的Fragment就不会调用,比如调用FragmentTransaction的add(Fragment fragment, String tag)方法。

(4)onActivityCreated(Bundle savedInstanceState):在Activity的onCreated执行完时会调用。

(5)onStart():Fragment对用户可见的时候调用,前提是Activity已经started。

(6)onResume():Fragment和用户之前可交互时会调用,前提是Activity已经resumed。

(7)onPause():Fragment和用户之前不可交互时会调用。

(8)onStop():Fragment不可见时会调用。

(9)onDestroyView():在移除Fragment相关视图层级时调用。

(10)onDestroy():最终清楚Fragment状态时会调用。

(11)onDetach():Fragment和Activity解除关联时调用。

8.2.3 Fragment的生命周期调用

1)创建Fragment

onAttach() —> onCreate() —> onCreateView() —> onActivityCreated() —> onStart() —> onResume()

2)按下Home键回到桌面 / 锁屏

onPause() —> onStop()

3)从桌面回到Fragment / 解锁

onStart() —> onResume()

4)切换到其他Fragment

onPause() —> onStop() —> onDestroyView()

5)切换回本身的Fragment

onCreateView() —> onActivityCreated() —> onStart() —> onResume()

6) 按下Back键退出

onPause() —> onStop() —> onDestroyView() —> onDestroy() —> onDetach()

注意:Fragment必须依赖于Activity,理解为Activity的寄生虫

详见1:Fragment生命周期 - 第壹时间 - 博客园 (cnblogs.com) 

详见2:Fragment的生命周期_杭州小白的博客-CSDN博客_fragment生命周期

8.3 Fragment用法

8.3.1 Fragment的静态加载

实现步骤:

  • Step 1:自定义一个Fragment类,需要继承Fragment或者他的子类,重写onCreateView()方法,在该方法中调用inflater.inflate()方法加载Fragment的布局文件并实例化view对象,接着返回加载的view对象
    public class BlankFragment1 extends Fragment {
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_blank1, container, false);
            Button btn = view.findViewById(R.id.btn);
            TextView tv = view.findViewById(R.id.tv);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    tv.setText("Yes,I am,and you!");
                }
            });
            return view;
        }
    }
  • Step 2:若第1步在Java目录下New Fragment类的话就会自动生成Fragment的布局(否则需要自己定义),就是fragment显示内容的
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".BlankFragment1"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/hello_blank_fragment"
            android:textSize="30sp"/>
    
        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="获取回答" />
    
    </LinearLayout>
  • Step 3:在需要加载Fragment的Activity对应的布局文件中添加fragment的标签,必需写name属性且是全限定类名哦,就是要包含Fragment的包名,还需要有一个id属性
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <fragment
            android:id="@+id/fragment1"
            android:name="com.example.fragmentbase.BlankFragment1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    
    </LinearLayout>
  • Step 4:Activity在onCreate( )方法中调用setContentView()加载布局文件即可

运行效果图: 

注:若要添加多个Fragment就直接创建多个继承Fragment的类,然后在activity_main.xml中通过name属性引用即可

8.3.2 Fragment的动态加载

实现步骤:

  • Step 1:自定义一个Fragment类,需要继承Fragment或者他的子类,重写onCreateView()方法,在该方法中调用inflater.inflate()方法加载Fragment的布局文件并实例化view对象,接着返回加载的view对象
  • Step 2:若第1步在Java目录下New Fragment类的话就会自动生成Fragment的布局(否则需要自己定义),就是fragment显示内容的
  • Step 3:在需要加载Fragment的Activity对应的布局文件中添加fragment的标签,必需写name属性且是全限定类名哦,就是要包含Fragment的包名,还需要有一个id属性
  • Step 4:Activity在onCreate( )方法中调用setContentView()加载布局文件即可
  • Step 5:通过getFragmentManager()获得FragmentManager对象fragmentManager;
  • Step 6:通过fragmentManager.beginTransaction()获得FragmentTransaction对象fragmentTransaction;
  • Step 7:调用fragmentTransaction的add()方法或者replace()方法加载Fragment;
    第一个参数:要传入的布局容器
    第二个参数:要加载的fragment对象
  • Step 8:在前面的基础上还需调用commit()方法提交事务。当然还有其他的方法如remove()等

注意:若点击back键按照添加过程退出时需要在commit()方法之前执行transation.addToBackStack(null);

MainActivity.java代码:其中BlankFragment1类和ItemFragment1类由AS自动生成

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn1 = findViewById(R.id.btn1);
        Button btn2 = findViewById(R.id.btn2);
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn1:
                replaceFragment(new BlankFragment1());
                break;
            case R.id.btn2:
                replaceFragment(new ItemFragment1());
                break;
        }
    }

    private void replaceFragment(Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.fragment1, fragment);

        transaction.addToBackStack(null);

        transaction.commit();
    }
}

运行效果图: 

8.4 Fragment的通信

8.4.1 Fragment与activity通信——原生Bundle

MainActivity.java代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn1 = findViewById(R.id.btn1);
        Button btn2 = findViewById(R.id.btn2);
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn1:
                BlankFragment blankFragment = new BlankFragment();
                Bundle bunlsle = new Bundle();
                bunlsle.putString("name", "张三");
                bunlsle.putInt("age", 18);
                blankFragment.setArguments(bunlsle);
                replaceFragment(blankFragment);
                break;
            case R.id.btn2:
                replaceFragment(new ItemFragment());
                break;
        }
    }

    private void replaceFragment(Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.fragment1, fragment);

        transaction.addToBackStack(null);

        transaction.commit();
    }
}

获取参数:在BlankFragment类中任何位置都可以通过Bundle bundle = getArguments()的bundle获取到通过blankFragment.setArguments(bunlsle)传递到该类的参数

运行效果图: 

8.4.2 Fragment与activity通信——Java类的接口

实现步骤:

  • 新建用于传递消息的接口IFragmentCallback
    public interface IFragmentCallback {
        void sendMsgToActivity(String msg);
        String getMsgFromActivity(String msg);
    }
  • 在需要进行触发消息传递的类BlankFragment中创建接口的实例并为其编写set方法public void setiFragmentCallback(IFragmentCallback iFragmentCallback),然后在onCreateView方法中编写监听器用于触发发送、获取消息
    public class BlankFragment extends Fragment {
    
        private static final String TAG = "TAG";
        private IFragmentCallback iFragmentCallback;
        private View view;
    
        public void setiFragmentCallback(IFragmentCallback iFragmentCallback) {
            this.iFragmentCallback = iFragmentCallback;
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            if (view == null) {
                view = inflater.inflate(R.layout.fragment_blank, container, false);
            }
            Button btn3 = view.findViewById(R.id.btn3);
            Button btn4 = view.findViewById(R.id.btn4);
            btn3.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    iFragmentCallback.sendMsgToActivity("hello, I'm from Fragment.");
                }
            });
            btn4.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    String msg = iFragmentCallback.getMsgFromActivity("hello, I'm Fragment.");
                    Toast.makeText(BlankFragment.this.getContext(), msg, Toast.LENGTH_SHORT).show();
                }
            });
            return view;
        }
    }
  • 在目标布局的方法中用BlankFragment对象调用setiFragmentCallback()方法进行对接口实例化
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button btn1 = findViewById(R.id.btn1);
            Button btn2 = findViewById(R.id.btn2);
            btn1.setOnClickListener(this);
            btn2.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.btn1:
                    BlankFragment blankFragment = new BlankFragment();
                    Bundle bunlsle = new Bundle();
                    bunlsle.putString("name", "张三");
                    bunlsle.putInt("age", 18);
                    blankFragment.setArguments(bunlsle);
                    blankFragment.setiFragmentCallback(new IFragmentCallback() {
                        @Override
                        public void sendMsgToActivity(String msg) {
                            Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
                        }
    
                        @Override
                        public String getMsgFromActivity(String msg) {
                            return "I'm from Activity.";
                        }
                    });
                    replaceFragment(blankFragment);
                    break;
                case R.id.btn2:
                    replaceFragment(new ItemFragment());
                    break;
            }
        }
    
        private void replaceFragment(Fragment fragment) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction transaction = fragmentManager.beginTransaction();
            transaction.replace(R.id.fragment1, fragment);
    
            transaction.addToBackStack(null);
    
            transaction.commit();
        }
    }

fragment_blank.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".BlankFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送数据"
        android:layout_marginTop="50dp"/>

    <Button
        android:id="@+id/btn4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="获取数据"
        android:layout_marginTop="100dp"/>

</FrameLayout>

运行效果图:

8.5 Fragment与ViewPager2的联合应用

8.5.1 ViewPage2的使用

使用步骤:

  1. 添加依赖
    在build.gradle文件中添加implementation 'androidx.viewpager2:viewpager2:1.0.0'
  2. 新建item_page.xml并编写其内容
    <?xml version="1.0" encoding="utf-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="wrap_content"
            android:layout_height="wrap_content"
            android:text="hello"
            android:layout_centerInParent="true"
            android:textSize="30sp"/>
    
    </RelativeLayout>
  3. 在activity_main.xml中使用ViewPager2
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/vp"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </LinearLayout>
  4. 自定义ViewPagerAdapter类继承RecyclerView.Adapter<VH>。其中VH是自定义继承自RecyclerView.ViewHolder的ViewPagerHolder类(可以写成匿名内部类)
    public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewPagerHolder> {
    
        private ArrayList<String> data = new ArrayList<>();
    
        public ViewPagerAdapter(ArrayList<String> data) {
            this.data = data;
        }
    
        @NonNull
        @Override
        public ViewPagerHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            ViewPagerHolder viewPagerHolder = new ViewPagerHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_pager, parent, false));
            return viewPagerHolder;
        }
    
        @Override
        public void onBindViewHolder(@NonNull ViewPagerHolder holder, int position) {
            holder.textView.setText(data.get(position).toString());
        }
    
        @Override
        public int getItemCount() {
            return data.size();
        }
    
        class ViewPagerHolder extends RecyclerView.ViewHolder{
    
            TextView textView;
    
            public ViewPagerHolder(@NonNull View itemView) {
                super(itemView);
                textView = itemView.findViewById(R.id.tv);
            }
        }
    }
  5. 在Java中装填数据和使用ViewPager2

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ArrayList<String> data = new ArrayList<>();
            for (int i = 0; i < 5; i++) {
                data.add(new String("你好,我是第"+i+"个Pager"));
            }
    
            ViewPager2 viewPager = findViewById(R.id.vp);
            ViewPagerAdapter pagerAdapter = new ViewPagerAdapter(data);
            viewPager.setAdapter(pagerAdapter);
        }
    }

运行效果图:

8.5.2 Fragment与ViewPager2实例WeChat首页)

FragmentPagerAdapter与FragmentStatePagerAdapter介绍

  • FragmentPagerAdapter
    FragmentPagerAdapter继承PagerAdapter。使用FragmentPagerAdapter 时,Fragment对象会一直存留在内存中,所以当有大量的显示页时,就不适合用FragmentPagerAdapter了,FragmentPagerAdapter 适用于只有少数的page情况,像选项卡。
  • FragmentStatePagerAdapter
    FragmentStateAdapter继承RecyclerView.Adapter。如果page比较多,这个时候你可以考虑使用FragmentStatePagerAdapter ,当使用FragmentStatePagerAdapter 时,如果Fragment不显示,那么Fragment对象会被销毁,(滑过后会保存当前界面,以及下一个界面和上一个界面(如果有),最多保存3个,其他会被销毁掉)但在回调onDestroy()方法之前会回调onSaveInstanceState(Bundle outState)方法来保存Fragment的状态,下次Fragment显示时通过onCreate(Bundle savedInstanceState)把存储的状态值取出来,FragmentStatePagerAdapter 比较适合页面比较多的情况,像一个页面的ListView 。
  • 如上所述,使用FragmentStatePagerAdapter更省内存,但是销毁和新建也是需要时间的。一般情况下,如果你是制作主页面,用3、4个Tab,那么可以考虑使用FragmentPagerAdapter,如果你是用于ViewPager展示数量较多的条目时,那么建议使用FragmentStatePagerAdapter。

WeChat首页的滑动窗口实现:

  1. 在activity_main.xml中使用ViewPager2
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
    
    </LinearLayout>
  2. 新建BlankFragment继承于Fragment并生成fragment_blank.xml文件
  3. 编写fragment_blank.xml文件
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".BlankFragment">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/hello_blank_fragment"
            android:textSize="30sp"
            android:gravity="center"/>
    
    </FrameLayout>
  4. 编写BlankFragment类
    public class BlankFragment extends Fragment {
    
        private static final String ARG_PARAM1 = "param1";
    
        private String mParam1;
    
        private View fragmentView;
    
        public BlankFragment() {
            // Required empty public constructor
        }
    
        public static BlankFragment newInstance(String param1) {
            BlankFragment fragment = new BlankFragment();
            Bundle args = new Bundle();
            args.putString(ARG_PARAM1, param1);
            fragment.setArguments(args);
            return fragment;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if (getArguments() != null) {
                mParam1 = getArguments().getString(ARG_PARAM1);
            }
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            if (fragmentView == null) {
                fragmentView = inflater.inflate(R.layout.fragment_blank, container, false);
            }
            TextView textView = fragmentView.findViewById(R.id.textView);
            textView.setText(mParam1);
            return fragmentView;
        }
    }
  5. 新建BlankFragment类的适配器MyFragmentPagerAdapter继承自FragmentStatePagerAdapter(LifeCycle是GoogleApp架构中推荐的一个组件,作用就是用来监听Activity与Fragment的生命周期变化。)
    public class MyFragmentPagerAdapter extends FragmentStateAdapter {
    
        ArrayList<Fragment> fragmentList = new ArrayList<>();
    
        public MyFragmentPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, ArrayList<Fragment> fragmentList) {
            super(fragmentManager, lifecycle);
            this.fragmentList = fragmentList;
        }
    
        @NonNull
        @Override
        public Fragment createFragment(int position) {
            return fragmentList.get(position);
        }
    
        @Override
        public int getItemCount() {
            return fragmentList.size();
        }
    }
  6. 编写MainActivity类
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ViewPager2 viewPager = findViewById(R.id.viewpager);
    
            ArrayList<Fragment> fragmentList = new ArrayList<>();
            fragmentList.add(BlankFragment.newInstance("微信聊天"));
            fragmentList.add(BlankFragment.newInstance("通讯录"));
            fragmentList.add(BlankFragment.newInstance("发现"));
            fragmentList.add(BlankFragment.newInstance("我的主页"));
            MyFragmentPagerAdapter myFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager(),getLifecycle(),fragmentList);
            viewPager.setAdapter(myFragmentPagerAdapter);
        }
    }
  7. 运行效果图

 WeChat首页的雏形实现:

  1. 准备图片并在src/main/res/drawable/新建资源,资源名为tab_wexin.xml(同理新建4个用于选择的资源并编写)
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@drawable/weixin_pressed" android:state_pressed="true"/>
        <item android:drawable="@drawable/weinxin_normal"/>
    </selector>
  2. 在src/main/res/color/新建tab_color.xml资源,作为颜色选择器
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:color="#09C15F" android:state_selected="true"/>
        <item android:color="@color/black"/>
    </selector>
  3. 在src/main/res/layout/新建bottom_layout.xml并编写内容
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:background="@color/gray">
    
        <LinearLayout
            android:id="@+id/tab_wechat"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:id="@+id/tab_wechat_iv"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:background="@drawable/tab_wechat"/>
            <TextView
                android:id="@+id/tab_wechat_tv"
                android:layout_width="32dp"
                android:layout_height="wrap_content"
                android:text="微信"
                android:gravity="center"
                android:textColor="@color/tab_color"/>
        </LinearLayout>
    
        <LinearLayout
            android:id="@+id/tab_contact"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:id="@+id/tab_contact_iv"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:background="@drawable/tab_contact"/>
            <TextView
                android:id="@+id/tab_contact_tv"
                android:layout_width="32dp"
                android:layout_height="wrap_content"
                android:text="通讯录"
                android:gravity="center"
                android:textColor="@color/tab_color"/>
        </LinearLayout>
    
        <LinearLayout
            android:id="@+id/tab_find"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:id="@+id/tab_find_iv"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:background="@drawable/tab_find"/>
            <TextView
                android:id="@+id/tab_find_tv"
                android:layout_width="32dp"
                android:layout_height="wrap_content"
                android:text="发现"
                android:gravity="center"
                android:textColor="@color/tab_color"/>
        </LinearLayout>
    
        <LinearLayout
            android:id="@+id/tab_profile"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:id="@+id/tab_profile_iv"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:background="@drawable/tab_profile"/>
            <TextView
                android:id="@+id/tab_profile_tv"
                android:layout_width="32dp"
                android:layout_height="wrap_content"
                android:text="我"
                android:gravity="center"
                android:textColor="@color/tab_color"/>
        </LinearLayout>
    
    </LinearLayout>
  4. 在activity_main.xml中用include标签引入bottom布局
    <include layout="@layout/bottom_layout"/>
  5. 编写MainActivity.java类
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private ViewPager2 viewPager;
        private LinearLayout llWechat, llContact, llFind, llProfile;
        private ImageView ivWechat, ivContact, ivFind, ivProfile, ivCurrent;
        private TextView tvWechat, tvContact, tvFind, tvProfile, tvCurrent;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initFragmentPage();
    
            initTabView();
    
        }
    
        private void initTabView() {
            llWechat = findViewById(R.id.tab_wechat);
            ivWechat = findViewById(R.id.tab_wechat_iv);
            tvWechat = findViewById(R.id.tab_wechat_tv);
    
            llContact = findViewById(R.id.tab_contact);
            ivContact = findViewById(R.id.tab_contact_iv);
            tvContact = findViewById(R.id.tab_contact_tv);
    
            llFind = findViewById(R.id.tab_find);
            ivFind = findViewById(R.id.tab_find_iv);
            tvFind = findViewById(R.id.tab_find_tv);
    
            llProfile = findViewById(R.id.tab_profile);
            ivProfile = findViewById(R.id.tab_profile_iv);
            tvProfile = findViewById(R.id.tab_profile_tv);
    
            ivWechat.setSelected(true);
            tvWechat.setSelected(true);
            ivCurrent = ivWechat;
            tvCurrent = tvWechat;
    
            llWechat.setOnClickListener(this);
            llContact.setOnClickListener(this);
            llFind.setOnClickListener(this);
            llProfile.setOnClickListener(this);
        }
    
        private void initFragmentPage() {
            viewPager = findViewById(R.id.viewpager);
    
            ArrayList<Fragment> fragmentList = new ArrayList<>();
            fragmentList.add(BlankFragment.newInstance("微信聊天"));
            fragmentList.add(BlankFragment.newInstance("通讯录"));
            fragmentList.add(BlankFragment.newInstance("发现"));
            fragmentList.add(BlankFragment.newInstance("我的主页"));
            MyFragmentPagerAdapter myFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager(),getLifecycle(),fragmentList);
            viewPager.setAdapter(myFragmentPagerAdapter);
    
            viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    super.onPageScrolled(position, positionOffset, positionOffsetPixels);
                }
    
                @Override
                public void onPageSelected(int position) {
                    super.onPageSelected(position);
                    switch (position){
                        case 0:
                            changeTab(ivWechat, tvWechat);
                            break;
                        case 1:
                            changeTab(ivContact, tvContact);
                            break;
                        case 2:
                            changeTab(ivFind, tvFind);
                            break;
                        case 3:
                            changeTab(ivProfile, tvProfile);
                            break;
                    }
                }
    
                @Override
                public void onPageScrollStateChanged(int state) {
                    super.onPageScrollStateChanged(state);
                }
            });
        }
    
        private void changeTab(ImageView imageView, TextView textView) {
            ivCurrent.setSelected(false);
            tvCurrent.setSelected(false);
            imageView.setSelected(true);
            textView.setSelected(true);
            ivCurrent = imageView;
            tvCurrent = textView;
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.tab_wechat:
                    changeTab(ivWechat, tvWechat);
                    viewPager.setCurrentItem(0);
                    break;
                case R.id.tab_contact:
                    changeTab(ivContact, tvContact);
                    viewPager.setCurrentItem(1);
                    break;
                case R.id.tab_find:
                    changeTab(ivFind, tvFind);
                    viewPager.setCurrentItem(2);
                    break;
                case R.id.tab_profile:
                    changeTab(ivProfile, tvProfile);
                    viewPager.setCurrentItem(3);
                    break;
            }
        }
    }
  6. 运行

运行效果图: 

第9章 数据存储

9.1 数据存储的选择

9.1.1 数据存储介绍

SP:保存配置信息或记忆信息(如记住密码)

SQLite:数据库

Room(新):数据库,更简洁

9.1.2 SP特点介绍

SP为SharedPreference(首选项)的缩写

首选项用于存储软件的配置信息:
window        .ini
android        .xml

自动登录,记住密码,主题记录等等
首选项不能存在太多的信息。特点:当程序运行首选项里面的数据会全部加载进内容(map的格式,key--value)

注意:很小、很简单的数据可以保存到首选项SP里面去

9.2 SP的简单使用

9.2.1 保存数据

保存目录:/data/data/包名/shared_prefs/文件名.xml

@override
public SharedPreferences getSharedPreferences(String name,int mode){
    return mBase.getSharedpreferences(name, mode);
}

参数1:SP文件的名字
参数2:SP保存的模式
        常规(每次保存都会更新/覆盖):Context.MODE_PRIVATE
        追加(每次保存都会追加到后面):Context.MODE_APPEND

9.2.2 获取数据

总代码:

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

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

    //保存数据到SP中
    public void saveToSP(View view) {
        SharedPreferences sp = getSharedPreferences("SPName", Context.MODE_PRIVATE);
        sp.edit().putString("book1","《九阳神功》").apply();
    }

    //获取SP中的数据
    public void getSPData(View view) {
        SharedPreferences sp = getSharedPreferences("SPName", Context.MODE_PRIVATE);
        String book1 = sp.getString("book1", "数据值不存在");
        Toast.makeText(this, book1, Toast.LENGTH_SHORT).show();
    }
}

运行效果:

9.2.3 SP实战(记住密码和自动登录)

注意:由于时间关系,没有考虑程序的健壮性,该项目有Bug,在点击自动登录时需要连勾保存密码,在取消勾选保存密码时需要取消勾选自动登录,该功能只需要实现复选框改变的监听函数即可。

activity_main代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="50dp"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="用户名:" />

        <EditText
            android:id="@+id/et_username"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="请输入用户名"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="密    码:" />

        <EditText
            android:id="@+id/et_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="请输入密码"
            android:inputType="textPassword"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <CheckBox
            android:id="@+id/cb_rememberPassword"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="记住密码"/>

        <CheckBox
            android:id="@+id/cb_autoLogin"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="自动登录"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_register"
            android:layout_width="wrap_content"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="注册"/>

        <Button
            android:id="@+id/btn_login"
            android:layout_width="wrap_content"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="登录"/>
    </LinearLayout>
</LinearLayout>

MainActivity代码:

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private SharedPreferences configSP;
    private EditText et_username, et_password;
    private CheckBox cb_rememberPassword, cb_autoLogin;
    private Button btn_register, btn_login;

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

        init();

        //若是打勾了,则下一次登录是回显数据
        boolean rememberPassword = configSP.getBoolean("rememberPassword", false);
        boolean autoLogin = configSP.getBoolean("autoLogin", false);
        if (rememberPassword){
            et_username.setText(configSP.getString("username", ""));
            et_password.setText(configSP.getString("password", ""));
            cb_rememberPassword.setChecked(true);
        }
        if (autoLogin){
            cb_autoLogin.setChecked(true);
            Toast.makeText(this, "自动登录成功", Toast.LENGTH_SHORT).show();
        }
    }

    private void init() {
        configSP = getSharedPreferences("config", Context.MODE_PRIVATE);

        et_username = findViewById(R.id.et_username);
        et_password = findViewById(R.id.et_password);
        cb_rememberPassword = findViewById(R.id.cb_rememberPassword);
        cb_autoLogin = findViewById(R.id.cb_autoLogin);
        btn_register = findViewById(R.id.btn_register);
        btn_login = findViewById(R.id.btn_login);

        MyOnClickListener myOnClickListener = new MyOnClickListener();
        btn_register.setOnClickListener(myOnClickListener);
        btn_login.setOnClickListener(myOnClickListener);
    }
    public class MyOnClickListener implements View.OnClickListener{

        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.btn_register:
                    break;
                case R.id.btn_login:
                    login(view);
                    break;
            }
        }
    }

    private void login(View view) {
        String username = et_username.getText().toString().trim();
        String password = et_password.getText().toString().trim();
        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)){
            Toast.makeText(this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
        } else {
            if (TextUtils.equals("admin", username) && TextUtils.equals("123456", password)){
                SharedPreferences.Editor edit = configSP.edit(); //获取到编辑
                if (cb_rememberPassword.isChecked()){ //是否需要保存密码
                    edit.putString("username", username);
                    edit.putString("password", password);
                    edit.putBoolean("rememberPassword", true);
                    edit.apply();
                }
                if (cb_autoLogin.isChecked()){ //是否需要自动登录
                    edit.putBoolean("autoLogin", true);
                    edit.apply();
                }Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();

            } else {
                Toast.makeText(this, "用户名或密码错误", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

运行效果图:

9.3 SQLite的简单使用

9.3.1 SQLite介绍

SQLite为关系型数据库
嵌入式的数据库,体积小功能强大,几十kb,嵌入式设备上:计算器、手表等。
在Android平台上,集成了一个嵌入式关系型数据库——SQLite,SQLite3支持NULL、INTEGER、REAL(浮点数字)、TEXT(字符串文本)和BLOB(二进制对象)数据类型,虽然它支持的类型只有五种,但实际上sqlite3也接受varchar(n)、char(n)、decimal(p,s)等数据类型,只不过在运算或保存时会转成对应的五种数据类型。

SQLite最大的特点是你可以把各种类型的数据保存到字段中,但是主键只能是Integer类型的。Sqlite数据库一般要求主键是_id,当然也可以是id。
Android里面的数据库是由底层的sqilte.c的代码来动态生成的。

9.3.2 SQLite的可视化工具

安装SQLite的可视化工具,将SQLite数据库导出,然后将导出的SQLite数据库拖入可视化工具中即可

SQLiteExpert官网链接:SQLite administration | SQLite Expert

更多SQLite的可视化工具:(4条消息) sqlite 免费的可视化工具_Lucas__liu的博客-CSDN博客_sqlite可视化工具

9.3.3 SQLiteOpenHelper类的方法介绍

方法名

作用

onCreate()

创建数据库,一个Helper类只会创建一次表

onUpgrade()

升级数据库

close()

关闭所有打开的数据库对象

execSQL()

执行增删改操作的SQL语句

rawQuery() 执行查询操作的SQL语句

insert()

插入数据

delete()

删除数据

query() 查询数据

update()

修改数据

getWritableDatabase()

创建或打开可写的数据库

getReadableDatabase()

创建或打开可读的数据

9.3.4 SQLite数据库的操作

1、创建SQLite数据库的辅助类(包括创库和创表)

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import androidx.annotation.Nullable;

//MySqliteOpenHelper是工具类 --> 单例模式(1.构造函数私有化  2.对外提供函数)

public class MySQLiteOpenHelper extends SQLiteOpenHelper {

    private static MySQLiteOpenHelper mySQLiteHelper = null;

    //2.对外提供函数
    public static synchronized MySQLiteOpenHelper getInstance(Context context){
        if (mySQLiteHelper == null){
            mySQLiteHelper = new MySQLiteOpenHelper(context,"user.db",null,1);
        }
        return mySQLiteHelper;
    }

    //1.构造函数私有化
    private MySQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL("create table persons(_id integer primary key autoincrement, name varchar(20), sex varchar(2), age integer)");
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
}

2、创建JavaBean类

public class Person {
    private String name;
    private String sex;
    private Integer age;

    public Person() {
    }

    public Person(String name, String sex, Integer age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                '}';
    }
}

3、编写activity_main.xml代码 

4、操作SQLite数据库

import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private MySQLiteOpenHelper myHelper;
    private String TAG = "MainActivity";

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

        initi();
    }

    private void initi() {
        myHelper = MySQLiteOpenHelper.getInstance(this);

        Button btn_insert = findViewById(R.id.btn_insert);
        Button btn_delete = findViewById(R.id.btn_delete);
        Button btn_query = findViewById(R.id.btn_query);
        Button btn_update = findViewById(R.id.btn_update);

        btn_insert.setOnClickListener(this);
        btn_delete.setOnClickListener(this);
        btn_query.setOnClickListener(this);
        btn_update.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_insert:
                Log.i(TAG, "插入数据");
                insert("张三", "女", 18);
                break;
            case R.id.btn_delete:
                Log.i(TAG, "删除数据");
                delete("李四");
                break;
            case R.id.btn_query:
                Log.i(TAG, "查询数据");
                query();
                break;
            case R.id.btn_update:
                Log.i(TAG, "更新数据");
                update("张三", "李四");
                break;
        }
    }

    private void insert(String name, String sex, Integer age) {
        SQLiteDatabase writableDatabase = myHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("name", name);
        values.put("sex", sex);
        values.put("age", age);
        long insertCount = writableDatabase.insert("persons", null, values);
        /**
         * 第一个参数:操作的表名
         * 第二个参数:插入的列没有数据匹配时的默认值
         * 第三个参数:插入的数据列表
         */
        if (insertCount > 0){
            Toast.makeText(this, "插入成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "插入失败", Toast.LENGTH_SHORT).show();
        }
    }

    private void delete(String name) {
        SQLiteDatabase writableDatabase = myHelper.getWritableDatabase();
        String[] whereArgs = {name};
        int deleteCount = writableDatabase.delete("persons", "name=?", whereArgs);
        /**
         * 第一个参数:操作的表名
         * 第二个参数:条件语句, 如果为null, 就删除所有行
         * 第三个参数:条件字符串中"?"占位符的匹配列表
         */
        if (deleteCount > 0){
            Toast.makeText(this, "删除成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "删除失败", Toast.LENGTH_SHORT).show();
        }
        writableDatabase.close();
    }

    private void query() {
        SQLiteDatabase readableDatabase = myHelper.getReadableDatabase();
        Cursor cursor = readableDatabase.query("persons", null, null, null, null, null, null);
        while (cursor.moveToNext()) {
            String name = cursor.getString(cursor.getColumnIndex("name"));
            String sex = cursor.getString(cursor.getColumnIndex("sex"));
            int age = cursor.getInt(cursor.getColumnIndex("age"));
            Person person = new Person(name, sex, age);
            Log.i(TAG, person.toString());
        }
        cursor.close();
        readableDatabase.close();
    }

    private void update(String name, String name1) {
        SQLiteDatabase writableDatabase = myHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("name", name1);
        String[] whereArgs = {name};
        int updateCount = writableDatabase.update("persons", values, "name=?", whereArgs);
        /**
         * 第一个参数:操作的表名
         * 第二个参数:更新的数据列表
         * 第三个参数:条件语句, 如果为null, 就更新所有行
         * 第四个参数:条件字符串中"?"占位符的匹配列表
         */
        if (updateCount > 0){
            Toast.makeText(this, "更新成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "更新失败", Toast.LENGTH_SHORT).show();
        }
        writableDatabase.close();
    }
}

5、运行效果图

注意:在数据库查询操作的cursor.getString(cursor.getColumnIndex("name"));处报错,但不影响编译(可分为两步写解决报错问题)
        操作顺序为插入-->查询-->更新--查询-->删除-->查询

9.4 Room的简单使用

9.4.1 Room的介绍

Room是一个对象关系映射(ORM)库。Room抽象了SQLite的使用,可以在充分利用SQLite的同时访问流畅的数据库。

Room官方文档介绍 https://developer.android.com/training/data-storage/room/

Room由三个重要的组件组成(三角色):Entity、Dao、Database。

  • Entity:数据库实体,系统根据Entity类创建数据库,里面规定了PrimaryKey,列名、表名等数据库必备设定
  • Dao:Database access object:定义了一些操作数据库的操作,比如说增删改查
  • Database:可以认为是完整的数据库,完整的数据库包括数据库信息和数据库操作,也就是EntityDao

9.4.2 Room三角色的实现

注意:Room操作数据库时都是基于主键操作的

1、Student(Entity)

import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;

@Entity(indices = {@Index(value = {"studentNumber"}, unique = true)})
public class Student {
    @PrimaryKey(autoGenerate = true)
    private Integer id;
    private String studentNumber;
    private String name;
    private String classes;

    public Student(Integer id, String studentNumber, String name, String classes) {
        this.id = id;
        this.studentNumber = studentNumber;
        this.name = name;
        this.classes = classes;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getStudentNumber() {
        return studentNumber;
    }

    public void setStudentNumber(String studentNumber) {
        this.studentNumber = studentNumber;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getClasses() {
        return classes;
    }

    public void setClasses(String classes) {
        this.classes = classes;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", studentNumber='" + studentNumber + '\'' +
                ", name='" + name + '\'' +
                ", classes='" + classes + '\'' +
                '}';
    }
}

2、StudentDao(Dao)

import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;

import java.util.List;

@Dao
public interface StudentDao {
    @Insert
    void insertStudent(Student student);

    @Insert
    void insertStudents(List<Student> students);

    @Query("DELETE FROM student WHERE studentNumber=:studentNumber")
    int deleteStudent(String studentNumber);

    @Query("DELETE FROM student")
    int deleteAllStudent();

    @Query("SELECT * FROM Student")
    List<Student> queryAllStudent();

    @Query("SELECT * FROM Student WHERE studentNumber=:studentNumber")
    Student queryStudentByStudentNumber(String studentNumber);

    @Query("SELECT * FROM Student WHERE name=:name")
    List<Student> queryStudentByName(String name);

    @Update
    int updateStudent(Student student);
}

3、StudentDatabase(Database)

import android.content.Context;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

/**
 * entities:传入所有Entity的class对象;
 * version:数据库版本号。
 * exportSchema:设置是否导出数据库schema,默认为true,需要在build.gradle中设置
 */
@Database(entities = {Student.class}, version = 1, exportSchema = false)
public abstract class StudentDatabase extends RoomDatabase {

    private static StudentDatabase studentDatabase = null;

    public static synchronized StudentDatabase getInstance(Context context){
        if (studentDatabase == null){
            studentDatabase = Room.databaseBuilder(context.getApplicationContext(), StudentDatabase.class, "student")
                    .allowMainThreadQueries() //强制在主线程执行
                    .build();
        }
        return studentDatabase;
    }

    public abstract StudentDao getStudentDao();
}

9.4.3 使用Room三角色

1、编写activity_main.xml

2、编写ActivityMain.java

import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "MainActivity";
    private StudentDao studentDao;

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

    private void init() {
        studentDao = StudentDatabase.getInstance(this).getStudentDao();

        Button btn_insert = findViewById(R.id.btn_insert);
        Button btn_delete = findViewById(R.id.btn_delete);
        Button btn_query = findViewById(R.id.btn_query);
        Button btn_update = findViewById(R.id.btn_update);

        btn_insert.setOnClickListener(this);
        btn_delete.setOnClickListener(this);
        btn_query.setOnClickListener(this);
        btn_update.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_insert:
                Log.i(TAG, "插入数据");
                insert("38241319601", "张三", "2019级计算机科学与技术六班");
                insert("38241319602", "李si", "2019级计算机科学与技术六班");
                insert("38241319603", "王五", "2019级计算机科学与技术六班");
                insert("38241319604", "赵六", "2019级计算机科学与技术六班");
                break;
            case R.id.btn_delete:
                Log.i(TAG, "删除数据");
                delete("38241319602");
                break;
            case R.id.btn_query:
                Log.i(TAG, "查询数据");
                queryAll();
                break;
            case R.id.btn_update:
                Log.i(TAG, "更新数据");
                update(2, "李四");
                break;
        }
    }

    private void insert(String studentNumber, String name, String classes) {
        Student student = new Student(null, studentNumber, name, classes);
        student.setStudentNumber(studentNumber);
        studentDao.insertStudent(student);
    }

    private void delete(String studentNumber) {
        int deleteCount = studentDao.deleteStudent(studentNumber);
        if (deleteCount > 0){
            Toast.makeText(this, "删除成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "删除失败", Toast.LENGTH_SHORT).show();
        }
    }

    private void queryAll() {
        List<Student> students = studentDao.queryAllStudent();
        for (Student student : students) {
            Log.i(TAG, student.toString());
        }
    }

    private void update(Integer id, String name) {
        Student student = new Student(id, null, name, null);
        int updateCount = studentDao.updateStudent(student);
        if (updateCount > 0){
            Toast.makeText(this, "更新成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "更新失败", Toast.LENGTH_SHORT).show();
        }
    }
}

6、运行效果

更多详见:1.0 Android基础入门教程 | 菜鸟教程 (runoob.com)

猜你喜欢

转载自blog.csdn.net/weixin_57542177/article/details/123591516
今日推荐