文章目录
- 安卓项目结构
- AndroidMainfest.xml
- res资源目录简介
- 基本概念
- Layout
- R类
- Application与Activity
- Context
- Intent
- 数据传递
- 可传递的数据类型
- intent.putExtra()和使用Bundle的区别
- 数据传递大小的限制
- 通过Intent 过滤器接收隐式 Intent:
- 单位和尺寸
- px与pt的区别
- dp与sp
- 布局
- LinearLayout
- RelativeLayout
- TableLayout
- FrameLayout
- ConstraintLayout
- ListView
- 基于ArrayAdapter
- 自定义Adaper
- 提升ListView的运行效率
- RecyclerView
- 基本属性
- 使用案例
- 布局(显示方式)
- 监听事件
- 利用View.onClickListener 和 onLongClickListener
- ViewPager
- ViewPager2
- 什么是懒加载?
- ViewPager与ViewPager2部分对比
- 常见api
- 动画
- 帧动画
- 补间动画
- 属性动画
安卓项目结构
app目录下包含三个主要的目录:
- manifest:默认生成一个AndroidMainfest.xml 文件,也称清单文件,在里边配置APP的权限,启动组件等基本配置信息。
- java:该文件下存放Activity的实现文件
- res:资源目录,存放图片、图标、颜色、字体等界面渲染所需要的资源。
在APP目录下有一个build.gradle
文件,俗称“模块的gradle”文件
或者APP的gradle文件
,这个文件中,包含有Android编译的版本号信息、APP的版本信息,JDK版本号、项目的依赖库等。
引入外部的依赖或者外部的SDK也在这个文件中添加。
在项目的根目录下还有一个gradle文件,这里指定了仓库名,全局依赖,项目根目录名等信息,该文件一般不需要更改。
AndroidMainfest.xml
也可以简称为「manifest文件」。清单文件非常重要,它告诉系统我们的app有哪些activity,用到了什么权限等等信息。
注意:所有的四大组件都需要在AndroidMainfest.xml 中进行注册!
res资源目录简介
简单介绍Android工程中的资源目录(resources),res。
资源是指代码使用的附加文件和静态内容,例如位图、布局定义、界面字符串、动画说明等。
把资源放进对应的目录后,可使用在项目R类中生成的资源ID来访问这些资源。形如R.drawable.icon,R.layout.main_activity。 R类是自动生成的。代表resources。
资源文件只能以小写字母和下划线做首字母,随后的名字中只能出现【a-z0-9_.】这些字符,否则R.java不会自动更新!!!
将各类资源放入项目 res/ 目录的特定子目录中。 子目录的名字特别重要。我们把不同的资源放到不同的子目录中(res子目录)。参考下面的表格。
注意:
不能将资源文件直接保存在res/目录内,因为会造成编译错误
- mipmap 目录是专门用于存放应用程序的启动图标(App Icon)资源的。mipmap 目录下的图标资源会自动进行缩放适应不同分辨率设备,而 drawable 目录下的图像资源需要手动提供适应不同屏幕密度的图像资源。
基本概念
Layout
-
Layout:布局文件,相当于前端的HTML文件。
-
LayoutParams相当于一个Layout的信息包,它封装了Layout的位置,高、宽等信息。
R类
-
当Android应用程序被编译,会自动生成一个R.java类,其中包含了所有的res/目录下资源的ID,如布局文件,资源文件,图片。 res目录下保存的文件大多数都会被编译,并且被赋予资源ID,这些ID被保存在R.java文件中,这样我们就可以在程序中通过ID来访问res类的资源。
-
资源文件只能以小写字母和下划线做首字母,随后的名字中只能出现【a-z0-9_.】这些字符,否则R.java不会自动更新!!!
Application与Activity
-
应用程序每次启动时,系统会为其创建一个
application对象且只有一个(单例类),用来存储系统的一些信息,相当于一个容器。
启动application时,系统会创建一个PID(进程ID),所有的activity都在这个进程上运行,在application创建时会初始化一个全局变量
,同一个应用的activity,都可以获取到这个变量,也就是说,某一个activity中改变了这个变量,其他activity里也会改变。application是对应用程序的抽象。
-
Activity表示屏幕中的一个活动,用于显示屏幕界面并与用户进行交互,
Activity是对可交互UI界面的抽象。
Context
Context 在 Android 开发中几乎无处不在,它是 Android 开发中最重要的东西,所以我们必须了解正确使用它。
==错误使用 Context 很容易导致 android 应用程序中的内存泄漏。==比如在单例的场景下传递activity的context。
Context:直译为语境或上下文,它有以下几个特点:
- 应用程序当前状态的上下文
- 用于获取有关活动和应用程序的信息
- 用于访问资源、数据库和共享首选项
- Activity和Application类都扩展了Context类
在Android中主要有两种Context:
- Application Context:它是应用程序中扩展的Context,与Application 生命周期相关,伴随Application的一生,只被创建和销毁一次。
- Activity context:存在于活动中的context,伴随活动的生命周期被创建和销毁。
什么时候用哪个Context?
- 永远记住,在单例的情况下(生命周期附加到应用程序生命周期),总是使用Application Context。
- 始终尝试使用最近的Context,当在Activity中,对于任何UI操作(例如显示 toast、对话框等),都需要使用 Activity 上下文。
Intent
Intent ,翻译为意图,是一个消息传递对象,您可以用来从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个:
- 启动 Activity
- 启动服务
- 传递广播
Intent 对象携带 Android 系统用来确定要启动哪个组件的信息(例如,准确的组件名称或应当接收该 Intent 的组件类别),以及收件人组件为了正确执行操作而使用的信息(例如,要采取的操作以及要处理的数据)。
Intent 中包含的主要信息如下:
-
组件名称:要启动的组件名称。这是一个可选项,如果不指定组件名称,则为
隐式Intent
,系统将根据其他 Intent 信息(例如,以下所述的操作、数据和类别)决定哪个组件应当接收 Intent。如需在应用中启动特定的组件,则应指定该组件的名称。 -
操作类型:您可以指定自己的操作,供 Intent 在您的应用内使用(或者供其他应用在您的应用中调用组件)。但是,您通常应该使用由Intent 类或其他框架类定义的操作常量。以下是一些用于启动 Activity 的常见操作:
- ACTION_VIEW
如果您拥有一些某项 Activity 可向用户显示的信息(例如,要使用图库应用查看的照片;或者要使用地图应用查看的地址),请通过 Intent 将此操作与 startActivity() 结合使用。 - ACTION_SEND
这也称为共享 Intent。如果您拥有一些用户可通过其他应用(例如,电子邮件应用或社交共享应用)共享的数据,则应使用 Intent 将此操作与 startActivity() 结合使用。
- ACTION_VIEW
-
Extra:携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 URI 一样,有些操作也使用特定的 extra。
您可以使用各种 putExtra() 方法添加 extra 数据,每种方法均接受两个参数:键名和值。您还可以创建一个包含所有 extra 数据的 Bundle 对象,然后使用 putExtras() 将 Bundle 插入 Intent 中。
数据传递
可传递的数据类型
- 8 种基本数据类型(boolean、 byte、 char、 short、 int、 long、 float、 double)、String
- Intent、Bundle
- Serializable对象、Parcelable及其对应数组、CharSequence 类型
- ArrayList,泛型参数类型为:、<? Extends Parcelable>
intent.putExtra()和使用Bundle的区别
Bundle:直译为捆绑,相当于一个数据容器,更多适用于:
- 连续传递数据:Activity A -> B -> C,使用putExtra(),则需写两次 intent = A->B 先写一遍 + 在B中取出来 & 再把值重新写到Intent中再跳到C;若使用 Bundle,则只需取出 & 传入 Bundle对象即可
- putExtra无法无法传递对象,而 Bundle 则可通过 putSerializable 传递对象
数据传递大小的限制
Intent 传递大数据,会出现 TransactionTooLargeException 的场景,这是因为 Intent 传递数据的大小是有限制的。
Android 系统使用一种称为 Binder 机制的进程间通信机制来传递 Intent。在此过程中,Intent 的数据被封装成一个 Parcel 对象,并通过 Binder 传递给目标组件。然而,Binder 机制对于单个事务(Transaction)的数据大小有一个限制,通常为 1MB,并且这里的 1MB 空间并不是当前操作独享的,而是由当前进程所共享。
为了避免出现 TransactionTooLargeException 异常,可以考虑以下解决方案:
-
使用其他数据传递机制:对于大数据传递,可以使用其他机制,如共享文件、ContentProvider 或者使用数据库等。
-
传递数据的引用:而不是将整个数据对象传递给目标组件,可以传递数据的引用或标识符,目标组件在需要时再获取数据。
-
分割数据:将大的数据拆分成较小的块进行传递,通过多个 Intent 或其他方式进行传递,然后在目标组件中重新组装。
-
使用 Parcelable 替代 Serializable:Parcelable 是一种 Android 提供的更高效的序列化机制,相对于 Serializable,它可以减少数据的大小。
通过Intent 过滤器接收隐式 Intent:
要公布应用可以接收哪些隐式 Intent,请在清单文件中使用 <intent-filter>
元素为每个应用组件声明一个或多个 Intent 过滤器。每个 Intent 过滤器均根据 Intent 的操作、数据和类别指定自身接受的 Intent 类型。仅当隐式 Intent 可以通过 Intent 过滤器之一传递时,系统才会将该 Intent 传递给应用组件。
<activity android:name="MainActivity"><!-- This activity is the main entry, should appear in app launcher --><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter>
</activity><activity android:name="ShareActivity"><!-- This activity handles "SEND" actions with text data --><intent-filter><action android:name="android.intent.action.SEND"/><category android:name="android.intent.category.DEFAULT"/><data android:mimeType="text/plain"/></intent-filter><!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data --><intent-filter><action android:name="android.intent.action.SEND"/><action android:name="android.intent.action.SEND_MULTIPLE"/><category android:name="android.intent.category.DEFAULT"/><data android:mimeType="application/vnd.google.panorama360+jpg"/><data android:mimeType="image/*"/><data android:mimeType="video/*"/></intent-filter>
</activity>
单位和尺寸
像素:是指在由一个数字序列表示的图像中的一个最小单位,称为像素。
px与pt的区别
- px:pixels(像素),不同设备显示效果相同
- pt:point,是一个标准的长度单位,1pt = 1/72英寸,用于印刷业,简单易用。
dp与sp
- dip:device independent pixels(设备独立像素)。不同设备有不同的显示效果,这与设备的硬件有关,一般我们为了支持WVGA、HVGA和QVGA推荐使用这个,不依赖像素。
- dp:就是dip
- sp:scaled pixels(放大像素),用于字体显示(best for textsize)
因为我们的APP可能需要在不同的设备上使用,使用dp可以根据设备不同,自适应大小(设备越大,1dp所占用的像素越多)。因此组件的大小常常使用dp为单位。
布局
安卓常用的布局方式有六种(绝对布局因灵活性太差已经弃用):
- 线性布局
- 相对布局
- 网格布局
- 表格布局
- 帧布局
- 约束布局
LinearLayout
LinearLayout里面可以放置多个view(这里称为子view,子项)。 子view可以是TextView,Button,或者是LinearLayout,RelativeLayout等等。 它们将会按顺序依次排布为一列或一行。
常用属性
- orientation:确定水平或竖直排布子view。 可选值有vertical和horizontal。
- gravity:决定子view的排布方式。gravity有“重力的意思”,引申为子view会向哪个方向靠拢,gravity有几个选项可以选择,我们常用的有start,end,left,right,top,bottom。
- 子view的layout_gravity:gravity是控制自己内部的子元素,layout_gravity是告诉父元素自己的位置。可以设置子view的layout_weight来控制空间占比,设置layout_weight的时候,一般要设置子view的layout_width(水平排布时)或者layout_height(垂直排布时)为0。
- divider:设置divider和showDivider属性,使得子view之间有分割线。
RelativeLayout
RelativeLayout和LinearLayout类似,都是ViewGroup,能“容纳”多个子view。RelativeLayout 是一个以相对位置
显示子视图的视图组。每个视图的位置可以指定为相对于同级元素的位置
(例如,在另一个视图的左侧或下方)或相对于父级
RelativeLayout 区域的位置(例如在底部、左侧或中心对齐)((由 ID 确定)的位置
)。
TableLayout
表格布局,通过设置表格的行列,构建布局。
常用属性
layout_column:显示在第几列
layout_columnSpan:横向跨几列
layout_columnWeight:剩余空间分配方式
layout_gravity:在网格中的显示位置
layout_row:显示在第几行
layout_rowSpan:横向跨几行
layout_rowWeight:纵向剩余空间分配
FrameLayout
帧布局:特点是子view是可以重叠的。
ConstraintLayout
ConstraintLayout 可让您使用扁平视图层次结构(无嵌套视图组)创建复杂的大型布局。它与RelativeLayout 相似,其中所有的视图均根据同级视图与父布局之间的关系进行布局,但其灵活性要高于 RelativeLayout。
注意:约束布局要求每个视图至少有两个约束条件:一个水平约束,一个垂直约束。如果缺少某个方向的约束,比如垂直,那么默认是贴近上边界。
还可以通过设定引导线(guide line),指定控件相对于基准线的约束布局。
ListView
ListView,列表视图,能够根据列表中选项个数自适应屏幕显示。ListView本身类似于布局容器,它的子View需要另外定义。在APP运行时,每个列表选项是一个子模块,这个子模块有视图,有对应的数据需要填充到视图,每个子模块还需要绑定对应的事件函数。
安卓开发中,使用了适配器
设计模式来处理ListView的显示流程。
适配器模式的定义为:将一个类的接口转为客户所期待的 另一种接口,从而使得原本接口不匹配而无法工作在一起的两个类,能够在一起工作。
ListView期待的是一个有视图有数据有交互功能的列表子模块,在程序运行中,我们先把数据和视图一起加工处理为listview期待的类型,再交给listview工作。
基于ArrayAdapter
如果列表中每个选项的内容可以由一个简单的基本数据类型表示,那么可以使用ArrayAdapter来实现。
例如下边的列表,只需要显示蓝牙的地址,那么只需要一个String类型作为数据传入。
构建步骤如下:
- 在activity的布局文件中:声明一个ListView。
- 额外编写一个device_name.xml,它是每个选项的视图,对于我们的需要而言,一个TextView足以。
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:textSize="18sp"android:padding="5dp"
/>
- 在activity中:
将device_name.xml构建为ArrayAdpter,然后把适配器交给ListView。当需要向列表中增加选项时,直接调用mPairedDevicesArrayAdapter.add()即可。
//初使化设备适配器存储数组
mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
mUnPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);//设置已配队设备列表
ListView pairedListView = findViewById(R.id.pairedListView);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener( mDeviceClickListener);// 设置新查找设备列表
ListView newDevicesListView = findViewById(R.id.unPairedListView);
newDevicesListView.setAdapter(mUnPairedDevicesArrayAdapter);
newDevicesListView.setOnItemClickListener(mDeviceClickListener);// 得到本地蓝牙句柄
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// //添加已配对设备到列表并显示
if (pairedDevices.size() > 0) {findViewById(R.id.pairedListView).setVisibility(View.VISIBLE);for (BluetoothDevice device : pairedDevices) {mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());}
} else {String noDevices = "没有找到已配对的设备。" ;mPairedDevicesArrayAdapter.add(noDevices);
}
自定义Adaper
只能显示一段文本的listview太单调了,我们现在就来对listview的界面进行定制,让其丰富内容。
构建步骤如下:
- 在activity.xml中声明一个ListView
- 构建每个列表选项中的数据类Fruit:
package com.example.listview2;
public class Fruit {
private int imageID;
private String name;
private String price;public int getImageID() {return imageID;}public String getName() {return name;}public String getPrice() {return price;}public Fruit(int imageID, String name, String price) {this.imageID = imageID;this.name = name;this.price = price;}
}
- 构建每个列表选项布局文件fruit_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:orientation="horizontal"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/fruit_image"android:src="@drawable/apple"android:layout_width="100dp"android:layout_height="80dp"/><TextViewandroid:id="@+id/fruit_name"android:layout_gravity="center_vertical"android:textSize="30sp"android:textColor="#000000"android:text="name"android:layout_marginLeft="10dp"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid:id="@+id/fruit_price"android:layout_gravity="center_vertical"android:textColor="#ff0000"android:text="price"android:textSize="30sp"android:layout_marginLeft="10dp"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
</LinearLayout>
- 自定义FruitAdpter类继承自ArrayAdpter
编写构造方法(构造方法需要传递上下文,数据内容),重写getView()函数。
package com.example.listview2;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
//用于将上下文、listview 子项布局的 id 和数据都传递过来
public class FruitAdapter extends ArrayAdapter<Fruit> {public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {super(context, resource, objects);}
//每个子项被滚动到屏幕内的时候会被调用@NonNull@Overridepublic View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {Fruit fruit=getItem(position);//得到当前项的 Fruit 实例//为每一个子项加载设定的布局View view=LayoutInflater.from(getContext()).inflate(R.layout.fruit_item,parent,false);//分别获取 image view 和 textview 的实例ImageView fruitimage =view.findViewById(R.id.fruit_image);TextView fruitname =view.findViewById(R.id.fruit_name);TextView fruitprice=view.findViewById(R.id.fruit_price);// 设置要显示的图片和文字fruitimage.setImageResource(fruit.getImageID());fruitname.setText(fruit.getName());fruitprice.setText(fruit.getPrice());return view;}
}
- 在activity中,准备数据,构建ListView并设置Adapter。
package com.example.listview2;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {//第一步:定义对象ListView listView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//第二步:绑定控件listView = (ListView) findViewById(R.id.list_view);//第三步:准备数据List<Fruit> fruitlist = new ArrayList<>();for (int i = 0; i <2 ; i++) {Fruit pineapple=new Fruit(R.drawable.pineapple,"菠萝","¥16.9 元/KG");fruitlist.add(pineapple);Fruit mango = new Fruit(R.drawable.mango, "芒果","¥29.9 元/kg");fruitlist.add(mango);Fruit pomegranate = new Fruit(R.drawable.pomegranate, "石榴","¥15元/kg");fruitlist.add(pomegranate);Fruit grape = new Fruit(R.drawable.grape, "葡萄","¥19.9 元/kg");fruitlist.add(grape);Fruit apple = new Fruit(R.drawable.apple, "苹果","¥20 元/kg");fruitlist.add(apple);Fruit orange = new Fruit(R.drawable.orange, "橙子","¥18.8 元/kg");fruitlist.add(orange);Fruit watermelon = new Fruit(R.drawable.watermelon, "西瓜","¥28.8元/kg");fruitlist.add(watermelon);}//第四步:设计每一个列表项的子布局//第五步:定义适配器 控件 -桥梁-数据FruitAdapter adapter=new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitlist);listView.setAdapter(adapter);}
}
提升ListView的运行效率
目前我们的ListView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当页面快速滚动的时候,这将成为性能的瓶颈。
优化方法一:
仅在convertView为null时才创建:
优化方法二:
新增内部类ViewHolder对控件实例进行缓存。
public class FruitAdapter extends ArrayAdapter {public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {super(context, resource, objects);}@NonNull@Overridepublic View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {ViewHolder viewHolder;if (convertView == null) {viewHolder = new ViewHolder();convertView = LayoutInflater.from(getContext()).inflate(R.layout.item, parent, false);viewHolder.avatar = convertView.findViewById(R.id.avatar);viewHolder.name = convertView.findViewById(R.id.fruit_name);viewHolder.price = convertView.findViewById(R.id.fruit_price);convertView.setTag(viewHolder);} else {viewHolder = (ViewHolder) convertView.getTag();}Fruit fruit = (Fruit) getItem(position);viewHolder.price.setText(fruit.getPrice().toString());viewHolder.name.setText(fruit.getName());viewHolder.avatar.setText(fruit.getAvatar());return convertView;}private final class ViewHolder {TextView avatar, name, price;}
RecyclerView
RecyclerView是ListView的升级版,它更加灵活,使用更加简单。在ListView中我们可以自己实现ViewHolder以及convertView进行优化,但是在RecyclerView中,它直接封装了ViewHolder的回收利用,也就是RecyclerView将ViewHolder标准化,我们不需要面向 view ,而是直接面向 ViewHolder 编写实现我们需要的 Adapter,这样一来,逻辑结构就变得非常清晰。
RecyclerView常常搭配线性布局和网格布局使用。
基本属性
- itemAnimator:增删动画
- itemDecoration:分割线
注意:RecyclerView 本身是不提供点击、长按事件的,而隔壁的 ListView 稳稳支持。对此,可能刚接触 RecyclerView 的同学会疯狂吐槽,怎么作为升级版的 RecyclerView 在这一点上还不如旧版呢?
显然不是。
ListView 中对于点击事件的处理,其实是有很大弊端的,它的 setOnItemClickListener() 方法是为子项注册点击事件,这就导致只能识别到这一整个子项,对于子项中的组件比如按钮就束手无策了。为此,RecyclerView 直接放弃了这个为子项注册点击事件的监听方法,所有点击事件都有具体 View 去注册,好处显而易见,我可以按需为组件注册点击事件,不存在点击不到的组件。
RecyclerView 的核心使用流程如下:
mRecyclerView = findView(R.id.id_recycler_view);
//设置布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
//设置adapter
mRecyclerView.setAdapter(mAdapter)
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.HORIZONTAL_LIST));
使用案例
下面就来介绍一下 如何通过 RecyclerView 轻松实现一个普通列表:
MainActivity.java:
public class MainActivity extends AppCompatActivity {private RecyclerView mRecyclerView;private MyAdapter mMyAdapter;private LinearLayoutManager mLayoutManager;private List<String> list;@Overrideprotected void onCreate(final Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initData();mRecyclerView = findViewById(R.id.recycler_view);mMyAdapter = new MyAdapter(list);mLayoutManager = new LinearLayoutManager(this);mRecyclerView.setLayoutManager(mLayoutManager);mRecyclerView.setAdapter(mMyAdapter);}private void initData() {list = new ArrayList<>();for (int i = 0; i <= 20; i++) {list.add("Item " + i);}}
}
MyAdapter.java:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {//数据源private List<String> mList;public MyAdapter(List<String> list) {mList = list;}//返回item个数@Overridepublic int getItemCount() {return mList.size() ;}//创建ViewHolder@NonNull@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {return new NormalHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false));}//填充视图@Overridepublic void onBindViewHolder(@NonNull final MyAdapter.ViewHolder holder, final int position) {holder.mView.setText(mList.get(position));}public class ViewHolder extends RecyclerView.ViewHolder {public TextView mView;public ViewHolder(View itemView) {super(itemView);mView = itemView.findViewById(R.id.text_view);}}
}
布局(显示方式)
listView默认是垂直布局,而recyclerView则更加灵活,运行我们自己设置它的布局。
可通过LayoutManager(LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager )设置线性布局、网格布局、瀑布流布局;
监听事件
RecycylerView 并没有处理点击事件的监听器,所以如果要监听 RecycylerView 的点击事件,我们需要自己写监听器。
下面就简单介绍几种实现方法。
推荐使用方法一和方法三
- 方法一:利用View.onClickListener 和 onLongClickListener
- 方法二:利用RecyclerView.OnItemTouchListener
- 方法三:利用GestureDetector(手势检测类)对方法二优化
利用View.onClickListener 和 onLongClickListener
- 在adapter中新建两个内部接口:
public interface OnItemClickListener {void onItemClick(View view, int position);
}public interface OnItemLongClickListener {void onItemLongClick(View view, int position);
}
- 新建两个私有变量用于保存用户设置的监听器,并公开一个设置监听器的方法:
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {this.mOnItemClickListener = mOnItemClickListener;
}public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {this.mOnItemLongClickListener = mOnItemLongClickListener;
}
- 在onBindViewHolder方法内,实现回调:
@Overridepublic void onBindViewHolder(final MyViewHolder holder, int position) {holder.tvTest.setText(stringList.get(position));
// ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
// lp.height = (int) (100 + Math.random() * 300);
// holder.itemView.setLayoutParams(lp);//判断是否设置了监听器if(mOnItemClickListener != null){//为ItemView设置监听器holder.itemView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {int position = holder.getLayoutPosition(); // 1mOnItemClickListener.onItemClick(holder.itemView,position); // 2}});}if(mOnItemLongClickListener != null){holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {int position = holder.getLayoutPosition();mOnItemLongClickListener.onItemLongClick(holder.itemView,position);//返回true 表示消耗了事件 事件不会继续传递return true;}});}}
这里实际上用到了子 Item View 的 onClickListener 和onLongClickListener这两个监听器,如果当前子item view被点击了,会触发点击事件进行回调,然后在内部接口处获取当前点击位置的position值,接着在我们保存的用户设置的监听器处进行再次回调,而这一次的回调是我们自己手动添加的,需要实现上面所述的接口。
修改完 TestAdapter后,我们接着在 MainActivity 中设置监听器,采用匿名内部类的形式实现了 onItemClickListener 、 onItemLongClickListener 接口,这种写法与一般的设置监听器的流程相同:
TestAdapter mTestAdapter = new TestAdapter(getList());
mTestAdapter.setOnItemClickListener(new TestAdapter.OnItemClickListener() {@Overridepublic void onItemClick(View view, int position) {Toast.makeText(MainActivity.this, "click " + getList().get(position), Toast.LENGTH_SHORT).show();}
});
mTestAdapter.setOnItemLongClickListener(new TestAdapter.OnItemLongClickListener() {@Overridepublic void onItemLongClick(View view, int position) {Toast.makeText(MainActivity.this,"long click "+getList().get(position),Toast.LENGTH_SHORT).show();}
});
rvTest.setAdapter(mTestAdapter);
ViewPager
一个简单的页面切换组件。
使用案例:
编写三个页面进行切换。
- 首先创建3个xml布局文件
- 在activity的布局文件中声明ViewPager
- 创建Adapter继承PagerAdapter,重写方法:
- getCount():获取viewpager中有多少个view
- instantiateItem():
- 将给定位置的view添加到viewgroup中,创建并显示处理
- 返回一个代表新增页面的Object(key),通常都是直接返回view本身就可以了,当然你也可以自定义自己的key,但是key和每个view要一一对应的关系。
- isViewFromObject()
判断instantiateItem(Viewgroup,int)函数所返回的key与一个页面视图是否是代表的同一个视图(即他俩是否是对应的,对应的表示同一个view),通常我们直接写成return view==object - destroyItem()
移除一个给定位置的页面,适配器有责任从容器中删除这个视图,这是为了确保在finish update(viewgroup)返回时视图能够被移除。
adapter的代码:
package com.example.myviewpager;import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;import java.util.List;public class MyAdapter extends PagerAdapter {private List<View> listview;public MyAdapter(List<View> listview) {this.listview = listview;}@NonNull@Overridepublic Object instantiateItem(@NonNull ViewGroup container, int position) {container.addView(listview.get(position),0);return listview.get(position);}@Overridepublic int getCount() {return listview.size();}@Overridepublic boolean isViewFromObject(@NonNull View view, @NonNull Object object) {return view==object;}@Overridepublic void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {container.removeView(listview.get(position));}
}
activity:
import android.view.LayoutInflater;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import androidx.viewpager.widget.ViewPager;import java.util.ArrayList;
import java.util.List;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);LayoutInflater lf=getLayoutInflater().from(this);View view1=lf.inflate(R.layout.layout1,null);View view2=lf.inflate(R.layout.layout2,null);View view3=lf.inflate(R.layout.layout3,null);List<View> viewList=new ArrayList<>();viewList.add(view1);viewList.add(view2);viewList.add(view3);ViewPager viewPager=findViewById(R.id.vp);//获取viewpagerMyAdapter myAdapter=new MyAdapter(viewList);viewPager.setAdapter(myAdapter);}
}
ViewPager2
ViewPage2是Jetpack中的其中一个组件,可以实现滑动切换页面的效果,通常可以搭配其他组件实现banner切换、以及类似于抖音短视频上下滑动切换播放的效果。
ViewPager2是基于RecyclerView实现的,自然继承了RecyclerView的众多优点,并且针对ViewPager存在的问题做了优化
- 支持
垂直方向的滑动且实现及其简单
- 完全支持RecyclerView的相关配置功能
支持多个PageTransformer
- 支持DiffUtil,
局部数据刷新和item动画
支持模拟用户滑动与禁止用户操作
- ViewPager(旧支持库)本身并不直接支持懒加载。在ViewPager中,所有的页面都会在初始化时被预加载,即使用户可能不会立即浏览到它们。
然而,可以通过自定义的方式在ViewPager中实现懒加载。一种常见的方法是在FragmentPagerAdapter或FragmentStatePagerAdapter中,重写instantiateItem()方法,并在该方法中控制页面的加载时机,使得只有当页面真正可见时才进行加载。
而ViewPager2(基于AndroidX库)则天生支持懒加载。
ViewPager2使用RecyclerView作为其基础实现,可以利用RecyclerView的特性来实现懒加载。RecyclerView会根据可见区域来判断需要加载哪些项,从而实现了懒加载的效果。
什么是懒加载?
懒加载(Lazy Loading)是一种延迟加载数据或资源的策略,它在需要使用数据或资源时才进行加载,而不是在一开始就预加载。这种策略的目的是提高性能和资源利用效率。
例如:图片懒加载:在应用或网页中加载大量图片时,可以使用懒加载策略。当图片滚动到可见区域时,才开始加载该图片,而不是一次性加载所有图片。
这样可以减少初始加载时间和网络带宽,并且避免同时加载大量图片导致的性能问题。
ViewPager与ViewPager2部分对比
常见api
//刷新Viewpager 同样支持recyclerView的局部刷新
notifyDataSetChanged()setUserInputEnabled(false);//禁止手动滑动setCurrentItem(0, false);//跳转到指定页面,false不带滚动动画setCurrentItem(0);//跳转到指定页面,带滚动动画addItemDecoration()//设置分割线 同RecyclerViewsetOffscreenPageLimit();//设置预加载数量setOrientation();//设置方向fakeDragBy(offsetPx)//代码模拟用户滑动页面。支持通过编程方式滚动。setPageTransformer()//设置滚动动画,参数可传 CompositePageTransformer,PageTransformer
动画
帧动画
Frame Animation
用多张图片来组成动画。一帧帧的播放图片,利用人眼视觉残留原理,给我们带来动画的感觉。它的原理的GIF图片、电影播放原理一样。
我们可以使用AnimationDrawable 来实现动画效果。
补间动画
Tween Animation
补间动画就是我们只需指定开始、结束的“关键帧“,而变化中的其他帧由系统来计算,不必自己一帧帧的去定义。
Android使用Animation代表抽象动画,包括四种子类:
- AlphaAnimation(透明度动画)
- ScaleAnimation(缩放动画)
- TranslateAnimation(位移动画)
- RotateAnimation(旋转动画)
一般都会采用动画资源文件来定义动画,把界面与逻辑分离
定义好anim文件后,我们可以通过AnimationUtils工具类来加载它们,加载成功后返回一个Animation。然后就可以通过View的startAnimation(anim)开始执行动画了。
属性动画
直接更改我们对象的属性。在上面提到的Tween Animation中,只是更改View的绘画效果而View的真实属性是不改变的
常用 Animator 类,ValueAnimator 等
Animator可加载动画资源文件
ValueAnimator可使用内置估值器,添加监听AnimatorUpdateListener,在每次变化时修改view的属性