安卓基础巩固(一):工程结构、基本概念、常用布局、基本组件、动画

安卓项目结构

在这里插入图片描述

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子目录)。参考下面的表格。
在这里插入图片描述

注意:

  1. 不能将资源文件直接保存在res/目录内,因为会造成编译错误
  2. 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:

  1. Application Context:它是应用程序中扩展的Context,与Application 生命周期相关,伴随Application的一生,只被创建和销毁一次。
  2. 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() 结合使用。
  • 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类型作为数据传入。
构建步骤如下:

  1. 在activity的布局文件中:声明一个ListView。
    在这里插入图片描述
  2. 额外编写一个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"
/>
  1. 在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的界面进行定制,让其丰富内容。
在这里插入图片描述
构建步骤如下:

  1. 在activity.xml中声明一个ListView
  2. 构建每个列表选项中的数据类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;
     }
}
  1. 构建每个列表选项布局文件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">
     <ImageView
         android:id="@+id/fruit_image"
         android:src="@drawable/apple"
         android:layout_width="100dp"
         android:layout_height="80dp"/>
     <TextView
         android: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"/>
     <TextView
         android: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>
  1. 自定义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
     @Override
     public 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;
     }
}
  1. 在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;
     @Override
     protected 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
    @Override
    public 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;

    @Override
    protected 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个数
    @Override
    public int getItemCount() {
        return mList.size() ;
    }

    //创建ViewHolder
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new NormalHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false));
    }

    //填充视图
    @Override
    public 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
  1. 在adapter中新建两个内部接口:
public interface OnItemClickListener {
    void onItemClick(View view, int position);
}
 
public interface OnItemLongClickListener {
    void onItemLongClick(View view, int position);
}
  1. 新建两个私有变量用于保存用户设置的监听器,并公开一个设置监听器的方法:
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
 
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
    this.mOnItemClickListener = mOnItemClickListener;
}
 
public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {
    this.mOnItemLongClickListener = mOnItemLongClickListener;
}
  1. 在onBindViewHolder方法内,实现回调:
    @Override
    public 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() {
                @Override
                public void onClick(View v) {
                    int position = holder.getLayoutPosition(); // 1
                    mOnItemClickListener.onItemClick(holder.itemView,position); // 2
                }
            });
        }
        if(mOnItemLongClickListener != null){
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public 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() {
    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(MainActivity.this, "click " + getList().get(position), Toast.LENGTH_SHORT).show();
    }
});
mTestAdapter.setOnItemLongClickListener(new TestAdapter.OnItemLongClickListener() {
    @Override
    public void onItemLongClick(View view, int position) {
        Toast.makeText(MainActivity.this,"long click "+getList().get(position),Toast.LENGTH_SHORT).show();
    }
});
rvTest.setAdapter(mTestAdapter);

ViewPager

一个简单的页面切换组件。
使用案例:

编写三个页面进行切换。

  1. 首先创建3个xml布局文件
  2. 在activity的布局文件中声明ViewPager
  3. 创建Adapter继承PagerAdapter,重写方法:
    • getCount():获取viewpager中有多少个view
    • instantiateItem():
      • 将给定位置的view添加到viewgroup中,创建并显示处理
      • 返回一个代表新增页面的Object(key),通常都是直接返回view本身就可以了,当然你也可以自定义自己的key,但是key和每个view要一一对应的关系。
  4. isViewFromObject()
    判断instantiateItem(Viewgroup,int)函数所返回的key与一个页面视图是否是代表的同一个视图(即他俩是否是对应的,对应的表示同一个view),通常我们直接写成return view==object
  5. 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
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        container.addView(listview.get(position),0);
        return listview.get(position);
    }

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

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

    @Override
    public 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 {

    @Override
    protected 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);//获取viewpager
        MyAdapter 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()//设置分割线 同RecyclerView
 
setOffscreenPageLimit();//设置预加载数量
 
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的属性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值